From 6637ba60cde50cec11022cd1ab40d7c88a9ca86c Mon Sep 17 00:00:00 2001 From: Drew Parsons <dparsons@debian.org> Date: Wed, 9 Nov 2022 18:07:28 +0100 Subject: [PATCH] New upstream version 1.9.0 --- .github/stale.yml | 20 ------ .github/workflows/ci.yaml | 61 +++++++++++++++++ .github/workflows/stale.yaml | 22 +++++++ .travis.yml | 14 ++-- CHANGELOG.md | 36 ++++++++++ README.md | 6 +- buffer_pool.go | 9 --- ci/go.mod | 5 ++ ci/go.sum | 2 + ci/mage.go | 10 +++ ci/magefile.go | 123 +++++++++++++++++++++++++++++++++++ entry.go | 86 ++++++++++++++---------- entry_test.go | 47 ++++++++++++- go.mod | 5 +- go.sum | 16 +++-- hook_test.go | 16 +++++ json_formatter.go | 5 +- logger.go | 15 ++++- logger_test.go | 30 ++++++++- logrus_test.go | 39 ++++++++++- terminal_check_unix.go | 2 +- text_formatter.go | 7 +- travis/install.sh | 20 +----- travis/lint.sh | 5 -- 24 files changed, 488 insertions(+), 113 deletions(-) delete mode 100644 .github/stale.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/stale.yaml create mode 100644 ci/go.mod create mode 100644 ci/go.sum create mode 100644 ci/mage.go create mode 100644 ci/magefile.go delete mode 100755 travis/lint.sh diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 27c3cf9d..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 14 -# Issues with these labels will never be considered stale -exemptLabels: - - question - - bug - - documentation -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false -# Only mark issues as stale for now until we go through backlog of PRs -only: issues diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..66f76aed --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,61 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + + lint: + name: Golang-CI Lint + timeout-minutes: 10 + strategy: + matrix: + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - uses: golangci/golangci-lint-action@v2 + with: + # must be specified without patch version + version: v1.46 + cross: + name: Cross + timeout-minutes: 10 + strategy: + matrix: + go-version: [1.17.x] + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Cross + working-directory: ci + run: go run mage.go -v -w ../ crossBuild + + test: + name: Unit test + timeout-minutes: 10 + strategy: + matrix: + go-version: [1.17.x] + platform: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Test + run: go test -race -v ./... diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 00000000..246c34d3 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,22 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v3 + with: + days-before-issue-stale: 30 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.travis.yml b/.travis.yml index 5e20aa41..c1dbd5a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,12 @@ git: depth: 1 env: - GO111MODULE=on -go: [1.13.x, 1.14.x] -os: [linux, osx] +go: 1.15.x +os: linux install: - ./travis/install.sh script: - - ./travis/cross_build.sh - - ./travis/lint.sh - - export GOMAXPROCS=4 - - export GORACE=halt_on_error=1 - - go test -race -v ./... - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi + - cd ci + - go run mage.go -v -w ../ crossBuild + - go run mage.go -v -w ../ lint + - go run mage.go -v -w ../ test diff --git a/CHANGELOG.md b/CHANGELOG.md index 584026d6..7567f612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# 1.8.1 +Code quality: + * move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer + * improve timestamp format documentation + +Fixes: + * fix race condition on logger hooks + + +# 1.8.0 + +Correct versioning number replacing v1.7.1. + +# 1.7.1 + +Beware this release has introduced a new public API and its semver is therefore incorrect. + +Code quality: + * use go 1.15 in travis + * use magefile as task runner + +Fixes: + * small fixes about new go 1.13 error formatting system + * Fix for long time race condiction with mutating data hooks + +Features: + * build support for zos + +# 1.7.0 +Fixes: + * the dependency toward a windows terminal library has been removed + +Features: + * a new buffer pool management API has been added + * a set of `<LogLevel>Fn()` functions have been added + # 1.6.0 Fixes: * end of line cleanup diff --git a/README.md b/README.md index 5796706d..b042c896 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/sirupsen/logrus) [](https://godoc.org/github.com/sirupsen/logrus) +# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [](https://travis-ci.org/sirupsen/logrus) [](https://pkg.go.dev/github.com/sirupsen/logrus) Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. @@ -341,7 +341,7 @@ import ( log "github.com/sirupsen/logrus" ) -init() { +func init() { // do something here to set environment depending on an environment variable // or command-line flag if Environment == "production" { @@ -402,7 +402,7 @@ func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { // source of the official loggers. serialized, err := json.Marshal(entry.Data) if err != nil { - return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err) } return append(serialized, '\n'), nil } diff --git a/buffer_pool.go b/buffer_pool.go index 4545dec0..c7787f77 100644 --- a/buffer_pool.go +++ b/buffer_pool.go @@ -26,15 +26,6 @@ func (p *defaultPool) Get() *bytes.Buffer { return p.pool.Get().(*bytes.Buffer) } -func getBuffer() *bytes.Buffer { - return bufferPool.Get() -} - -func putBuffer(buf *bytes.Buffer) { - buf.Reset() - bufferPool.Put(buf) -} - // SetBufferPool allows to replace the default logrus buffer pool // to better meets the specific needs of an application. func SetBufferPool(bp BufferPool) { diff --git a/ci/go.mod b/ci/go.mod new file mode 100644 index 00000000..e895e95a --- /dev/null +++ b/ci/go.mod @@ -0,0 +1,5 @@ +module github.com/sirupsen/logrus/ci + +go 1.15 + +require github.com/magefile/mage v1.11.0 diff --git a/ci/go.sum b/ci/go.sum new file mode 100644 index 00000000..edf273b8 --- /dev/null +++ b/ci/go.sum @@ -0,0 +1,2 @@ +github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= +github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= diff --git a/ci/mage.go b/ci/mage.go new file mode 100644 index 00000000..4273031d --- /dev/null +++ b/ci/mage.go @@ -0,0 +1,10 @@ +// +build ignore + +package main + +import ( + "github.com/magefile/mage/mage" + "os" +) + +func main() { os.Exit(mage.Main()) } diff --git a/ci/magefile.go b/ci/magefile.go new file mode 100644 index 00000000..ceda305c --- /dev/null +++ b/ci/magefile.go @@ -0,0 +1,123 @@ +//go:build mage + +package main + +import ( + "encoding/json" + "fmt" + "os" + "path" + "sort" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +func intersect(a, b []string) []string { + sort.Strings(a) + sort.Strings(b) + + res := make([]string, 0, func() int { + if len(a) < len(b) { + return len(a) + } + return len(b) + }()) + + for _, v := range a { + idx := sort.SearchStrings(b, v) + if idx < len(b) && b[idx] == v { + res = append(res, v) + } + } + return res +} + +// getBuildMatrix returns the build matrix from the current version of the go compiler +func getFullBuildMatrix() (map[string][]string, error) { + jsonData, err := sh.Output("go", "tool", "dist", "list", "-json") + if err != nil { + return nil, err + } + var data []struct { + Goos string + Goarch string + } + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + return nil, err + } + + matrix := map[string][]string{} + for _, v := range data { + if val, ok := matrix[v.Goos]; ok { + matrix[v.Goos] = append(val, v.Goarch) + } else { + matrix[v.Goos] = []string{v.Goarch} + } + } + + return matrix, nil +} + +func getBuildMatrix() (map[string][]string, error) { + minimalMatrix := map[string][]string{ + "linux": []string{"amd64"}, + "darwin": []string{"amd64", "arm64"}, + "freebsd": []string{"amd64"}, + "js": []string{"wasm"}, + "solaris": []string{"amd64"}, + "windows": []string{"amd64", "arm64"}, + } + + fullMatrix, err := getFullBuildMatrix() + if err != nil { + return nil, err + } + + for os, arches := range minimalMatrix { + if fullV, ok := fullMatrix[os]; !ok { + delete(minimalMatrix, os) + } else { + minimalMatrix[os] = intersect(arches, fullV) + } + } + return minimalMatrix, nil +} + +func CrossBuild() error { + matrix, err := getBuildMatrix() + if err != nil { + return err + } + + for os, arches := range matrix { + for _, arch := range arches { + env := map[string]string{ + "GOOS": os, + "GOARCH": arch, + } + if mg.Verbose() { + fmt.Printf("Building for GOOS=%s GOARCH=%s\n", os, arch) + } + if err := sh.RunWith(env, "go", "build", "./..."); err != nil { + return err + } + } + } + return nil +} + +func Lint() error { + gopath := os.Getenv("GOPATH") + if gopath == "" { + return fmt.Errorf("cannot retrieve GOPATH") + } + + return sh.Run(path.Join(gopath, "bin", "golangci-lint"), "run", "./...") +} + +// Run the test suite +func Test() error { + return sh.RunWith(map[string]string{"GORACE": "halt_on_error=1"}, + "go", "test", "-race", "-v", "./...") +} diff --git a/entry.go b/entry.go index 5a5cbfe7..71cdbbc3 100644 --- a/entry.go +++ b/entry.go @@ -78,6 +78,14 @@ func NewEntry(logger *Logger) *Entry { } } +func (entry *Entry) Dup() *Entry { + data := make(Fields, len(entry.Data)) + for k, v := range entry.Data { + data[k] = v + } + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} +} + // Returns the bytes representation of this entry from the formatter. func (entry *Entry) Bytes() ([]byte, error) { return entry.Logger.Formatter.Format(entry) @@ -123,11 +131,9 @@ func (entry *Entry) WithFields(fields Fields) *Entry { for k, v := range fields { isErrField := false if t := reflect.TypeOf(v); t != nil { - switch t.Kind() { - case reflect.Func: + switch { + case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func: isErrField = true - case reflect.Ptr: - isErrField = t.Elem().Kind() == reflect.Func } } if isErrField { @@ -212,54 +218,66 @@ func (entry Entry) HasCaller() (has bool) { entry.Caller != nil } -// This function is not declared with a pointer value because otherwise -// race conditions will occur when using multiple goroutines -func (entry Entry) log(level Level, msg string) { +func (entry *Entry) log(level Level, msg string) { var buffer *bytes.Buffer - // Default to now, but allow users to override if they want. - // - // We don't have to worry about polluting future calls to Entry#log() - // with this assignment because this function is declared with a - // non-pointer receiver. - if entry.Time.IsZero() { - entry.Time = time.Now() - } + newEntry := entry.Dup() - entry.Level = level - entry.Message = msg - entry.Logger.mu.Lock() - if entry.Logger.ReportCaller { - entry.Caller = getCaller() + if newEntry.Time.IsZero() { + newEntry.Time = time.Now() } - entry.Logger.mu.Unlock() - entry.fireHooks() + newEntry.Level = level + newEntry.Message = msg + + newEntry.Logger.mu.Lock() + reportCaller := newEntry.Logger.ReportCaller + bufPool := newEntry.getBufferPool() + newEntry.Logger.mu.Unlock() - buffer = getBuffer() + if reportCaller { + newEntry.Caller = getCaller() + } + + newEntry.fireHooks() + buffer = bufPool.Get() defer func() { - entry.Buffer = nil - putBuffer(buffer) + newEntry.Buffer = nil + buffer.Reset() + bufPool.Put(buffer) }() buffer.Reset() - entry.Buffer = buffer + newEntry.Buffer = buffer - entry.write() + newEntry.write() - entry.Buffer = nil + newEntry.Buffer = nil // To avoid Entry#log() returning a value that only would make sense for // panic() to use in Entry#Panic(), we avoid the allocation by checking // directly here. if level <= PanicLevel { - panic(&entry) + panic(newEntry) + } +} + +func (entry *Entry) getBufferPool() (pool BufferPool) { + if entry.Logger.BufferPool != nil { + return entry.Logger.BufferPool } + return bufferPool } func (entry *Entry) fireHooks() { + var tmpHooks LevelHooks entry.Logger.mu.Lock() - defer entry.Logger.mu.Unlock() - err := entry.Logger.Hooks.Fire(entry.Level, entry) + tmpHooks = make(LevelHooks, len(entry.Logger.Hooks)) + for k, v := range entry.Logger.Hooks { + tmpHooks[k] = v + } + entry.Logger.mu.Unlock() + + err := tmpHooks.Fire(entry.Level, entry) if err != nil { fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) } @@ -273,11 +291,14 @@ func (entry *Entry) write() { fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) return } - if _, err = entry.Logger.Out.Write(serialized); err != nil { + if _, err := entry.Logger.Out.Write(serialized); err != nil { fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) } } +// Log will log a message at the level given as parameter. +// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit. +// For this behaviour Entry.Panic or Entry.Fatal should be used instead. func (entry *Entry) Log(level Level, args ...interface{}) { if entry.Logger.IsLevelEnabled(level) { entry.log(level, fmt.Sprint(args...)) @@ -319,7 +340,6 @@ func (entry *Entry) Fatal(args ...interface{}) { func (entry *Entry) Panic(args ...interface{}) { entry.Log(PanicLevel, args...) - panic(fmt.Sprint(args...)) } // Entry Printf family functions diff --git a/entry_test.go b/entry_test.go index b98e14ad..41c47a2f 100644 --- a/entry_test.go +++ b/entry_test.go @@ -167,6 +167,28 @@ func TestEntryPanicf(t *testing.T) { entry.WithField("err", errBoom).Panicf("kaboom %v", true) } +func TestEntryPanic(t *testing.T) { + errBoom := fmt.Errorf("boom again") + + defer func() { + p := recover() + assert.NotNil(t, p) + + switch pVal := p.(type) { + case *Entry: + assert.Equal(t, "kaboom", pVal.Message) + assert.Equal(t, errBoom, pVal.Data["err"]) + default: + t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) + } + }() + + logger := New() + logger.Out = &bytes.Buffer{} + entry := NewEntry(logger) + entry.WithField("err", errBoom).Panic("kaboom") +} + const ( badMessage = "this is going to panic" panicMessage = "this is broken" @@ -210,7 +232,7 @@ func TestEntryWithIncorrectField(t *testing.T) { fn := func() {} - e := Entry{} + e := Entry{Logger: New()} eWithFunc := e.WithFields(Fields{"func": fn}) eWithFuncPtr := e.WithFields(Fields{"funcPtr": &fn}) @@ -247,6 +269,12 @@ func TestEntryLogfLevel(t *testing.T) { func TestEntryReportCallerRace(t *testing.T) { logger := New() entry := NewEntry(logger) + + // logging before SetReportCaller has the highest chance of causing a race condition + // to be detected, but doing it twice just to increase the likelyhood of detecting the race + go func() { + entry.Info("should not race") + }() go func() { logger.SetReportCaller(true) }() @@ -254,3 +282,20 @@ func TestEntryReportCallerRace(t *testing.T) { entry.Info("should not race") }() } + +func TestEntryFormatterRace(t *testing.T) { + logger := New() + entry := NewEntry(logger) + + // logging before SetReportCaller has the highest chance of causing a race condition + // to be detected, but doing it twice just to increase the likelyhood of detecting the race + go func() { + entry.Info("should not race") + }() + go func() { + logger.SetFormatter(&TextFormatter{}) + }() + go func() { + entry.Info("should not race") + }() +} diff --git a/go.mod b/go.mod index b3919d5e..8b3f6d37 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,8 @@ module github.com/sirupsen/logrus require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.2.2 - golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 + github.com/stretchr/testify v1.7.0 + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 ) go 1.13 diff --git a/go.sum b/go.sum index 1edc143b..e5fdc85b 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,14 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hook_test.go b/hook_test.go index b5cf077b..a2becc82 100644 --- a/hook_test.go +++ b/hook_test.go @@ -3,6 +3,7 @@ package logrus_test import ( "bytes" "encoding/json" + "fmt" "sync" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/require" . "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" . "github.com/sirupsen/logrus/internal/testutils" ) @@ -191,6 +193,20 @@ func TestAddHookRace(t *testing.T) { }) } +func TestAddHookRace2(t *testing.T) { + t.Parallel() + + for i := 0; i < 3; i++ { + testname := fmt.Sprintf("Test %d", i) + t.Run(testname, func(t *testing.T) { + t.Parallel() + + _ = test.NewGlobal() + Info(testname) + }) + } +} + type HookCallFunc struct { F func() } diff --git a/json_formatter.go b/json_formatter.go index ba7f2371..c96dc563 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -23,6 +23,9 @@ func (f FieldMap) resolve(key fieldKey) string { // JSONFormatter formats logs into parsable json type JSONFormatter struct { // TimestampFormat sets the format used for marshaling timestamps. + // The format to use is the same than for time.Format or time.Parse from the standard + // library. + // The standard Library already provides a set of predefined format. TimestampFormat string // DisableTimestamp allows disabling automatic timestamps in output @@ -118,7 +121,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, %w", err) } return b.Bytes(), nil diff --git a/logger.go b/logger.go index dbf627c9..5ff0aef6 100644 --- a/logger.go +++ b/logger.go @@ -12,7 +12,7 @@ import ( // LogFunction For big messages, it can be more efficient to pass a function // and only call it if the log level is actually enables rather than // generating the log message and then checking if the level is enabled -type LogFunction func()[]interface{} +type LogFunction func() []interface{} type Logger struct { // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a @@ -44,6 +44,9 @@ type Logger struct { entryPool sync.Pool // Function to exit the application, defaults to `os.Exit()` ExitFunc exitFunc + // The buffer pool used to format the log. If it is nil, the default global + // buffer pool will be used. + BufferPool BufferPool } type exitFunc func(int) @@ -192,6 +195,9 @@ func (logger *Logger) Panicf(format string, args ...interface{}) { logger.Logf(PanicLevel, format, args...) } +// Log will log a message at the level given as parameter. +// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit. +// For this behaviour Logger.Panic or Logger.Fatal should be used instead. func (logger *Logger) Log(level Level, args ...interface{}) { if logger.IsLevelEnabled(level) { entry := logger.newEntry() @@ -402,3 +408,10 @@ func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { logger.mu.Unlock() return oldHooks } + +// SetBufferPool sets the logger buffer pool. +func (logger *Logger) SetBufferPool(pool BufferPool) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.BufferPool = pool +} diff --git a/logger_test.go b/logger_test.go index f12a04e7..595c6821 100644 --- a/logger_test.go +++ b/logger_test.go @@ -25,7 +25,7 @@ func TestFieldValueError(t *testing.T) { t.Error("unexpected error", err) } _, ok := data[FieldKeyLogrusError] - require.True(t, ok) + require.True(t, ok, `cannot found expected "logrus_error" field: %v`, data) } func TestNoFieldValueError(t *testing.T) { @@ -67,3 +67,31 @@ func TestWarninglnNotEqualToWarning(t *testing.T) { assert.NotEqual(t, buf.String(), bufln.String(), "Warning() and Wantingln() should not be equal") } + +type testBufferPool struct { + buffers []*bytes.Buffer + get int +} + +func (p *testBufferPool) Get() *bytes.Buffer { + p.get++ + return new(bytes.Buffer) +} + +func (p *testBufferPool) Put(buf *bytes.Buffer) { + p.buffers = append(p.buffers, buf) +} + +func TestLogger_SetBufferPool(t *testing.T) { + out := &bytes.Buffer{} + l := New() + l.SetOutput(out) + + pool := new(testBufferPool) + l.SetBufferPool(pool) + + l.Info("test") + + assert.Equal(t, pool.get, 1, "Logger.SetBufferPool(): The BufferPool.Get() must be called") + assert.Len(t, pool.buffers, 1, "Logger.SetBufferPool(): The BufferPool.Put() must be called") +} diff --git a/logrus_test.go b/logrus_test.go index 9c3c920d..4edee283 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -588,15 +588,48 @@ func TestLoggingRaceWithHooksOnEntry(t *testing.T) { logger.AddHook(hook) entry := logger.WithField("context", "clue") - var wg sync.WaitGroup + var ( + wg sync.WaitGroup + mtx sync.Mutex + start bool + ) + + cond := sync.NewCond(&mtx) + wg.Add(100) - for i := 0; i < 100; i++ { + for i := 0; i < 50; i++ { + go func() { + cond.L.Lock() + for !start { + cond.Wait() + } + cond.L.Unlock() + for j := 0; j < 100; j++ { + entry.Info("info") + } + wg.Done() + }() + } + + for i := 0; i < 50; i++ { go func() { - entry.Info("info") + cond.L.Lock() + for !start { + cond.Wait() + } + cond.L.Unlock() + for j := 0; j < 100; j++ { + entry.WithField("another field", "with some data").Info("info") + } wg.Done() }() } + + cond.L.Lock() + start = true + cond.L.Unlock() + cond.Broadcast() wg.Wait() } diff --git a/terminal_check_unix.go b/terminal_check_unix.go index cc4fe6e3..04748b85 100644 --- a/terminal_check_unix.go +++ b/terminal_check_unix.go @@ -1,4 +1,4 @@ -// +build linux aix +// +build linux aix zos // +build !js package logrus diff --git a/text_formatter.go b/text_formatter.go index 3c28b54c..be2c6efe 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -53,7 +53,10 @@ type TextFormatter struct { // the time passed since beginning of execution. FullTimestamp bool - // TimestampFormat to use for display when a full timestamp is printed + // TimestampFormat to use for display when a full timestamp is printed. + // The format to use is the same than for time.Format or time.Parse from the standard + // library. + // The standard Library already provides a set of predefined format. TimestampFormat string // The fields are sorted by default for a consistent output. For applications @@ -235,6 +238,8 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin levelColor = yellow case ErrorLevel, FatalLevel, PanicLevel: levelColor = red + case InfoLevel: + levelColor = blue default: levelColor = blue } diff --git a/travis/install.sh b/travis/install.sh index 5fc40dd8..837c82d0 100755 --- a/travis/install.sh +++ b/travis/install.sh @@ -2,21 +2,7 @@ set -e -# Install golanci 1.21.0 -if [[ "$TRAVIS_GO_VERSION" =~ ^1\.13\. ]]; then - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0 -fi - -# Only do this for go1.12 when modules are on so that it doesn't need to be done when modules are off as well. -if [[ "$TRAVIS_GO_VERSION" =~ ^1\.13\. ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$GO111MODULE" == "on" ]]; then - GO111MODULE=off go get github.com/dgsb/gox -fi - -if [[ "$GO111MODULE" == "on" ]]; then - go mod download -fi - -if [[ "$GO111MODULE" == "off" ]]; then - # Should contain all regular (not indirect) modules from go.mod - go get github.com/stretchr/testify golang.org/x/sys/unix golang.org/x/sys/windows +# Install golanci 1.32.2 +if [[ "$TRAVIS_GO_VERSION" =~ ^1\.15\. ]]; then + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.32.2 fi diff --git a/travis/lint.sh b/travis/lint.sh deleted file mode 100755 index 0ed1d7c7..00000000 --- a/travis/lint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -if [[ "$TRAVIS_GO_VERSION" =~ ^1\.13\. ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$GO111MODULE" == "on" ]]; then - $(go env GOPATH)/bin/golangci-lint run ./... -fi -- GitLab