diff --git a/README.md b/README.md index 37be974..2c001de 100644 --- a/README.md +++ b/README.md @@ -82,16 +82,16 @@ The following environment variables change the behavior of the loggers when usin Example log output is shown below. The outputs are configure in order with: the default configuration, `LOG_ENCODING=json`, `LOG_DISPLAY_FIELDS=false`, `LOG_SHORT_TIME=true`, and `LOG_DISPLAY_MULTILINE_FIELDS=true`. ``` -[I] [2019/07/24 09:15:30.806] Accepted request from 68.6.165.7 caller=derision/main.go:20 requestId=12341234-1234-1234-1234-123412341234 sequence_number=2 +[I] [2019/07/24 09:15:30.806] Accepted request from 68.6.165.7 caller=derision/main.go:20 requestId=12341234-1234-1234-1234-123412341234 sequenceNumber=2 -{"caller":"derision/main.go:20","level":"info","message":"Accepted request from 68.6.165.7","requestId":"12341234-1234-1234-1234-123412341234","sequence_number":2,"timestamp":"2019-07-24T09:16:55.673-0700"} +{"caller":"derision/main.go:20","level":"info","message":"Accepted request from 68.6.165.7","requestId":"12341234-1234-1234-1234-123412341234","sequenceNumber":2,"timestamp":"2019-07-24T09:16:55.673-0700"} [I] [2019/07/24 09:15:49.517] Accepted request from 68.6.165.7 -[I] [09:17:56] Accepted request from 68.6.165.7 caller=derision/main.go:20 requestId=12341234-1234-1234-1234-123412341234 sequence_number=2 +[I] [09:17:56] Accepted request from 68.6.165.7 caller=derision/main.go:20 requestId=12341234-1234-1234-1234-123412341234 sequenceNumber=2 [I] [2019/07/24 09:16:38.117] Accepted request from 68.6.165.7 caller = derision/main.go:20 requestId = 12341234-1234-1234-1234-123412341234 - sequence_number = 2 + sequenceNumber = 2 ``` diff --git a/base_logger.go b/base_logger.go new file mode 100644 index 0000000..ba90e5b --- /dev/null +++ b/base_logger.go @@ -0,0 +1,87 @@ +package log + +import ( + "fmt" + "os" + "sync/atomic" + "time" + + "github.com/efritz/glock" +) + +type baseLogger interface { + Log(timestamp time.Time, level LogLevel, fields LogFields, msg string) error +} + +type baseWrapper struct { + logger baseLogger + level LogLevel + clock glock.Clock + exiter func() + sequence uint64 +} + +type baseShim struct { + wrapper *baseWrapper + fields LogFields +} + +// +// Shim + +func newBaseShim(logger baseLogger, level LogLevel, initialFields LogFields) Logger { + wrapper := &baseWrapper{ + logger, + level, + glock.NewRealClock(), + func() { os.Exit(1) }, + 0, + } + + return adaptShim(&baseShim{wrapper, initialFields}) +} + +func newTestShim(logger baseLogger, level LogLevel, initialFields LogFields, clock glock.Clock, exiter func()) Logger { + wrapper := &baseWrapper{ + logger, + level, + clock, + exiter, + 0, + } + + return adaptShim(&baseShim{wrapper, initialFields}) +} + +func (s *baseShim) WithFields(fields LogFields) logShim { + if len(fields) == 0 { + return s + } + + return &baseShim{s.wrapper, s.fields.concat(fields)} +} + +func (s *baseShim) LogWithFields(level LogLevel, fields LogFields, format string, args ...interface{}) { + if level > s.wrapper.level { + return + } + + seq := atomic.AddUint64(&s.wrapper.sequence, 1) + fields = addCaller(fields).normalizeTimeValues() + fields["sequenceNumber"] = seq + + s.wrapper.logger.Log( + s.wrapper.clock.Now().UTC(), + level, + s.fields.concat(fields), + fmt.Sprintf(format, args...), + ) + + if level == LevelFatal { + s.wrapper.exiter() + } +} + +func (s *baseShim) Sync() error { + return nil +} diff --git a/base_logger_mock_test.go b/base_logger_mock_test.go new file mode 100644 index 0000000..25cae20 --- /dev/null +++ b/base_logger_mock_test.go @@ -0,0 +1,160 @@ +// Code generated by github.com/efritz/go-mockgen 0.1.0; DO NOT EDIT. +// This file was generated by robots at +// 2019-10-07T12:19:58-05:00 +// using the command +// $ go-mockgen -f github.com/go-nacelle/log -i baseLogger -o base_logger_mock_test.go + +package log + +import ( + "sync" + "time" +) + +// MockBaseLogger is a mock impelementation of the baseLogger interface +// (from the package github.com/go-nacelle/log) used for unit testing. +type MockBaseLogger struct { + // LogFunc is an instance of a mock function object controlling the + // behavior of the method Log. + LogFunc *BaseLoggerLogFunc +} + +// NewMockBaseLogger creates a new mock of the baseLogger interface. All +// methods return zero values for all results, unless overwritten. +func NewMockBaseLogger() *MockBaseLogger { + return &MockBaseLogger{ + LogFunc: &BaseLoggerLogFunc{ + defaultHook: func(time.Time, LogLevel, LogFields, string) error { + return nil + }, + }, + } +} + +// surrogateMockBaseLogger is a copy of the baseLogger interface (from the +// package github.com/go-nacelle/log). It is redefined here as it is +// unexported in the source packge. +type surrogateMockBaseLogger interface { + Log(time.Time, LogLevel, LogFields, string) error +} + +// NewMockBaseLoggerFrom creates a new mock of the MockBaseLogger interface. +// All methods delegate to the given implementation, unless overwritten. +func NewMockBaseLoggerFrom(i surrogateMockBaseLogger) *MockBaseLogger { + return &MockBaseLogger{ + LogFunc: &BaseLoggerLogFunc{ + defaultHook: i.Log, + }, + } +} + +// BaseLoggerLogFunc describes the behavior when the Log method of the +// parent MockBaseLogger instance is invoked. +type BaseLoggerLogFunc struct { + defaultHook func(time.Time, LogLevel, LogFields, string) error + hooks []func(time.Time, LogLevel, LogFields, string) error + history []BaseLoggerLogFuncCall + mutex sync.Mutex +} + +// Log delegates to the next hook function in the queue and stores the +// parameter and result values of this invocation. +func (m *MockBaseLogger) Log(v0 time.Time, v1 LogLevel, v2 LogFields, v3 string) error { + r0 := m.LogFunc.nextHook()(v0, v1, v2, v3) + m.LogFunc.appendCall(BaseLoggerLogFuncCall{v0, v1, v2, v3, r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the Log method of the +// parent MockBaseLogger instance is invoked and the hook queue is empty. +func (f *BaseLoggerLogFunc) SetDefaultHook(hook func(time.Time, LogLevel, LogFields, string) error) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// Log method of the parent MockBaseLogger instance inovkes the hook at the +// front of the queue and discards it. After the queue is empty, the default +// hook function is invoked for any future action. +func (f *BaseLoggerLogFunc) PushHook(hook func(time.Time, LogLevel, LogFields, string) error) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *BaseLoggerLogFunc) SetDefaultReturn(r0 error) { + f.SetDefaultHook(func(time.Time, LogLevel, LogFields, string) error { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *BaseLoggerLogFunc) PushReturn(r0 error) { + f.PushHook(func(time.Time, LogLevel, LogFields, string) error { + return r0 + }) +} + +func (f *BaseLoggerLogFunc) nextHook() func(time.Time, LogLevel, LogFields, string) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *BaseLoggerLogFunc) appendCall(r0 BaseLoggerLogFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of BaseLoggerLogFuncCall objects describing +// the invocations of this function. +func (f *BaseLoggerLogFunc) History() []BaseLoggerLogFuncCall { + f.mutex.Lock() + history := make([]BaseLoggerLogFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// BaseLoggerLogFuncCall is an object that describes an invocation of method +// Log on an instance of MockBaseLogger. +type BaseLoggerLogFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 time.Time + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 LogLevel + // Arg2 is the value of the 3rd argument passed to this method + // invocation. + Arg2 LogFields + // Arg3 is the value of the 4th argument passed to this method + // invocation. + Arg3 string + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c BaseLoggerLogFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1, c.Arg2, c.Arg3} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c BaseLoggerLogFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} diff --git a/base_logger_test.go b/base_logger_test.go new file mode 100644 index 0000000..a74edaa --- /dev/null +++ b/base_logger_test.go @@ -0,0 +1,89 @@ +package log + +//go:generate go-mockgen -f github.com/go-nacelle/log -i baseLogger -o base_logger_mock_test.go + +import ( + "github.com/aphistic/sweet" + "github.com/efritz/glock" + . "github.com/efritz/go-mockgen/matchers" + . "github.com/onsi/gomega" +) + +type BaseLoggerSuite struct{} + +func (s *BaseLoggerSuite) TestLogFormat(t sweet.T) { + base := NewMockBaseLogger() + clock := glock.NewMockClock() + logger := newTestShim(base, LevelDebug, nil, clock, func() {}) + logger.LogWithFields(LevelInfo, nil, "test %d %d %d", 1, 2, 3) + + Expect(base.LogFunc).To(BeCalledOnceWith( + clock.Now().UTC(), + LevelInfo, + LogFields{ + // Note: this value refers to the line number containing `LogWithFields` in + // the test setup above. If code is added before that line, this value must + // be updated. + "caller": "log/base_logger_test.go:18", + "sequenceNumber": uint64(1), + }, + "test 1 2 3", + )) +} + +func (s *BaseLoggerSuite) TestWrappedLoggers(t sweet.T) { + base := NewMockBaseLogger() + clock := glock.NewMockClock() + logger := newTestShim(base, LevelDebug, LogFields{"init": "foo"}, clock, func() {}) + wrappedLogger := logger.WithFields(LogFields{"wrapped": "bar"}) + wrappedLogger.LogWithFields(LevelDebug, LogFields{"extra": "baz"}, "test %d %d %d", 1, 2, 3) + logger.LogWithFields(LevelDebug, LogFields{"extra": "bonk"}, "test %d %d %d", 1, 2, 3) + + Expect(base.LogFunc).To(BeCalledOnceWith( + clock.Now().UTC(), + LevelDebug, + LogFields{ + "init": "foo", + "wrapped": "bar", + "extra": "baz", + // Note: this value refers to the line number containing `LogWithFields` in + // the test setup above. If code is added before that line, this value must + // be updated. + "caller": "log/base_logger_test.go:39", + "sequenceNumber": uint64(1), + }, + "test 1 2 3", + )) + + Expect(base.LogFunc).To(BeCalledOnceWith( + clock.Now().UTC(), + LevelDebug, + LogFields{ + "init": "foo", + "extra": "bonk", + // Note: this value refers to the line number containing `LogWithFields` in + // the test setup above. If code is added before that line, this value must + // be updated. + "caller": "log/base_logger_test.go:40", + "sequenceNumber": uint64(2), + }, + "test 1 2 3", + )) +} + +func (s *BaseLoggerSuite) TestLogLevelFilter(t sweet.T) { + base := NewMockBaseLogger() + clock := glock.NewMockClock() + logger := newTestShim(base, LevelInfo, nil, clock, func() {}) + logger.LogWithFields(LevelDebug, nil, "test %d %d %d", 1, 2, 3) + Expect(base.LogFunc).NotTo(BeCalled()) +} + +func (s *BaseLoggerSuite) TestLogFatal(t sweet.T) { + base := NewMockBaseLogger() + clock := glock.NewMockClock() + called := false + logger := newTestShim(base, LevelInfo, nil, clock, func() { called = true }) + logger.LogWithFields(LevelFatal, nil, "test %d %d %d", 1, 2, 3) + Expect(called).To(BeTrue()) +} diff --git a/caller_test.go b/caller_test.go index 6a2c6c2..e048180 100644 --- a/caller_test.go +++ b/caller_test.go @@ -171,9 +171,9 @@ func (s *CallerSuite) TestTrimPath(t sweet.T) { Expect(trimPath("/foo/bar/baz/bonk")).To(Equal("baz/bonk")) } -func (s *CallerSuite) TestGomol(t sweet.T) { s.testBasic(InitGomolShim) } -func (s *CallerSuite) TestGomolWithFields(t sweet.T) { s.testFields(InitGomolShim) } -func (s *CallerSuite) TestGomolWithReplayAdapter(t sweet.T) { s.testReplayAdapter(InitGomolShim) } -func (s *CallerSuite) TestGomolWithRollupAdapter(t sweet.T) { s.testRollupAdapter(InitGomolShim) } -func (s *CallerSuite) TestGomolReplay(t sweet.T) { s.testReplay(InitGomolShim) } -func (s *CallerSuite) TestGomolRollup(t sweet.T) { s.testRollup(InitGomolShim) } +func (s *CallerSuite) TestLogger(t sweet.T) { s.testBasic(InitLogger) } +func (s *CallerSuite) TestLoggerWithFields(t sweet.T) { s.testFields(InitLogger) } +func (s *CallerSuite) TestLoggerWithReplayAdapter(t sweet.T) { s.testReplayAdapter(InitLogger) } +func (s *CallerSuite) TestLoggerWithRollupAdapter(t sweet.T) { s.testRollupAdapter(InitLogger) } +func (s *CallerSuite) TestLoggerReplay(t sweet.T) { s.testReplay(InitLogger) } +func (s *CallerSuite) TestLoggerRollup(t sweet.T) { s.testRollup(InitLogger) } diff --git a/config.go b/config.go index 4798a30..2364eca 100644 --- a/config.go +++ b/config.go @@ -9,7 +9,7 @@ type Config struct { LogLevel string `env:"log_level" file:"log_level" default:"info"` LogEncoding string `env:"log_encoding" file:"log_encoding" default:"console"` LogColorize bool `env:"log_colorize" file:"log_colorize" default:"true"` - LogJSONFieldNames map[string]string `env:"log_json_field_names" file:"log_json_field_names"` + LogJSONFieldNames map[string]string `env:"log_json_field_names" file:"log_json_field_names"` // TODO - not in config LogInitialFields LogFields `env:"log_fields" file:"log_fields"` LogShortTime bool `env:"log_short_time" file:"log_short_time" default:"false"` LogDisplayFields bool `env:"log_display_fields" file:"log_display_fields" default:"true"` @@ -47,7 +47,7 @@ func (c *Config) PostLoad() error { } func isLegalLevel(level string) bool { - for _, whitelisted := range []string{"debug", "info", "warning", "error", "fatal"} { + for _, whitelisted := range names { if level == whitelisted { return true } diff --git a/console_logger.go b/console_logger.go new file mode 100644 index 0000000..fcfc3f6 --- /dev/null +++ b/console_logger.go @@ -0,0 +1,46 @@ +package log + +import ( + "bytes" + "fmt" + "io" + "os" + "text/template" + "time" +) + +type consoleLogger struct { + templates map[LogLevel]*template.Template + colorize bool + stream io.Writer +} + +func newConsoleLogger(templates map[LogLevel]*template.Template, colorize bool) *consoleLogger { + return &consoleLogger{ + templates: templates, + colorize: colorize, + stream: os.Stderr, + } +} + +func (l *consoleLogger) Log(timestamp time.Time, level LogLevel, fields LogFields, msg string) error { + if !l.colorize { + level = LevelNone + } + + buffer := bytes.Buffer{} + err := l.templates[level].Execute(&buffer, map[string]interface{}{ + "timestamp": timestamp, + "level": level, + "level_name": level.String(), + "message": msg, + "fields": fields, + }) + + if err != nil { + return err + } + + fmt.Fprint(l.stream, buffer.String()+"\n") + return nil +} diff --git a/console_logger_test.go b/console_logger_test.go new file mode 100644 index 0000000..8404c40 --- /dev/null +++ b/console_logger_test.go @@ -0,0 +1,58 @@ +package log + +import ( + "bytes" + "text/template" + "time" + + "github.com/aphistic/sweet" + . "github.com/onsi/gomega" +) + +type ConsoleLoggerSuite struct{} + +func (s *ConsoleLoggerSuite) TestLevel(t sweet.T) { + parsed, err := template.New("test").Parse("test: {{.message}}") + Expect(err).To(BeNil()) + + var ( + templates = map[LogLevel]*template.Template{LevelInfo: parsed} + logger = newConsoleLogger(templates, true) + buffer = bytes.NewBuffer(nil) + timestamp = time.Unix(1503939881, 0) + ) + + logger.stream = buffer + + logger.Log( + timestamp, + LevelInfo, + LogFields{"attr1": 4321}, + "test 1234", + ) + + Expect(string(buffer.Bytes())).To(Equal("test: test 1234\n")) +} + +func (s *ConsoleLoggerSuite) TestColorDisabled(t sweet.T) { + parsed, err := template.New("test").Parse("test: {{.message}}") + Expect(err).To(BeNil()) + + var ( + templates = map[LogLevel]*template.Template{LevelNone: parsed} + logger = newConsoleLogger(templates, false) + buffer = bytes.NewBuffer(nil) + timestamp = time.Unix(1503939881, 0) + ) + + logger.stream = buffer + + logger.Log( + timestamp, + LevelInfo, + LogFields{"attr1": 4321}, + "test 1234", + ) + + Expect(string(buffer.Bytes())).To(Equal("test: test 1234\n")) +} diff --git a/emergency.go b/emergency.go index 0e55249..a46746b 100644 --- a/emergency.go +++ b/emergency.go @@ -17,7 +17,7 @@ func LogEmergencyErrors(message string, errs []error) { } func EmergencyLogger() Logger { - logger, _ := InitGomolShim(&Config{ + logger, _ := InitLogger(&Config{ LogLevel: "DEBUG", LogEncoding: "json", }) diff --git a/fields.go b/fields.go index 9408460..08637e7 100644 --- a/fields.go +++ b/fields.go @@ -13,6 +13,15 @@ func (f LogFields) clone() LogFields { return clone } +func (f LogFields) concat(fields LogFields) LogFields { + clone := f.clone() + for k, v := range fields { + clone[k] = v + } + + return clone +} + func (f LogFields) normalizeTimeValues() LogFields { for key, val := range f { switch v := val.(type) { diff --git a/logger_test.go b/fields_test.go similarity index 83% rename from logger_test.go rename to fields_test.go index 5cfef21..c4897d4 100644 --- a/logger_test.go +++ b/fields_test.go @@ -7,9 +7,9 @@ import ( . "github.com/onsi/gomega" ) -type LoggerSuite struct{} +type FieldsSuite struct{} -func (s *LoggerSuite) TestNormalizeTimeValues(t sweet.T) { +func (s *FieldsSuite) TestNormalizeTimeValues(t sweet.T) { var ( t1 = time.Unix(1503939881, 0) t2 = time.Unix(1503939891, 0) @@ -33,6 +33,6 @@ func (s *LoggerSuite) TestNormalizeTimeValues(t sweet.T) { Expect(time.Parse(JSONTimeFormat, fields["baz"].(string))).To(Equal(t2)) } -func (s *LoggerSuite) TestNormalizeTimeValuesOnNilFields(t sweet.T) { +func (s *FieldsSuite) TestNormalizeTimeValuesOnNilFields(t sweet.T) { Expect(LogFields(nil).normalizeTimeValues()).To(BeNil()) } diff --git a/go.mod b/go.mod index 65d7054..d98fe31 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,17 @@ module github.com/go-nacelle/log require ( - github.com/aphistic/gomol v0.0.0-20190314031446-1546845ba714 - github.com/aphistic/gomol-console v0.0.0-20180111152223-9fa1742697a8 github.com/aphistic/sweet v0.2.0 github.com/aphistic/sweet-junit v0.0.0-20190314030539-8d7e248096c2 github.com/efritz/glock v0.0.0-20181228234553-f184d69dff2c - github.com/golang/protobuf v1.3.1 // indirect - github.com/kr/pty v1.1.5 // indirect + github.com/efritz/go-mockgen v0.0.0-20190613153341-3425cf558834 + github.com/golang/protobuf v1.3.2 // indirect github.com/mattn/go-colorable v0.1.2 // indirect - github.com/onsi/ginkgo v1.8.0 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b github.com/onsi/gomega v1.5.0 - github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/objx v0.2.0 // indirect - golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56 // indirect - golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect - golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect - golang.org/x/text v0.3.2 // indirect - golang.org/x/tools v0.0.0-20190617190820-da514acc4774 // indirect + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect + golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect ) + +go 1.13 diff --git a/go.sum b/go.sum index 387e1d2..3537693 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,34 @@ -github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a h1:2KLQMJ8msqoPHIPDufkxVcoTtcmE5+1sL9950m4R9Pk= -github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= -github.com/aphistic/gomol v0.0.0-20190314031446-1546845ba714 h1:ml3df+ybkktxzxTLInLXEDqfoFQUMC8kQtdfv8iwI+M= -github.com/aphistic/gomol v0.0.0-20190314031446-1546845ba714/go.mod h1:/wJ/Wijq31ktyhrvSuqh8KPiPEtJLKU/T4KwxmBYk2w= -github.com/aphistic/gomol-console v0.0.0-20180111152223-9fa1742697a8 h1:tzgowv45TOFALtZLJ9y3k+krzOh2J8IkCvJ8T//6VAU= -github.com/aphistic/gomol-console v0.0.0-20180111152223-9fa1742697a8/go.mod h1:3w1309L1wdWg0BwrcOnhJA06I0lm7G7wIhjGmqig4DM= -github.com/aphistic/gomol-gelf v0.0.0-20170516042314-573e82a82082 h1:PgPqI/JnStmzwTof+PtT53Pz53dlrz2BmF7cn5CAwQM= -github.com/aphistic/gomol-gelf v0.0.0-20170516042314-573e82a82082/go.mod h1:jaNu5/0CyDa/8+Y5rqU7H3+wX9cbAAuJtEU89/XLDoc= -github.com/aphistic/gomol-json v1.1.0 h1:XJWwW8PxYOHf0f0FquuBWcgvZBvQ89nPxZsqQ9pfpro= -github.com/aphistic/gomol-json v1.1.0/go.mod h1:wEOdY9oByrlQ4KEXY2wY3GvCWKoyIg7WeChslvrTkik= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aphistic/sweet v0.0.0-20180618201346-68e18ab55a67/go.mod h1:iggGz3Cujwru5rGKuOi4u1rfI+38suzhVVJj8Ey7Q3M= github.com/aphistic/sweet v0.2.0 h1:I4z+fAUqvKfvZV/CHi5dV0QuwbmIvYYFDjG0Ss5QpAs= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= -github.com/aphistic/sweet-junit v0.0.0-20171005212431-6b78f7014f7c/go.mod h1:+rEpaBMG7nKCTS5rjybTdJwqNG0ayGoPUm+sCPBgi9Y= github.com/aphistic/sweet-junit v0.0.0-20190314030539-8d7e248096c2 h1:qDCG/a4+mCcRqj+QHTc1RNncar6rpg0oGz9ynH4IRME= github.com/aphistic/sweet-junit v0.0.0-20190314030539-8d7e248096c2/go.mod h1:+eL69RqmiKF2Jm3poefxF/ZyVNGXFdSsPq3ScBFtX9s= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/efritz/backoff v1.0.0 h1:r1DfNhA1J7p8kZ185J/hLPz2Bl5ezTicUr9KamEAOYw= -github.com/efritz/backoff v1.0.0/go.mod h1:/tKomesOo7ekklUHEHxBbzNpjyBiOoiDCif3AcO+OIU= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/efritz/glock v0.0.0-20181228234553-f184d69dff2c h1:Q3HKbZogL9GGZVdO3PiVCOxZmRCsQAgV1xfelXJF/dY= github.com/efritz/glock v0.0.0-20181228234553-f184d69dff2c/go.mod h1:4behwg5YZ7amYrI5VDO/1s68YXZQHklcyFQpVDDgB2w= +github.com/efritz/go-genlib v0.0.0-20190429143346-e1e478a98211/go.mod h1:zUniQY7pV7ciqIjXY8TyDHXaqHZJa3NQIiiYDycogYk= +github.com/efritz/go-mockgen v0.0.0-20190613153341-3425cf558834 h1:37JFXKsLttHkcvFFr4t68C21uInpCNAIvPeE7IYRXKA= +github.com/efritz/go-mockgen v0.0.0-20190613153341-3425cf558834/go.mod h1:2Ia+C4l9Ns2t8XwO8JVbJ6GUEL/Bwm4u1DAj7ApQdqE= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -43,12 +38,12 @@ github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= @@ -60,12 +55,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -73,16 +65,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -90,17 +81,17 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190429094411-2cc0cad0ac78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190128232029-0a99049195af/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190428024724-550556f78a90/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/gomol.go b/gomol.go deleted file mode 100644 index 4f7b28b..0000000 --- a/gomol.go +++ /dev/null @@ -1,194 +0,0 @@ -package log - -import ( - "fmt" - "os" - "strings" - "text/template" - - "github.com/aphistic/gomol" - console "github.com/aphistic/gomol-console" -) - -type GomolShim struct { - logger gomol.WrappableLogger -} - -var gomolLevels = map[LogLevel]gomol.LogLevel{ - LevelDebug: gomol.LevelDebug, - LevelInfo: gomol.LevelInfo, - LevelWarning: gomol.LevelWarning, - LevelError: gomol.LevelError, - LevelFatal: gomol.LevelFatal, -} - -// -// Shim - -func NewGomolLogger(logger *gomol.LogAdapter, initialFields LogFields) Logger { - return adaptShim((&GomolShim{logger}).WithFields(initialFields)) -} - -func (g *GomolShim) WithFields(fields LogFields) logShim { - if len(fields) == 0 { - return g - } - - return &GomolShim{gomol.NewLogAdapterFor(g.logger, gomol.NewAttrsFromMap(fields))} -} - -func (g *GomolShim) LogWithFields(level LogLevel, fields LogFields, format string, args ...interface{}) { - g.logger.Log(gomolLevels[level], gomol.NewAttrsFromMap(addCaller(fields).normalizeTimeValues()), format, args...) - - if level == LevelFatal { - g.logger.ShutdownLoggers() - os.Exit(1) - } -} - -func (g *GomolShim) Sync() error { - return gomol.ShutdownLoggers() -} - -// -// Init - -func InitGomolShim(c *Config) (Logger, error) { - level, err := gomol.ToLogLevel(c.LogLevel) - if err != nil { - return nil, err - } - - if err := gomol.ClearLoggers(); err != nil { - return nil, err - } - - gomol.SetLogLevel(level) - gomol.SetConfig(&gomol.Config{SequenceAttr: "sequence_number"}) - - switch c.LogEncoding { - case "console": - if err := setupConsoleLogger(c); err != nil { - return nil, err - } - - case "json": - if err := setupJSONLogger(c); err != nil { - return nil, err - } - } - - if err := gomol.InitLoggers(); err != nil { - return nil, err - } - - return NewGomolLogger(gomol.NewLogAdapter(nil), c.LogInitialFields), nil -} - -func setupConsoleLogger(c *Config) error { - consoleLogger, err := console.NewConsoleLogger(&console.ConsoleLoggerConfig{ - Colorize: true, - Writer: os.Stderr, - }) - - if err != nil { - return err - } - - tpl, err := newGomolConsoleTemplate( - c.LogColorize, - c.LogShortTime, - c.LogDisplayFields, - c.LogDisplayMultilineFields, - c.LogFieldBlacklist, - ) - - if err != nil { - return err - } - - consoleLogger.SetTemplate(tpl) - gomol.AddLogger(consoleLogger) - return nil -} - -func setupJSONLogger(c *Config) error { - gomol.AddLogger(newJSONLogger(c.LogJSONFieldNames)) - return nil -} - -func newGomolConsoleTemplate( - color bool, - shortTime bool, - displayFields bool, - displayMultilineFields bool, - blacklist []string, -) (*gomol.Template, error) { - var ( - attrPrefix = " " - attrPadding = "" - attrSuffix = "" - ) - - if displayMultilineFields { - attrPrefix = "\n " - attrPadding = " " - attrSuffix = "\n" - } - - fieldsTemplate := fmt.Sprintf( - ""+ - `{{if .Attrs}}`+ - `{{range $key, $val := .Attrs}}`+ - `{{if shouldDisplayAttr $key}}`+ - `%s{{$key}}%s=%s{{$val}}`+ - `{{end}}`+ - `{{end}}`+ - `%s`+ - `{{end}}`, - attrPrefix, - attrPadding, - attrPadding, - attrSuffix, - ) - - timeFormat := "2006/01/02 15:04:05.000" - if shortTime { - timeFormat = "15:04:05" - } - - text := - "" + - `{{color}}` + - `[{{ucase .LevelName | printf "%1.1s"}}] ` + - fmt.Sprintf(`[{{.Timestamp.Format "%s"}}] {{.Message}}`, timeFormat) + - `{{reset}}` - - if displayFields { - text += fieldsTemplate - } - - if !color { - text = removeColor(text) - } - - return gomol.NewTemplateWithFuncMap(text, template.FuncMap{ - "shouldDisplayAttr": shouldDisplayAttr(blacklist), - }) -} - -func removeColor(text string) string { - return strings.NewReplacer("{{color}}", "", "{{reset}}", "").Replace(text) -} - -func shouldDisplayAttr(blacklist []string) func(string) bool { - return func(attr string) bool { - for _, cmp := range blacklist { - if cmp == attr { - return false - } - } - - return true - } -} diff --git a/gomol_json.go b/gomol_json.go deleted file mode 100644 index 924e594..0000000 --- a/gomol_json.go +++ /dev/null @@ -1,82 +0,0 @@ -package log - -import ( - "encoding/json" - "fmt" - "io" - "os" - "time" - - "github.com/aphistic/gomol" -) - -type jsonLogger struct { - stream io.Writer - messageField string - timestampField string - levelField string - base *gomol.Base - isInitialized bool -} - -func newJSONLogger(fieldNames map[string]string) *jsonLogger { - return &jsonLogger{ - stream: os.Stderr, - messageField: getField(fieldNames, "message"), - timestampField: getField(fieldNames, "timestamp"), - levelField: getField(fieldNames, "level"), - } -} - -func (l *jsonLogger) SetBase(base *gomol.Base) { - l.base = base -} - -func (l *jsonLogger) InitLogger() error { - l.isInitialized = true - return nil -} - -func (l *jsonLogger) IsInitialized() bool { - return l.isInitialized -} - -func (l *jsonLogger) ShutdownLogger() error { - l.isInitialized = false - return nil -} - -func (l *jsonLogger) Logm(timestamp time.Time, level gomol.LogLevel, attrs map[string]interface{}, msg string) error { - mergedAttrs := map[string]interface{}{} - - if l.base != nil && l.base.BaseAttrs != nil { - for key, val := range l.base.BaseAttrs.Attrs() { - mergedAttrs[key] = val - } - } - - for key, val := range attrs { - mergedAttrs[key] = val - } - - // TOOD - test different attrs - mergedAttrs[l.messageField] = msg - mergedAttrs[l.timestampField] = timestamp.Format(JSONTimeFormat) - mergedAttrs[l.levelField] = level.String() - - out, err := json.Marshal(mergedAttrs) - if err != nil { - return err - } - - fmt.Fprint(l.stream, string(out)+"\n") - return nil -} - -func getField(fieldNames map[string]string, field string) string { - if value, ok := fieldNames[field]; ok { - return value - } - - return field -} diff --git a/gomol_json_test.go b/gomol_json_test.go deleted file mode 100644 index ff4169e..0000000 --- a/gomol_json_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package log - -import ( - "bytes" - "fmt" - "time" - - "github.com/aphistic/gomol" - "github.com/aphistic/sweet" - . "github.com/onsi/gomega" -) - -type GomolJSONSuite struct{} - -func (s *GomolJSONSuite) TestInitLogger(t sweet.T) { - logger := newJSONLogger(nil) - Expect(logger.IsInitialized()).To(BeFalse()) - logger.InitLogger() - Expect(logger.IsInitialized()).To(BeTrue()) -} - -func (s *GomolJSONSuite) TestShutdownLogger(t sweet.T) { - logger := newJSONLogger(nil) - logger.InitLogger() - Expect(logger.IsInitialized()).To(BeTrue()) - logger.ShutdownLogger() - Expect(logger.IsInitialized()).To(BeFalse()) -} - -func (s *GomolJSONSuite) TestLogm(t sweet.T) { - var ( - logger = newJSONLogger(nil) - buffer = bytes.NewBuffer(nil) - timestamp = time.Unix(1503939881, 0) - ) - - logger.stream = buffer - - logger.Logm( - timestamp, - gomol.LevelFatal, - LogFields{"attr1": 4321}, - "test 1234", - ) - - Expect(string(buffer.Bytes())).To(MatchJSON(fmt.Sprintf(`{ - "level": "fatal", - "message": "test 1234", - "timestamp": "%s", - "attr1": 4321 - }`, timestamp.Format(JSONTimeFormat)))) -} - -func (s *GomolJSONSuite) TestBaseAttrs(t sweet.T) { - var ( - logger = newJSONLogger(nil) - buffer = bytes.NewBuffer(nil) - base = gomol.NewBase() - timestamp = time.Unix(1503939881, 0) - ) - - base.SetAttr("attr1", 7890) - base.SetAttr("attr2", "val2") - - logger.SetBase(base) - logger.stream = buffer - - logger.Logm( - timestamp, - gomol.LevelDebug, - LogFields{ - "attr1": 4321, - "attr3": "val3", - }, - "test 1234", - ) - - Expect(string(buffer.Bytes())).To(MatchJSON(fmt.Sprintf(`{ - "level": "debug", - "message": "test 1234", - "timestamp": "%s", - "attr1": 4321, - "attr2": "val2", - "attr3": "val3" - }`, timestamp.Format(JSONTimeFormat)))) -} - -func (s *GomolJSONSuite) TestCustomFieldNames(t sweet.T) { - var ( - logger = newJSONLogger(map[string]string{ - "timestamp": "@timestamp", - "level": "log_level", - }) - buffer = bytes.NewBuffer(nil) - timestamp = time.Unix(1503939881, 0) - ) - - logger.stream = buffer - - logger.Logm( - timestamp, - gomol.LevelFatal, - LogFields{"attr1": 4321}, - "test 1234", - ) - - Expect(string(buffer.Bytes())).To(MatchJSON(fmt.Sprintf(`{ - "log_level": "fatal", - "message": "test 1234", - "@timestamp": "%s", - "attr1": 4321 - }`, timestamp.Format(JSONTimeFormat)))) -} diff --git a/init.go b/init.go new file mode 100644 index 0000000..f2957a9 --- /dev/null +++ b/init.go @@ -0,0 +1,132 @@ +package log + +import ( + "fmt" + "strings" + "text/template" + + "github.com/mgutz/ansi" +) + +func InitLogger(c *Config) (Logger, error) { + baseLogger, err := initBaseLogger(c) + if err != nil { + return nil, err + } + + return newBaseShim(baseLogger, parseLogLevel(c.LogLevel), c.LogInitialFields), nil +} + +func initBaseLogger(c *Config) (baseLogger, error) { + if c.LogEncoding == "json" { + return newJSONLogger(c.LogJSONFieldNames), nil + } + + tpl, err := newConsoleTemplate( + c.LogShortTime, + c.LogDisplayFields, + c.LogDisplayMultilineFields, + c.LogFieldBlacklist, + ) + + if err != nil { + return nil, err + } + + return newConsoleLogger(tpl, c.LogColorize), nil +} + +func newConsoleTemplate( + shortTime bool, + displayFields bool, + displayMultilineFields bool, + blacklist []string, +) (map[LogLevel]*template.Template, error) { + var ( + fieldPrefix = " " + fieldPadding = "" + fieldSuffix = "" + ) + + if displayMultilineFields { + fieldPrefix = "\n " + fieldPadding = " " + fieldSuffix = "\n" + } + + fieldsTemplate := fmt.Sprintf( + ""+ + `{{if .Fields}}`+ + `{{range $key, $val := .Fields}}`+ + `{{if shouldDisplayAttr $key}}`+ + `%s{{$key}}%s=%s{{$val}}`+ + `{{end}}`+ + `{{end}}`+ + `%s`+ + `{{end}}`, + fieldPrefix, + fieldPadding, + fieldPadding, + fieldSuffix, + ) + + timeFormat := "2006/01/02 15:04:05.000" + if shortTime { + timeFormat = "15:04:05" + } + + text := + "" + + `{{color}}` + + `[{{uppercase .LevelName | printf "%1.1s"}}] ` + + fmt.Sprintf(`[{{.Timestamp.Format "%s"}}] {{.Message}}`, timeFormat) + + `{{reset}}` + + if displayFields { + text += fieldsTemplate + } + + colors := map[LogLevel]string{ + LevelFatal: ansi.ColorCode("red+b"), + LevelError: ansi.ColorCode("red"), + LevelWarning: ansi.ColorCode("yellow"), + LevelInfo: ansi.ColorCode("green"), + LevelDebug: ansi.ColorCode("cyan"), + LevelNone: "", + } + + templates := map[LogLevel]*template.Template{} + for level, color := range colors { + functions := template.FuncMap{ + "color": color, + "reset": ansi.ColorCode("reset"), + "uppercase": strings.ToUpper, + "shouldDisplayAttr": shouldDisplayAttr(blacklist), + } + + if color == "" { + functions["reset"] = "" + } + + parsed, err := template.New(level.String()).Funcs(functions).Parse(text) + if err != nil { + return nil, err + } + + templates[level] = parsed + } + + return templates, nil +} + +func shouldDisplayAttr(blacklist []string) func(string) bool { + return func(attr string) bool { + for _, cmp := range blacklist { + if cmp == attr { + return false + } + } + + return true + } +} diff --git a/json_logger.go b/json_logger.go new file mode 100644 index 0000000..9d3c712 --- /dev/null +++ b/json_logger.go @@ -0,0 +1,50 @@ +package log + +import ( + "encoding/json" + "fmt" + "io" + "os" + "time" +) + +type jsonLogger struct { + stream io.Writer + messageField string + timestampField string + levelField string +} + +const JSONTimeFormat = "2006-01-02T15:04:05.000-0700" + +func newJSONLogger(fieldNames map[string]string) *jsonLogger { + return &jsonLogger{ + stream: os.Stderr, + messageField: getField(fieldNames, "message"), + timestampField: getField(fieldNames, "timestamp"), + levelField: getField(fieldNames, "level"), + } +} + +func (l *jsonLogger) Log(timestamp time.Time, level LogLevel, fields LogFields, msg string) error { + mergedFields := fields.clone() + mergedFields[l.messageField] = msg + mergedFields[l.timestampField] = timestamp.Format(JSONTimeFormat) + mergedFields[l.levelField] = level.String() + + out, err := json.Marshal(mergedFields) + if err != nil { + return err + } + + fmt.Fprint(l.stream, string(out)+"\n") + return nil +} + +func getField(fieldNames map[string]string, field string) string { + if value, ok := fieldNames[field]; ok { + return value + } + + return field +} diff --git a/json_logger_test.go b/json_logger_test.go new file mode 100644 index 0000000..1600786 --- /dev/null +++ b/json_logger_test.go @@ -0,0 +1,63 @@ +package log + +import ( + "bytes" + "fmt" + "time" + + "github.com/aphistic/sweet" + . "github.com/onsi/gomega" +) + +type JSONLoggerSuite struct{} + +func (s *JSONLoggerSuite) TestLog(t sweet.T) { + var ( + logger = newJSONLogger(nil) + buffer = bytes.NewBuffer(nil) + timestamp = time.Unix(1503939881, 0) + ) + + logger.stream = buffer + + logger.Log( + timestamp, + LevelFatal, + LogFields{"attr1": 4321}, + "test 1234", + ) + + Expect(string(buffer.Bytes())).To(MatchJSON(fmt.Sprintf(`{ + "level": "fatal", + "message": "test 1234", + "timestamp": "%s", + "attr1": 4321 + }`, timestamp.Format(JSONTimeFormat)))) +} + +func (s *JSONLoggerSuite) TestCustomFieldNames(t sweet.T) { + var ( + logger = newJSONLogger(map[string]string{ + "timestamp": "@timestamp", + "level": "log_level", + }) + buffer = bytes.NewBuffer(nil) + timestamp = time.Unix(1503939881, 0) + ) + + logger.stream = buffer + + logger.Log( + timestamp, + LevelFatal, + LogFields{"attr1": 4321}, + "test 1234", + ) + + Expect(string(buffer.Bytes())).To(MatchJSON(fmt.Sprintf(`{ + "log_level": "fatal", + "message": "test 1234", + "@timestamp": "%s", + "attr1": 4321 + }`, timestamp.Format(JSONTimeFormat)))) +} diff --git a/level.go b/level.go index f742dbc..ac8bcac 100644 --- a/level.go +++ b/level.go @@ -2,19 +2,37 @@ package log type LogLevel int +const ( + LevelFatal LogLevel = iota + LevelError + LevelWarning + LevelInfo + LevelDebug + LevelNone +) + +var names = map[LogLevel]string{ + LevelDebug: "debug", + LevelInfo: "info", + LevelWarning: "warning", + LevelError: "error", + LevelFatal: "fatal", +} + func (l LogLevel) String() string { - switch l { - case LevelDebug: - return "debug" - case LevelInfo: - return "info" - case LevelWarning: - return "warning" - case LevelError: - return "error" - case LevelFatal: - return "fatal" - default: - return "unknown" + if name, ok := names[l]; ok { + return name + } + + return "unknown" +} + +func parseLogLevel(name string) LogLevel { + for level, candidate := range names { + if candidate == name { + return level + } } + + return LevelNone } diff --git a/logger.go b/logger.go index 79dd608..3184ce3 100644 --- a/logger.go +++ b/logger.go @@ -19,16 +19,3 @@ type ( FatalWithFields(LogFields, string, ...interface{}) } ) - -const ( - LevelFatal LogLevel = iota - LevelError - LevelWarning - LevelInfo - LevelDebug -) - -const ( - ConsoleTimeFormat = "2006-01-02 15:04:05.000" - JSONTimeFormat = "2006-01-02T15:04:05.000-0700" -) diff --git a/main_test.go b/main_test.go index e1cf3d8..92a9945 100644 --- a/main_test.go +++ b/main_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/aphistic/sweet" - "github.com/aphistic/sweet-junit" + junit "github.com/aphistic/sweet-junit" . "github.com/onsi/gomega" ) @@ -19,10 +19,12 @@ func TestMain(m *testing.M) { sweet.Run(m, func(s *sweet.S) { s.RegisterPlugin(junit.NewPlugin()) - s.AddSuite(&LoggerSuite{}) + s.AddSuite(&BaseLoggerSuite{}) s.AddSuite(&CallerSuite{}) s.AddSuite(&ConfigSuite{}) - s.AddSuite(&GomolJSONSuite{}) + s.AddSuite(&ConsoleLoggerSuite{}) + s.AddSuite(&FieldsSuite{}) + s.AddSuite(&JSONLoggerSuite{}) s.AddSuite(&ReplaySuite{}) s.AddSuite(&RollupSuite{}) }) diff --git a/nil_logger.go b/nil_logger.go index 82d11c5..5fd4305 100644 --- a/nil_logger.go +++ b/nil_logger.go @@ -6,6 +6,13 @@ func NewNilLogger() Logger { return adaptShim(&NilShim{}) } -func (n *NilShim) WithFields(LogFields) logShim { return n } -func (n *NilShim) LogWithFields(LogLevel, LogFields, string, ...interface{}) {} -func (n *NilShim) Sync() error { return nil } +func (n *NilShim) WithFields(LogFields) logShim { + return n +} + +func (n *NilShim) LogWithFields(LogLevel, LogFields, string, ...interface{}) { +} + +func (n *NilShim) Sync() error { + return nil +} diff --git a/replay.go b/replay_adapter.go similarity index 100% rename from replay.go rename to replay_adapter.go diff --git a/replay_test.go b/replay_adapter_test.go similarity index 100% rename from replay_test.go rename to replay_adapter_test.go diff --git a/rollup.go b/rollup_adapter.go similarity index 100% rename from rollup.go rename to rollup_adapter.go diff --git a/rollup_test.go b/rollup_adapter_test.go similarity index 100% rename from rollup_test.go rename to rollup_adapter_test.go