From a803cd8cdc6f718f0d3e496323dad8fa6f69d138 Mon Sep 17 00:00:00 2001 From: Phus Lu Date: Mon, 22 Apr 2024 20:29:33 +0800 Subject: [PATCH] add TimeLocation for logger time formating (#66) * add TimeLocation for logger time formating * add tests and docs for time location --- README.md | 11 +++++++---- logger.go | 43 +++++++++++++++++++++++++++---------------- logger_test.go | 23 +++++++++++++++++++++++ 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e52f33d9..b8b857cc 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ var DefaultLogger = Logger{ Writer: &IOWriter{os.Stderr}, } -// A Logger represents an active logging object that generates lines of JSON output to an io.Writer. +// Logger represents an active logging object that generates lines of JSON output to an io.Writer. type Logger struct { // Level defines log levels. Level Level @@ -59,13 +59,16 @@ type Logger struct { // If Caller is negative, adds the full /path/to/file:line of the "caller" key. Caller int - // TimeField defines the time filed name in output. It uses "time" in if empty. + // TimeField defines the time field name in output. It uses "time" in if empty. TimeField string - // TimeFormat specifies the time format in output. It uses RFC3339 with millisecond if empty. - // If set with `TimeFormatUnix/TimeFormatUnixMs/TimeFormatUnixWithMs`, timestamps are formated. + // TimeFormat specifies the time format in output. Uses RFC3339 with millisecond if empty. + // If set to `TimeFormatUnix/TimeFormatUnixMs`, timestamps will be formatted. TimeFormat string + // TimeLocation specifices that the location of TimeFormat used. Uses time.Local if empty. + TimeLocation *time.Location + // Writer specifies the writer of output. It uses a wrapped os.Stderr Writer in if empty. Writer Writer } diff --git a/logger.go b/logger.go index 5c73f9e2..87e2ddf6 100644 --- a/logger.go +++ b/logger.go @@ -85,8 +85,8 @@ type Logger struct { // If set with `TimeFormatUnix`, `TimeFormatUnixMs`, times are formated as UNIX timestamp. TimeFormat string - // TimeUTC specifices that the timestamps should be UTC instead of system local time. - TimeUTC bool + // TimeLocation specifices that the location which TimeFormat used. It uses time.Local if empty. + TimeLocation *time.Location // Context specifies an optional context of logger. Context Context @@ -441,9 +441,6 @@ const smallsString = "00010203040506070809" + "90919293949596979899" var timeNow = time.Now -var timeUtcNow = func() time.Time { - return time.Now().UTC() -} var timeOffset, timeZone = func() (int64, string) { now := timeNow() _, n := now.Zone() @@ -456,14 +453,6 @@ func (l *Logger) silent(level Level) bool { } func (l *Logger) header(level Level) *Entry { - headerTimeFunc := timeNow - headerTimeOffset := timeOffset - - if l.TimeUTC { - headerTimeFunc = timeUtcNow - headerTimeOffset = 0 - } - e := epool.Get().(*Entry) e.buf = e.buf[:0] e.Level = level @@ -480,12 +469,29 @@ func (l *Logger) header(level Level) *Entry { e.buf = append(e.buf, l.TimeField...) e.buf = append(e.buf, '"', ':') } + offset := timeOffset + if l.TimeLocation != nil { + if l.TimeLocation == time.UTC { + offset = 0 + } else if l.TimeLocation == time.Local { + offset = timeOffset + } else { + format := l.TimeFormat + if format == "" { + format = "2006-01-02T15:04:05.999Z07:00" + } + e.buf = append(e.buf, '"') + e.buf = timeNow().In(l.TimeLocation).AppendFormat(e.buf, format) + e.buf = append(e.buf, '"') + goto headerlevel + } + } switch l.TimeFormat { case "": sec, nsec, _ := now() var tmp [32]byte var buf []byte - if headerTimeOffset == 0 { + if offset == 0 { // "2006-01-02T15:04:05.999Z" tmp[25] = '"' tmp[24] = 'Z' @@ -502,7 +508,7 @@ func (l *Logger) header(level Level) *Entry { buf = tmp[:31] } // date time - sec += 9223372028715321600 + headerTimeOffset // unixToInternal + internalToAbsolute + timeOffset + sec += 9223372028715321600 + offset // unixToInternal + internalToAbsolute + timeOffset year, month, day, _ := absDate(uint64(sec), true) hour, minute, second := absClock(uint64(sec)) // year @@ -640,9 +646,14 @@ func (l *Logger) header(level Level) *Entry { e.buf = append(e.buf, tmp[:]...) default: e.buf = append(e.buf, '"') - e.buf = headerTimeFunc().AppendFormat(e.buf, l.TimeFormat) + if l.TimeLocation == time.UTC { + e.buf = timeNow().UTC().AppendFormat(e.buf, l.TimeFormat) + } else { + e.buf = timeNow().AppendFormat(e.buf, l.TimeFormat) + } e.buf = append(e.buf, '"') } +headerlevel: // level switch level { case DebugLevel: diff --git a/logger_test.go b/logger_test.go index 3cac3c82..15eaa059 100644 --- a/logger_test.go +++ b/logger_test.go @@ -367,6 +367,29 @@ func TestLoggerTimeFormat(t *testing.T) { logger.Info().Int64("timestamp_ms", timeNow().UnixNano()/1000000).Msg("this is rfc3339 time log entry") } +func TestLoggerTimeLocation(t *testing.T) { + logger := Logger{} + + for _, format := range []string{"", time.RFC822} { + logger.TimeFormat = format + + logger.TimeLocation = nil + logger.Info().Msgf("this is TimeFormat=%#v TimeLocation=nil log entry", logger.TimeFormat) + + logger.TimeLocation = time.Local + logger.Info().Msgf("this is TimeFormat=%#v TimeLocation=time.Local log entry", logger.TimeFormat) + + logger.TimeLocation = time.UTC + logger.Info().Msgf("this is TimeFormat=%#v TimeLocation=time.UTC log entry", logger.TimeFormat) + + logger.TimeLocation, _ = time.LoadLocation("Asia/Singapore") + logger.Info().Msgf("this is TimeFormat=%#v TimeLocation=Asia/Singapore log entry", logger.TimeFormat) + + logger.TimeLocation, _ = time.LoadLocation("America/New_York") + logger.Info().Msgf("this is TimeFormat=%#v TimeLocation=America/New_York log entry", logger.TimeFormat) + } +} + func TestLoggerTimeOffset(t *testing.T) { logger := Logger{}