diff --git a/format.go b/format.go index 1b11655..7160674 100644 --- a/format.go +++ b/format.go @@ -14,6 +14,7 @@ import ( "path/filepath" "regexp" "runtime" + "strconv" "strings" "sync" "time" @@ -81,7 +82,7 @@ var defaultVerbsLayout = []string{ "s", "s", "s", - "s", + "0", "", } @@ -162,7 +163,7 @@ type stringFormatter struct { // %{message} Message (string) // %{longfile} Full file name and line number: /a/b/c/d.go:23 // %{shortfile} Final file name element and line number: d.go:23 -// %{callpath} Callpath like main.a.b.c...c "..." meaning recursive call +// %{callpath} Callpath like main.a.b.c...c "..." meaning recursive call ~. meaning truncated path // %{color} ANSI color based on log level // // For normal types, the output can be customized by using the 'verbs' defined @@ -179,6 +180,9 @@ type stringFormatter struct { // "%{color:bold}%{time:15:04:05} %{level:-8s}%{color:reset} %{message}" will // just colorize the time and level, leaving the message uncolored. // +// For the 'callpath' verb, the output can be adjusted to limit the printing +// the stack depth. i.e. '%{callpath:3}' will print '~.a.b.c' +// // Colors on Windows is unfortunately not supported right now and is currently // a no-op. // @@ -216,12 +220,12 @@ func NewStringFormatter(format string) (Formatter, error) { } // Handle layout customizations or use the default. If this is not for the - // time or color formatting, we need to prefix with %. + // time, color formatting or callpath, we need to prefix with %. layout := defaultVerbsLayout[verb] if m[4] != -1 { layout = format[m[4]:m[5]] } - if verb != fmtVerbTime && verb != fmtVerbLevelColor { + if verb != fmtVerbTime && verb != fmtVerbLevelColor && verb != fmtVerbCallpath { layout = "%" + layout } @@ -275,6 +279,12 @@ func (f *stringFormatter) Format(calldepth int, r *Record, output io.Writer) err output.Write([]byte(r.Time.Format(part.layout))) } else if part.verb == fmtVerbLevelColor { doFmtVerbLevelColor(part.layout, r.Level, output) + } else if part.verb == fmtVerbCallpath { + depth, err := strconv.Atoi(part.layout) + if err != nil { + depth = 0 + } + output.Write([]byte(formatCallpath(calldepth+1, depth))) } else { var v interface{} switch part.verb { @@ -314,8 +324,6 @@ func (f *stringFormatter) Format(calldepth int, r *Record, output io.Writer) err v = formatFuncName(part.verb, f.Name()) } } - case fmtVerbCallpath: - v = formatCallpath(calldepth + 1) default: panic("unhandled format part") } @@ -351,14 +359,19 @@ func formatFuncName(v fmtVerb, f string) string { panic("unexpected func formatter") } -func formatCallpath(calldepth int) string { +func formatCallpath(calldepth int, depth int) string { v := "" callers := make([]uintptr, 64) n := runtime.Callers(calldepth+2, callers) oldPc := callers[n-1] + start := n - 3 + if depth > 0 && start >= depth { + start = depth - 1 + v += "~." + } recursiveCall := false - for i := n - 3; i >= 0; i-- { + for i := start; i >= 0; i-- { pc := callers[i] if oldPc == pc { recursiveCall = true @@ -369,7 +382,7 @@ func formatCallpath(calldepth int) string { recursiveCall = false v += ".." } - if i < n-3 { + if i < start { v += "." } if f := runtime.FuncForPC(pc); f != nil { diff --git a/log_test.go b/log_test.go index 9e1d18c..c7a645f 100644 --- a/log_test.go +++ b/log_test.go @@ -44,14 +44,13 @@ func rec(log *Logger, r int) { rec(log, r-1) } -func TestLogCallpath(t *testing.T) { +func testCallpath(t *testing.T, format string, expect string) { buf := &bytes.Buffer{} SetBackend(NewLogBackend(buf, "", log.Lshortfile)) - SetFormatter(MustStringFormatter("%{callpath} %{message}")) - // SetFormatter(MustStringFormatter("%{callpath} %{message}")) + SetFormatter(MustStringFormatter(format)) - log := MustGetLogger("test") - rec(log, 6) + logger := MustGetLogger("test") + rec(logger, 6) parts := strings.SplitN(buf.String(), " ", 3) @@ -60,7 +59,7 @@ func TestLogCallpath(t *testing.T) { t.Errorf("incorrect filename: %s", parts[0]) } // Verify that the correct callpath is registered by go-logging - if !strings.HasPrefix(parts[1], "TestLogCallpath.rec...rec.a.b.c") { + if !strings.HasPrefix(parts[1], expect) { t.Errorf("incorrect callpath: %s", parts[1]) } // Verify that the correct message is registered by go-logging @@ -69,6 +68,15 @@ func TestLogCallpath(t *testing.T) { } } +func TestLogCallpath(t *testing.T) { + testCallpath(t, "%{callpath} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") + testCallpath(t, "%{callpath:-1} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") + testCallpath(t, "%{callpath:0} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") + testCallpath(t, "%{callpath:1} %{message}", "~.c") + testCallpath(t, "%{callpath:2} %{message}", "~.b.c") + testCallpath(t, "%{callpath:3} %{message}", "~.a.b.c") +} + func BenchmarkLogMemoryBackendIgnored(b *testing.B) { backend := SetBackend(NewMemoryBackend(1024)) backend.SetLevel(INFO, "")