Skip to content

Commit 33bbba5

Browse files
author
Michael Ng
authored
feat(log): Adds default logger implementation. (#30)
1 parent f1d7673 commit 33bbba5

File tree

7 files changed

+195
-6
lines changed

7 files changed

+195
-6
lines changed

optimizely/client/client.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,25 @@ import (
2020
"github.com/optimizely/go-sdk/optimizely/config"
2121
"github.com/optimizely/go-sdk/optimizely/decision"
2222
"github.com/optimizely/go-sdk/optimizely/entities"
23+
"github.com/optimizely/go-sdk/optimizely/logging"
2324
)
2425

26+
var logger = logging.GetLogger("Client")
27+
2528
// OptimizelyClient is the entry point to the Optimizely SDK
2629
type OptimizelyClient struct {
27-
decisionService decision.DecisionService
2830
configManager config.ProjectConfigManager
31+
decisionService decision.DecisionService
32+
isValid bool
2933
}
3034

3135
// IsFeatureEnabled returns true if the feature is enabled for the given user
3236
func (optly *OptimizelyClient) IsFeatureEnabled(featureKey string, userID string, attributes map[string]interface{}) bool {
37+
if !optly.isValid {
38+
logger.Error("Optimizely instance is not valid. Failing IsFeatureEnabled.", nil)
39+
return false
40+
}
41+
3342
userContext := entities.UserContext{ID: userID, Attributes: entities.UserAttributes{Attributes: attributes}}
3443

3544
// @TODO(mng): we should fetch the Feature entity from the config service instead of manually creating it here

optimizely/client/factory.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ type OptimizelyFactory struct {
2929
}
3030

3131
// Client returns a client initialized with the defaults
32-
func (factory OptimizelyFactory) Client() OptimizelyClient {
32+
func (f OptimizelyFactory) Client() OptimizelyClient {
3333
var projectConfig config.ProjectConfig
3434
var configManager config.ProjectConfigManager
35-
if factory.Datafile != nil {
36-
projectConfig = datafileProjectConfig.NewDatafileProjectConfig(factory.Datafile)
35+
if f.Datafile != nil {
36+
projectConfig = datafileProjectConfig.NewDatafileProjectConfig(f.Datafile)
3737

38-
if factory.SDKKey == "" {
38+
if f.SDKKey == "" {
3939
staticConfigManager := config.NewStaticProjectConfigManager(projectConfig)
4040
configManager = staticConfigManager
4141
}
@@ -45,6 +45,7 @@ func (factory OptimizelyFactory) Client() OptimizelyClient {
4545
client := OptimizelyClient{
4646
decisionService: decisionService,
4747
configManager: configManager,
48+
isValid: true,
4849
}
4950
return client
5051
}

optimizely/config/datafileProjectConfig/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ import (
2222

2323
"github.com/optimizely/go-sdk/optimizely/config/datafileProjectConfig/mappers"
2424
"github.com/optimizely/go-sdk/optimizely/entities"
25+
"github.com/optimizely/go-sdk/optimizely/logging"
2526
)
2627

28+
var logger = logging.GetLogger("DatafileProjectConfig")
29+
2730
// DatafileProjectConfig is a project config backed by a datafile
2831
type DatafileProjectConfig struct {
2932
audienceMap map[string]entities.Audience
@@ -36,7 +39,7 @@ type DatafileProjectConfig struct {
3639
func NewDatafileProjectConfig(jsonDatafile []byte) *DatafileProjectConfig {
3740
datafile, err := Parse(jsonDatafile)
3841
if err != nil {
39-
// @TODO(mng): handle error
42+
logger.Error("Error parsing datafile.", err)
4043
}
4144

4245
experiments, experimentKeyMap := mappers.MapExperiments(datafile.Experiments)
@@ -46,6 +49,7 @@ func NewDatafileProjectConfig(jsonDatafile []byte) *DatafileProjectConfig {
4649
experimentKeyToIDMap: experimentKeyMap,
4750
}
4851

52+
logger.Info("Datafile is valid.")
4953
return config
5054
}
5155

optimizely/logging/interface.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package logging
2+
3+
// OptimizelyLogConsumer consumes log messages produced by the log producers
4+
type OptimizelyLogConsumer interface {
5+
Log(level int, message string)
6+
SetLogLevel(logLevel int)
7+
}
8+
9+
// OptimizelyLogProducer produces log messages to be consumed by the log consumer
10+
type OptimizelyLogProducer interface {
11+
Debug(message string)
12+
Info(message string)
13+
Warning(message string)
14+
Error(message string, err interface{})
15+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package logging
2+
3+
import (
4+
"log"
5+
"os"
6+
)
7+
8+
// FilteredLevelLogConsumer is an implementation of the OptimizelyLogConsumer that filters by log level
9+
type FilteredLevelLogConsumer struct {
10+
level int
11+
logger *log.Logger
12+
}
13+
14+
// Log logs the message if it's log level is higher than or equal to the logger's set level
15+
func (l *FilteredLevelLogConsumer) Log(level int, message string) {
16+
if l.level <= level {
17+
l.logger.Println(message)
18+
}
19+
}
20+
21+
// SetLogLevel changes the log level to the given level
22+
func (l *FilteredLevelLogConsumer) SetLogLevel(level int) {
23+
l.level = level
24+
}
25+
26+
// NewStdoutFilteredLevelLogConsumer returns a new logger that logs to stdout
27+
func NewStdoutFilteredLevelLogConsumer(level int) *FilteredLevelLogConsumer {
28+
return &FilteredLevelLogConsumer{
29+
level: level,
30+
logger: log.New(os.Stdout, "[Optimizely]", log.LstdFlags),
31+
}
32+
}

optimizely/logging/logger.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package logging
2+
3+
import "fmt"
4+
5+
var defaultLogConsumer OptimizelyLogConsumer
6+
7+
const (
8+
_ = iota
9+
10+
// LogLevelDebug log level
11+
LogLevelDebug
12+
13+
// LogLevelInfo log level
14+
LogLevelInfo
15+
16+
// LogLevelWarning log level
17+
LogLevelWarning
18+
19+
// LogLevelError log level
20+
LogLevelError
21+
)
22+
23+
func init() {
24+
defaultLogConsumer = NewStdoutFilteredLevelLogConsumer(LogLevelInfo)
25+
}
26+
27+
// SetLogger replaces the default logger with the given logger
28+
func SetLogger(logger OptimizelyLogConsumer) {
29+
defaultLogConsumer = logger
30+
}
31+
32+
// SetLogLevel sets the log level to the given level
33+
func SetLogLevel(logLevel int) {
34+
defaultLogConsumer.SetLogLevel(logLevel)
35+
}
36+
37+
// GetLogger returns a log producer with the given name
38+
func GetLogger(name string) OptimizelyLogProducer {
39+
return NamedLogProducer{
40+
name: name,
41+
}
42+
}
43+
44+
// NamedLogProducer produces logs prefixed with its name
45+
type NamedLogProducer struct {
46+
name string
47+
}
48+
49+
// Debug logs the given message with a DEBUG level
50+
func (p NamedLogProducer) Debug(message string) {
51+
p.log(LogLevelDebug, message)
52+
}
53+
54+
// Info logs the given message with a INFO level
55+
func (p NamedLogProducer) Info(message string) {
56+
p.log(LogLevelInfo, message)
57+
}
58+
59+
// Warning logs the given message with a WARNING level
60+
func (p NamedLogProducer) Warning(message string) {
61+
p.log(LogLevelWarning, message)
62+
}
63+
64+
// Error logs the given message with a ERROR level
65+
func (p NamedLogProducer) Error(message string, err interface{}) {
66+
if err != nil {
67+
message = fmt.Sprintf("%s %v", message, err)
68+
}
69+
p.log(LogLevelError, message)
70+
}
71+
72+
func (p NamedLogProducer) log(logLevel int, message string) {
73+
// prepends the name to the message
74+
message = fmt.Sprintf("[%s] %s", p.name, message)
75+
defaultLogConsumer.Log(logLevel, message)
76+
}

optimizely/logging/logger_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package logging
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/stretchr/testify/mock"
10+
)
11+
12+
type MockOptimizelyLogger struct {
13+
mock.Mock
14+
loggedMessages []string
15+
}
16+
17+
func (m *MockOptimizelyLogger) Log(level int, message string) {
18+
m.Called(level, message)
19+
m.loggedMessages = append(m.loggedMessages, message)
20+
}
21+
22+
func (m *MockOptimizelyLogger) SetLogLevel(level int) {
23+
m.Called(level)
24+
}
25+
26+
func TestNamedLoggerInfo(t *testing.T) {
27+
testLogMessage := "Test info message"
28+
expectedLogMessage := "[test-info] Test info message"
29+
testLogger := new(MockOptimizelyLogger)
30+
testLogger.On("Log", LogLevelInfo, expectedLogMessage)
31+
32+
SetLogger(testLogger)
33+
34+
logProducer := GetLogger("test-info")
35+
logProducer.Info(testLogMessage)
36+
testLogger.AssertExpectations(t)
37+
assert.Equal(t, []string{expectedLogMessage}, testLogger.loggedMessages)
38+
}
39+
40+
func TestNamedLoggerError(t *testing.T) {
41+
testLogMessage := "Test error message"
42+
expectedLogMessage := "[test-error] Test error message I am an error object"
43+
testLogger := new(MockOptimizelyLogger)
44+
testLogger.On("Log", LogLevelError, expectedLogMessage)
45+
SetLogger(testLogger)
46+
47+
err := errors.New("I am an error object")
48+
logProducer := GetLogger("test-error")
49+
logProducer.Error(testLogMessage, err)
50+
testLogger.AssertExpectations(t)
51+
assert.Equal(t, []string{expectedLogMessage}, testLogger.loggedMessages)
52+
}

0 commit comments

Comments
 (0)