Skip to content

Commit 3370114

Browse files
author
Mike Davis
authored
feat(logger): Expand LogConsumer interface to have explicit field mapping. (#147)
* Updates the LogConsumer interface to accept a fields map[string]interface{} * Moves message formatting into the the LogConsumer implementation * Parameterize io.Writer in the default LogConsumer to facilitate testing
1 parent e58957c commit 3370114

File tree

5 files changed

+68
-36
lines changed

5 files changed

+68
-36
lines changed

pkg/logging/interface.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package logging
1919

2020
// OptimizelyLogConsumer consumes log messages produced by the log producers
2121
type OptimizelyLogConsumer interface {
22-
Log(level LogLevel, message string)
22+
Log(level LogLevel, message string, fields map[string]interface{})
2323
SetLogLevel(logLevel LogLevel)
2424
}
2525

pkg/logging/level_log_consumer.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
package logging
1919

2020
import (
21+
"fmt"
22+
"io"
2123
"log"
22-
"os"
2324
)
2425

2526
// FilteredLevelLogConsumer is an implementation of the OptimizelyLogConsumer that filters by log level
@@ -29,8 +30,10 @@ type FilteredLevelLogConsumer struct {
2930
}
3031

3132
// Log logs the message if it's log level is higher than or equal to the logger's set level
32-
func (l *FilteredLevelLogConsumer) Log(level LogLevel, message string) {
33+
func (l *FilteredLevelLogConsumer) Log(level LogLevel, message string, fields map[string]interface{}) {
3334
if l.level <= level {
35+
// prepends the name and log level to the message
36+
message = fmt.Sprintf("[%s][%s] %s", level, fields["name"], message)
3437
l.logger.Println(message)
3538
}
3639
}
@@ -40,10 +43,10 @@ func (l *FilteredLevelLogConsumer) SetLogLevel(level LogLevel) {
4043
l.level = level
4144
}
4245

43-
// NewStdoutFilteredLevelLogConsumer returns a new logger that logs to stdout
44-
func NewStdoutFilteredLevelLogConsumer(level LogLevel) *FilteredLevelLogConsumer {
46+
// NewFilteredLevelLogConsumer returns a new logger that logs to stdout
47+
func NewFilteredLevelLogConsumer(level LogLevel, out io.Writer) *FilteredLevelLogConsumer {
4548
return &FilteredLevelLogConsumer{
4649
level: level,
47-
logger: log.New(os.Stdout, "[Optimizely]", log.LstdFlags),
50+
logger: log.New(out, "[Optimizely]", log.LstdFlags),
4851
}
4952
}

pkg/logging/level_log_consumer_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,38 @@
1717
package logging
1818

1919
import (
20+
"bytes"
2021
"testing"
2122

2223
"github.com/stretchr/testify/assert"
2324
)
2425

25-
func TestNewStdoutFilteredLevelLogConsumer(t *testing.T) {
26-
newLogger := NewStdoutFilteredLevelLogConsumer(LogLevelInfo)
26+
func TestFilteredLogging(t *testing.T) {
27+
out := &bytes.Buffer{}
28+
newLogger := NewFilteredLevelLogConsumer(LogLevelInfo, out)
2729

2830
assert.Equal(t, newLogger.level, LogLevel(2))
2931
assert.NotNil(t, newLogger.logger)
3032

3133
newLogger.SetLogLevel(3)
3234
assert.Equal(t, newLogger.level, LogLevel(3))
3335

34-
newLogger.Log(1, "this is hidden")
35-
newLogger.Log(4, "this is visible")
36+
newLogger.Log(1, "this is hidden", map[string]interface{}{})
37+
assert.Equal(t, "", out.String())
38+
out.Reset()
39+
40+
newLogger.Log(4, "this is visible", map[string]interface{}{})
41+
assert.Contains(t, out.String(), "this is visible")
42+
out.Reset()
43+
}
44+
45+
func TestLogFormatting(t *testing.T) {
46+
out := &bytes.Buffer{}
47+
newLogger := NewFilteredLevelLogConsumer(LogLevelInfo, out)
48+
49+
newLogger.Log(LogLevelInfo, "test message", map[string]interface{}{"name": "test-name"})
50+
assert.Contains(t, out.String(), "test message")
51+
assert.Contains(t, out.String(), "[Info]")
52+
assert.Contains(t, out.String(), "[test-name]")
53+
assert.Contains(t, out.String(), "[Optimizely]")
3654
}

pkg/logging/logger.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
// Package logging //
1818
package logging
1919

20-
import "fmt"
20+
import (
21+
"fmt"
22+
"os"
23+
)
2124

2225
// LogLevel represents the level of the log (i.e. Debug, Info, Warning, Error)
2326
type LogLevel int
@@ -43,7 +46,7 @@ const (
4346
)
4447

4548
func init() {
46-
defaultLogConsumer = NewStdoutFilteredLevelLogConsumer(LogLevelInfo)
49+
defaultLogConsumer = NewFilteredLevelLogConsumer(LogLevelInfo, os.Stdout)
4750
}
4851

4952
// SetLogger replaces the default logger with the given logger
@@ -59,13 +62,13 @@ func SetLogLevel(logLevel LogLevel) {
5962
// GetLogger returns a log producer with the given name
6063
func GetLogger(name string) OptimizelyLogProducer {
6164
return NamedLogProducer{
62-
name: name,
65+
fields: map[string]interface{}{"name": name},
6366
}
6467
}
6568

6669
// NamedLogProducer produces logs prefixed with its name
6770
type NamedLogProducer struct {
68-
name string
71+
fields map[string]interface{}
6972
}
7073

7174
// Debug logs the given message with a DEBUG level
@@ -86,14 +89,11 @@ func (p NamedLogProducer) Warning(message string) {
8689
// Error logs the given message with a ERROR level
8790
func (p NamedLogProducer) Error(message string, err interface{}) {
8891
if err != nil {
89-
message = fmt.Sprintf("%s %v", message, err)
92+
message = fmt.Sprintf("%s: %v", message, err)
9093
}
9194
p.log(LogLevelError, message)
9295
}
9396

9497
func (p NamedLogProducer) log(logLevel LogLevel, message string) {
95-
96-
// prepends the name and log level to the message
97-
message = fmt.Sprintf("[%s][%s] %s", p.name, logLevel, message)
98-
defaultLogConsumer.Log(logLevel, message)
98+
defaultLogConsumer.Log(logLevel, message, p.fields)
9999
}

pkg/logging/logger_test.go

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ type MockOptimizelyLogger struct {
2929
loggedMessages []string
3030
}
3131

32-
func (m *MockOptimizelyLogger) Log(level LogLevel, message string) {
33-
m.Called(level, message)
32+
func (m *MockOptimizelyLogger) Log(level LogLevel, message string, fields map[string]interface{}) {
33+
m.Called(level, message, fields["name"])
3434
m.loggedMessages = append(m.loggedMessages, message)
3535
}
3636

@@ -40,56 +40,67 @@ func (m *MockOptimizelyLogger) SetLogLevel(level LogLevel) {
4040

4141
func TestNamedLoggerDebug(t *testing.T) {
4242
testLogMessage := "Test debug message"
43-
expectedLogMessage := "[test-debug][Debug] Test debug message"
43+
testLogName := "test-debug"
4444
testLogger := new(MockOptimizelyLogger)
45-
testLogger.On("Log", LogLevelDebug, expectedLogMessage)
45+
testLogger.On("Log", LogLevelDebug, testLogMessage, testLogName)
4646

4747
SetLogger(testLogger)
4848

49-
logProducer := GetLogger("test-debug")
49+
logProducer := GetLogger(testLogName)
5050
logProducer.Debug(testLogMessage)
5151
testLogger.AssertExpectations(t)
52-
assert.Equal(t, []string{expectedLogMessage}, testLogger.loggedMessages)
52+
assert.Equal(t, []string{testLogMessage}, testLogger.loggedMessages)
5353
}
5454

5555
func TestNamedLoggerInfo(t *testing.T) {
5656
testLogMessage := "Test info message"
57-
expectedLogMessage := "[test-info][Info] Test info message"
57+
testLogName := "test-info"
5858
testLogger := new(MockOptimizelyLogger)
59-
testLogger.On("Log", LogLevelInfo, expectedLogMessage)
59+
testLogger.On("Log", LogLevelInfo, testLogMessage, testLogName)
6060

6161
SetLogger(testLogger)
6262

63-
logProducer := GetLogger("test-info")
63+
logProducer := GetLogger(testLogName)
6464
logProducer.Info(testLogMessage)
6565
testLogger.AssertExpectations(t)
66-
assert.Equal(t, []string{expectedLogMessage}, testLogger.loggedMessages)
66+
assert.Equal(t, []string{testLogMessage}, testLogger.loggedMessages)
6767
}
6868

6969
func TestNamedLoggerWarning(t *testing.T) {
7070
testLogMessage := "Test warn message"
71-
expectedLogMessage := "[test-warn][Warning] Test warn message"
71+
testLogName := "test-warn"
7272
testLogger := new(MockOptimizelyLogger)
73-
testLogger.On("Log", LogLevelWarning, expectedLogMessage)
73+
testLogger.On("Log", LogLevelWarning, testLogMessage, testLogName)
7474

7575
SetLogger(testLogger)
7676

77-
logProducer := GetLogger("test-warn")
77+
logProducer := GetLogger(testLogName)
7878
logProducer.Warning(testLogMessage)
7979
testLogger.AssertExpectations(t)
80-
assert.Equal(t, []string{expectedLogMessage}, testLogger.loggedMessages)
80+
assert.Equal(t, []string{testLogMessage}, testLogger.loggedMessages)
8181
}
8282

8383
func TestNamedLoggerError(t *testing.T) {
8484
testLogMessage := "Test error message"
85-
expectedLogMessage := "[test-error][Error] Test error message I am an error object"
85+
testLogName := "test-error"
86+
expectedLogMessage := "Test error message: I am an error object"
8687
testLogger := new(MockOptimizelyLogger)
87-
testLogger.On("Log", LogLevelError, expectedLogMessage)
88+
testLogger.On("Log", LogLevelError, expectedLogMessage, testLogName)
8889
SetLogger(testLogger)
8990

9091
err := errors.New("I am an error object")
91-
logProducer := GetLogger("test-error")
92+
logProducer := GetLogger(testLogName)
9293
logProducer.Error(testLogMessage, err)
9394
testLogger.AssertExpectations(t)
9495
assert.Equal(t, []string{expectedLogMessage}, testLogger.loggedMessages)
9596
}
97+
98+
func TestSetLogLevel(t *testing.T) {
99+
testLogger := new(MockOptimizelyLogger)
100+
testLogger.On("SetLogLevel", LogLevelError)
101+
102+
SetLogger(testLogger)
103+
SetLogLevel(LogLevelError)
104+
105+
testLogger.AssertExpectations(t)
106+
}

0 commit comments

Comments
 (0)