diff --git a/global.go b/global.go index 22ebc57..b9f0021 100644 --- a/global.go +++ b/global.go @@ -2,6 +2,7 @@ package hclog import ( "sync" + "time" ) var ( @@ -14,6 +15,7 @@ var ( DefaultOptions = &LoggerOptions{ Level: DefaultLevel, Output: DefaultOutput, + TimeFn: time.Now, } ) diff --git a/intlogger.go b/intlogger.go index e2362e8..9a4ef31 100644 --- a/intlogger.go +++ b/intlogger.go @@ -60,6 +60,7 @@ type intLogger struct { callerOffset int name string timeFormat string + timeFn TimeFunction disableTime bool // This is an interface so that it's shared by any derived loggers, since @@ -116,6 +117,7 @@ func newLogger(opts *LoggerOptions) *intLogger { json: opts.JSONFormat, name: opts.Name, timeFormat: TimeFormat, + timeFn: time.Now, disableTime: opts.DisableTime, mutex: mutex, writer: newWriter(output, opts.Color), @@ -130,6 +132,9 @@ func newLogger(opts *LoggerOptions) *intLogger { if l.json { l.timeFormat = TimeFormatJSON } + if opts.TimeFn != nil { + l.timeFn = opts.TimeFn + } if opts.TimeFormat != "" { l.timeFormat = opts.TimeFormat } @@ -152,7 +157,7 @@ func (l *intLogger) log(name string, level Level, msg string, args ...interface{ return } - t := time.Now() + t := l.timeFn() l.mutex.Lock() defer l.mutex.Unlock() diff --git a/logger.go b/logger.go index 6a4665b..0a3f606 100644 --- a/logger.go +++ b/logger.go @@ -5,6 +5,7 @@ import ( "log" "os" "strings" + "time" ) var ( @@ -219,6 +220,8 @@ type StandardLoggerOptions struct { ForceLevel Level } +type TimeFunction = func() time.Time + // LoggerOptions can be used to configure a new logger. type LoggerOptions struct { // Name of the subsystem to prefix logs with @@ -248,6 +251,9 @@ type LoggerOptions struct { // The time format to use instead of the default TimeFormat string + // A function which is called to get the time object that is formatted using `TimeFormat` + TimeFn TimeFunction + // Control whether or not to display the time at all. This is required // because setting TimeFormat to empty assumes the default format. DisableTime bool diff --git a/logger_test.go b/logger_test.go index d70ed9e..b098b7e 100644 --- a/logger_test.go +++ b/logger_test.go @@ -249,6 +249,24 @@ func TestLogger(t *testing.T) { assert.Equal(t, str[:dataIdx], time.Now().Format(time.Kitchen)) }) + t.Run("use UTC time zone", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + TimeFormat: time.Kitchen, + TimeFn: func() time.Time { return time.Now().UTC() }, + }) + + logger.Info("this is test", "who", "programmer", "why", "testing is fun") + + str := buf.String() + dataIdx := strings.IndexByte(str, ' ') + + assert.Equal(t, str[:dataIdx], time.Now().UTC().Format(time.Kitchen)) + }) + t.Run("respects DisableTime", func(t *testing.T) { var buf bytes.Buffer @@ -657,6 +675,34 @@ func TestLogger_JSON(t *testing.T) { assert.Equal(t, val, time.Now().Format(time.Kitchen)) }) + t.Run("use UTC time zone", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + TimeFormat: time.Kitchen, + TimeFn: func() time.Time { return time.Now().UTC() }, + }) + + logger.Info("Lacatan banana") + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + val, ok := raw["@timestamp"] + if !ok { + t.Fatal("missing '@timestamp' key") + } + + assert.Equal(t, val, time.Now().UTC().Format(time.Kitchen)) + }) + t.Run("respects DisableTime", func(t *testing.T) { var buf bytes.Buffer logger := New(&LoggerOptions{