Skip to content

Commit

Permalink
feat(caller): add caller formatter
Browse files Browse the repository at this point in the history
Format caller source file, line and function name.

Fixes: #22
  • Loading branch information
aymanbagabas committed Mar 20, 2023
1 parent 7ed8ebf commit ecd1551
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 14 deletions.
43 changes: 29 additions & 14 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ type Logger struct {

isDiscard uint32

level int32
prefix string
timeFunc TimeFunction
timeFormat string
callerOffset int
formatter Formatter
level int32
prefix string
timeFunc TimeFunction
timeFormat string
callerOffset int
callerFormatter CallerFormatter
formatter Formatter

reportCaller bool
reportTimestamp bool
Expand Down Expand Up @@ -71,8 +72,8 @@ func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) {

if l.reportCaller {
// Call stack is log.Error -> log.log (2)
file, line, _ := l.fillLoc(l.callerOffset + 2)
caller := fmt.Sprintf("%s:%d", trimCallerPath(file), line)
file, line, fn := l.fillLoc(l.callerOffset + 2)
caller := l.callerFormatter(file, line, fn)
kvs = append(kvs, CallerKey, caller)
}

Expand Down Expand Up @@ -146,8 +147,8 @@ func location(skip int) (file string, line int, fn string) {
return file, line, f.Name()
}

// Cleanup a path by returning the last 2 segments of the path only.
func trimCallerPath(path string) string {
// Cleanup a path by returning the last n segments of the path only.
func trimCallerPath(path string, n int) string {
// lovely borrowed from zap
// nb. To make sure we trim the path correctly on Windows too, we
// counter-intuitively need to use '/' and *not* os.PathSeparator here,
Expand All @@ -160,16 +161,23 @@ func trimCallerPath(path string) string {
//
// for discussion on the issue on Go side.

// Return the full path if n is 0.
if n <= 0 {
return path
}

// Find the last separator.
idx := strings.LastIndexByte(path, '/')
if idx == -1 {
return path
}

// Find the penultimate separator.
idx = strings.LastIndexByte(path[:idx], '/')
if idx == -1 {
return path
for i := 0; i < n-1; i++ {
// Find the penultimate separator.
idx = strings.LastIndexByte(path[:idx], '/')
if idx == -1 {
return path
}
}

return path[idx+1:]
Expand Down Expand Up @@ -254,6 +262,13 @@ func (l *Logger) SetFormatter(f Formatter) {
l.formatter = f
}

// SetCallerFormatter sets the caller formatter.
func (l *Logger) SetCallerFormatter(f CallerFormatter) {
l.mu.Lock()
defer l.mu.Unlock()
l.callerFormatter = f
}

// With returns a new logger with the given keyvals added.
func (l *Logger) With(keyvals ...interface{}) *Logger {
sl := *l
Expand Down
17 changes: 17 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package log

import (
"fmt"
"time"
)

Expand All @@ -21,6 +22,20 @@ func NowUTC() time.Time {
return time.Now().UTC()
}

// CallerFormatter is the caller formatter.
type CallerFormatter func(string, int, string) string

// ShortCallerFormatter is a caller formatter that returns the last 2 levels of the path
// and line number.
func ShortCallerFormatter(file string, line int, funcName string) string {
return fmt.Sprintf("%s:%d", trimCallerPath(file, 2), line)
}

// LongCallerFormatter is a caller formatter that returns the full path and line number.
func LongCallerFormatter(file string, line int, funcName string) string {
return fmt.Sprintf("%s:%d", file, line)
}

// Options is the options for the logger.
type Options struct {
// TimeFunction is the time function for the logger. The default is time.Now.
Expand All @@ -35,6 +50,8 @@ type Options struct {
ReportTimestamp bool
// ReportCaller is whether the logger should report the caller location. The default is false.
ReportCaller bool
// CallerFormatter is the caller format for the logger. The default is CallerShort.
CallerFormatter CallerFormatter
// Fields is the fields for the logger. The default is no fields.
Fields []interface{}
// Formatter is the formatter for the logger. The default is TextFormatter.
Expand Down
47 changes: 47 additions & 0 deletions options_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package log

import (
"bytes"
"fmt"
"io/ioutil"
"testing"

Expand All @@ -22,3 +24,48 @@ func TestOptions(t *testing.T) {
require.Equal(t, DefaultTimeFormat, logger.timeFormat)
require.NotNil(t, logger.timeFunc)
}

func TestCallerFormatter(t *testing.T) {
var buf bytes.Buffer
l := NewWithOptions(&buf, Options{ReportCaller: true})
file, line, fn := l.fillLoc(0)
hi := func() { l.Info("hi") }
cases := []struct {
name string
expected string
format CallerFormatter
}{
{
name: "short caller formatter",
expected: fmt.Sprintf("INFO <log/options_test.go:%d> hi\n", line+1),
format: ShortCallerFormatter,
},
{
name: "long caller formatter",
expected: fmt.Sprintf("INFO <%s:%d> hi\n", file, line+1),
format: LongCallerFormatter,
},
{
name: "foo caller formatter",
expected: "INFO <foo> hi\n",
format: func(file string, line int, fn string) string {
return "foo"
},
},
{
name: "custom caller formatter",
expected: fmt.Sprintf("INFO <%s:%d:%s.func1> hi\n", file, line+1, fn),
format: func(file string, line int, fn string) string {
return fmt.Sprintf("%s:%d:%s", file, line, fn)
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf.Reset()
l.callerFormatter = c.format
hi()
require.Equal(t, c.expected, buf.String())
})
}
}
10 changes: 10 additions & 0 deletions pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,16 @@ func NewWithOptions(w io.Writer, o Options) *Logger {
timeFormat: o.TimeFormat,
formatter: o.Formatter,
fields: o.Fields,
callerFormatter: o.CallerFormatter,
}

l.SetOutput(w)
l.SetLevel(Level(l.level))

if l.callerFormatter == nil {
l.callerFormatter = ShortCallerFormatter
}

if l.timeFunc == nil {
l.timeFunc = time.Now
}
Expand Down Expand Up @@ -97,6 +102,11 @@ func SetFormatter(f Formatter) {
defaultLogger.SetFormatter(f)
}

// SetCallerFormatter sets the caller formatter for the default logger.
func SetCallerFormatter(f CallerFormatter) {
defaultLogger.SetCallerFormatter(f)
}

// SetPrefix sets the prefix for the default logger.
func SetPrefix(prefix string) {
defaultLogger.SetPrefix(prefix)
Expand Down

0 comments on commit ecd1551

Please sign in to comment.