From 7a444b66e88e5a283805e729bf5c4e82c5f03bba Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Thu, 28 Oct 2021 17:57:16 -0400 Subject: [PATCH 1/2] add `InferLevelsWithTimestamp` option --- README.md | 3 +++ interceptlogger.go | 7 ++--- intlogger.go | 7 ++--- logger.go | 9 +++++++ stdlog.go | 20 +++++++++++--- stdlog_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 101 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8df2504..f3eab5c 100644 --- a/README.md +++ b/README.md @@ -143,3 +143,6 @@ log.Printf("[DEBUG] %d", 42) Notice that if `appLogger` is initialized with the `INFO` log level _and_ you specify `InferLevels: true`, you will not see any output here. You must change `appLogger` to `DEBUG` to see output. See the docs for more information. + +If the log lines start with a timestamp you can use the +`InferLevelsWithTimestamp` option to try and ignore them. diff --git a/interceptlogger.go b/interceptlogger.go index 631baf2..ff42f1b 100644 --- a/interceptlogger.go +++ b/interceptlogger.go @@ -180,9 +180,10 @@ func (i *interceptLogger) StandardWriterIntercept(opts *StandardLoggerOptions) i func (i *interceptLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer { return &stdlogAdapter{ - log: i, - inferLevels: opts.InferLevels, - forceLevel: opts.ForceLevel, + log: i, + inferLevels: opts.InferLevels, + inferLevelsWithTimestamp: opts.InferLevelsWithTimestamp, + forceLevel: opts.ForceLevel, } } diff --git a/intlogger.go b/intlogger.go index fd51af1..24fa730 100644 --- a/intlogger.go +++ b/intlogger.go @@ -703,9 +703,10 @@ func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer { newLog.callerOffset = l.callerOffset + 4 } return &stdlogAdapter{ - log: &newLog, - inferLevels: opts.InferLevels, - forceLevel: opts.ForceLevel, + log: &newLog, + inferLevels: opts.InferLevels, + inferLevelsWithTimestamp: opts.InferLevelsWithTimestamp, + forceLevel: opts.ForceLevel, } } diff --git a/logger.go b/logger.go index 6a4665b..2bdab1c 100644 --- a/logger.go +++ b/logger.go @@ -212,6 +212,15 @@ type StandardLoggerOptions struct { // [DEBUG] and strip it off before reapplying it. InferLevels bool + // Indicate that some minimal parsing should be done on strings to try + // and detect their level and re-emit them while ignoring possible + // timestamp values in the beginning of the string. + // This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO], + // [DEBUG] and strip it off before reapplying it. + // The timestamp detection may result in false positives and incomplete + // string outputs. + InferLevelsWithTimestamp bool + // ForceLevel is used to force all output from the standard logger to be at // the specified level. Similar to InferLevels, this will strip any level // prefix contained in the logged string before applying the forced level. diff --git a/stdlog.go b/stdlog.go index 271d546..3cba04c 100644 --- a/stdlog.go +++ b/stdlog.go @@ -3,6 +3,7 @@ package hclog import ( "bytes" "log" + "regexp" "strings" ) @@ -10,9 +11,10 @@ import ( // and back into our Logger. This is basically the only way to // build upon *log.Logger. type stdlogAdapter struct { - log Logger - inferLevels bool - forceLevel Level + log Logger + inferLevels bool + inferLevelsWithTimestamp bool + forceLevel Level } // Take the data, infer the levels if configured, and send it through @@ -28,6 +30,10 @@ func (s *stdlogAdapter) Write(data []byte) (int, error) { // Log at the forced level s.dispatch(str, s.forceLevel) } else if s.inferLevels { + if s.inferLevelsWithTimestamp { + str = s.trimTimestamp(str) + } + level, str := s.pickLevel(str) s.dispatch(str, level) } else { @@ -74,6 +80,14 @@ func (s *stdlogAdapter) pickLevel(str string) (Level, string) { } } +func (s *stdlogAdapter) trimTimestamp(str string) string { + // Ignore characters commonly found in timestamp formats from the beginning + // of the input. + logTimestampRegexp := regexp.MustCompile(`^[\d\s\:\/\.\+-TZ]*`) + idx := logTimestampRegexp.FindStringIndex(str) + return str[idx[1]:] +} + type logWriter struct { l *log.Logger } diff --git a/stdlog_test.go b/stdlog_test.go index de7c3bf..83cafab 100644 --- a/stdlog_test.go +++ b/stdlog_test.go @@ -69,6 +69,68 @@ func TestStdlogAdapter_PickLevel(t *testing.T) { }) } +func TestStdlogAdapter_TrimTimestamp(t *testing.T) { + cases := []struct { + name string + input string + expect string + }{ + { + name: "Go log Ldate", + input: "2009/01/23 [ERR] message", + expect: "[ERR] message", + }, + { + name: "Go log Ldate|Ltime", + input: "2009/01/23 01:23:23 [ERR] message", + expect: "[ERR] message", + }, + { + name: "Go log Ldate|Ltime|Lmicroseconds", + input: "2009/01/23 01:23:23.123123 [ERR] message", + expect: "[ERR] message", + }, + { + name: "Go log Ltime", + input: "01:23:23 [ERR] message", + expect: "[ERR] message", + }, + { + name: "Go log Ltime|Lmicroseconds", + input: "01:23:23.123123 [ERR] message", + expect: "[ERR] message", + }, + { + name: "ISO 8601 date", + input: "2021-10-28 [ERR] message", + expect: "[ERR] message", + }, + { + name: "ISO 8601 date and time", + input: "2021-10-28T19:27:28+00:00 [ERR] message", + expect: "[ERR] message", + }, + { + name: "ISO 8601 date and time zulu", + input: "2021-10-28T19:27:28Z [ERR] message", + expect: "[ERR] message", + }, + { + name: "no timestamp", + input: "[ERR] message", + expect: "[ERR] message", + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var s stdlogAdapter + got := s.trimTimestamp(c.input) + assert.Equal(t, c.expect, got) + }) + } +} + func TestStdlogAdapter_ForceLevel(t *testing.T) { cases := []struct { name string @@ -188,8 +250,8 @@ func TestFromStandardLogger_helper(t *testing.T) { sl := log.New(&buf, "test-stdlib-log ", log.Ltime) hl := FromStandardLogger(sl, &LoggerOptions{ - Name: "hclog-inner", - IncludeLocation: true, + Name: "hclog-inner", + IncludeLocation: true, AdditionalLocationOffset: 1, }) From 98fd3133c3f6e708db88c1d8347afdff65629d58 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Mon, 6 Dec 2021 18:18:35 -0500 Subject: [PATCH 2/2] compile timestamp regex only once --- stdlog.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stdlog.go b/stdlog.go index 3cba04c..641f20c 100644 --- a/stdlog.go +++ b/stdlog.go @@ -7,6 +7,10 @@ import ( "strings" ) +// Regex to ignore characters commonly found in timestamp formats from the +// beginning of inputs. +var logTimestampRegexp = regexp.MustCompile(`^[\d\s\:\/\.\+-TZ]*`) + // Provides a io.Writer to shim the data out of *log.Logger // and back into our Logger. This is basically the only way to // build upon *log.Logger. @@ -81,9 +85,6 @@ func (s *stdlogAdapter) pickLevel(str string) (Level, string) { } func (s *stdlogAdapter) trimTimestamp(str string) string { - // Ignore characters commonly found in timestamp formats from the beginning - // of the input. - logTimestampRegexp := regexp.MustCompile(`^[\d\s\:\/\.\+-TZ]*`) idx := logTimestampRegexp.FindStringIndex(str) return str[idx[1]:] }