mirror of https://github.com/sirupsen/logrus.git
add hook caller
This commit is contained in:
parent
778f2e774c
commit
7c3858d6e9
|
@ -0,0 +1,174 @@
|
||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// refer import "log"
|
||||||
|
const (
|
||||||
|
Ldate = 1 << iota // the date in the local time zone: 2009/01/23
|
||||||
|
Ltime // the time in the local time zone: 01:23:23
|
||||||
|
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
|
||||||
|
Llongfile // full file name and line number: /a/b/c/d.go:23
|
||||||
|
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
|
||||||
|
LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone
|
||||||
|
LstdFlags = Ldate | Ltime // initial values for the standard logger
|
||||||
|
)
|
||||||
|
|
||||||
|
// CallerHook adds caller information to log entries.
|
||||||
|
//
|
||||||
|
// Sample usage:
|
||||||
|
// logrus.AddHook(caller.NewHook(&caller.CallerHookOptions{
|
||||||
|
// Field: "src",
|
||||||
|
// Flags: log.Lshortfile,
|
||||||
|
// }))
|
||||||
|
// logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||||
|
// logrus.Info("Test log")
|
||||||
|
// // time="2018-05-10T00:00:00-00:00" level=info msg="Test log" file="main.go:66"
|
||||||
|
|
||||||
|
type CallerHook struct {
|
||||||
|
CallerHookOptions *CallerHookOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHook creates a new caller hook with options. If options are nil or unspecified, options.Field defaults to "src"
|
||||||
|
// and options.Flags defaults to log.Llongfile
|
||||||
|
func NewHook(options *CallerHookOptions) *CallerHook {
|
||||||
|
//old
|
||||||
|
// Set default caller field to "src"
|
||||||
|
if options.Field == "" {
|
||||||
|
options.Field = "src"
|
||||||
|
}
|
||||||
|
// new
|
||||||
|
if options.FileAlias == "" {
|
||||||
|
options.FileAlias = "file"
|
||||||
|
}
|
||||||
|
if options.LineAlias == "" {
|
||||||
|
options.LineAlias = "line"
|
||||||
|
}
|
||||||
|
// Set default caller flag to Std logger log.Llongfile
|
||||||
|
if options.Flags == 0 {
|
||||||
|
// old
|
||||||
|
//options.Flags = log.Llongfile
|
||||||
|
// new
|
||||||
|
options.Flags = log.Lshortfile
|
||||||
|
}
|
||||||
|
return &CallerHook{options}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallerHookOptions stores caller hook options
|
||||||
|
type CallerHookOptions struct {
|
||||||
|
// new
|
||||||
|
FileAlias string //default:file
|
||||||
|
EnableFile bool
|
||||||
|
LineAlias string //default:line
|
||||||
|
EnableLine bool
|
||||||
|
// old
|
||||||
|
// Field to display caller info in
|
||||||
|
Field string
|
||||||
|
DisabledField bool
|
||||||
|
// Stores the flags
|
||||||
|
Flags int
|
||||||
|
|
||||||
|
//fileNmar
|
||||||
|
//Line Nmae
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasFlag returns true if the report caller options contains the specified flag
|
||||||
|
func (options *CallerHookOptions) HasFlag(flag int) bool {
|
||||||
|
return options.Flags&flag != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *CallerHook) Fire(entry *logrus.Entry) error {
|
||||||
|
|
||||||
|
// new
|
||||||
|
// get caller file and line here, it won't be available inside the goroutine
|
||||||
|
// 1 for the function that called us.
|
||||||
|
file, line := getCallerIgnoringLogMulti(1)
|
||||||
|
if hook.CallerHookOptions.HasFlag(log.Lshortfile) && !hook.CallerHookOptions.HasFlag(log.Llongfile) {
|
||||||
|
file = path.Base(file)
|
||||||
|
}
|
||||||
|
if !hook.CallerHookOptions.DisabledField {
|
||||||
|
entry.Data[hook.CallerHookOptions.Field] = fmt.Sprintf("%s:%d", file, line)
|
||||||
|
}
|
||||||
|
if hook.CallerHookOptions.EnableFile {
|
||||||
|
entry.Data[hook.CallerHookOptions.FileAlias] = file
|
||||||
|
}
|
||||||
|
if hook.CallerHookOptions.EnableLine {
|
||||||
|
entry.Data[hook.CallerHookOptions.LineAlias] = line
|
||||||
|
}
|
||||||
|
|
||||||
|
// old
|
||||||
|
//entry.Data[hook.CallerHookOptions.Field] = hook.callerInfo(entry.CallerFrames() + 1) // add 1 for this frame
|
||||||
|
//entry.Data[hook.CallerHookOptions.Field] = hook.callerInfo(1) // add 1 for this frame
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *CallerHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.WarnLevel,
|
||||||
|
logrus.InfoLevel,
|
||||||
|
logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//old
|
||||||
|
func (hook *CallerHook) callerInfo(skipFrames int) string {
|
||||||
|
// Follows output of Std logger
|
||||||
|
_, file, line, ok := runtime.Caller(skipFrames)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
} else {
|
||||||
|
// check flags
|
||||||
|
if hook.CallerHookOptions.HasFlag(log.Lshortfile) && !hook.CallerHookOptions.HasFlag(log.Llongfile) {
|
||||||
|
file = path.Base(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", file, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCaller returns the filename and the line info of a function
|
||||||
|
// further down in the call stack. Passing 0 in as callDepth would
|
||||||
|
// return info on the function calling getCallerIgnoringLog, 1 the
|
||||||
|
// parent function, and so on. Any suffixes passed to getCaller are
|
||||||
|
// path fragments like "/pkg/log/log.go", and functions in the call
|
||||||
|
// stack from that file are ignored.
|
||||||
|
func getCaller(callDepth int, suffixesToIgnore ...string) (file string, line int) {
|
||||||
|
// bump by 1 to ignore the getCaller (this) stackframe
|
||||||
|
callDepth++
|
||||||
|
outer:
|
||||||
|
for {
|
||||||
|
var ok bool
|
||||||
|
_, file, line, ok = runtime.Caller(callDepth)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range suffixesToIgnore {
|
||||||
|
if strings.HasSuffix(file, s) {
|
||||||
|
callDepth++
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//new
|
||||||
|
func getCallerIgnoringLogMulti(callDepth int) (string, int) {
|
||||||
|
// the +1 is to ignore this (getCallerIgnoringLogMulti) frame
|
||||||
|
return getCaller(callDepth+1, "logrus/hooks.go", "logrus/entry.go", "logrus/logger.go", "logrus/exported.go", "asm_amd64.s")
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logJson struct {
|
||||||
|
Level string
|
||||||
|
Msg string
|
||||||
|
Time time.Time
|
||||||
|
Src string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallerHook_ExportedInfo(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
// Caller hook
|
||||||
|
logrus.AddHook(NewHook(&CallerHookOptions{
|
||||||
|
Field: "src",
|
||||||
|
Flags: log.Lshortfile,
|
||||||
|
}))
|
||||||
|
logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||||
|
logrus.SetOutput(&buffer)
|
||||||
|
|
||||||
|
testCallerHookExportedInfo(t, &buffer, "Testing")
|
||||||
|
testCallerHookExportedInfo(t, &buffer, "Testing 2")
|
||||||
|
testCallerHookExportedInfo(t, &buffer, "Testing 3")
|
||||||
|
|
||||||
|
// Test in goroutines
|
||||||
|
count := 50
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(t *testing.T, wg *sync.WaitGroup, i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
logrus.Info(fmt.Sprintf("Testing for race conditions %d", i))
|
||||||
|
}(t, &wg, i)
|
||||||
|
}
|
||||||
|
// Wait for goroutines
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallerHookExportedInfo(t *testing.T, buffer *bytes.Buffer, msg string) {
|
||||||
|
var j logJson
|
||||||
|
logrus.Info(msg)
|
||||||
|
scanner := bufio.NewScanner(buffer)
|
||||||
|
scanner.Scan()
|
||||||
|
line := scanner.Text()
|
||||||
|
assert.NoError(t, scanner.Err(), "Scanner error")
|
||||||
|
json.Unmarshal([]byte(line), &j)
|
||||||
|
assert.Equal(t, "caller_test.go:54", j.Src, "%v", j)
|
||||||
|
assert.Equal(t, msg, j.Msg, "%v", j)
|
||||||
|
assert.Equal(t, "info", j.Level, "%v", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallerHook_ExportedEntryInfo(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
// Caller hook
|
||||||
|
logrus.AddHook(NewHook(&CallerHookOptions{
|
||||||
|
Field: "src",
|
||||||
|
Flags: log.Lshortfile,
|
||||||
|
}))
|
||||||
|
logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||||
|
logrus.SetOutput(&buffer)
|
||||||
|
|
||||||
|
testCallerHookExportedEntryInfo(t, &buffer, "Testing")
|
||||||
|
testCallerHookExportedEntryInfo(t, &buffer, "Testing 2")
|
||||||
|
testCallerHookExportedEntryInfo(t, &buffer, "Testing 3")
|
||||||
|
|
||||||
|
// Test in goroutines
|
||||||
|
count := 50
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(t *testing.T, wg *sync.WaitGroup, i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
logrus.Info(fmt.Sprintf("Testing for race conditions %d", i))
|
||||||
|
}(t, &wg, i)
|
||||||
|
}
|
||||||
|
// Wait for goroutines
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallerHookExportedEntryInfo(t *testing.T, buffer *bytes.Buffer, msg string) {
|
||||||
|
var j logJson
|
||||||
|
logrus.WithField("testcaller", "testcaller value").Info(msg)
|
||||||
|
scanner := bufio.NewScanner(buffer)
|
||||||
|
scanner.Scan()
|
||||||
|
line := scanner.Text()
|
||||||
|
assert.NoError(t, scanner.Err(), "Scanner error")
|
||||||
|
json.Unmarshal([]byte(line), &j)
|
||||||
|
assert.Equal(t, "caller_test.go:95", j.Src, "%v", j)
|
||||||
|
assert.Equal(t, msg, j.Msg, "%v", j)
|
||||||
|
assert.Equal(t, "info", j.Level, "%v", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallerHook_NewLoggerInfo(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
logr := logrus.New()
|
||||||
|
// Caller hook
|
||||||
|
logr.Hooks.Add(NewHook(&CallerHookOptions{
|
||||||
|
Field: "src",
|
||||||
|
Flags: log.Lshortfile,
|
||||||
|
}))
|
||||||
|
logr.Formatter = &logrus.JSONFormatter{}
|
||||||
|
logr.Out = &buffer
|
||||||
|
|
||||||
|
testCallerHookInfo(t, logr, "Testing")
|
||||||
|
testCallerHookInfo(t, logr, "Testing 2")
|
||||||
|
testCallerHookInfo(t, logr, "Testing 3")
|
||||||
|
|
||||||
|
// Test in goroutines
|
||||||
|
count := 50
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(t *testing.T, wg *sync.WaitGroup, logr *logrus.Logger, i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
logr.Info(fmt.Sprintf("Testing for race conditions %d", i))
|
||||||
|
}(t, &wg, logr, i)
|
||||||
|
}
|
||||||
|
// Wait for goroutines
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallerHook_LoggerInfo(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
logr := &logrus.Logger{
|
||||||
|
Out: &buffer,
|
||||||
|
Formatter: new(logrus.JSONFormatter),
|
||||||
|
Hooks: make(logrus.LevelHooks),
|
||||||
|
Level: logrus.InfoLevel,
|
||||||
|
}
|
||||||
|
// Caller hook
|
||||||
|
logr.Hooks.Add(NewHook(&CallerHookOptions{
|
||||||
|
Field: "src",
|
||||||
|
Flags: log.Lshortfile,
|
||||||
|
}))
|
||||||
|
|
||||||
|
testCallerHookInfo(t, logr, "Testing")
|
||||||
|
testCallerHookInfo(t, logr, "Testing 2")
|
||||||
|
testCallerHookInfo(t, logr, "Testing 3")
|
||||||
|
|
||||||
|
// Test in goroutines
|
||||||
|
count := 50
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(t *testing.T, wg *sync.WaitGroup, logr *logrus.Logger, i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
logr.Info(fmt.Sprintf("Testing for race conditions %d", i))
|
||||||
|
}(t, &wg, logr, i)
|
||||||
|
}
|
||||||
|
// Wait for goroutines
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallerHookInfo(t *testing.T, logr *logrus.Logger, msg string) {
|
||||||
|
var j logJson
|
||||||
|
logr.Info(msg)
|
||||||
|
scanner := bufio.NewScanner(logr.Out.(*bytes.Buffer))
|
||||||
|
scanner.Scan()
|
||||||
|
line := scanner.Text()
|
||||||
|
assert.NoError(t, scanner.Err(), "Scanner error")
|
||||||
|
json.Unmarshal([]byte(line), &j)
|
||||||
|
assert.Equal(t, "caller_test.go:169", j.Src, "%v", j)
|
||||||
|
assert.Equal(t, msg, j.Msg, "%v", j)
|
||||||
|
assert.Equal(t, "info", j.Level, "%v", j)
|
||||||
|
}
|
Loading…
Reference in New Issue