parent f9827c6d
Pipeline #65641416 passed with stages
in 77 minutes and 44 seconds
......@@ -600,15 +600,15 @@
revision = "abf152e5f3e97f2fafac028d2cc06c1feb87ffa5"
[[projects]]
digest = "1:5f3253413123167546a72c2702f2c38980a5170b5d3d92ca074f8115dbc700da"
digest = "1:058a59f2f81568c274e05d03b0540f0a42329d959a87d7628cca5118b52abe0a"
name = "github.com/sirupsen/logrus"
packages = [
".",
"hooks/test",
]
pruneopts = "N"
revision = "e1e72e9de974bd926e5c56f83753fba2df402ce5"
version = "v1.3.0"
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
version = "v1.4.2"
[[projects]]
digest = "1:e86042cb61313f9315878e33f2713cc1a1a1c29feeb04b64c164ed55266822d9"
......
......@@ -32,7 +32,7 @@ ignored = ["test", "appengine"]
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.3.0"
version = "1.4.0"
[[constraint]]
branch = "master"
......
......@@ -51,9 +51,9 @@ func Exit(code int) {
os.Exit(code)
}
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
// all handlers. The handlers will also be invoked when any Fatal log entry is
// made.
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
// any Fatal log entry is made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
......@@ -62,3 +62,15 @@ func Exit(code int) {
func RegisterExitHandler(handler func()) {
handlers = append(handlers, handler)
}
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
// any Fatal log entry is made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
// closing database connections, or sending a alert that the application is
// closing.
func DeferExitHandler(handler func()) {
handlers = append([]func(){handler}, handlers...)
}
......@@ -14,9 +14,61 @@ import (
func TestRegister(t *testing.T) {
current := len(handlers)
RegisterExitHandler(func() {})
if len(handlers) != current+1 {
t.Fatalf("expected %d handlers, got %d", current+1, len(handlers))
var results []string
h1 := func() { results = append(results, "first") }
h2 := func() { results = append(results, "second") }
RegisterExitHandler(h1)
RegisterExitHandler(h2)
if len(handlers) != current+2 {
t.Fatalf("expected %d handlers, got %d", current+2, len(handlers))
}
runHandlers()
if len(results) != 2 {
t.Fatalf("expected 2 handlers to be run, ran %d", len(results))
}
if results[0] != "first" {
t.Fatal("expected handler h1 to be run first, but it wasn't")
}
if results[1] != "second" {
t.Fatal("expected handler h2 to be run second, but it wasn't")
}
}
func TestDefer(t *testing.T) {
current := len(handlers)
var results []string
h1 := func() { results = append(results, "first") }
h2 := func() { results = append(results, "second") }
DeferExitHandler(h1)
DeferExitHandler(h2)
if len(handlers) != current+2 {
t.Fatalf("expected %d handlers, got %d", current+2, len(handlers))
}
runHandlers()
if len(results) != 2 {
t.Fatalf("expected 2 handlers to be run, ran %d", len(results))
}
if results[0] != "second" {
t.Fatal("expected handler h2 to be run first, but it wasn't")
}
if results[1] != "first" {
t.Fatal("expected handler h1 to be run second, but it wasn't")
}
}
......
......@@ -2,6 +2,7 @@ package logrus
import (
"bytes"
"context"
"fmt"
"os"
"reflect"
......@@ -69,6 +70,9 @@ type Entry struct {
// When formatter is called in entry.log(), a Buffer may be set to entry
Buffer *bytes.Buffer
// Contains the context set by the user. Useful for hook processing etc.
Context context.Context
// err may contain a field formatting error
err string
}
......@@ -97,6 +101,11 @@ func (entry *Entry) WithError(err error) *Entry {
return entry.WithField(ErrorKey, err)
}
// Add a context to the Entry.
func (entry *Entry) WithContext(ctx context.Context) *Entry {
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
}
// Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value})
......@@ -130,12 +139,12 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
data[k] = v
}
}
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr}
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
}
// Overrides the time of the Entry.
func (entry *Entry) WithTime(t time.Time) *Entry {
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err}
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
}
// getPackageName reduces a fully qualified function name to the package name
......@@ -156,20 +165,23 @@ func getPackageName(f string) string {
// getCaller retrieves the name of the first non-logrus calling function
func getCaller() *runtime.Frame {
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name())
pcs := make([]uintptr, 2)
_ = runtime.Callers(0, pcs)
logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
// now that we have the cache, we can skip a minimum count of known-logrus functions
// XXX this is dubious, the number of frames may vary store an entry in a logger interface
// XXX this is dubious, the number of frames may vary
minimumCallerDepth = knownLogrusFrames
})
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)
......@@ -298,7 +310,9 @@ func (entry *Entry) Panic(args ...interface{}) {
// Entry Printf family functions
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
entry.Log(level, fmt.Sprintf(format, args...))
if entry.Logger.IsLevelEnabled(level) {
entry.Log(level, fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Tracef(format string, args ...interface{}) {
......
......@@ -2,6 +2,7 @@ package logrus
import (
"bytes"
"context"
"fmt"
"testing"
"time"
......@@ -33,6 +34,19 @@ func TestEntryWithError(t *testing.T) {
}
func TestEntryWithContext(t *testing.T) {
assert := assert.New(t)
ctx := context.WithValue(context.Background(), "foo", "bar")
assert.Equal(ctx, WithContext(ctx).Context)
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
assert.Equal(ctx, entry.WithContext(ctx).Context)
}
func TestEntryPanicln(t *testing.T) {
errBoom := fmt.Errorf("boom time")
......@@ -139,3 +153,17 @@ func TestEntryWithIncorrectField(t *testing.T) {
assert.Equal(eWithFunc.err, `can not add field "func"`)
assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
}
func TestEntryLogfLevel(t *testing.T) {
logger := New()
buffer := &bytes.Buffer{}
logger.Out = buffer
logger.SetLevel(InfoLevel)
entry := NewEntry(logger)
entry.Logf(DebugLevel, "%s", "debug")
assert.NotContains(t, buffer.String(), "debug", )
entry.Logf(WarnLevel, "%s", "warn")
assert.Contains(t, buffer.String(), "warn", )
}
\ No newline at end of file
package logrus_test
import (
"os"
"path"
"runtime"
"strings"
"github.com/sirupsen/logrus"
)
func ExampleCustomFormatter() {
l := logrus.New()
l.SetReportCaller(true)
l.Out = os.Stdout
l.Formatter = &logrus.JSONFormatter{
DisableTimestamp: true,
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
s := strings.Split(f.Function, ".")
funcname := s[len(s)-1]
_, filename := path.Split(f.File)
return funcname, filename
},
}
l.Info("example of custom format caller")
// Output:
// {"file":"example_custom_caller_test.go","func":"ExampleCustomFormatter","level":"info","msg":"example of custom format caller"}
}
package logrus_test
import (
"github.com/sirupsen/logrus"
"os"
)
type DefaultFieldHook struct {
GetValue func() string
}
func (h *DefaultFieldHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h *DefaultFieldHook) Fire(e *logrus.Entry) error {
e.Data["aDefaultField"] = h.GetValue()
return nil
}
func ExampleDefaultField() {
l := logrus.New()
l.Out = os.Stdout
l.Formatter = &logrus.TextFormatter{DisableTimestamp: true, DisableColors: true}
l.AddHook(&DefaultFieldHook{GetValue: func() string { return "with its default value" }})
l.Info("first log")
// Output:
// level=info msg="first log" aDefaultField="with its default value"
}
......@@ -21,7 +21,7 @@ func (h *GlobalHook) Fire(e *logrus.Entry) error {
return nil
}
func Example() {
func ExampleGlobalVariableHook() {
l := logrus.New()
l.Out = os.Stdout
l.Formatter = &logrus.TextFormatter{DisableTimestamp: true, DisableColors: true}
......
package logrus
import (
"context"
"io"
"time"
)
......@@ -55,6 +56,11 @@ func WithError(err error) *Entry {
return std.WithField(ErrorKey, err)
}
// WithContext creates an entry from the standard logger and adds a context to it.
func WithContext(ctx context.Context) *Entry {
return std.WithContext(ctx)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
......
......@@ -190,3 +190,27 @@ func TestAddHookRace(t *testing.T) {
// actually assert on the hook
})
}
type HookCallFunc struct {
F func()
}
func (h *HookCallFunc) Levels() []Level {
return AllLevels
}
func (h *HookCallFunc) Fire(e *Entry) error {
h.F()
return nil
}
func TestHookFireOrder(t *testing.T) {
checkers := []string{}
h := LevelHooks{}
h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "first hook") }})
h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "second hook") }})
h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "third hook") }})
h.Fire(InfoLevel, &Entry{})
require.Equal(t, []string{"first hook", "second hook", "third hook"}, checkers)
}
......@@ -40,7 +40,7 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma
log(logger)
fields := make(map[string]string)
for _, kv := range strings.Split(buffer.String(), " ") {
for _, kv := range strings.Split(strings.TrimRight(buffer.String(), "\n"), " ") {
if !strings.Contains(kv, "=") {
continue
}
......
......@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"runtime"
)
type fieldKey string
......@@ -42,6 +43,12 @@ type JSONFormatter struct {
// }
FieldMap FieldMap
// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the json data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from json fields.
CallerPrettyfier func(*runtime.Frame) (function string, file string)
// PrettyPrint will indent all json logs
PrettyPrint bool
}
......@@ -82,8 +89,17 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
if entry.HasCaller() {
data[f.FieldMap.resolve(FieldKeyFunc)] = entry.Caller.Function
data[f.FieldMap.resolve(FieldKeyFile)] = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
funcVal := entry.Caller.Function
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
if f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
}
if funcVal != "" {
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
}
if fileVal != "" {
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
}
}
var b *bytes.Buffer
......@@ -98,7 +114,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(data); err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
}
return b.Bytes(), nil
......
package logrus
import (
"context"
"io"
"os"
"sync"
......@@ -124,6 +125,13 @@ func (logger *Logger) WithError(err error) *Entry {
return entry.WithError(err)
}
// Add a context to the log entry.
func (logger *Logger) WithContext(ctx context.Context) *Entry {
entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithContext(ctx)
}
// Overrides the time of the log entry.
func (logger *Logger) WithTime(t time.Time) *Entry {
entry := logger.newEntry()
......@@ -200,7 +208,7 @@ func (logger *Logger) Info(args ...interface{}) {
func (logger *Logger) Print(args ...interface{}) {
entry := logger.newEntry()
entry.Info(args...)
entry.Print(args...)
logger.releaseEntry(entry)
}
......@@ -256,7 +264,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
}
func (logger *Logger) Warningln(args ...interface{}) {
logger.Warn(args...)
logger.Warnln(args...)
}
func (logger *Logger) Errorln(args ...interface{}) {
......
......@@ -6,6 +6,7 @@ import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
......@@ -40,3 +41,25 @@ func TestNoFieldValueError(t *testing.T) {
_, ok := data[FieldKeyLogrusError]
require.False(t, ok)
}
func TestWarninglnNotEqualToWarning(t *testing.T) {
buf := &bytes.Buffer{}
bufln := &bytes.Buffer{}
formatter := new(TextFormatter)
formatter.DisableTimestamp = true
formatter.DisableLevelTruncation = true
l := &Logger{
Out: buf,
Formatter: formatter,
Hooks: make(LevelHooks),
Level: DebugLevel,
}
l.Warning("hello,", "world")
l.SetOutput(bufln)
l.Warningln("hello,", "world")
assert.NotEqual(t, buf.String(), bufln.String(), "Warning() and Wantingln() should not be equal")
}
......@@ -74,7 +74,7 @@ func (level Level) MarshalText() ([]byte, error) {
return []byte("panic"), nil
}
return nil, fmt.Errorf("not a valid lorus level %q", level)
return nil, fmt.Errorf("not a valid logrus level %d", level)
}
// A constant exposing all logging levels
......
......@@ -40,7 +40,29 @@ func TestReportCallerWhenConfigured(t *testing.T) {
assert.Equal(t, "testWithCaller", fields["msg"])
assert.Equal(t, "info", fields["level"])
assert.Equal(t,
"github.com/sirupsen/logrus_test.TestReportCallerWhenConfigured.func3", fields["func"])
"github.com/sirupsen/logrus_test.TestReportCallerWhenConfigured.func3", fields[FieldKeyFunc])
})
LogAndAssertJSON(t, func(log *Logger) {
log.ReportCaller = true
log.Formatter.(*JSONFormatter).CallerPrettyfier = func(f *runtime.Frame) (string, string) {
return "somekindoffunc", "thisisafilename"
}
log.Print("testWithCallerPrettyfier")
}, func(fields Fields) {
assert.Equal(t, "somekindoffunc", fields[FieldKeyFunc])
assert.Equal(t, "thisisafilename", fields[FieldKeyFile])
})
LogAndAssertText(t, func(log *Logger) {
log.ReportCaller = true
log.Formatter.(*TextFormatter).CallerPrettyfier = func(f *runtime.Frame) (string, string) {
return "somekindoffunc", "thisisafilename"
}
log.Print("testWithCallerPrettyfier")
}, func(fields map[string]string) {
assert.Equal(t, "somekindoffunc", fields[FieldKeyFunc])
assert.Equal(t, "thisisafilename", fields[FieldKeyFile])
})
}
......@@ -521,6 +543,13 @@ func TestParseLevel(t *testing.T) {
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
}
func TestLevelString(t *testing.T) {
var loggerlevel Level
loggerlevel = 32000
_ = loggerlevel.String()
}
func TestGetSetLevelRace(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
......@@ -714,3 +743,20 @@ func TestReportCallerOnTextFormatter(t *testing.T) {
l.Formatter.(*TextFormatter).DisableColors = true
l.WithFields(Fields{"func": "func", "file": "file"}).Info("test")
}
func TestSetReportCallerRace(t *testing.T) {
l := New()
l.Out = ioutil.Discard
l.SetReportCaller(true)
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
l.Error("Some Error")
wg.Done()