Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add InferLevelsWithTimestamp option #101

Merged
merged 2 commits into from Dec 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -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.
7 changes: 4 additions & 3 deletions interceptlogger.go
Expand Up @@ -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,
}
}

Expand Down
7 changes: 4 additions & 3 deletions intlogger.go
Expand Up @@ -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,
}
}

Expand Down
9 changes: 9 additions & 0 deletions logger.go
Expand Up @@ -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.
Expand Down
20 changes: 17 additions & 3 deletions stdlog.go
Expand Up @@ -3,16 +3,18 @@ package hclog
import (
"bytes"
"log"
"regexp"
"strings"
)

// 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.
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
Expand All @@ -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 {
Expand Down Expand Up @@ -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]*`)
lgfa29 marked this conversation as resolved.
Show resolved Hide resolved
idx := logTimestampRegexp.FindStringIndex(str)
return str[idx[1]:]
}

type logWriter struct {
l *log.Logger
}
Expand Down
66 changes: 64 additions & 2 deletions stdlog_test.go
Expand Up @@ -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
Expand Down Expand Up @@ -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,
})

Expand Down