diff --git a/intlogger.go b/intlogger.go index f8f6ebd..6099e67 100644 --- a/intlogger.go +++ b/intlogger.go @@ -124,7 +124,7 @@ func newLogger(opts *LoggerOptions) *intLogger { independentLevels: opts.IndependentLevels, } if opts.IncludeLocation { - l.callerOffset = offsetIntLogger + l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset } if l.json { diff --git a/logger.go b/logger.go index 83eafc1..7f36b1f 100644 --- a/logger.go +++ b/logger.go @@ -235,6 +235,10 @@ type LoggerOptions struct { // Include file and line information in each log line IncludeLocation bool + // AdditionalLocationOffset is the number of additional stack levels to skip + // when finding the file and line information for the log line + AdditionalLocationOffset int + // The time format to use instead of the default TimeFormat string diff --git a/logger_test.go b/logger_test.go index b1ef347..6e4bd58 100644 --- a/logger_test.go +++ b/logger_test.go @@ -176,6 +176,30 @@ func TestLogger(t *testing.T) { assert.Equal(t, "[INFO] go-hclog/logger_test.go:169: test: this is test: who=programmer why=\"testing is fun\"\n", rest) }) + t.Run("includes the caller location excluding helper functions", func(t *testing.T) { + var buf bytes.Buffer + + logMe := func(l Logger) { + l.Info("this is test", "who", "programmer", "why", "testing is fun") + } + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + IncludeLocation: true, + AdditionalLocationOffset: 1, + }) + + logMe(logger) + + str := buf.String() + dataIdx := strings.IndexByte(str, ' ') + rest := str[dataIdx+1:] + + // This test will break if you move this around, it's line dependent, just fyi + assert.Equal(t, "[INFO] go-hclog/logger_test.go:193: test: this is test: who=programmer why=\"testing is fun\"\n", rest) + }) + t.Run("prefixes the name", func(t *testing.T) { var buf bytes.Buffer @@ -805,6 +829,36 @@ func TestLogger_JSON(t *testing.T) { assert.Equal(t, fmt.Sprintf("%v:%d", file, line-1), raw["@caller"]) }) + t.Run("includes the caller location excluding helper functions", func(t *testing.T) { + var buf bytes.Buffer + + logMe := func(l Logger) { + l.Info("this is test", "who", "programmer", "why", "testing is fun") + } + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + IncludeLocation: true, + AdditionalLocationOffset: 1, + }) + + logMe(logger) + _, file, line, ok := runtime.Caller(0) + require.True(t, ok) + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, fmt.Sprintf("%v:%d", file, line-1), raw["@caller"]) + }) + t.Run("handles non-serializable entries", func(t *testing.T) { var buf bytes.Buffer diff --git a/stdlog_test.go b/stdlog_test.go index 446fa56..de7c3bf 100644 --- a/stdlog_test.go +++ b/stdlog_test.go @@ -181,3 +181,32 @@ func TestFromStandardLogger(t *testing.T) { prefix := "test-stdlib-log " require.Equal(t, prefix, actual[:16]) } + +func TestFromStandardLogger_helper(t *testing.T) { + var buf bytes.Buffer + + sl := log.New(&buf, "test-stdlib-log ", log.Ltime) + + hl := FromStandardLogger(sl, &LoggerOptions{ + Name: "hclog-inner", + IncludeLocation: true, + AdditionalLocationOffset: 1, + }) + + helper := func() { + hl.Info("this is a test", "name", "tester", "count", 1) + } + + helper() + _, file, line, ok := runtime.Caller(0) + require.True(t, ok) + + actual := buf.String() + suffix := fmt.Sprintf( + "[INFO] go-hclog/%s:%d: hclog-inner: this is a test: name=tester count=1\n", + filepath.Base(file), line-1) + require.Equal(t, suffix, actual[25:]) + + prefix := "test-stdlib-log " + require.Equal(t, prefix, actual[:16]) +}