mirror of
https://github.com/docker/cli.git
synced 2026-08-24 10:05:37 -05:00
Merge pull request #7222 from thaJeztah/bump_logrus
vendor: github.com/sirupsen/logrus v1.10.1
This commit is contained in:
+1
-1
@@ -46,7 +46,7 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/sirupsen/logrus v1.10.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||
|
||||
+4
-6
@@ -32,7 +32,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
@@ -153,7 +152,6 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
@@ -176,8 +174,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q=
|
||||
github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -187,8 +185,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
||||
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 h1:TvtdmeYsYEij78hS4oxnwikoiLdIrgav3BA+CbhaDAI=
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
|
||||
+10
-38
@@ -1,12 +1,9 @@
|
||||
version: "2"
|
||||
run:
|
||||
tests: false
|
||||
linters:
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- contextcheck
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
@@ -22,46 +19,21 @@ linters:
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- noctx
|
||||
- protogetter
|
||||
- reassign
|
||||
- recvcheck
|
||||
- rowserrcheck
|
||||
- spancheck
|
||||
- sqlclosecheck
|
||||
- testifylint
|
||||
- unparam
|
||||
- zerologlint
|
||||
disable:
|
||||
- prealloc
|
||||
settings:
|
||||
errcheck:
|
||||
check-type-assertions: false
|
||||
check-blank: false
|
||||
lll:
|
||||
line-length: 100
|
||||
tab-width: 4
|
||||
prealloc:
|
||||
simple: false
|
||||
range-loops: false
|
||||
for-loops: false
|
||||
whitespace:
|
||||
multi-if: false
|
||||
multi-func: false
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gosec
|
||||
- musttag
|
||||
- noctx # TODO: enable once we switch to Go 1.24+.
|
||||
- linters: # TODO: remove once golangci-lint is updated with https://github.com/golangci/golangci-lint/pull/6584
|
||||
- gocheckcompilerdirectives
|
||||
text: 'compiler directive unrecognized: //go:fix'
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
language: go
|
||||
go_import_path: github.com/sirupsen/logrus
|
||||
git:
|
||||
depth: 1
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
go: 1.15.x
|
||||
os: linux
|
||||
install:
|
||||
- ./travis/install.sh
|
||||
script:
|
||||
- cd ci
|
||||
- go run mage.go -v -w ../ crossBuild
|
||||
- go run mage.go -v -w ../ lint
|
||||
- go run mage.go -v -w ../ test
|
||||
+201
-38
@@ -1,90 +1,246 @@
|
||||
# 1.8.1
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.10.1
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix a regression introduced in v1.10.0 where `TextFormatter` could panic
|
||||
when formatting nil or panicking `error` and `fmt.Stringer` values.
|
||||
* Allow function-backed implementations of `error` as field values.
|
||||
|
||||
## 1.10.0
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix reentrant logging deadlocks in formatter paths.
|
||||
* Fix race conditions in formatter and entry handling.
|
||||
* Fix generic `Log`, `Logf`, `Logln`, and `LogFn` methods unexpectedly
|
||||
panicking when called with `PanicLevel`. Use the corresponding `Panic`
|
||||
methods when panic behavior is desired.
|
||||
* Improve concurrency safety around formatter and hook access.
|
||||
|
||||
Features:
|
||||
|
||||
* Add `slog` hook for forwarding Logrus entries to `log/slog`.
|
||||
* Add `slog.Handler` for forwarding `log/slog` records to a Logrus logger,
|
||||
including levels, fields, groups, context, time, and optional caller
|
||||
reporting. The hook and handler can also be combined to help migrate
|
||||
between Logrus and `log/slog`.
|
||||
* Add minimal, composable logging interfaces for each log level. This enables
|
||||
consumers to depend on narrower interfaces, making it easier to substitute
|
||||
or adapt logging implementations.
|
||||
* Allow `Entry.Caller` to be set explicitly and preserve it across derived
|
||||
entries, enabling custom caller detection without Logrus overwriting
|
||||
caller information when `ReportCaller` is enabled.
|
||||
|
||||
Changed:
|
||||
|
||||
* Raise minimum supported Go version to 1.23.
|
||||
* TextFormatter now renders `[]byte` values as raw/quoted strings instead of slice-of-ints.
|
||||
* TextFormatter now uses distinct dimmed colors for debug and trace output.
|
||||
* TextFormatter now automatically enables colors on Windows terminals with ANSI support,
|
||||
matching the behavior on other platforms.
|
||||
* `Entry.HasCaller` is now deprecated in favor of checking `Entry.Caller` directly.
|
||||
* Deprecated `MutexWrap`, which was unintentionally exposed as public API.
|
||||
It remains available as an alias for compatibility but should not be used
|
||||
directly.
|
||||
|
||||
Performance:
|
||||
|
||||
* Significantly improve TextFormatter performance and reduce allocations.
|
||||
* Optimize common Entry and Logger hot paths.
|
||||
* Reduce allocations in caller reporting.
|
||||
* ~17% lower geomean runtime and ~27% higher formatter throughput overall.
|
||||
* Common enabled logging paths are ~30–44% faster.
|
||||
* TextFormatter paths are up to ~40% faster, with allocation counts reduced
|
||||
by 25–74% across the measured formatter cases.
|
||||
|
||||
|
||||
## 1.9.4
|
||||
|
||||
Fixes:
|
||||
|
||||
* Remove uses of deprecated `ioutil` package
|
||||
|
||||
Features:
|
||||
|
||||
* Add GNU/Hurd support
|
||||
* Add WASI wasip1 support
|
||||
|
||||
Code quality:
|
||||
|
||||
* Update minimum supported Go version to 1.17
|
||||
* Documentation updates
|
||||
|
||||
|
||||
## 1.9.3
|
||||
|
||||
Fixes:
|
||||
|
||||
* Re-apply fix for potential denial of service in logrus.Writer() when logging >64KB single-line payloads without newlines (#1376)
|
||||
* Fix panic in Writer
|
||||
|
||||
|
||||
## 1.9.2
|
||||
|
||||
Fixes:
|
||||
|
||||
* Revert Writer DoS fix (#1376) due to regression
|
||||
|
||||
|
||||
## 1.9.1
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix potential denial of service in logrus.Writer() when logging >64KB single-line payloads without newlines (#1376)
|
||||
|
||||
|
||||
## 1.9.0
|
||||
|
||||
Fixes:
|
||||
|
||||
* Multiple concurrency and race condition fixes
|
||||
* Improve Windows terminal and ANSI handling
|
||||
|
||||
Code quality:
|
||||
|
||||
* Internal cleanups and modernization
|
||||
|
||||
|
||||
## 1.8.3
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix potential denial of service in logrus.Writer() when logging >64KB single-line payloads without newlines (#1376)
|
||||
|
||||
|
||||
## 1.8.2
|
||||
|
||||
Features:
|
||||
|
||||
* Add support for the logger private buffer pool (#1253)
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix race condition for SetFormatter and SetReportCaller
|
||||
* Fix data race in hooks test package
|
||||
|
||||
## 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
|
||||
## 1.8.0
|
||||
|
||||
Correct versioning number replacing v1.7.1.
|
||||
|
||||
# 1.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
|
||||
## 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
|
||||
## 1.6.0
|
||||
|
||||
Fixes:
|
||||
|
||||
* end of line cleanup
|
||||
* revert the entry concurrency bug fix which leads to deadlock under some circumstances
|
||||
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
||||
|
||||
Features:
|
||||
|
||||
* add an option to the `TextFormatter` to completely disable fields quoting
|
||||
|
||||
# 1.5.0
|
||||
## 1.5.0
|
||||
|
||||
Code quality:
|
||||
|
||||
* add golangci linter run on travis
|
||||
|
||||
Fixes:
|
||||
|
||||
* add mutex for hooks concurrent access on `Entry` data
|
||||
* caller function field for go1.14
|
||||
* fix build issue for gopherjs target
|
||||
|
||||
Feature:
|
||||
|
||||
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
||||
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
||||
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
||||
|
||||
# 1.4.2
|
||||
## 1.4.2
|
||||
|
||||
* Fixes build break for plan9, nacl, solaris
|
||||
# 1.4.1
|
||||
|
||||
## 1.4.1
|
||||
|
||||
This new release introduces:
|
||||
|
||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||
|
||||
# 1.4.0
|
||||
## 1.4.0
|
||||
|
||||
This new release introduces:
|
||||
|
||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||
* Fix race condition in `getCaller` (#916).
|
||||
|
||||
|
||||
# 1.3.0
|
||||
## 1.3.0
|
||||
|
||||
This new release introduces:
|
||||
|
||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||
|
||||
Fixes:
|
||||
|
||||
* Building prometheus node_exporter on AIX (#840)
|
||||
* Race condition in TextFormatter (#468)
|
||||
* Travis CI import path (#868)
|
||||
@@ -92,20 +248,26 @@ Fixes:
|
||||
* Pointer to func as field in JSONFormatter (#870)
|
||||
* Properly marshal Levels (#873)
|
||||
|
||||
# 1.2.0
|
||||
## 1.2.0
|
||||
|
||||
This new release introduces:
|
||||
|
||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||
* A new trace level named `Trace` whose level is below `Debug`
|
||||
* A configurable exit function to be called upon a Fatal trace
|
||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||
|
||||
# 1.1.1
|
||||
## 1.1.1
|
||||
|
||||
This is a bug fix release.
|
||||
|
||||
* fix the build break on Solaris
|
||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||
|
||||
# 1.1.0
|
||||
## 1.1.0
|
||||
|
||||
This new release introduces:
|
||||
|
||||
* several fixes:
|
||||
* a fix for a race condition on entry formatting
|
||||
* proper cleanup of previously used entries before putting them back in the pool
|
||||
@@ -122,9 +284,10 @@ This new release introduces:
|
||||
* the field sort function is now configurable for text formatter
|
||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||
|
||||
# 1.0.6
|
||||
## 1.0.6
|
||||
|
||||
This new release introduces:
|
||||
|
||||
* a new api WithTime which allows to easily force the time of the log entry
|
||||
which is mostly useful for logger wrapper
|
||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||
@@ -134,71 +297,71 @@ This new release introduces:
|
||||
* a new configuration of the textformatter to configure the name of the default keys
|
||||
* a new configuration of the text formatter to disable the level truncation
|
||||
|
||||
# 1.0.5
|
||||
## 1.0.5
|
||||
|
||||
* Fix hooks race (#707)
|
||||
* Fix panic deadlock (#695)
|
||||
|
||||
# 1.0.4
|
||||
## 1.0.4
|
||||
|
||||
* Fix race when adding hooks (#612)
|
||||
* Fix terminal check in AppEngine (#635)
|
||||
|
||||
# 1.0.3
|
||||
## 1.0.3
|
||||
|
||||
* Replace example files with testable examples
|
||||
|
||||
# 1.0.2
|
||||
## 1.0.2
|
||||
|
||||
* bug: quote non-string values in text formatter (#583)
|
||||
* Make (*Logger) SetLevel a public method
|
||||
|
||||
# 1.0.1
|
||||
## 1.0.1
|
||||
|
||||
* bug: fix escaping in text formatter (#575)
|
||||
|
||||
# 1.0.0
|
||||
## 1.0.0
|
||||
|
||||
* Officially changed name to lower-case
|
||||
* bug: colors on Windows 10 (#541)
|
||||
* bug: fix race in accessing level (#512)
|
||||
|
||||
# 0.11.5
|
||||
## 0.11.5
|
||||
|
||||
* feature: add writer and writerlevel to entry (#372)
|
||||
|
||||
# 0.11.4
|
||||
## 0.11.4
|
||||
|
||||
* bug: fix undefined variable on solaris (#493)
|
||||
|
||||
# 0.11.3
|
||||
## 0.11.3
|
||||
|
||||
* formatter: configure quoting of empty values (#484)
|
||||
* formatter: configure quoting character (default is `"`) (#484)
|
||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||
|
||||
# 0.11.2
|
||||
## 0.11.2
|
||||
|
||||
* bug: fix windows terminal detection (#476)
|
||||
|
||||
# 0.11.1
|
||||
## 0.11.1
|
||||
|
||||
* bug: fix tty detection with custom out (#471)
|
||||
|
||||
# 0.11.0
|
||||
## 0.11.0
|
||||
|
||||
* performance: Use bufferpool to allocate (#370)
|
||||
* terminal: terminal detection for app-engine (#343)
|
||||
* feature: exit handler (#375)
|
||||
|
||||
# 0.10.0
|
||||
## 0.10.0
|
||||
|
||||
* feature: Add a test hook (#180)
|
||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||
* performance: avoid re-allocations on `WithFields` (#335)
|
||||
|
||||
# 0.9.0
|
||||
## 0.9.0
|
||||
|
||||
* logrus/text_formatter: don't emit empty msg
|
||||
* logrus/hooks/airbrake: move out of main repository
|
||||
@@ -210,25 +373,25 @@ This new release introduces:
|
||||
* logrus/core: support `WithError` on logger
|
||||
* logrus/core: Solaris support
|
||||
|
||||
# 0.8.7
|
||||
## 0.8.7
|
||||
|
||||
* logrus/core: fix possible race (#216)
|
||||
* logrus/doc: small typo fixes and doc improvements
|
||||
|
||||
|
||||
# 0.8.6
|
||||
## 0.8.6
|
||||
|
||||
* hooks/raven: allow passing an initialized client
|
||||
|
||||
# 0.8.5
|
||||
## 0.8.5
|
||||
|
||||
* logrus/core: revert #208
|
||||
|
||||
# 0.8.4
|
||||
## 0.8.4
|
||||
|
||||
* formatter/text: fix data race (#218)
|
||||
|
||||
# 0.8.3
|
||||
## 0.8.3
|
||||
|
||||
* logrus/core: fix entry log level (#208)
|
||||
* logrus/core: improve performance of text formatter by 40%
|
||||
@@ -236,24 +399,24 @@ This new release introduces:
|
||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||
* formatter/text: print structs more verbosely
|
||||
|
||||
# 0.8.2
|
||||
## 0.8.2
|
||||
|
||||
* logrus: fix more Fatal family functions
|
||||
|
||||
# 0.8.1
|
||||
## 0.8.1
|
||||
|
||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||
|
||||
# 0.8.0
|
||||
## 0.8.0
|
||||
|
||||
* logrus: defaults to stderr instead of stdout
|
||||
* hooks/sentry: add special field for `*http.Request`
|
||||
* formatter/text: ignore Windows for colors
|
||||
|
||||
# 0.7.3
|
||||
## 0.7.3
|
||||
|
||||
* formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
## 0.7.2
|
||||
|
||||
* formatter/text: Add configuration option for time format (#158)
|
||||
|
||||
+39
-53
@@ -3,13 +3,10 @@
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger.
|
||||
|
||||
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
||||
simply too hard to do in a way that won't break many people's projects, which is
|
||||
the last thing you want from your Logging library (again...).
|
||||
|
||||
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
||||
security, (backwards compatible) bug fixes, and performance (where we are
|
||||
limited by the interface).
|
||||
**Logrus is in maintenance mode.** The project focuses on security, bug fixes,
|
||||
and performance improvements. New features are not planned, aside from changes
|
||||
required to provide interoperability with other logging ecosystems (e.g., Go's
|
||||
[log/slog](https://pkg.go.dev/log/slog)).
|
||||
|
||||
I believe Logrus' biggest contribution is to have played a part in today's
|
||||
widespread use of structured logging in Golang. There doesn't seem to be a
|
||||
@@ -23,18 +20,6 @@ about structured logging in Go today. Check out, for example,
|
||||
[zap]: https://github.com/uber-go/zap
|
||||
[apex]: https://github.com/apex/log
|
||||
|
||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||
this caused issues in the community and we needed a standard. Some environments
|
||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||
Everything using `logrus` will need to use the lower-case:
|
||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||
|
||||
To fix Glide, see [these
|
||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||
For an in-depth explanation of the casing issue, see [this
|
||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||
@@ -43,35 +28,27 @@ plain text):
|
||||
With `logrus.SetFormatter(&logrus.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```text
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```json lines
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
{"level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!","size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.","size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `logrus.SetFormatter(&logrus.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](https://pkg.go.dev/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
```bash
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" animal=orca err="It's over 9000!" number=100 omg=true size=9009
|
||||
```
|
||||
|
||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||
|
||||
```go
|
||||
@@ -88,29 +65,32 @@ If you wish to add the calling method as a field, instruct the logger via:
|
||||
```go
|
||||
logrus.SetReportCaller(true)
|
||||
```
|
||||
|
||||
This adds the caller as 'method' like so:
|
||||
|
||||
```json
|
||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by","time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||
```
|
||||
|
||||
```text
|
||||
```bash
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||
```
|
||||
|
||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||
environment via benchmarks:
|
||||
|
||||
```bash
|
||||
go test -bench=.*CallerTracing
|
||||
go test -bench=ReportCaller
|
||||
```
|
||||
|
||||
#### Case-sensitivity
|
||||
|
||||
The organization's name was changed to lower-case--and this will not be changed
|
||||
back. If you are getting import conflicts due to case sensitivity, please use
|
||||
the lower-case import: `github.com/sirupsen/logrus`.
|
||||
The organization's name was [changed to lower-case][1]. If you are getting import
|
||||
conflicts due to case sensitivity, please use the lower-case import:
|
||||
`github.com/sirupsen/logrus`.
|
||||
|
||||
[1]: https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -289,6 +269,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: Syslog hooks also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||
@@ -367,18 +348,21 @@ Splunk or Logstash.
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
* [`logrus.TextFormatter`](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter)
|
||||
logs the event in colors if the logger output is a TTY, otherwise without colors.
|
||||
* To force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`. For Windows, see
|
||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||
`DisableColors` field to `true`.
|
||||
* On modern Windows terminals with ANSI (Virtual Terminal) support, TextFormatter
|
||||
automatically enables colored output.
|
||||
* If your environment does not support ANSI escape sequences, wrap the logger output
|
||||
using [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable)
|
||||
and set `ForceColors` (or `CLICOLOR_FORCE=1`) to enable colors through the wrapper.
|
||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||
truncation set the `DisableLevelTruncation` field to `true`.
|
||||
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
||||
* All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter).
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter).
|
||||
* [`logrus.JSONFormatter`](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter)
|
||||
logs fields as JSON.
|
||||
|
||||
Third-party logging formatters:
|
||||
|
||||
@@ -390,10 +374,12 @@ Third-party logging formatters:
|
||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Save log to files.
|
||||
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
||||
* [`easy-logrus-formatter`](https://github.com/WeiZhixiong/easy-logrus-formatter). Provide a user-friendly formatter for logrus.
|
||||
* [`redactrus`](https://github.com/ibreakthecloud/redactrus). Redacts sensitive information like password, apikeys, email, etc. from logs.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
`Fields` type (`map[string]any`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
@@ -516,4 +502,4 @@ Situations when locking is not needed include:
|
||||
|
||||
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
||||
|
||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
||||
(Refer to <http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/>)
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ func Exit(code int) {
|
||||
//
|
||||
// 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 database connections, or sending an alert that the application is
|
||||
// closing.
|
||||
func RegisterExitHandler(handler func()) {
|
||||
handlers = append(handlers, handler)
|
||||
@@ -69,7 +69,7 @@ func RegisterExitHandler(handler func()) {
|
||||
//
|
||||
// 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 database connections, or sending an alert that the application is
|
||||
// closing.
|
||||
func DeferExitHandler(handler func()) {
|
||||
handlers = append([]func(){handler}, handlers...)
|
||||
|
||||
+8
-14
@@ -5,9 +5,13 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
bufferPool BufferPool
|
||||
)
|
||||
var bufferPool BufferPool = &defaultPool{
|
||||
pool: &sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type BufferPool interface {
|
||||
Put(*bytes.Buffer)
|
||||
@@ -27,17 +31,7 @@ func (p *defaultPool) Get() *bytes.Buffer {
|
||||
}
|
||||
|
||||
// SetBufferPool allows to replace the default logrus buffer pool
|
||||
// to better meets the specific needs of an application.
|
||||
// to better meet the specific needs of an application.
|
||||
func SetBufferPool(bp BufferPool) {
|
||||
bufferPool = bp
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetBufferPool(&defaultPool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+13
-13
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
|
||||
Output:
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
For a full guide visit https://github.com/sirupsen/logrus
|
||||
*/
|
||||
|
||||
+268
-146
@@ -4,9 +4,11 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -17,8 +19,10 @@ var (
|
||||
// qualified package name, cached at first use
|
||||
logrusPackage string
|
||||
|
||||
// Positions in the call stack when tracing to report the calling method
|
||||
minimumCallerDepth int
|
||||
// Positions in the call stack when tracing to report the calling method.
|
||||
//
|
||||
// Start at the bottom of the stack before the package-name cache is primed.
|
||||
minimumCallerDepth = 1
|
||||
|
||||
// Used for caller information initialisation
|
||||
callerInitOnce sync.Once
|
||||
@@ -29,68 +33,116 @@ const (
|
||||
knownLogrusFrames int = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
// start at the bottom of the stack before the package-name cache is primed
|
||||
minimumCallerDepth = 1
|
||||
}
|
||||
|
||||
// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError].
|
||||
var ErrorKey = "error"
|
||||
|
||||
// Entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
||||
// reused and passed around as much as you wish to avoid field duplication.
|
||||
// Entry represents a single log event. It may be either an intermediate
|
||||
// entry (created via WithField(s), WithContext, etc.) or a final entry
|
||||
// that is emitted when one of the level methods (Trace, Debug, Info,
|
||||
// Warn, Error, Fatal, Panic) is called.
|
||||
//
|
||||
//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver.
|
||||
// An Entry always belongs to a Logger. A nil Logger is invalid and will
|
||||
// cause a panic when the entry is logged. Use [NewEntry] or Logger methods
|
||||
// to construct entries.
|
||||
//
|
||||
// Entries are safe to reuse for adding fields and may be passed around
|
||||
// to avoid field duplication. Each log operation operates on a copy
|
||||
// of the Entry’s data to avoid mutation during formatting.
|
||||
//
|
||||
//nolint:recvcheck // Entry methods intentionally use both pointer and value receivers.
|
||||
type Entry struct {
|
||||
// Logger is the Logger that owns this entry and is responsible for
|
||||
// formatting, hooks, and output. It must not be nil. An Entry without
|
||||
// a Logger is invalid and will panic when logged.
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
// Data contains all user-defined fields attached to this entry.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
// Time is the timestamp for the log event. If zero when the entry is
|
||||
// logged, it defaults to the current time.
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||
// Level is the severity of the log entry. It is set when the entry
|
||||
// is fired and reflects the level used for that log call.
|
||||
Level Level
|
||||
|
||||
// Calling method, with package name
|
||||
// Caller contains the calling method information.
|
||||
//
|
||||
// When [Logger.ReportCaller] is enabled, Caller is populated automatically at
|
||||
// log time if it is nil. Hooks and formatters may inspect Caller.
|
||||
//
|
||||
// Applications generally should not modify Caller unless they intentionally
|
||||
// want to provide custom caller information.
|
||||
Caller *runtime.Frame
|
||||
|
||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
// Message is the log message supplied to one of the logging methods
|
||||
// (Trace, Debug, Info, Warn, Error, Fatal, or Panic). It is set when
|
||||
// the entry is logged.
|
||||
Message string
|
||||
|
||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
||||
// Buffer is a reusable buffer provided to the formatter. It is set
|
||||
// before formatting in the normal log path; when nil, formatters
|
||||
// allocate their own.
|
||||
Buffer *bytes.Buffer
|
||||
|
||||
// Contains the context set by the user. Useful for hook processing etc.
|
||||
// Context carries user-provided context for hooks and formatters.
|
||||
Context context.Context
|
||||
|
||||
// err may contain a field formatting error
|
||||
// err contains internal field-formatting errors.
|
||||
err string
|
||||
}
|
||||
|
||||
// NewEntry creates a new [Entry] associated with the provided Logger.
|
||||
// The logger must not be nil. Passing a nil logger results in a
|
||||
// panic when a logging method (e.g., [Entry.Info], [Entry.Error], etc.)
|
||||
// is called.
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, plus one optional. Give a little extra room.
|
||||
Data: make(Fields, 6),
|
||||
// Reserve default predefined fields and a little extra room.
|
||||
Data: make(Fields, defaultFields+3),
|
||||
}
|
||||
}
|
||||
|
||||
// Dup creates a copy of the entry for further modification.
|
||||
//
|
||||
// Data is cloned to avoid mutating the original entry. Other fields
|
||||
// (Logger, Time, Context, etc.) are copied by value.
|
||||
func (entry *Entry) Dup() *Entry {
|
||||
data := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
dup := entry.dup()
|
||||
dup.Data = maps.Clone(entry.Data)
|
||||
return dup
|
||||
}
|
||||
|
||||
// dup copies the entry fields shared by derived entries except Data, which
|
||||
// callers must copy or initialize as appropriate for their use.
|
||||
func (entry *Entry) dup() *Entry {
|
||||
return &Entry{
|
||||
Logger: entry.Logger,
|
||||
Time: entry.Time,
|
||||
Caller: entry.Caller,
|
||||
Context: entry.Context,
|
||||
err: entry.err,
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
||||
}
|
||||
|
||||
// Bytes returns the bytes representation of this entry from the formatter.
|
||||
func (entry *Entry) Bytes() ([]byte, error) {
|
||||
return entry.Logger.Formatter.Format(entry)
|
||||
// Snapshot the formatter under the lock to protect against concurrent
|
||||
// SetFormatter calls, then release the lock before formatting.
|
||||
// This avoids a data race and prevents a deadlock if Format() triggers
|
||||
// reentrant logging (e.g., a field's MarshalJSON calls logrus).
|
||||
//
|
||||
// See:
|
||||
//
|
||||
// - https://github.com/sirupsen/logrus/issues/1440
|
||||
// - https://github.com/sirupsen/logrus/issues/1448
|
||||
entry.Logger.mu.Lock()
|
||||
formatter := entry.Logger.Formatter
|
||||
entry.Logger.mu.Unlock()
|
||||
|
||||
return formatter.Format(entry)
|
||||
}
|
||||
|
||||
// String returns the string representation from the reader and ultimately the
|
||||
@@ -112,58 +164,61 @@ func (entry *Entry) WithError(err error) *Entry {
|
||||
|
||||
// WithContext adds a context to the Entry.
|
||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||
dataCopy := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
dataCopy[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
||||
dup := entry.dup()
|
||||
dup.Data = maps.Clone(entry.Data)
|
||||
dup.Context = ctx
|
||||
return dup
|
||||
}
|
||||
|
||||
// WithField adds a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
func (entry *Entry) WithField(key string, value any) *Entry {
|
||||
dup := entry.dup()
|
||||
dup.Data = maps.Clone(entry.Data)
|
||||
dup.addField(key, value)
|
||||
return dup
|
||||
}
|
||||
|
||||
// WithFields adds a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := make(Fields, len(entry.Data)+len(fields))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
dup := entry.dup()
|
||||
dup.Data = make(Fields, len(entry.Data)+len(fields))
|
||||
maps.Copy(dup.Data, entry.Data)
|
||||
|
||||
for key, value := range fields {
|
||||
dup.addField(key, value)
|
||||
}
|
||||
fieldErr := entry.err
|
||||
for k, v := range fields {
|
||||
isErrField := false
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
switch {
|
||||
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
||||
isErrField = true
|
||||
}
|
||||
}
|
||||
if isErrField {
|
||||
tmp := fmt.Sprintf("can not add field %q", k)
|
||||
if fieldErr != "" {
|
||||
fieldErr = entry.err + ", " + tmp
|
||||
} else {
|
||||
fieldErr = tmp
|
||||
}
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
||||
return dup
|
||||
}
|
||||
|
||||
// WithTime overrides the time of the Entry.
|
||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||
dataCopy := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
dataCopy[k] = v
|
||||
dup := entry.dup()
|
||||
dup.Data = maps.Clone(entry.Data)
|
||||
dup.Time = t
|
||||
return dup
|
||||
}
|
||||
|
||||
func (entry *Entry) addField(key string, value any) {
|
||||
if _, ok := value.(error); !ok {
|
||||
t := reflect.TypeOf(value)
|
||||
if t != nil && (t.Kind() == reflect.Func || t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Func) {
|
||||
if entry.err != "" {
|
||||
entry.err += ", skipping unsupported field " + strconv.Quote(key)
|
||||
} else {
|
||||
entry.err = "skipping unsupported field " + strconv.Quote(key)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
||||
|
||||
if entry.Data == nil {
|
||||
entry.Data = make(Fields, 1)
|
||||
}
|
||||
entry.Data[key] = value
|
||||
}
|
||||
|
||||
// getPackageName reduces a fully qualified function name to the package name
|
||||
// There really ought to be to be a better way...
|
||||
// There really ought to be a better way...
|
||||
func getPackageName(f string) string {
|
||||
for {
|
||||
lastPeriod := strings.LastIndex(f, ".")
|
||||
@@ -186,7 +241,7 @@ func getCaller() *runtime.Frame {
|
||||
_ = runtime.Callers(0, pcs)
|
||||
|
||||
// dynamic get the package name and the minimum caller depth
|
||||
for i := 0; i < maximumCallerDepth; i++ {
|
||||
for i := range maximumCallerDepth {
|
||||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||
if strings.Contains(funcName, "getCaller") {
|
||||
logrusPackage = getPackageName(funcName)
|
||||
@@ -215,16 +270,47 @@ func getCaller() *runtime.Frame {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entry Entry) HasCaller() (has bool) {
|
||||
return entry.Logger != nil &&
|
||||
entry.Logger.ReportCaller &&
|
||||
entry.Caller != nil
|
||||
// HasCaller reports whether this Entry contains caller information.
|
||||
//
|
||||
// Caller may be set explicitly, or populated at log time when
|
||||
// [Logger.ReportCaller] is enabled.
|
||||
//
|
||||
// Deprecated: use [Entry.Caller] != nil instead.
|
||||
//
|
||||
//go:fix inline
|
||||
func (entry Entry) HasCaller() bool {
|
||||
return entry.Caller != nil
|
||||
}
|
||||
|
||||
func (entry *Entry) log(level Level, msg string) {
|
||||
var buffer *bytes.Buffer
|
||||
func (entry *Entry) logArgs(level Level, panicAfter bool, args ...any) {
|
||||
entry.log(level, panicAfter, sprint(args...))
|
||||
}
|
||||
|
||||
newEntry := entry.Dup()
|
||||
func (entry *Entry) logf(level Level, panicAfter bool, format string, args ...any) {
|
||||
entry.log(level, panicAfter, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// logln uses Sprintln for multiple arguments to preserve Println-style
|
||||
// spacing between args, then trims the trailing newline.
|
||||
func (entry *Entry) logln(level Level, panicAfter bool, args ...any) {
|
||||
if len(args) <= 1 {
|
||||
entry.log(level, panicAfter, sprint(args...))
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintln(args...)
|
||||
msg = msg[:len(msg)-1] // Trim the newline added by Sprintln; logging adds its own.
|
||||
entry.log(level, panicAfter, msg)
|
||||
}
|
||||
|
||||
// log writes msg at level. If panicAfter is true, it panics with the fully
|
||||
// populated entry after hooks and output have completed.
|
||||
//
|
||||
// The explicit flag keeps panic behavior limited to Panic, Panicf, and
|
||||
// Panicln while avoiding a return value used only as the panic value.
|
||||
// See #1283 and commits f96066e and 5f8c666.
|
||||
func (entry *Entry) log(level Level, panicAfter bool, msg string) {
|
||||
newEntry := entry.dup()
|
||||
newEntry.Data = maps.Clone(entry.Data)
|
||||
|
||||
if newEntry.Time.IsZero() {
|
||||
newEntry.Time = time.Now()
|
||||
@@ -233,17 +319,24 @@ func (entry *Entry) log(level Level, msg string) {
|
||||
newEntry.Level = level
|
||||
newEntry.Message = msg
|
||||
|
||||
newEntry.Logger.mu.Lock()
|
||||
reportCaller := newEntry.Logger.ReportCaller
|
||||
logger := newEntry.Logger
|
||||
logger.mu.Lock()
|
||||
reportCaller := logger.ReportCaller
|
||||
bufPool := newEntry.getBufferPool()
|
||||
newEntry.Logger.mu.Unlock()
|
||||
logger.mu.Unlock()
|
||||
|
||||
if reportCaller {
|
||||
// Preserve explicitly set caller information.
|
||||
if reportCaller && newEntry.Caller == nil {
|
||||
newEntry.Caller = getCaller()
|
||||
}
|
||||
|
||||
newEntry.fireHooks()
|
||||
buffer = bufPool.Get()
|
||||
// Select hooks based on the level for this log call. Hooks receive the
|
||||
// Entry and may mutate it, but that does not affect which hooks are
|
||||
// fired for this event.
|
||||
hooks := logger.hooksForLevel(level)
|
||||
newEntry.fireHooks(hooks)
|
||||
|
||||
buffer := bufPool.Get()
|
||||
defer func() {
|
||||
newEntry.Buffer = nil
|
||||
buffer.Reset()
|
||||
@@ -251,15 +344,12 @@ func (entry *Entry) log(level Level, msg string) {
|
||||
}()
|
||||
buffer.Reset()
|
||||
newEntry.Buffer = buffer
|
||||
|
||||
newEntry.write()
|
||||
|
||||
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 here so the panic value contains the fully populated entry without
|
||||
// requiring log to return it to the caller.
|
||||
if panicAfter {
|
||||
panic(newEntry)
|
||||
}
|
||||
}
|
||||
@@ -271,175 +361,207 @@ func (entry *Entry) getBufferPool() (pool BufferPool) {
|
||||
return bufferPool
|
||||
}
|
||||
|
||||
func (entry *Entry) fireHooks() {
|
||||
var tmpHooks LevelHooks
|
||||
entry.Logger.mu.Lock()
|
||||
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)
|
||||
func (entry *Entry) fireHooks(hooks []Hook) {
|
||||
for _, hook := range hooks {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to fire hook:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) write() {
|
||||
// Snapshot the formatter under the lock to protect against concurrent
|
||||
// SetFormatter calls, then release the lock before formatting.
|
||||
// This avoids a deadlock when Format() triggers reentrant logging (e.g.,
|
||||
// a field's MarshalJSON calls logrus). See #1448, #1440.
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
formatter := entry.Logger.Formatter
|
||||
entry.Logger.mu.Unlock()
|
||||
|
||||
serialized, err := formatter.Format(entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to format entry:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Re-acquire the lock to serialize writes to the underlying io.Writer.
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to write to log:", 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{}) {
|
||||
// Log logs a message at the specified level.
|
||||
//
|
||||
// Using Log with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. Log treats the level as logging severity only;
|
||||
// use [Entry.Panic] or [Entry.Fatal] when those side effects are desired.
|
||||
func (entry *Entry) Log(level Level, args ...any) {
|
||||
const panicAfter = false
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.log(level, fmt.Sprint(args...))
|
||||
entry.logArgs(level, panicAfter, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Trace(args ...interface{}) {
|
||||
func (entry *Entry) Trace(args ...any) {
|
||||
entry.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
func (entry *Entry) Debug(args ...any) {
|
||||
entry.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
func (entry *Entry) Print(args ...any) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
func (entry *Entry) Info(args ...any) {
|
||||
entry.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
func (entry *Entry) Warn(args ...any) {
|
||||
entry.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
func (entry *Entry) Warning(args ...any) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
func (entry *Entry) Error(args ...any) {
|
||||
entry.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
func (entry *Entry) Fatal(args ...any) {
|
||||
entry.Log(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
entry.Log(PanicLevel, args...)
|
||||
func (entry *Entry) Panic(args ...any) {
|
||||
const panicAfter = true
|
||||
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||
entry.logArgs(PanicLevel, panicAfter, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
||||
// Logf logs a formatted message at the specified level.
|
||||
//
|
||||
// Using Logf with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. Logf treats the level as logging severity only;
|
||||
// use [Entry.Panicf] or [Entry.Fatalf] when those side effects are desired.
|
||||
func (entry *Entry) Logf(level Level, format string, args ...any) {
|
||||
const panicAfter = false
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, fmt.Sprintf(format, args...))
|
||||
entry.logf(level, panicAfter, format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
||||
func (entry *Entry) Tracef(format string, args ...any) {
|
||||
entry.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
func (entry *Entry) Debugf(format string, args ...any) {
|
||||
entry.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
func (entry *Entry) Infof(format string, args ...any) {
|
||||
entry.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
func (entry *Entry) Printf(format string, args ...any) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
func (entry *Entry) Warnf(format string, args ...any) {
|
||||
entry.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
func (entry *Entry) Warningf(format string, args ...any) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
func (entry *Entry) Errorf(format string, args ...any) {
|
||||
entry.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
func (entry *Entry) Fatalf(format string, args ...any) {
|
||||
entry.Logf(FatalLevel, format, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
entry.Logf(PanicLevel, format, args...)
|
||||
func (entry *Entry) Panicf(format string, args ...any) {
|
||||
const panicAfter = true
|
||||
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||
entry.logf(PanicLevel, panicAfter, format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
||||
// Logln logs a message at the specified level with Println-style spacing.
|
||||
//
|
||||
// Using Logln with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. Logln treats the level as logging severity only;
|
||||
// use [Entry.Panicln] or [Entry.Fatalln] when those side effects are desired.
|
||||
func (entry *Entry) Logln(level Level, args ...any) {
|
||||
const panicAfter = false
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, entry.sprintlnn(args...))
|
||||
entry.logln(level, panicAfter, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Traceln(args ...interface{}) {
|
||||
func (entry *Entry) Traceln(args ...any) {
|
||||
entry.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
func (entry *Entry) Debugln(args ...any) {
|
||||
entry.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
func (entry *Entry) Infoln(args ...any) {
|
||||
entry.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
func (entry *Entry) Println(args ...any) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
func (entry *Entry) Warnln(args ...any) {
|
||||
entry.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
func (entry *Entry) Warningln(args ...any) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
func (entry *Entry) Errorln(args ...any) {
|
||||
entry.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
func (entry *Entry) Fatalln(args ...any) {
|
||||
entry.Logln(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
entry.Logln(PanicLevel, args...)
|
||||
func (entry *Entry) Panicln(args ...any) {
|
||||
const panicAfter = true
|
||||
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||
entry.logln(PanicLevel, panicAfter, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
// sprint is fmt.Sprint with fast paths for zero or one string argument.
|
||||
func sprint(args ...any) string {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
if msg, ok := args[0].(string); ok {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return fmt.Sprint(args...)
|
||||
}
|
||||
|
||||
+82
-87
@@ -6,11 +6,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
// std is the package-level standard logger, similar to the default logger
|
||||
// in the stdlib [log] package.
|
||||
var std = New()
|
||||
|
||||
// StandardLogger returns the package-level standard logger used by
|
||||
// the top-level logging functions.
|
||||
func StandardLogger() *Logger {
|
||||
return std
|
||||
}
|
||||
@@ -41,7 +42,7 @@ func GetLevel() Level {
|
||||
return std.GetLevel()
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
||||
// IsLevelEnabled checks if logging for the given level is enabled for the standard logger.
|
||||
func IsLevelEnabled(level Level) bool {
|
||||
return std.IsLevelEnabled(level)
|
||||
}
|
||||
@@ -51,9 +52,10 @@ func AddHook(hook Hook) {
|
||||
std.AddHook(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
// WithError creates an entry from the standard logger and adds an error to it,
|
||||
// using the value defined in [ErrorKey] as key.
|
||||
func WithError(err error) *Entry {
|
||||
return std.WithField(ErrorKey, err)
|
||||
return std.WithError(err)
|
||||
}
|
||||
|
||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||
@@ -61,210 +63,203 @@ 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`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
// WithField creates an entry from the standard logger and adds a single field.
|
||||
// For multiple fields, prefer [WithFields] over chaining WithField calls.
|
||||
func WithField(key string, value any) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
// WithFields creates an entry from the standard logger and adds the fields to it.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// WithTime creates an entry from the standard logger and overrides the time of
|
||||
// logs generated with it.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
// WithTime creates an entry from the standard logger and overrides the time
|
||||
// used for logs generated with it.
|
||||
func WithTime(t time.Time) *Entry {
|
||||
return std.WithTime(t)
|
||||
}
|
||||
|
||||
// Trace logs a message at level Trace on the standard logger.
|
||||
func Trace(args ...interface{}) {
|
||||
// Trace logs a message at level [TraceLevel] on the standard logger.
|
||||
func Trace(args ...any) {
|
||||
std.Trace(args...)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
// Debug logs a message at level [DebugLevel] on the standard logger.
|
||||
func Debug(args ...any) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
// Print logs a message at level [InfoLevel] on the standard logger.
|
||||
func Print(args ...any) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
// Info logs a message at level [InfoLevel] on the standard logger.
|
||||
func Info(args ...any) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
// Warn logs a message at level [WarnLevel] on the standard logger.
|
||||
func Warn(args ...any) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
// Warning logs a message at level [WarnLevel] on the standard logger.
|
||||
func Warning(args ...any) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
// Error logs a message at level [ErrorLevel] on the standard logger.
|
||||
func Error(args ...any) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
// Panic logs a message at level [PanicLevel] on the standard logger.
|
||||
func Panic(args ...any) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatal(args ...interface{}) {
|
||||
// Fatal logs a message at level [FatalLevel] on the standard logger,
|
||||
// then exits the process with status 1.
|
||||
func Fatal(args ...any) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// TraceFn logs a message from a func at level Trace on the standard logger.
|
||||
// TraceFn logs a message from a func at level [TraceLevel] on the standard logger.
|
||||
func TraceFn(fn LogFunction) {
|
||||
std.TraceFn(fn)
|
||||
}
|
||||
|
||||
// DebugFn logs a message from a func at level Debug on the standard logger.
|
||||
// DebugFn logs a message from a func at level [DebugLevel] on the standard logger.
|
||||
func DebugFn(fn LogFunction) {
|
||||
std.DebugFn(fn)
|
||||
}
|
||||
|
||||
// PrintFn logs a message from a func at level Info on the standard logger.
|
||||
// PrintFn logs a message from a func at level [InfoLevel] on the standard logger.
|
||||
func PrintFn(fn LogFunction) {
|
||||
std.PrintFn(fn)
|
||||
}
|
||||
|
||||
// InfoFn logs a message from a func at level Info on the standard logger.
|
||||
// InfoFn logs a message from a func at level [InfoLevel] on the standard logger.
|
||||
func InfoFn(fn LogFunction) {
|
||||
std.InfoFn(fn)
|
||||
}
|
||||
|
||||
// WarnFn logs a message from a func at level Warn on the standard logger.
|
||||
// WarnFn logs a message from a func at level [WarnLevel] on the standard logger.
|
||||
func WarnFn(fn LogFunction) {
|
||||
std.WarnFn(fn)
|
||||
}
|
||||
|
||||
// WarningFn logs a message from a func at level Warn on the standard logger.
|
||||
// WarningFn logs a message from a func at level [WarnLevel] on the standard logger.
|
||||
func WarningFn(fn LogFunction) {
|
||||
std.WarningFn(fn)
|
||||
}
|
||||
|
||||
// ErrorFn logs a message from a func at level Error on the standard logger.
|
||||
// ErrorFn logs a message from a func at level [ErrorLevel] on the standard logger.
|
||||
func ErrorFn(fn LogFunction) {
|
||||
std.ErrorFn(fn)
|
||||
}
|
||||
|
||||
// PanicFn logs a message from a func at level Panic on the standard logger.
|
||||
// PanicFn logs a message from a func at level [PanicLevel] on the standard logger.
|
||||
func PanicFn(fn LogFunction) {
|
||||
std.PanicFn(fn)
|
||||
}
|
||||
|
||||
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
// FatalFn logs a message from a func at level [FatalLevel] on the standard logger,
|
||||
// then exits the process with status 1.
|
||||
func FatalFn(fn LogFunction) {
|
||||
std.FatalFn(fn)
|
||||
}
|
||||
|
||||
// Tracef logs a message at level Trace on the standard logger.
|
||||
func Tracef(format string, args ...interface{}) {
|
||||
// Tracef logs a message at level [TraceLevel] on the standard logger.
|
||||
func Tracef(format string, args ...any) {
|
||||
std.Tracef(format, args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
// Debugf logs a message at level [DebugLevel] on the standard logger.
|
||||
func Debugf(format string, args ...any) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
// Printf logs a message at level [InfoLevel] on the standard logger.
|
||||
func Printf(format string, args ...any) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
// Infof logs a message at level [InfoLevel] on the standard logger.
|
||||
func Infof(format string, args ...any) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
// Warnf logs a message at level [WarnLevel] on the standard logger.
|
||||
func Warnf(format string, args ...any) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
// Warningf logs a message at level [WarnLevel] on the standard logger.
|
||||
func Warningf(format string, args ...any) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
// Errorf logs a message at level [ErrorLevel] on the standard logger.
|
||||
func Errorf(format string, args ...any) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
// Panicf logs a message at level [PanicLevel] on the standard logger.
|
||||
func Panicf(format string, args ...any) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
// Fatalf logs a message at level [FatalLevel] on the standard logger,
|
||||
// then exits the process with status 1.
|
||||
func Fatalf(format string, args ...any) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Traceln logs a message at level Trace on the standard logger.
|
||||
func Traceln(args ...interface{}) {
|
||||
// Traceln logs a message at level [TraceLevel] on the standard logger.
|
||||
func Traceln(args ...any) {
|
||||
std.Traceln(args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
// Debugln logs a message at level [DebugLevel] on the standard logger.
|
||||
func Debugln(args ...any) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
// Println logs a message at level [InfoLevel] on the standard logger.
|
||||
func Println(args ...any) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
// Infoln logs a message at level [InfoLevel] on the standard logger.
|
||||
func Infoln(args ...any) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
// Warnln logs a message at level [WarnLevel] on the standard logger.
|
||||
func Warnln(args ...any) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
// Warningln logs a message at level [WarnLevel] on the standard logger.
|
||||
func Warningln(args ...any) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
// Errorln logs a message at level [ErrorLevel] on the standard logger.
|
||||
func Errorln(args ...any) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
// Panicln logs a message at level [PanicLevel] on the standard logger.
|
||||
func Panicln(args ...any) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalln(args ...interface{}) {
|
||||
// Fatalln logs a message at level [FatalLevel] on the standard logger,
|
||||
// then exits the process with status 1.
|
||||
func Fatalln(args ...any) {
|
||||
std.Fatalln(args...)
|
||||
}
|
||||
|
||||
+30
-17
@@ -2,27 +2,40 @@ package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
// defaultTimestampFormat is the layout used to format entry timestamps
|
||||
// when a formatter has not specified a custom TimestampFormat.
|
||||
// It follows time.RFC3339 and is applied unless timestamps are disabled.
|
||||
defaultTimestampFormat = time.RFC3339
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
FieldKeyLogrusError = "logrus_error"
|
||||
FieldKeyFunc = "func"
|
||||
FieldKeyFile = "file"
|
||||
|
||||
// defaultFields is the number of commonly included predefined log entry fields
|
||||
// (msg, level, time). It is used as a capacity hint when constructing
|
||||
// intermediate collections during formatting (for example, the fixed key list).
|
||||
//
|
||||
// It does not include the optional "logrus_error", "func", or "file" fields.
|
||||
defaultFields = 3
|
||||
)
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
FieldKeyLogrusError = "logrus_error"
|
||||
FieldKeyFunc = "func"
|
||||
FieldKeyFile = "file"
|
||||
)
|
||||
|
||||
// Formatter is implemented by types that format log entries. It receives an
|
||||
// [*Entry], which contains:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
// - entry.Message: the message passed to logging methods such as [Info], [Warn], [Error]
|
||||
// - entry.Time: the timestamp
|
||||
// - entry.Level: the log level
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
// Additional fields added with [WithField] or [WithFields] are available in
|
||||
// [Entry.Data]. Format should return the formatted log entry as a byte slice,
|
||||
// which is written to [Logger.Out].
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
@@ -30,12 +43,12 @@ type Formatter interface {
|
||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
|
||||
+23
-14
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type fieldKey string
|
||||
@@ -20,7 +21,13 @@ func (f FieldMap) resolve(key fieldKey) string {
|
||||
return string(key)
|
||||
}
|
||||
|
||||
// JSONFormatter formats logs into parsable json
|
||||
// JSONFormatter formats logs into parsable JSON.
|
||||
//
|
||||
// Fields from [Entry.Data] are included in the JSON object together with the
|
||||
// standard fields derived from the entry. If a field conflicts with a standard
|
||||
// field, it is prefixed with "fields.". Standard field names can be customized
|
||||
// through FieldMap. When DataKey is set, fields from [Entry.Data] are nested
|
||||
// under that key instead.
|
||||
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
|
||||
@@ -61,7 +68,8 @@ type JSONFormatter struct {
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+4)
|
||||
caller := entry.Caller
|
||||
data := make(Fields, len(entry.Data)+defaultFields)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
@@ -73,13 +81,14 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if f.DataKey != "" {
|
||||
newData := make(Fields, 4)
|
||||
if f.DataKey != "" && len(entry.Data) > 0 {
|
||||
newData := make(Fields, defaultFields+1)
|
||||
newData[f.DataKey] = data
|
||||
data = newData
|
||||
}
|
||||
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
hasCaller := caller != nil
|
||||
prefixFieldClashes(data, f.FieldMap, hasCaller)
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
@@ -94,11 +103,13 @@ 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() {
|
||||
funcVal := entry.Caller.Function
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
if caller != nil {
|
||||
var funcVal, fileVal string
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
funcVal, fileVal = f.CallerPrettyfier(caller)
|
||||
} else {
|
||||
funcVal = caller.Function
|
||||
fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10)
|
||||
}
|
||||
if funcVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||
@@ -108,11 +119,9 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
b := entry.Buffer
|
||||
if b == nil {
|
||||
b = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(b)
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
ansiReset = "\x1b[0m" // reset attributes
|
||||
ansiRed = "\x1b[31m" // red
|
||||
ansiYellow = "\x1b[33m" // yellow
|
||||
ansiCyan = "\x1b[36m" // cyan
|
||||
ansiDimCyan = "\x1b[2;36m" // dim cyan
|
||||
ansiDimWhite = "\x1b[2;37m" // dim white (light gray)
|
||||
)
|
||||
|
||||
type lvlPrefix struct {
|
||||
full string
|
||||
truncated string
|
||||
padded string
|
||||
}
|
||||
|
||||
func colorize(level Level, s string) string {
|
||||
color := ansiCyan
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
color = ansiDimWhite
|
||||
case DebugLevel:
|
||||
color = ansiDimCyan
|
||||
case WarnLevel:
|
||||
color = ansiYellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
color = ansiRed
|
||||
case InfoLevel:
|
||||
color = ansiCyan
|
||||
}
|
||||
return color + s + ansiReset
|
||||
}
|
||||
|
||||
func formatLevel(level Level, disableTrunc, pad bool, maxLen int) string {
|
||||
upper := strings.ToUpper(level.String())
|
||||
|
||||
if pad && maxLen > len(upper) {
|
||||
upper += strings.Repeat(" ", maxLen-len(upper))
|
||||
}
|
||||
|
||||
if !pad && !disableTrunc && len(upper) > 4 {
|
||||
upper = upper[:4]
|
||||
}
|
||||
|
||||
return colorize(level, upper)
|
||||
}
|
||||
|
||||
var levelPrefixOnce = sync.OnceValues(func() (map[Level]lvlPrefix, lvlPrefix) {
|
||||
var maxLevel Level
|
||||
maxLen := 0
|
||||
for _, lvl := range AllLevels {
|
||||
if lvl > maxLevel {
|
||||
maxLevel = lvl
|
||||
}
|
||||
if l := len(lvl.String()); l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
}
|
||||
|
||||
prefix := make(map[Level]lvlPrefix, len(AllLevels))
|
||||
for _, lvl := range AllLevels {
|
||||
prefix[lvl] = lvlPrefix{
|
||||
full: formatLevel(lvl, true, false, maxLen),
|
||||
truncated: formatLevel(lvl, false, false, maxLen),
|
||||
padded: formatLevel(lvl, true, true, maxLen),
|
||||
}
|
||||
}
|
||||
|
||||
unknownLevel := maxLevel + 1
|
||||
unknown := lvlPrefix{
|
||||
full: formatLevel(unknownLevel, true, false, maxLen),
|
||||
truncated: formatLevel(unknownLevel, false, false, maxLen),
|
||||
padded: formatLevel(unknownLevel, true, true, maxLen),
|
||||
}
|
||||
|
||||
return prefix, unknown
|
||||
})
|
||||
|
||||
func levelPrefix(level Level, disableTrunc, pad bool) string {
|
||||
prefix, unknown := levelPrefixOnce()
|
||||
|
||||
p, ok := prefix[level]
|
||||
if !ok {
|
||||
p = unknown
|
||||
}
|
||||
|
||||
switch {
|
||||
case pad:
|
||||
return p.padded
|
||||
case !disableTrunc:
|
||||
return p.truncated
|
||||
default:
|
||||
return p.full
|
||||
}
|
||||
}
|
||||
+107
-49
@@ -12,17 +12,19 @@ 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() []any
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||
// something more adventurous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks LevelHooks
|
||||
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
@@ -38,37 +40,44 @@ type Logger struct {
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged.
|
||||
Level Level
|
||||
|
||||
// Used to sync writing to the log. Locking is enabled by Default
|
||||
mu MutexWrap
|
||||
mu mutexWrap
|
||||
|
||||
// Reusable empty entry
|
||||
entryPool sync.Pool
|
||||
|
||||
// Function to exit the application, defaults to `os.Exit()`
|
||||
ExitFunc exitFunc
|
||||
ExitFunc func(int)
|
||||
|
||||
// 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)
|
||||
// MutexWrap is the mutex implementation used by [Logger].
|
||||
//
|
||||
// Deprecated: MutexWrap is an implementation detail of Logger and should not be used directly.
|
||||
type MutexWrap = mutexWrap
|
||||
|
||||
type MutexWrap struct {
|
||||
type mutexWrap struct {
|
||||
lock sync.Mutex
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Lock() {
|
||||
func (mw *mutexWrap) Lock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Unlock() {
|
||||
func (mw *mutexWrap) Unlock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Disable() {
|
||||
func (mw *mutexWrap) Disable() {
|
||||
mw.disabled = true
|
||||
}
|
||||
|
||||
@@ -104,7 +113,7 @@ func (logger *Logger) newEntry() *Entry {
|
||||
}
|
||||
|
||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||
entry.Data = map[string]interface{}{}
|
||||
entry.Data = map[string]any{}
|
||||
logger.entryPool.Put(entry)
|
||||
}
|
||||
|
||||
@@ -112,7 +121,7 @@ func (logger *Logger) releaseEntry(entry *Entry) {
|
||||
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||
// this new returned entry.
|
||||
// If you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
func (logger *Logger) WithField(key string, value any) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithField(key, value)
|
||||
@@ -148,7 +157,12 @@ func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||
return entry.WithTime(t)
|
||||
}
|
||||
|
||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||
// Logf logs a formatted message at the specified level.
|
||||
//
|
||||
// Using Logf with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. Logf treats the level as logging severity only;
|
||||
// use [Logger.Panicf] or [Logger.Fatalf] when those side effects are desired.
|
||||
func (logger *Logger) Logf(level Level, format string, args ...any) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Logf(level, format, args...)
|
||||
@@ -156,49 +170,55 @@ func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
||||
func (logger *Logger) Tracef(format string, args ...any) {
|
||||
logger.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
func (logger *Logger) Debugf(format string, args ...any) {
|
||||
logger.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
func (logger *Logger) Infof(format string, args ...any) {
|
||||
logger.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
func (logger *Logger) Printf(format string, args ...any) {
|
||||
entry := logger.newEntry()
|
||||
entry.Printf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
func (logger *Logger) Warnf(format string, args ...any) {
|
||||
logger.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
func (logger *Logger) Warningf(format string, args ...any) {
|
||||
logger.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
func (logger *Logger) Errorf(format string, args ...any) {
|
||||
logger.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
func (logger *Logger) Fatalf(format string, args ...any) {
|
||||
logger.Logf(FatalLevel, format, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
logger.Logf(PanicLevel, format, args...)
|
||||
func (logger *Logger) Panicf(format string, args ...any) {
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
entry.Panicf(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{}) {
|
||||
// Log logs a message at the specified level.
|
||||
//
|
||||
// Using Log with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. Log treats the level as logging severity only;
|
||||
// use [Logger.Panic] or [Logger.Fatal] when those side effects are desired.
|
||||
func (logger *Logger) Log(level Level, args ...any) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Log(level, args...)
|
||||
@@ -206,6 +226,11 @@ func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// LogFn logs a message returned by fn at the specified level.
|
||||
//
|
||||
// Using LogFn with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. LogFn treats the level as logging severity only;
|
||||
// use [Logger.PanicFn] or [Logger.FatalFn] when those side effects are desired.
|
||||
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
@@ -214,43 +239,47 @@ func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Trace(args ...interface{}) {
|
||||
func (logger *Logger) Trace(args ...any) {
|
||||
logger.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
func (logger *Logger) Debug(args ...any) {
|
||||
logger.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
func (logger *Logger) Info(args ...any) {
|
||||
logger.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
func (logger *Logger) Print(args ...any) {
|
||||
entry := logger.newEntry()
|
||||
entry.Print(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
func (logger *Logger) Warn(args ...any) {
|
||||
logger.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
func (logger *Logger) Warning(args ...any) {
|
||||
logger.Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
func (logger *Logger) Error(args ...any) {
|
||||
logger.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
func (logger *Logger) Fatal(args ...any) {
|
||||
logger.Log(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
logger.Log(PanicLevel, args...)
|
||||
func (logger *Logger) Panic(args ...any) {
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
entry.Panic(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) TraceFn(fn LogFunction) {
|
||||
@@ -289,10 +318,19 @@ func (logger *Logger) FatalFn(fn LogFunction) {
|
||||
}
|
||||
|
||||
func (logger *Logger) PanicFn(fn LogFunction) {
|
||||
logger.LogFn(PanicLevel, fn)
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
entry.Panic(fn()...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||
// Logln logs a message at the specified level with Println-style spacing.
|
||||
//
|
||||
// Using Logln with [PanicLevel] or [FatalLevel] intentionally does not
|
||||
// trigger a panic or exit. Logln treats the level as logging severity only;
|
||||
// use [Logger.Panicln] or [Logger.Fatalln] when those side effects are desired.
|
||||
func (logger *Logger) Logln(level Level, args ...any) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Logln(level, args...)
|
||||
@@ -300,43 +338,47 @@ func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Traceln(args ...interface{}) {
|
||||
func (logger *Logger) Traceln(args ...any) {
|
||||
logger.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
func (logger *Logger) Debugln(args ...any) {
|
||||
logger.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
func (logger *Logger) Infoln(args ...any) {
|
||||
logger.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
func (logger *Logger) Println(args ...any) {
|
||||
entry := logger.newEntry()
|
||||
entry.Println(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
func (logger *Logger) Warnln(args ...any) {
|
||||
logger.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
func (logger *Logger) Warningln(args ...any) {
|
||||
logger.Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
func (logger *Logger) Errorln(args ...any) {
|
||||
logger.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
func (logger *Logger) Fatalln(args ...any) {
|
||||
logger.Logln(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
logger.Logln(PanicLevel, args...)
|
||||
func (logger *Logger) Panicln(args ...any) {
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
entry.Panicln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Exit(code int) {
|
||||
@@ -375,7 +417,22 @@ func (logger *Logger) AddHook(hook Hook) {
|
||||
logger.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
||||
// hooksForLevel returns a snapshot of the hooks registered for the given level.
|
||||
// The returned slice is a shallow copy and may be used without holding logger.mu.
|
||||
func (logger *Logger) hooksForLevel(level Level) []Hook {
|
||||
logger.mu.Lock()
|
||||
hooks := logger.Hooks[level]
|
||||
if len(hooks) == 0 {
|
||||
logger.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
out := make([]Hook, len(hooks))
|
||||
copy(out, hooks)
|
||||
logger.mu.Unlock()
|
||||
return out
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if logging for the given level is enabled.
|
||||
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
||||
return logger.level() >= level
|
||||
}
|
||||
@@ -394,6 +451,7 @@ func (logger *Logger) SetOutput(output io.Writer) {
|
||||
logger.Out = output
|
||||
}
|
||||
|
||||
// SetReportCaller sets whether the caller stack frame must be logged.
|
||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
@@ -403,9 +461,9 @@ func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
oldHooks := logger.Hooks
|
||||
logger.Hooks = hooks
|
||||
logger.mu.Unlock()
|
||||
return oldHooks
|
||||
}
|
||||
|
||||
|
||||
+129
-92
@@ -1,13 +1,13 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to [WithFields].
|
||||
type Fields map[string]interface{}
|
||||
type Fields map[string]any
|
||||
|
||||
// Level type
|
||||
//
|
||||
@@ -16,39 +16,56 @@ type Level uint32
|
||||
|
||||
// Convert the Level to a string. E.g. [PanicLevel] becomes "panic".
|
||||
func (level Level) String() string {
|
||||
if b, err := level.MarshalText(); err == nil {
|
||||
return string(b)
|
||||
} else {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
return "trace"
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
case "trace":
|
||||
return TraceLevel, nil
|
||||
}
|
||||
return parseLevel([]byte(lvl))
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
func parseLevel(b []byte) (Level, error) {
|
||||
switch {
|
||||
case bytes.EqualFold(b, []byte("panic")):
|
||||
return PanicLevel, nil
|
||||
case bytes.EqualFold(b, []byte("fatal")):
|
||||
return FatalLevel, nil
|
||||
case bytes.EqualFold(b, []byte("error")):
|
||||
return ErrorLevel, nil
|
||||
case bytes.EqualFold(b, []byte("warn")),
|
||||
bytes.EqualFold(b, []byte("warning")):
|
||||
return WarnLevel, nil
|
||||
case bytes.EqualFold(b, []byte("info")):
|
||||
return InfoLevel, nil
|
||||
case bytes.EqualFold(b, []byte("debug")):
|
||||
return DebugLevel, nil
|
||||
case bytes.EqualFold(b, []byte("trace")):
|
||||
return TraceLevel, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("not a valid logrus Level: %q", b)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (level *Level) UnmarshalText(text []byte) error {
|
||||
l, err := ParseLevel(string(text))
|
||||
l, err := parseLevel(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,23 +77,11 @@ func (level *Level) UnmarshalText(text []byte) error {
|
||||
|
||||
func (level Level) MarshalText() ([]byte, error) {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
return []byte("trace"), nil
|
||||
case DebugLevel:
|
||||
return []byte("debug"), nil
|
||||
case InfoLevel:
|
||||
return []byte("info"), nil
|
||||
case WarnLevel:
|
||||
return []byte("warning"), nil
|
||||
case ErrorLevel:
|
||||
return []byte("error"), nil
|
||||
case FatalLevel:
|
||||
return []byte("fatal"), nil
|
||||
case PanicLevel:
|
||||
return []byte("panic"), nil
|
||||
case TraceLevel, DebugLevel, InfoLevel, WarnLevel, ErrorLevel, FatalLevel, PanicLevel:
|
||||
return []byte(level.String()), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||
}
|
||||
|
||||
// AllLevels exposing all logging levels.
|
||||
@@ -91,7 +96,7 @@ var AllLevels = []Level{
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
// on your instance of logger, obtained with [logrus.New].
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
@@ -113,78 +118,110 @@ const (
|
||||
TraceLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
// Compile-time interface assertions.
|
||||
var (
|
||||
_ StdLogger = &log.Logger{}
|
||||
_ StdLogger = &Entry{}
|
||||
_ StdLogger = &Logger{}
|
||||
_ StdLogger = (*log.Logger)(nil)
|
||||
_ StdLogger = (*Entry)(nil)
|
||||
_ StdLogger = (*Logger)(nil)
|
||||
|
||||
_ FieldLogger = (*Logger)(nil)
|
||||
_ FieldLogger = (*Entry)(nil)
|
||||
_ FieldLogger = Ext1FieldLogger(nil)
|
||||
|
||||
_ DebugLogger = (*Logger)(nil)
|
||||
_ InfoLogger = (*Logger)(nil)
|
||||
_ WarnLogger = (*Logger)(nil)
|
||||
_ ErrorLogger = (*Logger)(nil)
|
||||
_ TraceLogger = (*Logger)(nil)
|
||||
|
||||
_ DebugLogger = (*Entry)(nil)
|
||||
_ InfoLogger = (*Entry)(nil)
|
||||
_ WarnLogger = (*Entry)(nil)
|
||||
_ ErrorLogger = (*Entry)(nil)
|
||||
_ TraceLogger = (*Entry)(nil)
|
||||
|
||||
_ Ext1FieldLogger = (*Logger)(nil)
|
||||
_ Ext1FieldLogger = (*Entry)(nil)
|
||||
)
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger ([log.Logger]) and a logrus logger.
|
||||
// There's no standard interface, so this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
Print(args ...any)
|
||||
Printf(format string, args ...any)
|
||||
Println(args ...any)
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
Fatal(args ...any)
|
||||
Fatalf(format string, args ...any)
|
||||
Fatalln(args ...any)
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
Panic(args ...any)
|
||||
Panicf(format string, args ...any)
|
||||
Panicln(args ...any)
|
||||
}
|
||||
|
||||
// FieldLogger extends the [StdLogger] interface, generalizing
|
||||
// the [Entry] and [Logger] types.
|
||||
type FieldLogger interface {
|
||||
WithField(key string, value interface{}) *Entry
|
||||
WithField(key string, value any) *Entry
|
||||
WithFields(fields Fields) *Entry
|
||||
WithError(err error) *Entry
|
||||
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
StdLogger
|
||||
DebugLogger
|
||||
InfoLogger
|
||||
WarnLogger
|
||||
ErrorLogger
|
||||
|
||||
Debug(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
// Legacy warning aliases. These are kept on FieldLogger for backwards
|
||||
// compatibility, but are intentionally omitted from [WarnLogger].
|
||||
|
||||
Debugln(args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
Warningln(args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
|
||||
// IsDebugEnabled() bool
|
||||
// IsInfoEnabled() bool
|
||||
// IsWarnEnabled() bool
|
||||
// IsErrorEnabled() bool
|
||||
// IsFatalEnabled() bool
|
||||
// IsPanicEnabled() bool
|
||||
Warning(args ...any)
|
||||
Warningf(format string, args ...any)
|
||||
Warningln(args ...any)
|
||||
}
|
||||
|
||||
// Ext1FieldLogger (the first extension to [FieldLogger]) is superfluous, it is
|
||||
// here for consistency. Do not use. Use [FieldLogger], [Logger] or [Entry]
|
||||
// instead.
|
||||
// DebugLogger provides convenience functions to log messages at level [DebugLevel].
|
||||
type DebugLogger interface {
|
||||
Debug(args ...any)
|
||||
Debugf(format string, args ...any)
|
||||
Debugln(args ...any)
|
||||
}
|
||||
|
||||
// InfoLogger provides convenience functions to log messages at level [InfoLevel].
|
||||
type InfoLogger interface {
|
||||
Info(args ...any)
|
||||
Infof(format string, args ...any)
|
||||
Infoln(args ...any)
|
||||
}
|
||||
|
||||
// WarnLogger provides convenience functions to log messages at level [WarnLevel].
|
||||
type WarnLogger interface {
|
||||
Warn(args ...any)
|
||||
Warnf(format string, args ...any)
|
||||
Warnln(args ...any)
|
||||
}
|
||||
|
||||
// ErrorLogger provides convenience functions to log messages at level [ErrorLevel].
|
||||
type ErrorLogger interface {
|
||||
Error(args ...any)
|
||||
Errorf(format string, args ...any)
|
||||
Errorln(args ...any)
|
||||
}
|
||||
|
||||
// TraceLogger provides convenience functions to log messages at level [TraceLevel].
|
||||
type TraceLogger interface {
|
||||
Trace(args ...any)
|
||||
Tracef(format string, args ...any)
|
||||
Traceln(args ...any)
|
||||
}
|
||||
|
||||
// Ext1FieldLogger is FieldLogger extended with Trace-level methods.
|
||||
//
|
||||
// New code should prefer the smallest applicable interface, such as
|
||||
// [FieldLogger] or [TraceLogger], or use [Logger] or [Entry] directly.
|
||||
type Ext1FieldLogger interface {
|
||||
FieldLogger
|
||||
Tracef(format string, args ...interface{})
|
||||
Trace(args ...interface{})
|
||||
Traceln(args ...interface{})
|
||||
TraceLogger
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
// +build appengine
|
||||
//go:build appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
func checkIfTerminal(_ any) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
// +build darwin dragonfly freebsd netbsd openbsd hurd
|
||||
// +build !js
|
||||
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd || hurd) && !tinygo
|
||||
|
||||
package logrus
|
||||
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
// +build js
|
||||
|
||||
package logrus
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
return false
|
||||
}
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
// +build js nacl plan9
|
||||
//go:build js || nacl || plan9 || wasi || wasip1 || tinygo
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
func checkIfTerminal(_ any) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+6
-2
@@ -1,4 +1,4 @@
|
||||
// +build !appengine,!js,!windows,!nacl,!plan9
|
||||
//go:build !appengine && !js && !windows && !nacl && !plan9 && !wasi && !wasip1 && !tinygo
|
||||
|
||||
package logrus
|
||||
|
||||
@@ -10,7 +10,11 @@ import (
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
return isTerminal(int(v.Fd()))
|
||||
fd := v.Fd()
|
||||
if fd > uintptr(^uint(0)>>1) {
|
||||
return false
|
||||
}
|
||||
return isTerminal(int(fd))
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
//go:build solaris && !tinygo
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
//go:build (linux || aix || zos) && !js && !wasi
|
||||
// +build linux aix zos
|
||||
// +build !js
|
||||
// +build !wasi
|
||||
//go:build (linux || aix || zos) && !tinygo
|
||||
|
||||
package logrus
|
||||
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
//go:build wasi
|
||||
// +build wasi
|
||||
|
||||
package logrus
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
return false
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
//go:build wasip1
|
||||
// +build wasip1
|
||||
|
||||
package logrus
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
return false
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build !appengine,!js,windows
|
||||
//go:build windows && !appengine
|
||||
|
||||
package logrus
|
||||
|
||||
|
||||
+317
-174
@@ -3,30 +3,32 @@ package logrus
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
red = 31
|
||||
yellow = 33
|
||||
blue = 36
|
||||
gray = 37
|
||||
)
|
||||
var baseTimestamp = time.Now()
|
||||
|
||||
var baseTimestamp time.Time
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
}
|
||||
|
||||
// TextFormatter formats logs into text
|
||||
// TextFormatter formats logs into text.
|
||||
//
|
||||
// Output is logfmt-like: key=value pairs separated by spaces. Fields from
|
||||
// [Entry.Data] are included together with the standard fields derived from the
|
||||
// entry. If a field conflicts with a standard field, it is prefixed with
|
||||
// "fields.". Standard field names can be customized through FieldMap.
|
||||
//
|
||||
// Field keys are written as-is (unquoted and unescaped) in the plain
|
||||
// (non-colored) format; only field values may be quoted depending on
|
||||
// DisableQuote, ForceQuote, QuoteEmptyFields, and the value content.
|
||||
//
|
||||
// When colors are enabled, ANSI escape sequences may be added for presentation.
|
||||
// For fully escaped structured output (including safe keys), use JSONFormatter.
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
@@ -64,7 +66,7 @@ type TextFormatter struct {
|
||||
// be desired.
|
||||
DisableSorting bool
|
||||
|
||||
// The keys sorting function, when uninitialized it uses sort.Strings.
|
||||
// The keys sorting function, when uninitialized it uses slices.Sort.
|
||||
SortingFunc func([]string)
|
||||
|
||||
// Disables the truncation of the level text to 4 characters.
|
||||
@@ -77,16 +79,22 @@ type TextFormatter struct {
|
||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||
QuoteEmptyFields bool
|
||||
|
||||
// Whether the logger's out is to a terminal
|
||||
isTerminal bool
|
||||
// Whether the logger's out is to a terminal. Don't use this field
|
||||
// directly; use TextFormatter.isTerminal instead.
|
||||
terminal bool
|
||||
|
||||
// FieldMap allows users to customize the names of keys for default fields.
|
||||
// Mapped keys are written as-is, so they should be safe for plain-text output.
|
||||
//
|
||||
// As an example:
|
||||
//
|
||||
// formatter := &TextFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message"}}
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message",
|
||||
// },
|
||||
// }
|
||||
FieldMap FieldMap
|
||||
|
||||
// CallerPrettyfier can be set by the user to modify the content
|
||||
@@ -96,54 +104,78 @@ type TextFormatter struct {
|
||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||
|
||||
terminalInitOnce sync.Once
|
||||
|
||||
// The max length of the level text, generated dynamically on init
|
||||
levelTextMaxLength int
|
||||
}
|
||||
|
||||
func (f *TextFormatter) init(entry *Entry) {
|
||||
if entry.Logger != nil {
|
||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
||||
}
|
||||
// Get the max length of the level text
|
||||
for _, level := range AllLevels {
|
||||
levelTextLength := utf8.RuneCount([]byte(level.String()))
|
||||
if levelTextLength > f.levelTextMaxLength {
|
||||
f.levelTextMaxLength = levelTextLength
|
||||
}
|
||||
func (f *TextFormatter) isTerminal(entry *Entry) bool {
|
||||
if entry == nil || entry.Logger == nil {
|
||||
// Don't run the terminalInitOnce without a logger, otherwise we'd
|
||||
// cache the default (false) forever even if a logger is attached
|
||||
// later.
|
||||
return false
|
||||
}
|
||||
|
||||
f.terminalInitOnce.Do(func() {
|
||||
entry.Logger.mu.Lock()
|
||||
out := entry.Logger.Out
|
||||
entry.Logger.mu.Unlock()
|
||||
|
||||
f.terminal = checkIfTerminal(out)
|
||||
})
|
||||
|
||||
return f.terminal
|
||||
}
|
||||
|
||||
func (f *TextFormatter) isColored() bool {
|
||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
||||
|
||||
if f.EnvironmentOverrideColors {
|
||||
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
||||
case ok && force != "0":
|
||||
isColored = true
|
||||
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
||||
isColored = false
|
||||
}
|
||||
func (f *TextFormatter) isColored(isTerminal bool) bool {
|
||||
if f.DisableColors {
|
||||
return false
|
||||
}
|
||||
|
||||
return isColored && !f.DisableColors
|
||||
colored := f.ForceColors || isTerminal
|
||||
if !f.EnvironmentOverrideColors {
|
||||
return colored
|
||||
}
|
||||
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok {
|
||||
return force != "0"
|
||||
}
|
||||
if os.Getenv("CLICOLOR") == "0" {
|
||||
return false
|
||||
}
|
||||
return colored
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields)
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
data := make(Fields, len(entry.Data))
|
||||
maps.Copy(data, entry.Data)
|
||||
isColored := f.isColored(f.isTerminal(entry))
|
||||
|
||||
caller := entry.Caller
|
||||
hasCaller := caller != nil
|
||||
prefixFieldClashes(data, f.FieldMap, hasCaller)
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
var funcVal, fileVal string
|
||||
b := entry.Buffer
|
||||
if b == nil {
|
||||
b = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
fixedKeys := make([]string, 0, 4+len(data))
|
||||
if isColored {
|
||||
f.printColored(b, entry, keys, data)
|
||||
} else {
|
||||
f.printPlain(b, entry, keys, data)
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printPlain(b *bytes.Buffer, entry *Entry, keys []string, data Fields) {
|
||||
caller := entry.Caller
|
||||
hasCaller := caller != nil
|
||||
|
||||
fixedKeys := make([]string, 0, len(keys)+defaultFields)
|
||||
if !f.DisableTimestamp {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
||||
}
|
||||
@@ -154,12 +186,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
if entry.err != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
||||
}
|
||||
if entry.HasCaller() {
|
||||
|
||||
var funcVal, fileVal string
|
||||
if caller != nil {
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
funcVal, fileVal = f.CallerPrettyfier(caller)
|
||||
} else {
|
||||
funcVal = entry.Caller.Function
|
||||
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
funcVal = caller.Function
|
||||
fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10)
|
||||
}
|
||||
|
||||
if funcVal != "" {
|
||||
@@ -172,152 +206,108 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
|
||||
if !f.DisableSorting {
|
||||
if f.SortingFunc == nil {
|
||||
sort.Strings(keys)
|
||||
// Default sorting does not sort the "fixed keys";
|
||||
// see https://github.com/sirupsen/logrus/commit/73bc94e60c753099e8bae902f81fbd6e7dd95f26
|
||||
slices.Sort(keys)
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
} else {
|
||||
if !f.isColored() {
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
f.SortingFunc(fixedKeys)
|
||||
} else {
|
||||
f.SortingFunc(keys)
|
||||
}
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
f.SortingFunc(fixedKeys)
|
||||
}
|
||||
} else {
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
f.terminalInitOnce.Do(func() { f.init(entry) })
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
if f.isColored() {
|
||||
f.printColored(b, entry, keys, data, timestampFormat)
|
||||
} else {
|
||||
|
||||
for _, key := range fixedKeys {
|
||||
var value interface{}
|
||||
switch {
|
||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
||||
value = entry.Time.Format(timestampFormat)
|
||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
||||
value = entry.Level.String()
|
||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
||||
value = entry.Message
|
||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
||||
value = entry.err
|
||||
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
||||
value = funcVal
|
||||
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
||||
value = fileVal
|
||||
default:
|
||||
value = data[key]
|
||||
for _, key := range fixedKeys {
|
||||
var value any
|
||||
switch {
|
||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
||||
if f.TimestampFormat == "" {
|
||||
value = entry.Time.Format(defaultTimestampFormat)
|
||||
} else {
|
||||
value = entry.Time.Format(f.TimestampFormat)
|
||||
}
|
||||
f.appendKeyValue(b, key, value)
|
||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
||||
value = entry.Level.String()
|
||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
||||
value = entry.Message
|
||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
||||
value = entry.err
|
||||
case key == f.FieldMap.resolve(FieldKeyFunc) && hasCaller:
|
||||
value = funcVal
|
||||
case key == f.FieldMap.resolve(FieldKeyFile) && hasCaller:
|
||||
value = fileVal
|
||||
default:
|
||||
value = data[key]
|
||||
}
|
||||
f.appendKeyValue(b, key, value)
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel, TraceLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
case InfoLevel:
|
||||
levelColor = blue
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())
|
||||
if !f.DisableLevelTruncation && !f.PadLevelText {
|
||||
levelText = levelText[0:4]
|
||||
}
|
||||
if f.PadLevelText {
|
||||
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
|
||||
// Based on the max level text length.
|
||||
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
|
||||
// Formats the level text by appending spaces up to the max length, for example:
|
||||
// - "INFO "
|
||||
// - "WARNING"
|
||||
levelText = fmt.Sprintf(formatString, levelText)
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields) {
|
||||
// Remove a single newline if it already exists in the message to keep
|
||||
// the behavior of logrus text_formatter the same as the stdlib log package
|
||||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||
|
||||
caller := ""
|
||||
if entry.HasCaller() {
|
||||
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
|
||||
var callerText string
|
||||
if caller := entry.Caller; caller != nil {
|
||||
var funcVal, fileVal string
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
funcVal, fileVal = f.CallerPrettyfier(caller)
|
||||
} else {
|
||||
if caller.Function != "" {
|
||||
funcVal = caller.Function + "()"
|
||||
}
|
||||
fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10)
|
||||
}
|
||||
|
||||
if fileVal == "" {
|
||||
caller = funcVal
|
||||
callerText = funcVal
|
||||
} else if funcVal == "" {
|
||||
caller = fileVal
|
||||
callerText = fileVal
|
||||
} else {
|
||||
caller = fileVal + " " + funcVal
|
||||
callerText = fileVal + " " + funcVal
|
||||
}
|
||||
}
|
||||
|
||||
levelText := levelPrefix(entry.Level, f.DisableLevelTruncation, f.PadLevelText)
|
||||
switch {
|
||||
case f.DisableTimestamp:
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
||||
_, _ = fmt.Fprintf(b, "%s%s %-44s ", levelText, callerText, entry.Message)
|
||||
case !f.FullTimestamp:
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
||||
_, _ = fmt.Fprintf(b, "%s[%04d]%s %-44s ", levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), callerText, entry.Message)
|
||||
default:
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
_, _ = fmt.Fprintf(b, "%s[%s]%s %-44s ", levelText, entry.Time.Format(timestampFormat), callerText, entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
||||
f.appendValue(b, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||
if f.ForceQuote {
|
||||
return true
|
||||
}
|
||||
if f.QuoteEmptyFields && len(text) == 0 {
|
||||
return true
|
||||
}
|
||||
if f.DisableQuote {
|
||||
return false
|
||||
}
|
||||
for _, ch := range text {
|
||||
//nolint:staticcheck // QF1001: could apply De Morgan's law
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||
return true
|
||||
if !f.DisableSorting {
|
||||
if f.SortingFunc == nil {
|
||||
slices.Sort(keys)
|
||||
} else {
|
||||
f.SortingFunc(keys)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
// Keys use the same color as the level-prefix.
|
||||
for _, k := range keys {
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(colorize(entry.Level, k))
|
||||
b.WriteByte('=')
|
||||
f.appendValue(b, data[k])
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||
// appendKeyValue writes key=value. Keys are written verbatim (unquoted/unescaped);
|
||||
// values are subject to quoting/escaping.
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value any) {
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
@@ -326,15 +316,168 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf
|
||||
f.appendValue(b, value)
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||
stringVal, ok := value.(string)
|
||||
if !ok {
|
||||
stringVal = fmt.Sprint(value)
|
||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value any) {
|
||||
// Fast paths.
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
f.appendString(b, v)
|
||||
return
|
||||
case []byte:
|
||||
f.appendBytes(b, v)
|
||||
return
|
||||
case bool:
|
||||
var raw [8]byte
|
||||
f.appendBytes(b, strconv.AppendBool(raw[:0], v))
|
||||
return
|
||||
case error:
|
||||
f.appendError(b, v)
|
||||
return
|
||||
case fmt.Stringer:
|
||||
f.appendStringer(b, v)
|
||||
return
|
||||
}
|
||||
|
||||
if !f.needsQuoting(stringVal) {
|
||||
b.WriteString(stringVal)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%q", stringVal)
|
||||
// Handle common primitives.
|
||||
var raw [64]byte
|
||||
var num []byte
|
||||
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||
case int8:
|
||||
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||
case int16:
|
||||
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||
case int32:
|
||||
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||
case int64:
|
||||
num = strconv.AppendInt(raw[:0], v, 10)
|
||||
|
||||
case uint:
|
||||
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||
case uint8:
|
||||
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||
case uint16:
|
||||
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||
case uint32:
|
||||
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||
case uint64:
|
||||
num = strconv.AppendUint(raw[:0], v, 10)
|
||||
case uintptr:
|
||||
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||
|
||||
case float32:
|
||||
num = strconv.AppendFloat(raw[:0], float64(v), 'g', -1, 32)
|
||||
case float64:
|
||||
num = strconv.AppendFloat(raw[:0], v, 'g', -1, 64)
|
||||
|
||||
default:
|
||||
f.appendString(b, fmt.Sprint(value))
|
||||
return
|
||||
}
|
||||
|
||||
f.appendNumeric(b, num)
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendString(b *bytes.Buffer, s string) {
|
||||
quote := f.ForceQuote || (f.QuoteEmptyFields && len(s) == 0) || (!f.DisableQuote && needsQuoting(s))
|
||||
if !quote {
|
||||
b.WriteString(s)
|
||||
return
|
||||
}
|
||||
if len(s) == 0 {
|
||||
b.WriteString(`""`)
|
||||
return
|
||||
}
|
||||
|
||||
var tmp [128]byte
|
||||
b.Write(strconv.AppendQuote(tmp[:0], s))
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendBytes(b *bytes.Buffer, bs []byte) {
|
||||
quote := f.ForceQuote || (f.QuoteEmptyFields && len(bs) == 0) || (!f.DisableQuote && needsQuotingBytes(bs))
|
||||
if !quote {
|
||||
b.Write(bs)
|
||||
return
|
||||
}
|
||||
if len(bs) == 0 {
|
||||
b.WriteString(`""`)
|
||||
return
|
||||
}
|
||||
|
||||
var tmp [128]byte
|
||||
b.Write(strconv.AppendQuote(tmp[:0], string(bs)))
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendNumeric(b *bytes.Buffer, out []byte) {
|
||||
if f.ForceQuote {
|
||||
var tmp [128]byte
|
||||
b.Write(strconv.AppendQuote(tmp[:0], string(out)))
|
||||
return
|
||||
}
|
||||
b.Write(out)
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendError(b *bytes.Buffer, v error) {
|
||||
defer f.recoverValue(b, v, "Error")
|
||||
|
||||
f.appendString(b, v.Error())
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendStringer(b *bytes.Buffer, v fmt.Stringer) {
|
||||
defer f.recoverValue(b, v, "String")
|
||||
|
||||
f.appendString(b, v.String())
|
||||
}
|
||||
|
||||
func (f *TextFormatter) recoverValue(b *bytes.Buffer, v any, method string) {
|
||||
if r := recover(); r != nil {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer && rv.IsNil() {
|
||||
f.appendString(b, "<nil>")
|
||||
} else {
|
||||
f.appendString(b, fmt.Sprintf("%%!v(PANIC=%s method: %v)", method, r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// needsQuoting returns true if the string contains any byte that
|
||||
// requires quoting. It returns false when every byte is "safe" according
|
||||
// to isSafeByte.
|
||||
func needsQuoting(s string) bool {
|
||||
// use an index loop (avoid rune decoding).
|
||||
for i := range len(s) {
|
||||
c := s[i]
|
||||
if !isSafeByte(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// needsQuotingBytes returns true if the byte slice contains any byte that
|
||||
// requires quoting. It returns false when every byte is "safe" according
|
||||
// to isSafeByte.
|
||||
func needsQuotingBytes(bs []byte) bool {
|
||||
for _, c := range bs {
|
||||
if !isSafeByte(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isSafeByte returns true if the byte is allowed unquoted (ASCII and in the allowlist).
|
||||
// It purposely uses byte arithmetic (no runes) for performance.
|
||||
func isSafeByte(ch byte) bool {
|
||||
ok := ch < 0x80 && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
switch ch {
|
||||
case '-', '.', '_', '/', '@', '^', '+':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -30,7 +30,7 @@ func (entry *Entry) Writer() *io.PipeWriter {
|
||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
var printFunc func(args ...interface{})
|
||||
printFunc := entry.Print
|
||||
|
||||
// Determine which log function to use based on the specified log level
|
||||
switch level {
|
||||
@@ -48,8 +48,6 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||
printFunc = entry.Fatal
|
||||
case PanicLevel:
|
||||
printFunc = entry.Panic
|
||||
default:
|
||||
printFunc = entry.Print
|
||||
}
|
||||
|
||||
// Start a new goroutine to scan the input and write it to the logger using the specified print function.
|
||||
@@ -63,7 +61,7 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||
}
|
||||
|
||||
// writerScanner scans the input from the reader and writes it to the logger
|
||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...any)) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
// Set the buffer size to the maximum token size to avoid buffer overflows
|
||||
|
||||
Vendored
+2
-2
@@ -277,8 +277,8 @@ github.com/prometheus/procfs/internal/util
|
||||
# github.com/russross/blackfriday/v2 v2.1.0
|
||||
## explicit
|
||||
github.com/russross/blackfriday/v2
|
||||
# github.com/sirupsen/logrus v1.9.4
|
||||
## explicit; go 1.17
|
||||
# github.com/sirupsen/logrus v1.10.1
|
||||
## explicit; go 1.23
|
||||
github.com/sirupsen/logrus
|
||||
# github.com/spf13/cobra v1.10.2
|
||||
## explicit; go 1.15
|
||||
|
||||
Reference in New Issue
Block a user