Skip to content

Commit

Permalink
feat: Add the ability to send user feedback to the console even when …
Browse files Browse the repository at this point in the history
…logging to file. (#568)

Relevant issue(s)
Resolves #507

Description
This PR adds the ability to send user feedback to the console even when we configure Defra to send the log to a file. Feedback is sent in plain text as a simple message.
  • Loading branch information
fredcarle committed Jul 6, 2022
1 parent c3c6495 commit b14bb85
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 3 deletions.
68 changes: 65 additions & 3 deletions logging/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ package logging

import (
"context"
stdlog "log"
"os"
"sync"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

type logger struct {
name string
logger *zap.Logger
syncLock sync.RWMutex
name string
logger *zap.Logger
consoleLogger *stdlog.Logger
syncLock sync.RWMutex
}

var _ Logger = (*logger)(nil)
Expand Down Expand Up @@ -93,6 +96,55 @@ func (l *logger) FatalE(ctx context.Context, message string, err error, keyvals
l.logger.Fatal(message, toZapFields(kvs)...)
}

func (l *logger) FeedbackDebug(ctx context.Context, message string, keyvals ...KV) {
l.Debug(ctx, message, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) FeedbackInfo(ctx context.Context, message string, keyvals ...KV) {
l.Info(ctx, message, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) FeedbackWarn(ctx context.Context, message string, keyvals ...KV) {
l.Warn(ctx, message, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) FeedbackError(ctx context.Context, message string, keyvals ...KV) {
l.Error(ctx, message, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) FeedbackErrorE(ctx context.Context, message string, err error, keyvals ...KV) {
l.ErrorE(ctx, message, err, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) FeedbackFatal(ctx context.Context, message string, keyvals ...KV) {
l.Fatal(ctx, message, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) FeedbackFatalE(ctx context.Context, message string, err error, keyvals ...KV) {
l.FatalE(ctx, message, err, keyvals...)
if l.consoleLogger != nil {
l.consoleLogger.Println(message)
}
}

func (l *logger) Flush() error {
return l.logger.Sync()
}
Expand All @@ -118,6 +170,16 @@ func (l *logger) ApplyConfig(config Config) {
// We need sync the old log before swapping it out
_ = l.logger.Sync()
l.logger = newLogger

if !willOutputToStderr(config.OutputPaths) {
if config.pipe != nil { // for testing purposes only
l.consoleLogger = stdlog.New(config.pipe, "", 0)
} else {
l.consoleLogger = stdlog.New(os.Stderr, "", 0)
}
} else {
l.consoleLogger = nil
}
}

func buildZapLogger(name string, config Config) (*zap.Logger, error) {
Expand Down
14 changes: 14 additions & 0 deletions logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ type Logger interface {
Fatal(ctx context.Context, message string, keyvals ...KV)
// FatalE logs a message and an error at fatal log level. Key-value pairs can be added.
FatalE(ctx context.Context, message string, err error, keyvals ...KV)
// FeedbackDebug calls Debug and sends the message to stderr if logs are sent to a file.
FeedbackDebug(ctx context.Context, message string, keyvals ...KV)
// FeedbackInfo calls Info and sends the message to stderr if logs are sent to a file.
FeedbackInfo(ctx context.Context, message string, keyvals ...KV)
// FeedbackWarn calls Warn and sends the message to stderr if logs are sent to a file.
FeedbackWarn(ctx context.Context, message string, keyvals ...KV)
// FeedbackError calls Error and sends the message to stderr if logs are sent to a file.
FeedbackError(ctx context.Context, message string, keyvals ...KV)
// FeedbackErrorE calls ErrorE and sends the message to stderr if logs are sent to a file.
FeedbackErrorE(ctx context.Context, message string, err error, keyvals ...KV)
// FeedbackFatal calls Fatal and sends the message to stderr if logs are sent to a file.
FeedbackFatal(ctx context.Context, message string, keyvals ...KV)
// FeedbackFatalE calls FatalE and sends the message to stderr if logs are sent to a file.
FeedbackFatalE(ctx context.Context, message string, err error, keyvals ...KV)
// Flush flushes any buffered log entries.
Flush() error
// ApplyConfig updates the logger with a new config.
Expand Down
91 changes: 91 additions & 0 deletions logging/logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,97 @@ func TestLogWritesMessagesToLogGivenUpdatedLogPath(t *testing.T) {
}
}

func logFeedbackDebug(l Logger, c context.Context, m string) { l.FeedbackDebug(c, m) }
func logFeedbackInfo(l Logger, c context.Context, m string) { l.FeedbackInfo(c, m) }
func logFeedbackWarn(l Logger, c context.Context, m string) { l.FeedbackWarn(c, m) }
func logFeedbackError(l Logger, c context.Context, m string) { l.FeedbackError(c, m) }
func logFeedbackErrorE(l Logger, c context.Context, m string) {
l.FeedbackErrorE(c, m, fmt.Errorf("test error"))
}

func getFeedbackLogLevelTestCase() []LogLevelTestCase {
return []LogLevelTestCase{
{Debug, logFeedbackDebug, "DEBUG", false, false, true},
{Debug, logFeedbackDebug, "DEBUG", false, false, false},
{Debug, logFeedbackInfo, "INFO", false, false, false},
{Debug, logFeedbackWarn, "WARN", false, false, false},
{Debug, logFeedbackError, "ERROR", false, false, false},
{Debug, logFeedbackError, "ERROR", true, true, false},
{Debug, logFeedbackErrorE, "ERROR", false, false, false},
{Debug, logFeedbackErrorE, "ERROR", true, true, false},
{Info, logFeedbackDebug, "", false, false, false},
{Info, logFeedbackInfo, "INFO", false, false, true},
{Info, logFeedbackInfo, "INFO", false, false, false},
{Info, logFeedbackWarn, "WARN", false, false, false},
{Info, logFeedbackError, "ERROR", false, false, false},
{Info, logFeedbackError, "ERROR", true, true, false},
{Info, logFeedbackErrorE, "ERROR", false, false, false},
{Info, logFeedbackErrorE, "ERROR", true, true, false},
{Warn, logFeedbackDebug, "", false, false, false},
{Warn, logFeedbackInfo, "", false, false, false},
{Warn, logFeedbackWarn, "WARN", false, false, true},
{Warn, logFeedbackWarn, "WARN", false, false, false},
{Warn, logFeedbackError, "ERROR", false, false, false},
{Warn, logFeedbackError, "ERROR", true, true, false},
{Warn, logFeedbackErrorE, "ERROR", false, false, false},
{Warn, logFeedbackErrorE, "ERROR", true, true, false},
{Error, logFeedbackDebug, "", false, false, false},
{Error, logFeedbackInfo, "", false, false, false},
{Error, logFeedbackWarn, "", false, false, false},
{Error, logFeedbackError, "ERROR", false, false, true},
{Error, logFeedbackError, "ERROR", false, false, false},
{Error, logFeedbackError, "ERROR", true, true, false},
{Error, logFeedbackErrorE, "ERROR", false, false, false},
{Error, logFeedbackErrorE, "ERROR", true, true, false},
{Fatal, logFeedbackDebug, "", false, false, true},
{Fatal, logFeedbackDebug, "", false, false, false},
{Fatal, logFeedbackInfo, "", false, false, false},
{Fatal, logFeedbackWarn, "", false, false, false},
{Fatal, logFeedbackError, "", false, false, false},
{Fatal, logFeedbackErrorE, "", false, false, false},
}
}

func TestLogWritesMessagesToFeedbackLog(t *testing.T) {
for i, tc := range getFeedbackLogLevelTestCase() {
ctx := context.Background()
b := &bytes.Buffer{}
logger, logPath := getLogger(t, func(c *Config) {
c.Level = NewLogLevelOption(tc.LogLevel)
c.EnableStackTrace = NewEnableStackTraceOption(tc.WithStackTrace)
c.EnableCaller = NewEnableCallerOption(tc.WithCaller)
c.pipe = b
})
logMessage := "test log message"

tc.LogFunc(logger, ctx, logMessage)
logger.Flush()

logLines, err := getLogLines(t, logPath)
if err != nil {
t.Fatal(err)
}

if tc.ExpectedLogLevel == "" {
assert.Len(t, logLines, 0)
} else {
if len(logLines) != 1 {
t.Fatalf("expecting exactly 1 log line but got %d lines for tc %d", len(logLines), i)
}

assert.Equal(t, logMessage, logLines[0]["msg"])
assert.Equal(t, tc.ExpectedLogLevel, logLines[0]["level"])
assert.Equal(t, "TestLogName", logLines[0]["logger"])
_, hasStackTrace := logLines[0]["stacktrace"]
assert.Equal(t, tc.ExpectStackTrace, hasStackTrace)
_, hasCaller := logLines[0]["caller"]
assert.Equal(t, tc.WithCaller, hasCaller)
}

assert.Equal(t, logMessage+"\n", b.String())
}
}

func TestLogWritesMessagesToLogGivenPipeWithValidPath(t *testing.T) {
defer clearConfig()
defer clearRegistry("TestLogName")
Expand Down

0 comments on commit b14bb85

Please sign in to comment.