Compare commits

...

1018 Commits

Author SHA1 Message Date
re 22f067d83b fix repos 2022-12-21 15:59:06 +03:00
re b06ab7d9aa upd 2022-12-21 15:31:06 +03:00
re e451092dba Update 'go.mod' 2022-12-12 14:27:58 +03:00
David Bariod f8bf7650dc
Merge pull request #1343 from sirupsen/dbd-upd-dep
update dependencies
2022-07-19 09:08:54 +02:00
David Bariod ebc9029252 update dependencies 2022-07-19 08:45:10 +02:00
Simon Eskildsen 56c843c73d
Merge pull request #1337 from izhakmo/fix-cve
update gopkg.in/yaml.v3 to v3.0.1
2022-06-13 07:17:07 -04:00
izhakmo 41b4ee686d update gopkg.in/yaml.v3 to v3.0.1 2022-06-06 18:41:45 +03:00
David Bariod f98ed3eb76
Merge pull request #1333 from nathanejohnson/bumpxsys
bump version of golang.org/x/sys dependency
2022-06-06 06:16:01 +02:00
Nathan Johnson 2b8f60a012 bump version of golangci-lint 2022-06-02 09:52:03 -05:00
Nathan Johnson 0db10ef84a bump version of golang.org/x/sys dependency
fixes #1332
2022-06-01 20:17:29 -05:00
Simon Eskildsen 85981c0459
Merge pull request #1263 from rubensayshi/fix-race 2022-01-12 18:45:10 -05:00
David Bariod 79c5ab66aa
Merge pull request #1283 from sirupsen/dbd-log-doc
Improve Log methods documentation
2021-09-12 16:09:16 +02:00
David Bariod 5f8c666a13 Improve Log methods documentation 2021-09-12 16:03:49 +02:00
David Bariod 5418b6e7a4
Merge pull request #1282 from sirupsen/dbd-ci-no-cross
reduce the list of cross build target
2021-09-12 16:02:09 +02:00
David Bariod 25e89b7d23 do not run the linter on windows 2021-09-12 15:59:08 +02:00
David Bariod f25cd754cf remove duplicated build constraints line 2021-09-12 15:58:50 +02:00
David Bariod 51f2599bdd reduce the list of cross build target 2021-09-12 15:52:09 +02:00
David Bariod accc7da667
Merge pull request #1277 from anajavi/patch-1
ci: add go 1.17 to test matrix
2021-09-12 08:16:54 +02:00
anajavi 0926db15e5
ci: run only on go 1.17 2021-09-12 08:49:26 +03:00
David Bariod 22d63b740b
Merge pull request #1281 from sirupsen/dbd-auto-stale-issues
indicates issues as stale automatically
2021-09-11 15:01:29 +02:00
David Bariod 526e535580 indicates issues as stale automatically 2021-09-11 15:00:32 +02:00
David Bariod b53d94c8ad
Merge pull request #1266 from runphp/patch-1
Update README.md
2021-09-11 14:47:43 +02:00
David Bariod de2d2027ff
Merge pull request #1280 from sirupsen/bug-1275
bump golang.org/x/sys depency version
2021-09-11 14:20:32 +02:00
David Bariod dff9872c76 bump golang.org/x/sys depency version 2021-09-11 14:09:47 +02:00
anajavi 15b98b1d72
ci: add go 1.17 to test matrix 2021-09-02 13:45:20 +03:00
heui f5f6a033d3
Update README.md 2021-06-24 09:52:04 +08:00
Ruben de Vries 78f838918d
fix race condition for SetFormatter and properly fix SetReportCaller race as well 2021-06-16 11:57:31 +02:00
David Bariod b50299cfaa
Merge pull request #1253 from edoger/buffer-pool
Add support for the logger private buffer pool.
2021-04-22 15:34:36 +02:00
Qingshan Luo 1818363d79 Add support for the logger private buffer pool. 2021-04-20 10:48:30 +08:00
David Bariod fdf1618bf7
Merge pull request #1249 from injustease/docs/badge
Change godoc badge to pkg.go.dev badge
2021-03-18 10:57:23 +01:00
Billy Zaelani Malik b1c1cea8f6 Change godoc badge to pkg.go.dev badge 2021-03-18 08:53:31 +07:00
David Bariod bde44a27f3
Merge pull request #1246 from thaJeztah/bump_testify
go.mod: github.com/stretchr/testify v1.7.0
2021-03-12 16:18:03 +01:00
Sebastiaan van Stijn 9b555f4fd7
go.mod: github.com/stretchr/testify v1.7.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-03-12 15:51:52 +01:00
David Bariod fe9e9fcbba
Merge pull request #1239 from thaJeztah/move_to_gha
CI: use GitHub Actions
2021-03-09 13:46:41 +01:00
David Bariod bdc0db8ead
Merge pull request #1244 from sirupsen/dbd-release
update changelog
2021-03-09 11:28:17 +01:00
David Bariod 1bfef4b986 update changelog 2021-03-09 11:27:33 +01:00
David Bariod 7a997b9285 improve documentation about timestamp format 2021-03-08 18:02:30 +01:00
Sebastiaan van Stijn cbd14ede4d
CI: use GitHub Actions
Use GitHub actions to run golang-ci-lint, cross, and test.

The "Test()" target in Mage was a plain "go test -v ./...", and should be
portable to other CI systems if needed; running it through the mage file
effectively resulted in "compile a go binary to run go".

The "Lint()" target in Mage relied on Travis CI setting up Golang-CI lint
before it was executed, which required bash. Moving it to GitHub actions
also allowed it to be run on Windows. Golang CI can still be run locally
either through the mage file (which is kept for now), or running
`golangci-lint run ./...` after installing golangci-lint.

The "CrossBuild() Mage target is still used to perform cross-compile, as it
contains code to generate the GOOS/GOARCH matrix, which makes it easier
to run locally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-02-19 14:17:47 +01:00
Sebastiaan van Stijn bdb7d4c531
go.mod: update golang.org/x/sys to fix openbsd/mips64 on Go 1.16
This should hopefully fix cross-compile on openbsd/mips64 on Go 1.16

    Building for GOOS=openbsd GOARCH=mips64
    # golang.org/x/sys/unix
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/fcntl.go:26:42: undefined: Flock_t
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/ioctl.go:26:47: undefined: Winsize
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/ioctl.go:37:47: undefined: Termios
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/ioctl.go:55:42: undefined: Winsize
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/ioctl.go:61:42: undefined: Termios
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/syscall_openbsd.go:34:6: missing function body
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/syscall_unix_gc.go:12:6: missing function body
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/syscall_unix_gc.go:13:6: missing function body
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/syscall_unix_gc.go:14:6: missing function body
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/syscall_unix_gc.go:15:6: missing function body
    Error: ../../../go/pkg/mod/golang.org/x/sys@v0.0.0-20191026070338-33540a1f6037/unix/ioctl.go:61:42: too many errors

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-02-19 13:57:50 +01:00
David Bariod f104497f2b
Merge pull request #1238 from thaJeztah/move_mage
CI: run mage with "-v" to not discard output, and move mage to a submodule
2021-02-19 13:54:12 +01:00
Sebastiaan van Stijn 1d8091a7e9
move "mage" to a separate module
Move the magefile related files to a submodule, so that it
does not become a dependency for logrus itself.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-02-19 12:47:40 +01:00
Sebastiaan van Stijn feebf74e97
travis: run mage with -v to not discard output
Before this:

    $ go run mage.go lint
    Error: running "/Users/sebastiaan/go/bin/golangci-lint run ./..." failed with exit code 1
    exit status 1

    $ go run mage.go test
    Error: running "go test -race -v ./..." failed with exit code 1
    exit status 1

After this:

    $ go run mage.go -v lint
    Running target: Lint
    exec: /Users/sebastiaan/go/bin/golangci-lint run ./...
    entry.go:89:6: `iamNotUsed` is unused (deadcode)
    func iamNotUsed() {
         ^
    Error: running "/Users/sebastiaan/go/bin/golangci-lint run ./..." failed with exit code 1
    exit status 1

    $ go run mage.go -v test
    Running target: Test
    exec: go test -race -v ./...
    === RUN   TestRegister
    ...
    ?   	github.com/sirupsen/logrus/internal/testutils	[no test files]
    FAIL
    Error: running "go test -race -v ./..." failed with exit code 1
    exit status 1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-02-19 12:42:03 +01:00
David Bariod 6cff360233
Merge pull request #1234 from sirupsen/dbd-cleanup
fix race condition AddHook and traces
2021-02-18 08:49:52 +01:00
David Bariod d172886045 fix race condition AddHook and traces 2021-02-17 18:14:05 +01:00
David Bariod d59e5619da
Merge pull request #1231 from sirupsen/dbd-cleanup
cleanup
2021-02-17 17:48:57 +01:00
David Bariod 35ab8d8fef update changelog 2021-02-16 18:18:17 +01:00
David Bariod 5cb4bf65c6 code and comments clean up 2021-02-16 18:13:03 +01:00
David Bariod 15ca3c0694
Merge pull request #1230 from sirupsen/new-release
update changelog
2021-02-16 11:21:45 +01:00
David Bariod 67c117ceb0 update changelog 2021-02-16 11:20:51 +01:00
David Bariod 88d56b69b5
Merge pull request #1229 from sirupsen/fix-data-entry-race
fix for entry data field race condition
2021-02-16 11:14:48 +01:00
David Bariod ac6e35b4c2 fix for entry data field race condition 2021-02-16 10:31:51 +01:00
David Bariod f513f99c15
Merge pull request #1228 from sirupsen/travis_upd
update travis scripts
2021-02-14 16:57:45 +01:00
David Bariod 003c63ac69 undo golang version bump 2021-02-14 16:46:49 +01:00
David Bariod 6a61186a64 update travis scripts
* bump to go 1.16
* remove unneeded part in travis/install.sh
2021-02-13 07:07:38 +01:00
David Bariod c6da0523dd
Merge pull request #1208 from sirupsen/magefile
migrate ci target from bash scripts to magefile
2021-02-13 07:05:01 +01:00
David Bariod 3986c92379 add a test target in the magefile 2021-02-13 06:45:48 +01:00
David Bariod cd4bf4ef8d
Merge pull request #1212 from alecbz/alec/remove-dead-panic
Remove dead panic in Entry.Panic
2020-12-17 17:18:52 +01:00
David Bariod 44d983dbc2
Merge pull request #1185 from CreativeCactus/patch-1
Replace %v with %w for error
2020-12-17 16:44:23 +01:00
David Bariod b02b418f8f migrate lint script to a mage target 2020-12-17 15:58:09 +01:00
Alec Benzer 02fcb16005 Remove dead panic in Entry.Panic
[Entry.log itself panics][0] when the log level is set to PanicLevel, (and
PanicLevel is always eneabled) so this second panic will never be reached.

[0]: 8ae478eb8a/entry.go (L253)
2020-12-15 18:25:34 -05:00
David Bariod 4b818a50d4 migrate cross build target from bash script to mage 2020-11-26 06:33:19 +01:00
David Bariod 8ae478eb8a
Merge pull request #1197 from sirupsen/travis_build
bump go and golangci-lint versions in travis ci
2020-11-26 06:17:10 +01:00
David Bariod 6121f5c019
Merge pull request #1207 from sirupsen/remove_stale_bot
desactivate stale bot
2020-11-24 10:53:45 +01:00
David Bariod e3e79b6306 desactivate stale bot 2020-11-24 08:47:25 +01:00
David Bariod a752a62f5e
Merge pull request #1206 from l-lindsay/zosbuild
Add build tag to enable a successful build for zos
2020-11-21 19:30:40 +01:00
l-lindsay be16a81096 Add build tag to enable a successful build for zos 2020-11-20 14:25:07 -05:00
David Bariod 89b92b94dd one more linter error fixed 2020-11-08 07:19:28 +01:00
David Bariod e328a4e3f4 fix linter errors 2020-11-08 07:07:05 +01:00
David Bariod 581900062e bump golangci-lint version 2020-11-06 13:26:15 +01:00
David Bariod 0d28e29335 bump golang versions in travis ci 2020-11-06 13:14:07 +01:00
CreativeCactus c81a54c5aa
Replace %v with %w for error
https://golang.org/pkg/fmt/#Errorf
2020-10-01 17:36:44 +10:00
David Bariod d131c24e23
Merge pull request #1183 from shogo82148/update-changelog-1.7.0
update changelog with v1.7.0
2020-09-30 13:26:25 +02:00
Ichinose Shogo c6b865f1d2
update changelog with v1.7.0 2020-09-29 15:47:36 +09:00
David Bariod 6699a89a23
Merge pull request #1145 from sirupsen/custom_buffer_pool
Add an API to plug a custom buffer free item mangement system
2020-05-28 10:56:38 +02:00
David Bariod 64a59449f3 Add an API to plug a custom buffer free item mangement system 2020-05-28 10:47:50 +02:00
David Bariod 42baed85eb
Merge pull request #1144 from sohel-sheikh/patch-1
Update doc for new logger
2020-05-26 09:04:19 +02:00
Sohel 630ea450e6
Update doc for new logger
Update default formatter for new logger from JsonFormatter to TextFormatter
2020-05-26 10:26:28 +05:30
David Bariod 20dcf91051
Merge pull request #1142 from sirupsen/function-log
Function log
2020-05-19 17:10:51 +02:00
David Bariod ba4da53cff Improve tests for logger.*Fn functions 2020-05-19 17:02:33 +02:00
David Bariod d7edea4451 Merge branch 'feature/function-log' of https://github.com/Azer0s/logrus into Azer0s-feature/function-log 2020-05-19 16:58:31 +02:00
David Bariod e8fa988410
Merge pull request #1077 from dmgk/master
Add support for freebsd/arm64
2020-05-19 16:53:15 +02:00
David Bariod 0de04f1584
Merge pull request #1088 from tklauser/simplify-windows
Simplify checkIfTerminal for Windows
2020-05-19 16:51:28 +02:00
Tobias Klauser 86657918d4 Simplify checkIfTerminal for Windows
Instead of relying on EnableVirtualTerminalProcessing from
github.com/konsorten/go-windows-terminal-sequences which just calls
GetConsoleMode, sets ENABLE_VIRTUAL_TERMINAL_PROCESSING and calls
SetConsoleMode with the new modified mode, implement it directly inside
checkIfTerminal. This also avoids the duplicate call to GetConsoleMode.
2020-05-06 15:58:24 +02:00
David Bariod 60c74ad9be update CHANGELOG.md with 1.5.0 and 1.6.0 version contents 2020-05-02 15:06:20 +02:00
David Bariod e8e563a823 Merge remote-tracking branch 'origin/master' into thlacroix-disable-quotes 2020-04-29 09:21:39 +02:00
David Bariod 0fd458a22e complete documetation on TextFormatter.DisableQuote 2020-04-29 09:15:21 +02:00
David Bariod 4d96c600d9 Merge branch 'disable-quotes' of https://github.com/thlacroix/logrus into thlacroix-disable-quotes 2020-04-29 09:10:49 +02:00
David Bariod a5b02471f8
Merge pull request #1136 from ialidzhikov/nit/line-endings
Change CRLF line endings to LF
2020-04-29 09:09:56 +02:00
David Bariod 163c051d4a
Merge pull request #1137 from sirupsen/fix_crash_windows
update github.com/konsorten/go-windows-terminal-sequences dependency …
2020-04-29 09:07:31 +02:00
David Bariod e79215d3d5 update github.com/konsorten/go-windows-terminal-sequences dependency to v1.0.3 2020-04-29 08:56:01 +02:00
ialidzhikov 4989a3fd5d Change CRLF line endings to LF
Signed-off-by: ialidzhikov <i.alidjikov@gmail.com>
2020-04-29 00:25:29 +03:00
Thomas Lacroix aff00feb0a Adds additional test cases for DisableQuote 2020-04-23 17:16:50 +02:00
Thomas Lacroix c7455de10a Adds flag to disable quotes in TextFormatter 2020-04-23 14:02:38 +02:00
Mark Phelps 91ef3ab5d5
Merge pull request #1131 from sirupsen/revert-1047
Revert #1047
2020-04-16 11:24:31 -04:00
Mark Phelps 03155c5499 Revert #1047 2020-04-16 11:13:51 -04:00
Ariel Simulevski 7d248fa1b1
Add loggers that take functions as input 2020-04-10 12:42:19 +02:00
Mark Phelps d417be0fe6
Merge pull request #1108 from cirelli94/fix-wrong-caller
Fix wrong caller
2020-03-22 09:43:59 -04:00
Mark Phelps 32fd107816
Merge pull request #1103 from sirupsen/fix-go114
fix logrus for go 1.14
2020-03-22 09:36:45 -04:00
Mark Phelps ddb57a2a54
Merge pull request #1110 from Dattax/patch-1
Title updates
2020-03-22 09:32:25 -04:00
Mark Phelps a635f0489d
Merge pull request #1113 from davidraleigh/html-escape
resolved conflicts for DisableHTMLEscape in json_formatter.go pull request #524
2020-03-22 09:27:13 -04:00
Mark Phelps 4ddc9cf62e
Merge pull request #1116 from admacleod/master
Resolve race condition with SetReportCaller() and Entry
2020-03-22 09:24:01 -04:00
Alisdair MacLeod e3e40605a2 remove errant whitespace 2020-03-19 10:02:20 +00:00
Alisdair MacLeod ba670baee1 fix deadlock in previous entry race condition fix 2020-03-19 10:01:29 +00:00
Alisdair MacLeod b28acda22d fix race condition in entry 2020-03-19 09:32:08 +00:00
Alisdair MacLeod e76a5c4450 create test to prove issue sirupsen/logrus#954 2020-03-19 09:29:19 +00:00
David Raleigh 0fb945b034 resolved conflicts 2020-03-13 13:22:47 -04:00
Deep Datta 0882384258
Title updates
Removed the non-breaking spaces in the ReadMe
2020-03-11 17:26:45 -07:00
Fabrizio Cirelli fa25593b15 Removed useless files 2020-03-09 14:45:58 +01:00
Fabrizio Cirelli af6ac8cee6 Fix wrong caller 2020-03-09 12:39:00 +01:00
David Bariod 7ea96a3284
Merge pull request #1102 from hlcfan/get-right-logrus-pkg-name
Fix caller package name
2020-03-06 11:24:46 +01:00
Mark Phelps 53000c4c0f
Merge pull request #921 from sosiska/patch-1
Rewrite if-else-if-else to switch statements
2020-03-04 09:42:50 -05:00
Mark Phelps 334dd7729c
Merge pull request #1060 from sirupsen/improve_withfield_doc
improve Logger.WithField documentation
2020-03-04 09:32:56 -05:00
Mark Phelps 1e936e2d75
Merge pull request #1091 from nolleh/master
add caption-json-formatter
2020-03-04 09:14:34 -05:00
David Bariod ab4d0e6ead run CI for go 1.13 and 1.14 2020-03-03 09:10:14 +01:00
Alex S 86a84a9d18 Get right logrus package name 2020-03-02 21:47:38 +08:00
Mark Phelps 77ab282a06
Merge pull request #1047 from lwsanty/fix-race-conditions-on-entry
fix race conditions on entry
2020-02-26 18:44:58 -05:00
Mark Phelps 7fab003954
Merge pull request #633 from bogem/patch-1
Fix typos in docs for New()
2020-02-26 18:21:58 -05:00
Mark Phelps 494ec951d1
Only mark issues as stale for now until we go through backlog of PRs
Only mark issues as stale for now until we go through backlog of PRs
2020-02-26 18:18:33 -05:00
Sébastien Lavoie 24566a3fc4
Merge pull request #924 from bunyk/master
Add hook to send logs to custom writer #678
2020-02-26 17:31:00 -05:00
Mark Phelps f5d95b63a6
Update stale.yml 2020-02-26 12:01:50 -05:00
Mark Phelps eef122e96e
Create stale.yml 2020-02-26 11:58:33 -05:00
Mark Phelps 2f069ddd45
Merge pull request #1072 from taywrobel/bugfix/dataBleedAcrossEntities
Fix entry data bleed when using WithContext and WithTime
2020-02-25 12:31:25 -05:00
Mark Phelps 63d9911443
Merge pull request #1082 from hlcfan/patch-1
Fix typo
2020-02-25 10:53:46 -05:00
Simon Eskildsen 4ecd9a62bd readme: maintenance-mode 2020-02-25 07:40:03 -05:00
Simon Eskildsen 947831125f
Merge pull request #1094 from devil418/fix-readme
Remove annoying punctuation in Readme for better screen reader accesssibility
2020-02-04 17:46:15 -05:00
Mikolaj Holysz 6895a36b17 Remove annoying punctuation in Readme for better screen reader accessibility.
One entry in the Logrus formatters list in the readme contained
a lot of extraneous punctuation.

When read with a screen reader, nothing but a bunch of question marks and weird symbol names could be heard,
making the line impossible to understand.
2020-01-28 19:39:15 +01:00
nolleh 779e0e214d add caption-json-formatter 2020-01-21 00:42:07 +09:00
Alex Shi b70d15e202
Fix typo 2019-12-18 14:10:06 +08:00
Dmitri Goutnik 28e212178a
Add support for freebsd/arm64 2019-12-09 07:44:42 -05:00
Taylor Wrobel 8fbaf3dbd0 Make Entry WithContext and WithTime copy tests more clear
Clarifies the data used in the EntryWithContextCopiesData test and
adds an equivalent test to verify the behavior of WithTime.
2019-12-03 13:50:59 -08:00
Taylor Wrobel bcc146f96b Fix entity data bleed when using WithContext and WithTime
Creates a copy of the data map when using WithContext to create a
child entity.  Without this, the data map of the parent entitiy,
which is exposed in the entity struct, is shared between a parent
and all children instances.

This can create bugs of shared or overwritten data when a parent
entity is used to make children in differing contexts, and behaves
differently than `WithField` and its diritivites which does make
a copy of the data.

Additionally implements the same logic for WithTime, for API
consistency in behavior.
2019-11-27 20:20:42 -08:00
David Bariod 4fd274e0b8 improve Logger.WithField documentation 2019-10-28 19:22:23 +01:00
David Bariod 67a7fdcf74
Merge pull request #1054 from sirupsen/del_old_doc
remove obsolete documentation
2019-10-26 13:39:18 +02:00
David Bariod 9746113fa8 remove obsolete documentation 2019-10-26 08:50:02 +02:00
David Bariod f4ece9c82f
Merge pull request #1052 from sirupsen/activate_linter
run golangci-lint on travis
2019-10-25 17:13:36 +02:00
David Bariod 12176f2f72
Merge pull request #1053 from sirupsen/travis_rm_go111
remove go1.11.x from travis build matrix
2019-10-25 17:12:32 +02:00
David Bariod 9df6f6aa0b add x rights on travis/lint.sh 2019-10-25 14:57:57 +02:00
David Bariod 88d44306be remove go1.11.x from travis build matrix 2019-10-25 14:53:19 +02:00
David Bariod b77b626665 run golangci-lint on travis 2019-10-25 14:49:48 +02:00
lwsanty c7278b2d7a fix race conditions on entry
closes #1046
2019-10-23 20:55:57 +03:00
Edward Muller d5d4df1108
Merge pull request #1040 from sirupsen/ffz/Travis
Clean up travis
2019-10-22 18:09:08 -07:00
Edward Muller b6a9e5632b
Merge pull request #1042 from sirupsen/ffz/ForceQuote
ForceQuote option to TextFormatter
2019-10-22 18:08:39 -07:00
Edward Muller 007cacdd34
Force Quote
Closed #1005
2019-10-14 22:53:51 -07:00
Edward Muller fb62dbe2f2
fix broken test 2019-10-14 13:23:44 -07:00
Edward Muller 843e0aaa75
Merge pull request #1016 from freeformz/ffz/returnEarly
return early
2019-10-14 11:32:52 -07:00
Edward Muller d12cdc065f
deadcode 2019-10-13 17:48:58 -07:00
Edward Muller 46015a925f
test the error to ensure there isn't an unexpected error in the test 2019-10-13 17:48:58 -07:00
Edward Muller f9951ccddd
Associate this example with what it's an example for 2019-10-13 17:43:54 -07:00
Edward Muller 68e6dbbcb7
Exclude go1.13.x from modules off, only build all on go1.13 modules on 2019-10-13 17:30:04 -07:00
Edward Muller 08cf62cb80
This should make gox a little nicer 2019-10-13 17:15:32 -07:00
Edward Muller ad9f41a0cd
pull all the install into a single location 2019-10-13 16:56:28 -07:00
Edward Muller 75440f2ebe
get some other deps 2019-10-13 16:51:57 -07:00
Edward Muller 8ec9a493ec
Enable all of these to see what fails 2019-10-13 16:47:33 -07:00
Edward Muller d30efdb30d
go mod verify; go mod tidy 2019-10-13 16:39:54 -07:00
Edward Muller 7b6c0d11ad
Disable modules, run on osx 2019-10-13 16:39:38 -07:00
Edward Muller 39a5ad1294
Merge pull request #991 from muesli/conversion-fixes
Avoid unnecessary conversion
2019-10-12 11:05:20 -07:00
Edward Muller 0bbebc5e2d
Merge pull request #1024 from flimzy/gopherjs
Add terminal_check_js.go file, for compatibility with GopherJS
2019-10-12 10:59:10 -07:00
Edward Muller 9e05426313
Merge pull request #990 from muesli/assign-fixes
Fixed ineffectual assignment in test
2019-10-12 10:36:50 -07:00
Edward Muller e33eea30de
Merge pull request #1017 from freeformz/ffz/docWriter
some minimal documentation for Logger.Writer{,Level}
2019-10-12 10:33:50 -07:00
Edward Muller e5e927cae6
Merge pull request #1032 from zput/ReadMeAddThirdFormatter
ReadMe.md file adds The Third Formatter link
2019-10-12 10:32:27 -07:00
Edward Muller 890ead5200
Merge pull request #1034 from flowonyx/patch-1
Fixed some typos in README.md
2019-10-12 10:30:36 -07:00
Edward Muller 107a185f83
Merge pull request #1037 from psampaz/travis_go_113
add Go 1.13 in travis
2019-10-12 10:29:57 -07:00
Pantelis Sampaziotis 0cb0485e38 add Go 1.13 in travis 2019-10-11 16:42:11 +03:00
Joel Williams 6d035663cd
Fixed some typos in README.md
Fixed a few typos and grammatical issues in the README.md. I hope this is helpful just to make a small improvement.
2019-10-04 02:25:08 +02:00
zxc 3f89e2545f ReadMe.md file adds The Third Formatter link 2019-09-29 11:27:08 +08:00
Jonathan Hall 305ec52856 Add terminal_check_js.go file, for compatibility with GopherJS 2019-09-19 11:06:10 +02:00
Edward Muller 6cd8d684fd
some minimal documentation for Logger.Writer{,Level}
This also includes two examples extracted from the readme.
2019-09-05 19:34:46 -07:00
Edward Muller 60320cbc2c
return early
This makes it easier to read / understand and is more idiomatic.
2019-09-05 16:09:16 -07:00
tbunyk 8b0b8a88f2 Merge remote-tracking branch 'upstream/master' 2019-09-03 15:29:19 +03:00
Simon Eskildsen de736cf91b readme: we have great maintainers now 2019-08-07 06:34:36 -04:00
Christian Muehlhaeuser 0c8c93fe4d
Avoid unnecessary conversion
No need to convert here.
2019-07-20 03:29:10 +02:00
Christian Muehlhaeuser d4257626ad
Fixed ineffectual assignment in test
Don't assign l if it's not being checked/used afterwards.
2019-07-20 03:27:21 +02:00
David Bariod 07a84ee741
Merge pull request #977 from lynncyrin/pad-option
Add an pad option log level text
2019-07-01 16:35:06 +02:00
Lynn Cyrin 539b8af839
its => it's 2019-06-27 18:35:00 -07:00
Lynn Cyrin af6ed964ef len => RuneCount
note: this is not currently easily testable without a larger diff that refactors the levels
2019-06-26 20:48:04 -07:00
Lynn Cyrin dcce32597d avoid escapes! h/t @therealplato 2019-06-26 20:37:17 -07:00
Lynn Cyrin 8ba442aca6 init the loggers in tests 2019-06-24 20:50:37 -07:00
Lynn Cyrin 693469de8f dynamically space the level text 2019-06-24 20:42:20 -07:00
Lynn Cyrin b5cc19ce3e
bump ci 2019-06-05 00:49:35 -07:00
Lynn Cyrin bef31a5df9
wording shift 2019-06-05 00:41:05 -07:00
Lynn Cyrin 691f1b6074
add a space back 2019-06-05 00:36:14 -07:00
Lynn Cyrin 2d641d1668
update the readme 2019-06-05 00:33:48 -07:00
Lynn Cyrin 58f7e00129
update comments 2019-06-05 00:27:10 -07:00
Lynn Cyrin fa0d2a82ff
add implementation and tests 2019-06-05 00:10:46 -07:00
Lynn Cyrin 23045ec6d1
tracking commit 2019-06-04 20:09:59 -07:00
Lynn Cyrin b851829a69
tracking commit 2019-06-04 20:08:45 -07:00
David Bariod 2a22dbedba
Merge pull request #962 from Clivern/patch-1
Update Example to Append to the log file not to overwrite
2019-05-18 15:52:02 +02:00
David Bariod 839c75faf7 Release 1.4.2 2019-05-18 12:38:48 +02:00
David Bariod cfb9d25dff
Merge pull request #969 from sirupsen/plan9_build
fix build break for plan9
2019-05-18 12:33:47 +02:00
David Bariod 744fc4caad fix build break for plan9 2019-05-18 11:44:18 +02:00
David Bariod e0108d9285
Merge pull request #966 from nlepage/feature/nacl
Add a checkTerminal for nacl to support running on play.golang.org
2019-05-18 10:33:20 +02:00
David Bariod f2849a8fb2
add full cross compilation in travis (#963)
* add full cross compilation in travis

* reduce the travis build matrix

* disable cross build for plan9 and nacl
2019-05-18 10:27:12 +02:00
Nicolas Lepage 1bc909a4f8
Add a checkTerminal for nacl to support running on play.golang.org 2019-05-14 09:13:07 +02:00
A. F 0006e8ce1a
Update README.md 2019-05-11 00:40:04 +02:00
David Bariod f0375eb5b5
Merge pull request #961 from sirupsen/ci_build_matrix
remove go 1.10 from ci build matrix
2019-05-10 09:41:01 +02:00
David Bariod 1a601d2059 remove go 1.10 from ci build matrix 2019-05-10 06:44:02 +02:00
Clément Chigot 5521996833 Update x/sys/unix to fix AIX support 2019-04-23 11:34:05 +02:00
David Bariod 9b3cdde74f
Merge pull request #946 from sirupsen/solaris_build
Fix solaris build
2019-04-03 11:10:19 +02:00
David Bariod c1b61542d7 Fix solaris build 2019-04-03 10:46:44 +02:00
David Bariod 8bdbc7bcc0 Release 1.4.1 2019-04-02 18:14:07 +02:00
David Bariod ba0a156bcc
Merge pull request #937 from tandr/test-travis-3-plat
Test more platforms
2019-04-02 17:52:19 +02:00
David Bariod 717f2ccd7d
Merge pull request #944 from hrxu01/remove_field_if_val_is_empty_for_func_file_filed
remove field if val is empty string for func and file field
2019-04-02 09:30:20 +02:00
David Bariod 8fe117bf7f
Merge pull request #943 from tandr/unexport-isterminal
Unexport IsTerminal
2019-04-02 09:14:47 +02:00
Haoran Xu 6c615e1abe remove field if val is empty string for func and file field in text formatter 2019-04-02 11:43:56 +08:00
Andrey Tcherepanov ede5b639cd Make isTerminal un-exported 2019-04-01 10:16:11 -06:00
Andrey Tcherepanov 3e06420226 Move files to main directory 2019-04-01 10:15:09 -06:00
Adam Renberg Tamm a6c0064cfa
Merge pull request #941 from hrxu01/return_new_entry_for_Entry.WithContext
return new entry for Entry.WithContext
2019-03-31 15:19:41 +02:00
Haoran Xu 38bc297a3d return new entry for Entry.WithContext 2019-03-29 14:04:26 +08:00
Andrey Tcherepanov 7d700cdff0 Test more platforms
It would be 5 * 3 = 15 runs
2019-03-28 16:13:21 -06:00
Sébastien Lavoie aefd7ecb90
Merge pull request #935 from pacmessica/move-terminal-pkg
Move terminal package
2019-03-28 09:44:47 -04:00
Jessica Paczuski c49ef1d4bf Move terminal package
fixes issue where terminal_check_notappengine.go can't access terminal
package since terminal package is in an internal package
2019-03-28 11:32:49 +01:00
David Bariod 787e519fa8
Merge pull request #932 from tandr/drop-crypto-2
Drop x/crypto package dependency
2019-03-28 09:41:56 +01:00
Andrey Tcherepanov 5d8c3bffc9 Updated travis.yml 2019-03-27 10:59:38 -06:00
Andrey Tcherepanov 41ee4dd365 Moved moved unix-related parts into terminal 2019-03-27 10:56:55 -06:00
Andrey Tcherepanov 7de3dd8c8b Removed golang.org/x/crypto refs 2019-03-27 10:56:55 -06:00
Andrey Tcherepanov 10ff0d07c3 Got rid of IsTerminal call to reduce external dependencies 2019-03-27 10:56:55 -06:00
David Bariod 1115b87d62
Merge pull request #927 from ceh-forks/ci-go-1-12
Test with Go 1.12
2019-03-27 09:37:13 +01:00
Emil Hessman c076594430 Add Go 1.12 to Travis CI build matrix 2019-03-27 07:08:19 +01:00
tbunyk c88f8de1fe Add Bytes() method to Entry, and use it to avoid double type cast 2019-03-18 16:07:31 +02:00
tbunyk 470f2e08fc Fix some test conditions 2019-03-15 13:19:10 +02:00
tbunyk d8e3add56f Add hook to send logs to custom writer #678 2019-03-15 13:09:11 +02:00
Kirill Motkov 1487633cc3 Rewrite if-else-if-else to switch statements 2019-03-13 12:38:09 +03:00
Adam Renberg Tamm dae0fa8d5b
Merge pull request #920 from sirupsen/changelog-v140
Add CHANGELOG for v1.4.0
2019-03-11 17:20:41 +01:00
Adam Renberg Tamm 02141df9f0 Add CHANGELOG for v1.4.0 2019-03-11 16:27:44 +01:00
David Bariod 5bd5a315a5
Merge pull request #919 from tgwizard/entry-context
Add WithContext
2019-03-11 15:47:26 +01:00
Adam Renberg Tamm 68e41f673a Add WithContext 2019-03-11 15:38:19 +01:00
Sébastien Lavoie 131eba2470
Merge pull request #901 from CodeLingoBot/rewrite
Fix error formatting based on best practices from Code Review Comments
2019-03-11 10:33:21 -04:00
David Bariod d7b6bf5e4d
Merge pull request #916 from sirupsen/fix_race_getcaller
Fix race getcaller
2019-03-06 14:14:08 +01:00
David Bariod cf1b9fd15e fix sync.Once usage instead of adding a mutex lock 2019-03-06 14:08:02 +01:00
georlav b9d451406d fix ReportCaller race condition 2019-03-04 20:58:21 +02:00
David Bariod c9b4f5af6d
Merge pull request #913 from sirupsen/example_caller_prettyfier
Example caller prettyfier
2019-03-03 12:00:21 +01:00
David Bariod 99a5172d62 Add and example for CallerPrettyfier 2019-03-03 11:53:51 +01:00
David Bariod 5c2b39a4f8 Remove debug trace 2019-03-03 11:52:04 +01:00
David Bariod fa3c1df513
Merge pull request #911 from sirupsen/caller_prettyfier_text_formatter
Add a CallerPrettyfier callback to the text formatter
2019-02-27 13:42:49 +01:00
David Bariod ffec2f2e0a Add a CallerPrettyfier callback to the text formatter 2019-02-27 13:35:45 +01:00
David Bariod cbce296565
Merge pull request #909 from sirupsen/caller_prettyfier
Add a CallerPrettyfier callback to the json formatter
2019-02-27 13:13:02 +01:00
David Bariod 5e9b246bea Add a CallerPrettyfier callback to the json formatter 2019-02-27 13:02:20 +01:00
noushavandijk 4f5fd631f1 Fix infinite recursion on unknown Level.String()
Using `%q` in the error string causes `MarshalText` to recurse (by calling String()).
2019-02-20 09:22:53 -08:00
David Bariod cdb2f3857c
Merge pull request #897 from antonfisher/patch-1
Add nested-logrus-formatter to README.md
2019-02-10 14:00:17 +01:00
David Bariod 1261c1f8a1
Merge pull request #903 from gavincabbage/entry-logf-level
prevent string formatting in Entry.Logf when log level is not enabled
2019-02-10 13:38:47 +01:00
Gavin Cabbage c4e4882020 prevent string formatting in Entry.Logf when log level is not enabled 2019-02-06 14:51:33 -05:00
CodeLingo Bot 774bb8e43f Fix error formatting based on best practices from Code Review Comments
Signed-off-by: CodeLingo Bot <bot@codelingo.io>
2019-02-05 05:52:27 +00:00
Georgi Dimitrov 4ea4861398 Add a DeferExitHandler function
Useful for running exit handlers in the same order as defer statements
2019-01-29 10:29:38 -08:00
Anton Fisher 68a2b575f1
Add nested-logrus-formatter to README.md
Hi,
I'd like to propose another one third-party formatter for logrus (https://github.com/antonfisher/nested-logrus-formatter).
Thanks.
2019-01-26 14:16:28 -08:00
Richard Poirier 7d8d63893b
Merge pull request #893 from jiangxin/jx/fix-warningln
logger: fix wrong callback method
2019-01-22 11:28:20 -08:00
Jiang Xin f61e48bb8e logger: fix wrong callback method
Fix wrong callback in `logger.go`, and add test cases:

1. `logger.Warningln` should call `logger.Warnln`, not `logger.Warn`.

2. It's ok for `logger.Print` to call `entry.Info`, but calling `entry.Print`
   is better.

Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>
2019-01-22 20:46:10 +08:00
David Bariod 659e47340e
Merge pull request #890 from sirupsen/hook_fire_order_test
Add a unit test to ensure hook are called in their registration order
2019-01-17 13:59:11 +01:00
David Bariod 0f544bf278 Add a unit test to ensure hook are called in their registration order 2019-01-17 13:42:39 +01:00
David Bariod 11dad09cee
Merge pull request #889 from sirupsen/default_field_hook
Add an example hook which adds default fields
2019-01-17 09:42:32 +01:00
David Bariod a99ca4776d Add an example hook which adds default fields 2019-01-17 09:37:03 +01:00
Sergey Romanov 78fb3852d9 Remove unused variables in TextFormatter 2019-01-11 11:22:21 -08:00
Rich Poirier eef6b768ab Update Changelog for 1.3.0 2019-01-04 17:46:35 -08:00
Richard Poirier e1e72e9de9
Merge pull request #863 from lugray/generic_log
Add Generic Log functions with level via argument
2019-01-02 14:40:11 -08:00
Lisa Ugray bd9534b799 Test Log 2019-01-02 14:59:36 -05:00
Lisa Ugray e8fd0ba609 Remove sensitivity to file line changes 2019-01-02 14:58:51 -05:00
David Bariod 6180652bb0
Merge pull request #878 from sirupsen/json_level_marshalling
Implement TextUnmarshaller interface for Level type
2018-12-26 17:51:57 +01:00
David Bariod ff695daa36 Implement TextUnmarshaller interface for Level type
Since the implementation of the TextMarshaller interface
we could not unmarshal previously json marshalled Level value.
Fixes #873
2018-12-26 17:43:14 +01:00
Lisa Ugray a6668e7a60 Add Generic Log functions with level via argument 2018-12-20 15:49:25 -05:00
David Bariod 2067ea4241
Merge pull request #874 from sirupsen/fieldErrorBugFix
do not clear error formatting informative field
2018-12-15 16:21:19 +01:00
David Bariod 9abefb94aa do not clear error formatting informative field 2018-12-14 17:01:34 +01:00
David Bariod 9f049671f1
Merge pull request #864 from ceriath/patch-1
Remove colored output on windows
2018-12-10 09:02:29 +01:00
Ceriath d962013756 respect ForceColor and environment variables over OS check 2018-12-09 21:47:44 +01:00
David Bariod 93ebe33ece
Merge pull request #870 from smacker/skip_func_pointer_type_value_in_fields
Skip func pointer type value in fields
2018-12-09 15:03:58 +01:00
David Bariod 3f1910655c
Merge pull request #868 from dolmen/fix-travis-import-path
Travis: fix checkout dir to help contributors run Travis on their fork
2018-12-07 06:46:55 +01:00
Maxim Sukharev 08e8d6501d Skip func pointer type value in fields
Before there was introduced a fix for JSONFormatter when func type value
passed as Field. This commit does the same but for pointer to func.

Ref:
https://github.com/sirupsen/logrus/issues/642
https://github.com/sirupsen/logrus/pull/832
2018-12-05 19:14:19 +01:00
Olivier Mengué 0c5e33c7e0 Travis: fix checkout dir to help contributors run Travis on their fork
Set the Travis-CI checkout path to allow contributors running Travis-CI
on their fork to have Travis-CI launching using the canonical import
path instead of the path of the fork.
2018-11-25 23:15:12 +01:00
David Bariod 29d7eb25e8
Merge pull request #857 from h12w/master
fix race condition caused by writing to entry.Data
2018-11-22 07:14:47 +01:00
ceriath f1b98e4006
ignore expected color on windows 2018-11-16 15:19:37 +01:00
ceriath e9026580bf
Disable colored output on windows entirely
Currently the textformatter on windows outputs 

``←[31mERRO←[0m[0000] test windows``

when coloring is not disabled explicitly.
However, windows up to windows 8.1 does not support colored output on cmd entirely. Windows 10 added support for it, which is off by default and has to be enabled via registry or environment variable. Therefore i suggest removing colored output on windows entirely to make the output usable again.
2018-11-16 15:17:01 +01:00
David Bariod 91da99df23
Merge pull request #860 from xrstf/master
Fix hook example
2018-11-16 14:29:20 +01:00
David Bariod b0e671819e
Merge pull request #841 from sirupsen/fix_aix_build
Attempt to fix build break on aix
2018-11-16 14:26:04 +01:00
xrstf eab2c444ac fix hook example 2018-11-09 13:34:45 +01:00
Hǎi-Liàng "Hal" Wáng c7183bf629 fix missing parameter 2018-11-06 10:01:28 +00:00
Hǎi-Liàng "Hal" Wáng 2cafb78db2 fix race condition caused by writing to entry.Data, using the same technique as JSONFormatter 2018-11-05 12:35:02 +00:00
David Bariod 44067abb19 Merge remote-tracking branch 'upstream/master' 2018-11-03 07:28:19 +01:00
David Bariod 7eeb7b7cbd
Merge pull request #849 from FabienM/add-gelf-formatter
Add GELF to third party formatters
2018-11-01 08:55:17 +01:00
David Bariod bcd833dfe8 v1.2.0 changelog 2018-11-01 08:39:56 +01:00
David Bariod fd2308367e
Merge pull request #854 from sirupsen/fix_panic_text_formatter
fix panic in text formatter
2018-10-31 07:17:07 +01:00
David Bariod d10c2f9e3c fix panic in text formatter
The panic was caused to a nil pointer access when report
caller was activated with output coloring disabled

Fixes #852
2018-10-31 07:08:09 +01:00
David Bariod 566a5f6908
Merge pull request #845 from yanana/make-level-implement-text-unmarshaler
Make logrus.Level implement encoding.TextUnmarshaler
2018-10-29 07:08:39 +01:00
David Bariod 251d6bf713
Merge pull request #850 from sirupsen/caller_feature
Add optional log field with calling method
2018-10-28 18:17:14 +01:00
David Bariod 5a78c38d0e make file name comparison os independant 2018-10-28 18:12:32 +01:00
David Bariod d2654b752f add file and line number in output when report caller is enabled 2018-10-28 17:39:39 +01:00
David Bariod fa01b53097 move test functions and test utils functions in their own package 2018-10-28 14:22:33 +01:00
David Bariod ec57031db1 store a runtime.Frame in Entry instead of the caller function name 2018-10-28 14:21:49 +01:00
David Bariod 975c406ddb Use a sync.Once to init the reportCaller data 2018-10-27 15:21:30 +02:00
David Bariod 5fcd19eae6 add a SetReportCaller on Logger object 2018-10-27 15:19:06 +02:00
David Bariod 64d5b7e66c Merge branch 'master' into caller_feature 2018-10-27 15:10:52 +02:00
Fabien Meurillon 0c525822dc
Add GELF to third party formatters 2018-10-24 11:03:07 +02:00
Shun Yanaura 5c1f2cd52c Make logrus.Level implement encoding.TextUnmarshaler 2018-10-22 04:20:01 +09:00
David Bariod 4fabf2fffc
Merge pull request #846 from sirupsen/gomod_konsorten_version
Fix the version of windows coloring library dependency
2018-10-21 12:09:20 +02:00
David Bariod bb98c6c533 Fix the version of windows coloring library dependency 2018-10-21 11:53:14 +02:00
David Bariod 08e90462da
Merge pull request #844 from loren-osborn/pull/652_AddTraceLevelLogging
Added TRACE level logging.
2018-10-20 07:09:04 +02:00
Loren Osborn ed3ffa0190 PR#844: Added Trace to TestLogLevelEnabled() (requested by @dgsb) 2018-10-19 09:15:13 -07:00
Loren Osborn b54cafe5ce Addresses @stevvooe's backward compatibility concerns. 2018-10-18 10:12:27 -07:00
Maxim Korolyov ef9d84e9b3 Added trace log level. 2018-10-17 19:42:01 -07:00
Giedrius Dubinskas c7a33dc5de Add Trace level logging 2018-10-17 19:12:50 -07:00
drampull 4981d8161c Added TRACE level logging. 2018-10-17 18:22:00 -07:00
David Bariod 680f584d62
Merge pull request #842 from dgsb/master
Add an example for tracing global variable with hook
2018-10-16 13:22:44 +02:00
David Bariod 9c7692ccff disable colors on hook example 2018-10-15 21:32:20 +02:00
David Bariod f2ab87f230 Add an example for tracing global variable with hook 2018-10-15 21:20:03 +02:00
David Bariod ff92509e2c Attempt to fix build break on aix 2018-10-12 06:57:07 +02:00
David Bariod 4582136994
Merge pull request #837 from caalberts/test-fatal
Implements #813 - Add option to panic in `test.NewNullLogger`
2018-10-10 22:06:18 +02:00
Albert Salim a13c5db57c Fix typo in comment 2018-10-10 21:59:03 +08:00
Albert Salim 4346c76f26 Remove unnecessary wrapper function on `os.Exit` 2018-10-10 21:57:58 +08:00
Albert Salim 99bc300c8d Add a method Exit on Logger that calls `os.Exit` or alternate exit function.
This keeps backward compatibility for static declaration of logger
that does not specify `ExitFunc` field.
2018-10-10 21:54:15 +08:00
David Bariod ad15b42461 Update changelog for v1.1.1 release 2018-10-08 22:30:39 +02:00
David Bariod 3f90cee1e4 Rationalize os specific build constraints 2018-10-06 15:42:00 +02:00
Albert Salim 2be620216a Add option to panic in `test.NewNullLogger` to allow testing of
calls to `Fatal*`

See #813
2018-10-06 18:08:19 +08:00
David Bariod 1ed61965b9
Merge pull request #832 from sirupsen/skip_func_value
Skip func type value in fields.
2018-09-30 22:58:21 +02:00
David Bariod 7b467df697 Skip func type value in fields.
We skip those unprintable fields and an error field
instead of dropping the whole trace.

Fixes #642
2018-09-30 22:51:02 +02:00
David Bariod a67f783a38 Update changelog for v1.1.0 release 2018-09-25 21:35:46 +02:00
David Bariod 6cfd37fe59
Merge pull request #830 from sirupsen/custom_sorting_func
Add custom sorting function in text formatter
2018-09-25 13:52:27 +02:00
David Bariod 73bc94e60c Add custom sorting function in text formatter 2018-09-25 13:45:23 +02:00
David Bariod 33a1e118e1
Merge pull request #825 from dgsb/windows_build
Add missing module dependency for windows build
2018-09-19 13:51:26 +02:00
David Bariod 5a88d3c21d Add missing module dependency for windows build 2018-09-19 13:46:16 +02:00
David Bariod 568026db28
Merge pull request #823 from mdittmer/fix-appengine
Fix AppEngine builds
2018-09-18 20:41:03 +02:00
Mark Dittmer 629982b495 DisableColors in two tests to fix AppEngine configuration 2018-09-18 13:54:23 -04:00
Mark Dittmer 0a8fc8d77c Add AppEngine test configurations to travis to a void regression 2018-09-17 13:56:18 -04:00
Mark Dittmer f1ce1baf56 Fix copypasta 2018-09-17 13:52:43 -04:00
Mark Dittmer 90501cfcc5 Fix AppEngine builds
`go build -tags appengine [...]` currently yields:

```
.../github.com/sirupsen/logrus/text_formatter.go:84:4: undefined: initTerminal
```

This change implements `initTerminal` for AppEngine builds.
2018-09-17 13:50:48 -04:00
David Bariod d23a0f20d4
Merge pull request #822 from sirupsen/konsorten-win10-color-support
windows 10 color support
2018-09-16 10:20:35 +02:00
David Bariod 98c898cc2d Fix gopherjs build constraint name 2018-09-16 10:14:59 +02:00
David Bariod c38641a38d Merge branch 'win10-color-support' of git://github.com/konsorten/logrus into konsorten-win10-color-support 2018-09-16 09:12:22 +02:00
David Bariod f3df9aeffd Merge branch 'master' of git://github.com/mariannefeng/logrus into mariannefeng-master 2018-09-08 11:18:16 +02:00
David Bariod eed7c22374 Fix travis build for go 1.11 with modules 2018-09-08 09:44:05 +02:00
David Bariod 66895ce165 Fix module name and remove unused dependencies 2018-09-08 08:27:05 +02:00
David Bariod dea96f0092 Merge branch 'master' of git://github.com/dgodd/logrus into dgodd-master 2018-09-08 08:17:42 +02:00
David Bariod cd7816122a
Merge pull request #818 from whuang8/patch-1
Fix spelling in Entry.Buffer comment
2018-09-08 08:16:38 +02:00
William Huang 88eb166d31
Fix spelling in Entry.Buffer comment 2018-09-06 21:11:16 -05:00
Dave Goddard f75951b604 Add go module support
Travis will only run the "go get" install lines if using go 1.10.x
2018-09-06 09:54:44 -03:00
David Bariod 3791101e14
Merge pull request #816 from sirupsen/syslog_hook_example
Use syslog instead of airbrake as syslog example
2018-09-04 22:21:35 +02:00
David Bariod 4bcb47b846 commit to trigger appveyor build 2018-09-04 22:15:13 +02:00
David Bariod 8b120431f3 Fix example build on windows 2018-09-03 21:58:50 +02:00
David Bariod 7556e245e2 Use syslog instead of airbrake as syslog example
The purpose is to reduce package dependencies, fixes #809.
2018-09-02 20:22:06 +02:00
David Bariod 78fa2915c1
Merge pull request #812 from sirupsen/bump_go_version_travis
bump go toolchain version in travis
2018-08-30 22:11:51 +02:00
David Bariod e58aa84bc1 bump go toolchain version in travis 2018-08-30 22:06:53 +02:00
David Bariod 56da607151
Merge pull request #811 from sirupsen/add_changelog
Add previously forgotten v1.0.6 description in changelog
2018-08-30 07:21:54 +02:00
David Bariod 98d0f313fe Add previously forgotten v1.0.6 description in changelog
fixes #802
2018-08-30 07:17:54 +02:00
David Bariod 51df1d3148
Merge pull request #762 from lhauspie/master
feat: new methods to check enabled log level
2018-08-27 07:22:11 +02:00
Logan HAUSPIE 90bf2e7f39
feat(LogLevel): taking in account code review from David Bariod 2018-08-27 00:16:06 +02:00
Logan HAUSPIE 0ab534bf6c
Merge remote-tracking branch 'upstream/master' 2018-08-26 23:36:08 +02:00
David Bariod 49fbef4694
Merge pull request #647 from ChimeraCoder/logger-docstring
Fix typo in docstring
2018-08-26 21:09:48 +02:00
David Bariod b24eae79a4 Merge branch 'hooks_replace' of git://github.com/betrok/logrus into betrok-hooks_replace 2018-08-26 20:44:53 +02:00
David Bariod 2c7fc976fe Merge remote-tracking branch 'upstream/master' 2018-08-26 20:12:16 +02:00
betrok 13d10d8d89 return old hooks from RelplaceHooks 2018-08-26 14:40:51 +03:00
David Bariod 3ed92f8c1f Merge branch 'cleanup-exported' of git://github.com/conjmurph/logrus into conjmurph-cleanup-exported 2018-08-26 11:58:08 +02:00
David Bariod 0908e58e06
Merge pull request #685 from HectorMalot/master
TextFormatter behaviour aligned with stdlib log (fixes #167)
2018-08-26 11:06:30 +02:00
betrok 7a0120e2c6 logger.ReplaceHooks 2018-08-22 12:10:05 +03:00
Aaron Greenlee fc587f31c8
Merge pull request #798 from cheungpat/gopherjs-compat
Fix GopherJS build tags
2018-08-16 21:25:29 -04:00
David Bariod e4b0c6d782
Merge pull request #800 from sirupsen/nolith-clicolor
Add CLICOLOR suport. Rework on #787
2018-08-13 17:35:01 +02:00
David Bariod b5e6fae4fb Cleanup on unit test on isColored 2018-08-13 17:27:32 +02:00
David Bariod cadf2ceaf8 Add unit test for TextFormatter.isColored 2018-08-09 15:01:49 +02:00
David Bariod eb968b6506 Fix for CLICOLOR_FORCE handling 2018-08-09 15:00:46 +02:00
Dennis 8a6a17c003
Fixed missing brace after wrong merge 2018-08-05 22:40:58 +02:00
Dennis f9ef1703ff
Merge branch 'master' into master 2018-08-05 22:34:28 +02:00
Kwok-kuen Cheung d950ecd55b Remove unnecessary text_formatter file 2018-08-06 00:43:58 +08:00
Kwok-kuen Cheung da39da2348 Keep terminal check naming convention 2018-08-06 00:43:49 +08:00
Alessio Caiazza 37d651c1f2 Add CLICOLOR support
This implement CLICOLOR and CLICOLOR_FORCE check on terminal coloring
as defined in https://bixense.com/clicolors/
2018-08-01 17:37:01 +02:00
David Bariod d329d24db4
Merge pull request #796 from sirupsen/fix/entry_empty_fields
Ensure a new entry data fields are empty
2018-07-31 18:13:55 +02:00
David Bariod 179037fcd4 Ensure a new entry data fields are empty
Fixes #795
2018-07-31 18:08:27 +02:00
David Bariod a4096716b0 Merge branch 'taylorchu/json_use_buf_pool' into master 2018-07-29 10:37:48 +02:00
David Bariod 7b58bf1472
Merge pull request #794 from sirupsen/logger_benchmark
Add logger benchmark
2018-07-28 17:25:36 +02:00
David Bariod d3162770a8 Add logger benchmark 2018-07-28 17:21:06 +02:00
David Bariod 87dfa988d1 Merge branch 'json_use_buf_pool' of git://github.com/taylorchu/logrus into taylorchu-json_use_buf_pool 2018-07-28 11:01:11 +02:00
David Bariod c108f5553c Merge branch 'field-logger-with-hooks-race' of git://github.com/thundercat1/logrus into master 2018-07-23 13:53:07 +02:00
David Bariod 3e01752db0 Merge branch 'moriyoshi/refix-707' of git://github.com/moriyoshi/logrus into fix_firehooks 2018-07-21 09:00:01 +02:00
David Bariod a1f2e46d92
Merge pull request #792 from dgsb/master
limit the build/test matrix to the two latest stable version
2018-07-20 13:41:35 +02:00
David Bariod 54db2bb29a limit the build/test matrix to the two latest stable version 2018-07-20 13:35:37 +02:00
David Bariod 07e1216af7
Merge pull request #791 from dgsb/master
properly fix the hooks race test
2018-07-20 13:34:42 +02:00
David Bariod 6999e59e73 properly fix the hooks race test 2018-07-20 13:16:19 +02:00
David Bariod 92052687f8 Merge branch 'override-time' of git://github.com/sbrisson2/logrus into sbrisson2-override-time 2018-07-12 22:16:18 +02:00
Simon Brisson 725f3be199 Adds WithTime to Logger and Entry types, as well as a pure module-level function. 2018-07-12 13:25:17 -04:00
Simon Brisson 52b92f5b89 Allows overriding Entry.Time. 2018-07-03 11:38:02 -04:00
David Bariod e54a77765a
Merge pull request #779 from daskol/iss-241
[#241] Allow to set writer during logger usage.
2018-07-02 07:55:18 +02:00
Daniel Bershatsky fc9bbf2f57 [#241] Allow to set writer during logger usage. 2018-07-01 23:57:16 +03:00
David Bariod dd2931c82a
Merge pull request #771 from codepainters/master
Support for Entry data under nested JSON dictionary.
2018-07-01 18:24:02 +02:00
David Bariod 56faed7e3d
Merge pull request #743 from dgsb/master
Improve documentation for Fatal* class functions
2018-07-01 18:02:41 +02:00
David Bariod e3292c4c4d
Merge pull request #677 from neilisaac/text-field-map
Add FieldMap support to TextFormatter
2018-06-25 07:25:43 +02:00
Christian Stewart eed1c0f832 Fix GopherJS build tags
The GopherJS build tag is "js" not "gopherjs"

Signed-off-by: Christian Stewart <christian@paral.in>
2018-06-20 21:39:23 -07:00
Przemyslaw Wegrzyn 2ce6c0cb44 Support for Entry data under nested JSON dictionary. 2018-06-19 14:31:57 +02:00
Neil Isaac 6b28c2c7d7 error message 2018-06-18 21:39:53 -04:00
Neil Isaac 5d60369ef3 Fixed prefixFieldClashes for TextFormatter and added coverage 2018-06-18 21:32:35 -04:00
Neil Isaac 21326f6618 Merge remote-tracking branch 'origin/master' into text-field-map 2018-06-18 21:08:59 -04:00
David Bariod 75068beb13
Merge pull request #756 from matejb/patch-1
Fix Logger.WithField description
2018-06-18 13:14:19 +02:00
Logan HAUSPIE 4225d694ba
feat: new methods to check enabled log level
Adding 6 methods on 'exported', 'logger' and 'entry':
- IsDebugEnabled() bool
- IsInfoEnabled() bool
- IsWarnEnabled() bool
- IsErrorEnabled() bool
- IsFatalEnabled() bool
- IsPanicEnabled() bool

Replace duplicated 'if logger.level() >= XxxxLevel' by a call to the new methods in 'logger' and 'entry'

Closes #761
2018-06-06 22:45:35 +02:00
Moriyoshi Koizumi 070c81def3 Revert the change introduced in #707 and do the proper fix. Fixes #729 2018-05-30 09:50:59 +00:00
Simon Eskildsen ea8897e799
Merge pull request #758 from dgsb/hook_wiki
Move the hook services list to a wiki page
2018-05-23 03:42:43 -04:00
David Bariod 098a5a7cd7 Move the hook services list to a wiki page 2018-05-19 21:26:32 +02:00
Matej Baćo caed59ec68
Fix Logger.WithField doscription
I was puzzled by function documentation not mentioning it works with Error level, so I had to check it out by creating example before I add logrus as a dependency on the company project. Example confirmed what logic was telling me that Logger.WithFields works with Error level of logs.
This is is a fix of this small documentation oversight.
2018-05-17 11:02:39 +02:00
taylorchu aa6766adfe PERF: use buffer pool in json formatter
benchmark             old ns/op     new ns/op     delta
BenchmarkLogrus-8     4163          4369          +4.95%

benchmark             old allocs     new allocs     delta
BenchmarkLogrus-8     36             31             -13.89%

benchmark             old bytes     new bytes     delta
BenchmarkLogrus-8     3027          2163          -28.54%
2018-05-15 16:46:37 -07:00
Simon Eskildsen 0dad3b6953
Merge pull request #752 from dgsb/bug/751
Fix a race condition in TestLoggingWithHooksRace
2018-05-15 11:40:58 -04:00
Simon Eskildsen bde08903c7
Merge pull request #744 from dbs5/master
added Anexia CloudLog to list of hooks
2018-05-15 00:41:40 -04:00
Simon Eskildsen 7971176ef8
Merge pull request #748 from LyricTian/master
add mysql hook
2018-05-15 00:41:24 -04:00
Simon Eskildsen 620a8739dc
Merge pull request #754 from dgsb/bug/753
Update go versions in travis configuration.
2018-05-15 00:41:04 -04:00
David Bariod b1e82bef65 Update go versions in travis configuration.
We only keep the latest 3 language release.
Fixes #753
2018-05-13 10:29:45 +02:00
David Bariod 8369e2f077 Fix a race condition in TestLoggingWithHooksRace 2018-05-12 15:52:19 +02:00
lyric 507c822874 add mysql hook 2018-04-27 17:53:47 +08:00
dbs5 e63a8df340 added Anexia CloudLog to list of hooks 2018-04-16 20:20:18 +02:00
David Bariod 5513c60034 Improve documentation for Fatal* class functions 2018-04-16 14:16:44 +02:00
Felix Kollmann 2f58bc83cb Unified terminal initialization code handling 2018-04-03 04:55:52 +02:00
Felix Kollmann 9bc59a5969 Fixed initTerminal() was run for non-terminals 2018-04-03 04:50:50 +02:00
Felix Kollmann cf5eba7dfd Simplified file structure 2018-04-03 04:47:29 +02:00
Felix Kollmann c9a46a1e7c Added terminal check on Windows 2018-04-03 04:40:58 +02:00
Felix Kollmann 7d2a5214bf Extended conditions to include non-native builds 2018-04-03 01:23:44 +02:00
Felix Kollmann f142d8145b Improved building of non-windows code 2018-04-03 01:15:45 +02:00
Felix Kollmann bb487e068c Added support for text coloring on Windows 10 2018-04-03 00:25:30 +02:00
Stephen Day 778f2e774c
Merge pull request #407 from roganartu/master
Added option to disable level text truncation in default text formatter
2018-03-29 15:59:52 -07:00
Stephen Day a0285bf3ac
Merge pull request #709 from silverlyra/dynamic-clash
Have prefixFieldClashes respect the JSON FieldMap
2018-03-29 14:32:20 -07:00
Stephen Day 6ecf50095b
Merge pull request #713 from earlzo/hotfix/wrong-comment
Fixed: wrong comment for NewEntry
2018-03-29 14:30:34 -07:00
Stephen Day 7406e22f35
Merge pull request #726 from wla80/master
Fix run-on sentence
2018-03-29 14:30:08 -07:00
Stephen Day 41f384185c
Merge pull request #739 from hondrus/patch-2
delete dead link
2018-03-29 14:22:02 -07:00
Olzhas Ilyubayev 19b9c9e1ff
delete dead link
https://github.com/ripcurld00d/logrus-logzio-hook isn't available now
2018-03-28 12:10:14 +06:00
Wilson b537da569f Fix run-on sentence 2018-03-22 14:05:31 -07:00
Marianne Feng 723dd3cd1f changed prettyprinting to use spaces as opposed to /t 2018-03-20 18:20:51 -07:00
Marianne Feng 509bff05a7 Merge branch 'master' of github.com:sirupsen/logrus 2018-03-20 18:18:22 -07:00
Stephen Day 90150a8ed1
Merge pull request #711 from oxgrouby/master
reamde: add logrus-clickhouse-hook
2018-03-14 18:07:03 -07:00
Simon Eskildsen c155da1940 changelog: add 1.0.5 2018-03-11 18:51:37 -04:00
Dave Clendenan c74e39f432
Merge branch 'master' into master 2018-03-08 15:53:25 -08:00
Stephen Day f4ee691250
Merge pull request #722 from dylanmei/add-kafka-rest-proxy-hook
Add Kafka REST Proxy hook to README
2018-02-28 14:01:33 -08:00
Stephen Day 796df9f552
Merge pull request #717 from gracenoah/gopherjs
add gopherjs build tag
2018-02-28 13:44:18 -08:00
Dylan Meissner 91b159d34d Add Kafka REST Proxy hook to README 2018-02-27 16:51:24 -08:00
Grace Noah c840e59446 add gopherjs build tag
it behaves the same way as the appengine tag

fix #716
2018-02-23 21:35:25 +00:00
earlzo 1893e9a3ed Fixed: comment 2018-02-20 16:28:12 +08:00
Andrew Rezcov f4118d2ead reamde: add logrus-clickhouse-hook 2018-02-16 16:50:54 +03:00
Lyra Naeseth efab7f37b7 Have prefixFieldClashes respect the JSON FieldMap
Currently, it will fix clashes that don't exist and miss clashes that
do exist due to field remapping.

Fixes #708
2018-02-13 17:49:16 -08:00
Simon Eskildsen 8c0189d9f6
Merge pull request #707 from imjching/logger-with-hooks-race
Fix race condition in entry fireHooks() method
2018-02-13 09:31:10 -05:00
Jay Ching Lim be569094e9
Make fireHooks() method receive a copy of Entry structure to avoid race conditions
Signed-off-by: Jay Ching Lim <imjching@users.noreply.github.com>
2018-02-12 17:26:48 -05:00
Stephen Day 9f91ab2ef9
Merge pull request #704 from phillipj/patch-1
Fix typo in README.md
2018-02-07 10:27:17 -08:00
Stephen Day dcf2d47dfb
Merge pull request #697 from jjcollinge/patch-1
Add Application Insights hook to README
2018-02-06 17:40:45 -08:00
Phillip Johnsen 178041e53c
Fix typo in README.md 2018-02-05 21:59:23 +01:00
Michael Haines 828a649ef2 rename fieldLogger to entry 2018-02-05 12:52:11 -07:00
Michael Haines eeb653535c Lock mutex before formatting to avoid race 2018-02-05 12:44:11 -07:00
Michael Haines efbfdb5f09 Add failing test for using a FieldLogger with hooks inside goroutines 2018-02-05 12:42:00 -07:00
Joni Collinge 0cf9f0bff9
Made text consistent with other hooks 2018-01-30 16:00:33 +00:00
Joni Collinge 516f6c178d
Add Application Insights hook to README
Adds README link to Application Insights hook
2018-01-30 15:58:49 +00:00
Antoine Grondin 768a92a026
Merge pull request #695 from mauricio/master
Fix deadlock on panics at Entry.log
2018-01-29 20:18:52 +02:00
Tony Lee 269eab0f22
Merge branch 'master' into master 2018-01-23 23:04:29 -05:00
Maurício Linhares 977e03308a
Fix deadlock on panics at Entry.log
When calling Entry.log a panic inside some of the
locking blocks could cause the whole logger to deadlock.

One of the ways this could happen is for a hook to cause
a panic, when this happens the lock is never unlocked and
the library deadlocks, causing the code that is calling
it to deadlock as well.

This changes how locking happens with unlocks at defer
blocks so even if a panic happens somewhere along the log
call the library will still unlock and continue to function.
2018-01-22 10:52:46 -05:00
Dennis de Reus 92aece568b TextFormatter behaviour aligned with stdlib log (fixes #167)
stdlib `log` adds a newline at the end of a message if none is present,
otherwise does not. Before this change logrus would always add
a newline, resulting in inconsistent behaviour if stdlib log was
replaced with logrus, and a user would e.g. use 'log.printf("test\n")'
2017-12-29 20:26:35 +01:00
conor eb156905d7 remove .gitignore changes and update AddHook 2017-12-21 14:16:49 -05:00
conor 20cc8e2bc3 remove .gitignore changes 2017-12-21 14:10:48 -05:00
conor 0c03a05a0e mirror and wrap Logger instance methods in exported.go 2017-12-21 14:04:49 -05:00
Simon Eskildsen d682213848 changelog: 1.0.4 2017-12-05 15:32:29 -05:00
Simon Eskildsen 49f0a85ee5
Merge pull request #635 from ernestoalejo/master
Split terminal check to add build tags to support App Engine.
2017-12-05 15:30:37 -05:00
Marianne Feng 1917d221a6 Merge branch 'master' of github.com:mariannefeng/logrus 2017-11-30 23:11:56 -08:00
Neil Isaac b9eceae8f6
fix example 2017-11-21 22:56:37 -05:00
Neil Isaac bf1fb70b2b Add FieldMap support to TestFormatter 2017-11-21 22:43:47 -05:00
Simon Eskildsen 95cd2b9c79
Merge pull request #644 from marccarre/add-promrus-hook
Add promrus to list of hooks.
2017-11-18 07:42:23 -05:00
Simon Eskildsen cb6f9634ca
Merge pull request #658 from xentek/patch-1
Adds `logbeat` hook to README
2017-11-18 07:41:52 -05:00
Simon Eskildsen 5c6f722619
Merge pull request #661 from klautcomputing/patch-1
Fixes a typo in README.md
2017-11-18 07:41:40 -05:00
Simon Eskildsen 4dd868ba91
Merge pull request #669 from StevenYCChou/patch-1
Fix typo in README.md
2017-11-18 07:40:44 -05:00
Yen-Cheng Chou 73a1342386
Fix typo in README.md 2017-11-14 16:58:00 -05:00
Marianne Feng 10d6a5b427 removed useless line from readme 2017-11-06 16:28:37 -08:00
Marianne Feng 639325f81a added pretty print option for json logs 2017-11-06 16:19:47 -08:00
Felix Glaser 9700beb9b6
Update README.md 2017-11-01 14:11:54 -04:00
Eric Marden 1858a8574d Adds `logbeat` hook to README
Adds link to new hook for sending logrus entries to Opbeat service.
2017-10-26 13:58:09 -05:00
Aditya Mukerjee c44d524628 Fix typo in docstring 2017-10-09 11:18:43 -04:00
Marc CARRE 4844e5856d Add promrus to list of hooks. 2017-10-04 15:51:55 +01:00
Ernesto Alejo 7d3ddc68a3 Split terminal check to add build tags to support App Engine. 2017-09-06 19:34:58 +02:00
Albert Nigmatzianov 6137e6b13d Fix typo in docs for New() 2017-09-02 15:05:25 +05:00
Albert Nigmatzianov e59e5eaa92 Fix typo in docs for New() 2017-09-02 15:04:38 +05:00
Albert Nigmatzianov 1fb8c53680 Fix typo in docs for New() 2017-09-02 15:03:37 +05:00
Dave Clendenan 40f571805d Merge branch 'master' of https://github.com/dclendenan/logrus 2017-08-30 15:43:36 -07:00
Dave Clendenan eab1019f63 Merge branch 'master' of https://github.com/sirupsen/logrus 2017-08-30 15:39:09 -07:00
Damien Mathieu 89742aefa4 Merge pull request #612 from ChimeraCoder/add-hook-race
Fix data race for hooks
2017-08-22 15:27:46 +02:00
Damien Mathieu 84573d5f03 Merge pull request #627 from kpfaulkner/azuretablehookref
Added reference to AzureTableHook
2017-08-21 09:31:01 +02:00
Ken Faulkner cd1114dc25 Added reference to AzureTableHook 2017-08-20 12:47:38 +10:00
Aditya Mukerjee 9bc52e3981 Fix test assertion 2017-08-17 15:22:06 +01:00
Aditya Mukerjee c830992a61 Take lock on mutex when firing hooks 2017-08-17 15:22:06 +01:00
Aditya Mukerjee 66230b2871 Add test for race condition in hooks 2017-08-17 15:22:06 +01:00
Aditya Mukerjee 3d1341ce2c Add AddHook method for logger 2017-08-17 15:22:06 +01:00
Damien Mathieu 68806b4b77 Merge pull request #613 from rossmcdonald/rossmcdonald-add-telegram-to-readme
Add Telegram hook to README.md
2017-08-17 10:55:56 +02:00
Damien Mathieu 68e63515d5 Merge pull request #621 from tracer0tong/master
Update README.md to fix link to Kafka hook
2017-08-17 10:41:08 +02:00
Tracer Tong 5efed00cb0 Update README.md to fix link to Kafka hook
Current link is broken. I have made a mix from couple of dead projects and I think I will support my version for long time.
2017-08-17 16:33:45 +09:00
Paul Seiffert f006c2ac47
changelog: bump to v1.0.3 2017-08-15 22:20:55 +02:00
Paul Seiffert e98d2a2169 Merge pull request #616 from rafecolton/make-examples-testable
Replace example files with testable examples
2017-08-15 22:19:09 +02:00
Rafe Colton e3e7388b95 Replace example files with testable examples 2017-08-10 10:46:39 -04:00
Ross McDonald 3bd397e07f Add Telegram hook to README.md. 2017-08-07 16:36:23 -05:00
Dave Clendenan e3d17767d1 MD formatting 2017-08-02 17:35:14 -07:00
Dave Clendenan 9ce1c9e3b5 add github path to log message in readme 2017-08-02 17:33:13 -07:00
Dave Clendenan b1db1b9c67 regex assertion rather than literal, for github path 2017-08-02 17:28:13 -07:00
Dave Clendenan 3cb9e18ef9 test updates
- add tests for callers accessed directly or via function pointer
  (results are unchanged)
- undo unwanted capitalization/export in previous commit
2017-08-02 17:21:18 -07:00
Dave Clendenan 7d48cb786e Merge branch 'master' of https://github.com/sirupsen/logrus
Conflicts:
	alt_exit_test.go
	formatter.go
	json_formatter.go
2017-08-02 13:18:39 -07:00
Damien Mathieu 181d419aa9 Merge pull request #604 from sirupsen/use_ssh_terminal
Remove os-specific IsTerminal methods
2017-07-28 09:42:14 +02:00
dmathieu e66f22976f remove os-specific IsTerminal methods, and use x/crypto/ssh/terminal instead 2017-07-27 15:26:52 +02:00
Damien Mathieu 9aa7601a11 Merge pull request #605 from sirupsen/fix_tempfile_all_platforms
Fix creating temp files on non-unix platforms
2017-07-27 15:26:26 +02:00
dmathieu 60f3438580 fix creating temp files on non-unix platforms 2017-07-27 14:46:09 +02:00
dmathieu d4ae98b177 improve test failure messages for alt_exit_test 2017-07-27 14:06:49 +02:00
Damien Mathieu c2f40cf579 Merge pull request #600 from AndreKR/appveyor
Add appveyor.yml
2017-07-27 09:51:31 +02:00
André Hänsel 6f38f401f7 Add appveyor.yml 2017-07-27 05:09:24 +02:00
Damien Mathieu abee6f9b06 Merge pull request #598 from sirupsen/goling_formatters
Fix golint issues in formatters
2017-07-26 20:39:46 +02:00
Damien Mathieu 259b4b7f45 Merge pull request #594 from sirupsen/sys_unix
Use x/sys/unix instead of syscall
2017-07-26 14:47:05 +02:00
dmathieu 325575f181 fix golint issues in formatters 2017-07-26 14:26:30 +02:00
dmathieu 8a90bf3fff use x/sys for non-unix files 2017-07-26 10:51:31 +02:00
dmathieu f4125cea1b get Termios from x/sys/unix 2017-07-26 10:51:28 +02:00
dmathieu 0af92424f9 install x/sys/unix before running the travis tests 2017-07-26 10:50:52 +02:00
Christy Perez a9ca4bfe68 switch terminal_linux to x/sys/unix from syscall
Closes sirupsen/logrus/issues/515

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com>
2017-07-26 10:50:51 +02:00
Damien Mathieu 3114d6f617 Merge pull request #597 from DAddYE/master
IsTerminal should work in nacl too
2017-07-26 10:09:27 +02:00
Davide D'Agostino 31e110ccae IsTerminal should work in nacl too
I'm running logrus on a nacl environment and I get:

```
github.com/sirupsen/logrus/text_formatter.go:70: undefined: IsTerminal
```
2017-07-25 14:20:14 -07:00
Damien Mathieu 8d4f6a97d3 Merge pull request #595 from mozillazg/patch-1
readme: remove duplicate godoc link
2017-07-25 17:02:49 +02:00
Huang Huang a663abbf13 readme: remove duplicate godoc link 2017-07-25 22:52:40 +08:00
Damien Mathieu 86bd21e371 Merge pull request #593 from sirupsen/buffer_length
Use buffer length to avoid generating strings every time
2017-07-25 10:20:43 +02:00
dmathieu 211aba39c8 use buffer length to avoid generating strings every time 2017-07-25 09:39:35 +02:00
Damien Mathieu 3eef8ce63d Merge pull request #591 from sirupsen/fix_trailing_space
Fix trailing space
2017-07-22 13:39:13 +02:00
dmathieu 159e991025 only add a space between text entries if there is already content
So we don't have a trailing space at the end of each log line
2017-07-21 16:14:28 +02:00
dmathieu b264ba77c3 add test in text formatter checking the formatting 2017-07-21 16:06:23 +02:00
dmathieu 00386b3fbd remove unfinished doc sentence
Closes #521
2017-07-19 17:47:53 +02:00
Damien Mathieu edfd0b9f75 Merge pull request #589 from sirupsen/test_subpackages
Test all subpackages too
2017-07-19 17:47:00 +02:00
dmathieu a4149b6fd1 travis needs the airbrake hook 2017-07-19 16:53:07 +02:00
dmathieu bfff600029 test all subpackages too 2017-07-19 16:31:49 +02:00
dmathieu fdea1df936 add joonix/log to the formatters
Closes #520
2017-07-19 16:24:10 +02:00
Damien Mathieu e49c59d69b Merge pull request #587 from sirupsen/logrus_syslog
Rename logrus_syslog package to syslog
2017-07-18 16:06:42 +02:00
dmathieu f30ff25fb7 rename logrus_syslog package to syslog
Package names shouldn't be using underscores. There is also nothing
wrong with naming this package `syslog`. People can include it with any
name they wish.
2017-07-18 15:18:46 +02:00
Damien Mathieu 5ff5dd844d Merge pull request #585 from DmitriyMV/improve-doc
Improve logrus.Entry.Level documentation
2017-07-17 09:50:14 +02:00
DmitriyMV 95002bc717 Improve logrus.Entry.Level documentation 2017-07-14 15:04:50 +03:00
Damien Mathieu 51dc0fc643 Merge pull request #580 from sirupsen/remove_quote_character
Remove QuoteCharacter option
2017-07-13 13:57:24 +02:00
dmathieu 2727ac94b0 remove QuoteCharacter option
This seems to be one of the most reported issues, as it makes it a lot
harder to safely escape strings.

This option is very much an edge case, and it's causing too much issues
compared to what it provide.
2017-07-13 13:46:35 +02:00
dmathieu a3f95b5c42 add changelog entry about making SetLevel public 2017-07-13 13:42:50 +02:00
Damien Mathieu c26a3edef1 Merge pull request #574 from stevejarvis/master
Export logger's set level function to address #503.
2017-07-13 13:42:06 +02:00
Damien Mathieu 1fe8319fca Merge pull request #584 from Jimdo/changelog
Mention quoting bugfix in CHANGELOG
2017-07-12 18:35:25 +02:00
Paul Seiffert 1fb8942542
Mention quoting bugfix in CHANGELOG 2017-07-12 18:16:53 +02:00
Damien Mathieu 3f40c78a45 Merge pull request #583 from Jimdo/fix_quoting_nonstring
Quote non-string Values
2017-07-12 18:14:13 +02:00
Paul Seiffert b019ab48c5
Error is not a special case anymore 2017-07-12 17:54:01 +02:00
Paul Seiffert 5f89343f84
Generalize test case 2017-07-12 17:54:01 +02:00
Paul Seiffert 4c4851c96a
Reduce duplicate code 2017-07-12 17:16:13 +02:00
Paul Seiffert b9cfd82645
Quote non-string values if necessary 2017-07-12 17:15:13 +02:00
dmathieu 10e5e38b53 remove ^ from custom quote characters
As of #563, this charater is not quoted anymore.
2017-07-12 14:33:57 +02:00
Damien Mathieu 6f87387fae Merge pull request #563 from meatballhat/unquote-more-chars
Allow more chars in unquoted text formatter output
2017-07-12 13:55:30 +02:00
Damien Mathieu 398dd088c2 Merge pull request #399 from pkieltyka/master
TextFormatter: brighten up the blue
2017-07-12 13:42:20 +02:00
Simon Eskildsen 7f976d3a76 readme: link to comment on casing 2017-07-10 14:22:29 -04:00
Simon Eskildsen 75b918d052 Merge pull request #575 from Jimdo/quote_safely
Safely format data when printing
2017-07-10 13:09:21 -04:00
Paul Seiffert 04a001ce50
Mention bugfix in changelog 2017-07-10 17:40:03 +02:00
Paul Seiffert 0025402362
Extract quoting into separate method 2017-07-10 14:18:45 +02:00
Paul Seiffert 0383f49850
Use custom quote char and escape it 2017-07-10 14:09:37 +02:00
kpcyrd f78f8d07f6
Safely format data when printing
Fixes #531
2017-07-10 13:12:47 +02:00
Simon Eskildsen 3d84af1ae0 Merge pull request #545 from shuLhan/shuLhan-hook-mattermost
README: add hook for Mattermost
2017-07-09 13:36:25 -04:00
Simon Eskildsen 8c2f155eec Merge pull request #556 from Deseao/master
Update test hook name
2017-07-09 13:35:57 -04:00
Simon Eskildsen 70f6d018e0 Merge pull request #571 from mcandre/patch-1
fix casing
2017-07-09 13:35:17 -04:00
Steve Jarvis 20d755ea5e Export logger's set level function to address #503. 2017-07-07 16:32:18 -04:00
Andrew Pennebaker 3963c935b8 fix casing
fix casing leftover from older logrus
2017-07-06 17:21:34 -05:00
Simon Eskildsen 59d0ca41e5 readme: add note about lookign for maintainers 2017-07-06 09:44:07 -04:00
Simon Eskildsen 7dd06bf38e readme: clarify casing issues further 2017-06-29 20:54:20 -04:00
Simon Eskildsen 3d4380f53a readme: remove < 1.0.0 note from readme 2017-06-20 10:45:10 -04:00
Dan Buch 5d67428857
Allow more chars in unquoted text formatter output 2017-06-18 09:48:52 -04:00
Alex Haynes a279ebafa6 Update hook name
There is no hooks/null
2017-06-13 13:28:47 -05:00
Simon Eskildsen 85b1699d50 ci: v1.0.0 fix travis 2017-06-08 18:14:41 -04:00
Simon Eskildsen 202f25545e changelog: bump to v1.0.0 2017-06-08 17:02:02 -04:00
Simon Eskildsen 68cec9f21f hooks: remove non-released null hook 2017-06-06 16:59:45 -04:00
Simon Eskildsen abb8cd67b6 readme: update with reference to fixing glide 2017-06-06 16:59:25 -04:00
Simon Eskildsen 33a34430d1 Merge pull request #548 from shinji62/feature/adding_syslogtls_hook_readme
Add TLS syslog hook to the readme.
2017-06-06 15:08:02 -04:00
Simon Eskildsen 5b44a589ca Merge pull request #547 from bobtfish/rename_syslog
Update due to rename of github account
2017-06-06 15:07:04 -04:00
Simon Eskildsen d3731ac026 readme: more explicit case instructions 2017-06-06 15:05:30 -04:00
Etourneau Gwenn a62b1531c0
Updating Readme to add syslogtls repo. 2017-05-25 09:53:41 +09:00
Tomas Doran 0afea37159 Update due to rename of github account 2017-05-19 13:00:16 +01:00
Shulhan c37067a498 README: add hook for Mattermost 2017-05-18 17:18:15 +07:00
Simon Eskildsen 5e5dc89865 travis: clean up 2017-05-15 06:59:10 -04:00
Simon Eskildsen 0efc3b9c6f travis: don't go get recursively 2017-05-15 06:34:13 -04:00
Simon Eskildsen ce6942b8d7 Merge pull request #507 from alxrem/patch-1
Fixed typo in the comment
2017-05-15 06:33:06 -04:00
Simon Eskildsen 8ac8861ee5 Merge pull request #511 from jqin/master
Fix the JSONFormatter example comment
2017-05-15 06:32:00 -04:00
Simon Eskildsen df6d5a7115 travis: don't go get 2017-05-15 06:31:11 -04:00
Simon Eskildsen 1ba5ca0adb Merge pull request #482 from chmllr/patch-1
Fixes typo
2017-05-15 06:27:40 -04:00
Simon Eskildsen 109740c07e Merge pull request #508 from tsarpaul/patch-2
Added SQS Hook
2017-05-15 06:27:12 -04:00
Simon Eskildsen a5274db378 Merge pull request #541 from hacdias/patch-1
Enable ANSI colors for Windows 10 and newer
2017-05-15 06:25:19 -04:00
Henrique Dias 532c891a89 Update terminal_windows.go 2017-05-12 22:28:26 +01:00
Simon Eskildsen acfabf31db logrus: use lower-case in comments and examples 2017-05-12 15:21:58 -04:00
Simon Eskildsen f1444e62a8 hooks: add null logger test 2017-05-12 15:15:51 -04:00
Simon Eskildsen a9ab54b9d5 readme: add note on casing 2017-05-12 15:09:16 -04:00
Simon Eskildsen a06c2db727 hooks: add a null logger for the lower-case import 2017-05-12 14:57:36 -04:00
Simon Eskildsen 79043dd8d5 Merge pull request #540 from JodeZer/master
fix readme example go import path
2017-05-12 14:52:41 -04:00
Henrique Dias 3454d74a4c Enable ANSI colors on Windows 10 and newer 2017-05-12 12:12:49 +01:00
Simon Eskildsen 62f94013e5 Merge pull request #527 from flimzy/race
Protect test hook entries with a mutex
2017-05-12 06:11:14 -04:00
Simon Eskildsen 21173bb50a Merge pull request #535 from mattias-lundell/patch-1
Fixed small typo in example.
2017-05-12 06:07:14 -04:00
Simon Eskildsen 2c677e6a5e Merge pull request #512 from LTD-Beget/fix/datarace-setlevel
Fix SetLevel data-race
2017-05-12 06:06:32 -04:00
JodeZer 8df512bed5 fix readme example go import path 2017-05-11 18:19:16 +08:00
Mattias Lundell e3715134c9 Fixed small typo in example. 2017-05-05 14:18:10 +02:00
Simon Eskildsen 5b60b3d3ee Merge pull request #533 from urjitbhatia/master
Fix firehose hook url
2017-05-04 09:10:19 +02:00
Simon Eskildsen 727dd38ae7 Merge pull request #534 from dlespiau/20170503-fix-md-links
readme: Fix md links
2017-05-04 09:08:58 +02:00
Simon Eskildsen 508f304878 Merge pull request #528 from flimzy/go1.8
Add Go 1.8 to the testing matrix
2017-05-03 20:34:05 +02:00
Damien Lespiau 8aa045e295 readme: Fix md links
There were a couple of spaces between ']' and '(' causing the links to
be misrendered.

Signed-off-by: Damien Lespiau <damien.lespiau@intel.com>
2017-05-03 17:23:41 +01:00
Urjit Singh Bhatia 012baad06c Fix firehose hook url
Firehose hook url was broken. Correct url is: https://github.com/beaubrewer/logrus_firehose
2017-05-02 10:47:34 -07:00
Jonathan Hall dba7a9fd25 Add Go 1.8 to the testing matrix
And use the .x versions, so the latest versions of each Go release is used.
2017-04-20 09:50:47 +02:00
Jonathan Hall 7e4197a54f Add a small warning comment 2017-04-20 09:44:21 +02:00
Jonathan Hall cc6ca6f305 Protect test entries with a mutex 2017-04-20 09:44:21 +02:00
Jonathan Hall ab2de9ffb1 Fix a few comments to conform to Go standards 2017-04-20 09:29:15 +02:00
Jay Qin 6223de399c Fix the JSONFormatter example comment 2017-03-23 09:50:46 -07:00
DmitriyMV 3bcb09397d This commit fixes data race using atomics. We switch type of level from uint8 to uint32 but due memory alignment on most platforms it will not result in any additional memory. 2017-03-23 19:13:49 +03:00
Paul Litvak fec838f389 Added SQS Hook 2017-03-22 22:32:29 +02:00
Alexey Remizov 6054749f37 Fixed typo in the comment 2017-03-22 17:09:05 +03:00
Simon Eskildsen 10f801ebc3 Merge pull request #498 from beaubrewer/patch-1
Update README.md
2017-03-17 10:32:14 -04:00
Simon Eskildsen f3b677a4dc Merge pull request #499 from Bo0mer/master
Fix typo
2017-03-17 10:32:04 -04:00
Simon Eskildsen 547e984ad9 Merge pull request #480 from xulike666/fix-dead-url
Fix dead url for alt_exit.go
2017-03-15 11:13:20 -04:00
Simon Eskildsen 9f8e3f5df8 Merge pull request #490 from kz/patch-1
Add Discordrus, a hook for Discord, to README.md
2017-03-15 11:13:05 -04:00
Simon Eskildsen 634aa8bc0a Merge pull request #494 from esap/patch-1
add import for example
2017-03-15 11:12:54 -04:00
Simon Eskildsen 79fbc614d9 Merge pull request #485 from mohanraj-r/patch-1
Add missing quotes around `user_ip` in example for "Default Fields"
2017-03-15 11:12:14 -04:00
Simon Eskildsen ba1b36c82c release 0.11.5 2017-03-14 15:23:53 -04:00
Simon Eskildsen 924f89f0e3 changelog for #372 2017-03-14 15:23:30 -04:00
Simon Eskildsen 1deb2db2a6 Merge pull request #372 from glasser/glasser/entry-writer
Add Writer and WriterLevel to Entry
2017-03-08 15:36:35 -05:00
Ivan Borshukov f0542780a2 Fix typo 2017-03-07 18:02:20 +02:00
David Glasser 1fccefa2f4 Add Writer and WriterLevel to Entry
This lets you do things like:

    cmd := exec.Command("command")
    stdout := logrus.WithField("fd", "stdout").Writer()
    defer stdout.Close()
    stderr := logrus.WithField("fd", "stderr").Writer()
    defer stderr.Close()
    cmd.Stdout = stdout
    cmd.Stderr = stderr
2017-03-06 16:24:57 -08:00
Beau N. Brewer add54587ab Update README.md
Added AWS Firehose hook. This provides us with a nice log steam to S3 workflow with very little effort.
2017-03-01 12:26:05 -07:00
Simon Eskildsen 0208149b40 changelog: 0.11.4 2017-02-27 07:44:09 -05:00
Simon Eskildsen cf5e096eea Merge pull request #493 from 0xE0F/master
Fixed compilation for Solaris
2017-02-27 07:43:41 -05:00
Simon Eskildsen dc71819687 release 0.11.3 2017-02-27 07:07:50 -05:00
Simon Eskildsen 4058491e25 changelog: add 481 2017-02-27 07:07:31 -05:00
Simon Eskildsen aee0dbac51 Merge pull request #481 from darrenmcc/import-io
Fixed import
2017-02-27 07:05:40 -05:00
一零村长 96acd6ab04 add import for example 2017-02-24 11:58:06 +08:00
Denis Barakhtanov f9d4a063d1 Fixed compilation for Solaris
There are missing "io" import and unused variable
Releated issues #471 #289
2017-02-24 10:16:21 +11:00
Kelvin Zhang 9fd28e6cca Add Discordrus, a hook for Discord, to README.md 2017-02-17 14:09:07 +00:00
Simon Eskildsen 7f4b1adc79 changelog: add entries for formatter 2017-02-15 11:43:24 -05:00
Simon Eskildsen 67bca5dc4f Merge pull request #484 from bbrks/text-formatter-quote-config
Text formatter quote configuration
2017-02-15 11:41:55 -05:00
Ben Brooks e98cd92ccf
Address PR comments 2017-02-15 13:08:26 +00:00
Mohan Raj Rajamanickam ca9493dc53 Fix quoted key field in Readme 2017-02-14 16:37:12 -08:00
Ben Brooks b545aee819
Add TextFormatter config for custom quote runes 2017-02-14 11:51:23 +00:00
Ben Brooks cfca98e6d9
Add 'QuoteEmptyFields' option to TextFormatter 2017-02-14 10:53:03 +00:00
Darren McCleary 6b682c5933 More import fixes 2017-02-13 10:56:35 -05:00
Dave Clendenan 4b900796a4 Merge branch 'master' of https://github.com/sirupsen/logrus
Conflicts:
	CHANGELOG.md
2017-02-10 07:38:59 -08:00
Christian Müller 56103bcb33 Fixes typo 2017-02-10 16:30:30 +01:00
Darren McCleary d82ae3267f Fixed import 2017-02-08 14:03:21 -05:00
Aaron.L.Xu 169c157a10 fix dead url for alt_exit.go 2017-02-08 19:18:14 +08:00
Simon Eskildsen 3f603f494d readme: add section on default fields 2017-02-07 13:34:08 -05:00
Simon Eskildsen c078b1e43f changelog: add fixing windows terminal detection 2017-02-07 06:49:18 -05:00
Simon Eskildsen 066a382098 Merge pull request #476 from majimboo/patch-1
Fixed missing imports for windows terminal
2017-02-07 06:48:23 -05:00
Simon Eskildsen ac38cb37a8 readme: fix markdown code syntax for stdlib example 2017-02-07 06:40:37 -05:00
Majid Arif Siddiqui 5c8f1691bc Fixed missing imports for windows terminal 2017-02-07 11:28:14 +08:00
Simon Eskildsen 080ca65fb5 readme: add section on overriding stdlib logger 2017-02-06 19:56:22 -05:00
Simon Eskildsen feda23452a readme: link to colorable for windows coloring 2017-02-06 19:51:07 -05:00
Simon Eskildsen b9def5b3c3 readme: even more hooks 2017-02-06 19:44:51 -05:00
Simon Eskildsen 38f1ab3057 changelog: add for 0.11.0 and 0.11.1 2017-02-06 19:40:39 -05:00
Simon Eskildsen e2fcfb2fba readme: add some entries from stale PRs 2017-02-06 19:40:31 -05:00
Simon Eskildsen 03bf27ef26 Merge pull request #471 from sirupsen/terminal-actual-fd
text_formatter: detect tty based on fd
2017-02-06 19:32:13 -05:00
Simon Eskildsen 141e6dc6a6 readme: update with example of logging to file 2017-02-06 19:23:53 -05:00
Simon Eskildsen 11fbf0fa42 text_formatter: fix race 2017-02-06 19:16:49 -05:00
Simon Eskildsen 1726e1744a text_formatter: detect tty based on fd 2017-02-06 19:16:49 -05:00
Dave Clendenan bc6d984670 add caller logic to DisableTimestamp case 2017-02-03 15:08:53 -08:00
Dave Clendenan 41d9b87f18 Merge branch 'master' of https://github.com/sirupsen/logrus
Conflicts:
	text_formatter.go
2017-02-03 14:56:03 -08:00
Tony Lee 1f59c9ad12 Add DisableLevelTruncation description to README 2017-01-24 23:05:25 +11:00
Tony Lee 31c0a5e6d9 Merge github.com/sirupsen/logrus into github.com/roganartu/logrus 2017-01-24 22:52:54 +11:00
Stephen Day 61e43dc76f Merge pull request #467 from bengadbois/small_code_cleanup
Small var declaration cleanup
2017-01-12 17:19:11 -08:00
Stephen Day 800b0fc4a6 Merge pull request #465 from at15/fix/miniTS
Remove miniTS in TextFormatter
2017-01-11 13:52:18 -08:00
Ben Gadbois f761cee910 Small var declaration cleanup 2017-01-11 19:19:12 +01:00
at15 5ed3e7dc93
Remove miniTS in TextFormatter
- Related issues: https://github.com/sirupsen/logrus/issues/457
- miniTS use current time instead of time the log function is called,
which is inaccurate when hook takes a long time
- `miniTS` is removed and replaced by
`int(entry.Time.Sub(baseTimestamp)/time.Second)` in `printColored`
which is the only usage of `miniTS`
2017-01-10 21:43:36 -08:00
Stephen Day d4158e8bbd Merge pull request #452 from cjellick/readme-update
Update readme example for switching  output
2017-01-10 12:37:04 -08:00
Simon Eskildsen 2f991e541c Merge pull request #455 from puddingfactory/master
Add Logentrus to README.md (hook for Logentries)
2017-01-10 15:30:36 -05:00
Stephen Day 9b48ece7fc Merge pull request #400 from carolynvs/disable-color-timestamp
Allow disabling timestamps with colored output
2017-01-06 14:16:01 -08:00
puddingfactory cf456d321e Add Logentrus, hook for Logentries, to list 2016-12-10 15:36:15 -06:00
Dave Clendenan 88dd8df1f8 responses to code review
- field rename to be more properly generic
 - drop rewrite of main.main
2016-12-06 12:53:21 -08:00
Dave Clendenan d8fd23467c add syntax hilighting to new example blocks 2016-12-02 10:09:30 -08:00
Dave Clendenan 2e7c40ede0 README formatting tweak 2016-12-02 09:57:30 -08:00
Dave Clendenan 802fba19a4 add note on caller-reporting overhead to README 2016-12-02 09:52:11 -08:00
Dave Clendenan 5840194571 Merge branch 'master' of https://github.com/Sirupsen/logrus
Conflicts:
	alt_exit_test.go
	json_formatter_test.go
2016-12-02 09:38:12 -08:00
Stephen Day 881bee4e20 Merge pull request #446 from AndrewBurian/json-disable-timestamp
Json disable timestamp
2016-12-01 18:35:07 -08:00
Craig Jellick 7d228b51ce Update readme example for switching output
Clarifies that stderr is the default, not stdout.
2016-12-01 17:37:34 -07:00
Simon Eskildsen 3c3917e625 readme: apologize for casing issue 2016-12-01 18:55:03 -05:00
Simon Eskildsen 26809363aa Revert "Merge pull request #384 from mnzt/master"
This reverts commit 42b84f9ec6, reversing
changes made to cf60a8c5d5.
2016-12-01 18:53:16 -05:00
Dave Clendenan 306956c385 tweak timing tests to handle slower VMs and older GoLang 2016-12-01 11:12:44 -08:00
Dave Clendenan f08011a10f merge upstream, add tests, full method context
- added benchmarks, and assertions for timeliness
- replaced regex usage in package-name handling with a straight
  comparison to a cached value
- log the fullly-qualified method name, to remove ambiguity
    eg: github.com/fflintstone/yabba.dabba.doo
- clean up possibly-unsafe assumptions about using the standard logger,
  remove global lookup function to enforce safe usage
2016-12-01 10:23:00 -08:00
Dave Clendenan 65f3af38f7 simplify hasCaller check 2016-11-30 15:15:38 -08:00
Dave Clendenan a5c845c224 responses to review comments
- empty string as marker for failure to discover calling function
 - tighten up logger usage - don't rely on std logger internally

Also fix ordering of expected/got in logrus_test.go to ensure correct
output form test failures.
2016-11-30 14:07:10 -08:00
Simon Eskildsen 42b84f9ec6 Merge pull request #384 from mnzt/master
Renaming upper-case 'Sirupsen' to 'sirupsen'
2016-11-30 15:35:45 -05:00
Stephen Day cf60a8c5d5 Merge pull request #449 from danielgtaylor-isp/patch-1
Fix typo in README
2016-11-30 12:16:55 -08:00
Dave Clendenan 4575b7a64d revert slight added complexity in NewEntry() 2016-11-30 11:36:48 -08:00
Dave Clendenan 05a8f4db95 fix test description 2016-11-30 10:47:03 -08:00
Daniel Taylor 90915c9326 Fix typo in README 2016-11-29 10:52:28 -08:00
Dave Clendenan 962ceebd51 Merge branch 'master' of https://github.com/Sirupsen/logrus 2016-11-29 09:46:00 -08:00
Dave Clendenan 348bace269 doc updates, and relabel ReportMethod
in the Logrus context it's the caller, so use that internally.  Label
stays as 'method' since in the context of the log event that seems more
correct.
2016-11-29 09:35:34 -08:00
Dave Clendenan 1e21450408 push compilation even higher, to reduce to one call 2016-11-28 16:22:33 -08:00
Stephen Day e400ff7861 Merge pull request #447 from stevvooe/revert-example-compilation
Compile examples filtered in #439
2016-11-28 14:57:24 -08:00
Dave Clendenan 8161d932a1 performance: precompile regex before iterating 2016-11-28 14:47:38 -08:00
Dave Clendenan 473c3448ab Add README notes and CHANGELOG entries
documentation for usage of the new optional calling-method logging
2016-11-28 13:43:38 -08:00
Dave Clendenan 93af604ba7 First cut at adding calling method
If log.SetReportMethod(true) then method=PACKAGE.FUNCTION will be added
as a field to log lines.
eg: time="2016-11-25T19:04:43-08:00" level=info method=main msg="log
testing"

TODO: documentation, examples
2016-11-25 19:02:56 -08:00
Stephen J Day 6ecd392994
travis: build Go 1.7 as well, removed by revert
Signed-off-by: Stephen J Day <stephen.day@docker.com>
2016-11-23 16:20:51 -08:00
Stephen J Day a89950b151 Revert "Updated .travis.yml to ignore /examples/"
This reverts commit 140886f9dc.
2016-11-23 16:19:58 -08:00
Andrew Burian c92f90003f Switched hardcoded string for const value 2016-11-21 10:16:24 -08:00
Andrew Burian 1d329ad042 Added option to disable JSON timestamp
Tests verify both the default and disabled case.
2016-11-21 10:09:59 -08:00
Stephen Day a437dfd246 Merge pull request #396 from onetwopunch/customzable-json-keys
Added customizable keys to JSON formatter
2016-11-18 11:45:39 -08:00
Ryan Canty fcf4b8f229
Added comment documentation for FieldMap 2016-11-18 11:02:11 -08:00
Ryan Canty d5ca23f998
Added FieldMap to reduce potential struct bloat 2016-11-17 15:16:46 -08:00
Ryan Canty b2c6f8aa8b Added resolve method to clean up Format 2016-11-17 11:28:41 -08:00
Ryan Canty 2173899f8f Added customizable keys to JSON formatter 2016-11-16 21:56:00 -08:00
Simon Eskildsen abc6f20dab Merge pull request #440 from gemnasium/add-pglogrus
Add pglogrus to README
2016-11-11 07:09:51 -05:00
Simon Eskildsen 528e33852c Remove legacy go versions 2016-11-11 07:09:38 -05:00
Simon Eskildsen 1445b7a382 Merge pull request #439 from irfansharif/pre-go1.6-builds
Updated .travis.yml to ignore /examples/
2016-11-08 14:08:11 -05:00
Philippe Lafoucrière 65aed8c493 Add pglogrus to README 2016-11-03 21:59:20 -04:00
Simon Eskildsen d264929707 Merge pull request #419 from aaronlehmann/fix-error-formatting
Fix formatting of wrapped errors when colors are used
2016-11-03 17:40:07 -04:00
irfan sharif 140886f9dc
Updated .travis.yml to ignore /examples/
/examples/hook/hook.go with it's child dependency on airbrake/gobrake is
not backwards compatible pre-go1.6 due to use of the following:
  - os.LookupEnv (introduced in go1.5)
  - http.StatusTooManyRequests (introduced in go1.6)
ignoring the fetch and explicit test of /examples/ fixes failing go1.3,
go1.4, go1.5 builds.
2016-11-03 01:28:25 -04:00
Simon Eskildsen 380f64d344 Merge pull request #412 from sagar8192/add-scribe-hook-description
Add scribe hook
2016-10-31 10:45:02 -04:00
Simon Eskildsen 8de4982a7a Merge pull request #413 from toorop/master
Add Pushover Hook
2016-10-31 10:44:55 -04:00
Simon Eskildsen 7371813290 Merge pull request #421 from heralight/master
Update README.md
2016-10-31 10:44:43 -04:00
Simon Eskildsen 551ec64450 Merge pull request #422 from JJ/master
Adds new logz.io hook by @ripcurld00d
2016-10-31 10:44:29 -04:00
JJ Merelo ea5eab4f4b Adds new logz.io hook by @ripcurld00d 2016-10-09 13:02:24 +02:00
Alexandre Richonnier 0c8a99c9b8 Update README.md
add link to Logrus and Viper integration Helper
2016-10-05 09:20:26 +02:00
Aaron Lehmann f76d643702 Fix formatting of wrapped errors when colors are used
There are two different code paths for rendering a key/value pair. The
non-color version uses a type switch that handles specific types such as
"error", and the color version uses the %+v printf format specifier.
This causes an inconsistency between the two formats. In particular,
errors created using the github.com/pkg/errors package will include a
stack trace of where the error was created when printed to the terminal,
but not to a file. Printing the stack trace as part of the log field is
probably not the right behavior.

The output is also inconsistent between the two forms because strings
are not quoted/escaped when colors are used. This can make log output
unparseable.

Fix this by making both code paths use the type switch and escaping
rules. Fix the escaping code to pass the error value to Fprintf, not the
error itself, which seems to be necessary to avoid blank output with
errors created by github.com/pkg/errors.
2016-09-28 13:48:28 +01:00
Stéphane Depierrepont aka Toorop f7be9f0695 Add Pushover Hook 2016-09-19 16:09:08 +02:00
Sagar Sadashiv Patwardhan 32472f55b6 Add scribe hook 2016-09-18 12:42:16 -07:00
Tony Lee e5b6713580 Added testing for DisableLevelTruncation 2016-09-01 00:28:23 +10:00
Tony Lee 7a1f601cfd Added ability to disable level text truncation. Fixes #406 2016-08-31 23:55:04 +10:00
Simon Eskildsen 3ec0642a7f Merge pull request #343 from kaneshin/appengine-support
terminal: Include appengine tag to compile for GAE
2016-08-29 16:23:21 -04:00
Carolyn Van Slyck 98b74aac5b Allow disabling timestamps with colored output 2016-08-24 17:24:54 -05:00
Peter Kieltyka b97e8d402d TextFormatter: brighten up the blue 2016-08-23 13:23:35 -04:00
Toby 2e779aca86
Correcting typos 2016-08-16 17:04:58 +01:00
Aaron Greenlee 08a8a7c27e Merge pull request #370 from frostyplanet/bufferpool
Optimise speed
2016-08-13 11:40:05 -04:00
Aaron Greenlee 58d2a6a703 Merge pull request #358 from gpolaert/patch-1
Add new hook for Logmatic.io
2016-08-13 11:39:25 -04:00
plan 53cbb9dc6d Reuse entry from the same logger 2016-08-11 01:51:00 +08:00
plan 4c4ffbea17 Add document for logger.SetNoLock() 2016-08-11 01:51:00 +08:00
plan bc35b026f0 Provide logger.SetNoLock() to remove locking during log output
Locking is enabled by default. When file is opened with appending mode,
it's safe to write concurrently to a file. In this case user can
choose to disable the lock.
2016-08-11 01:35:34 +08:00
plan cb2bda2c54 Add benchmark for logger 2016-08-11 01:35:34 +08:00
plan 69df0d2ed7 Use Buffer pool to allocate bytes.Buffer for formatter
Entry.Reader() seams not necessary, removed
2016-08-11 01:35:34 +08:00
Toby c8b0c0e43b
Fixing import for airbrake and adding build constraint to example hook 2016-07-27 13:12:26 +01:00
Toby ed63efede8
re-adding airbrake import 2016-07-27 13:00:56 +01:00
Toby a5dbcc756c
Renaming 'Sirupsen' to 'sirupsen' 2016-07-18 16:36:46 +01:00
Aaron Greenlee a283a10442 Improved documentation of Fatal Handlers 2016-07-15 22:56:31 -04:00
Aaron Greenlee 1d4b5462f8 Merge pull request #375 from powerchordinc/master
Added Support to Call Handlers on Fatal
2016-07-15 22:42:55 -04:00
Aaron Greenlee fcebd8de86 Merge pull request #356 from tevino/patch-1
Corrected a comment's typo
2016-07-15 22:20:23 -04:00
Simon Eskildsen 32055c351e Merge pull request #382 from lpetre/remove_logstash_formatter
Removing logstash formatter
2016-07-12 20:17:32 -04:00
Luke Petre 4ee95f9462 Removing logstash formatter 2016-07-12 21:23:56 +01:00
Aaron Greenlee 357c4eae02 Revised import path of logrus for pull-request CI 2016-06-24 10:49:02 -04:00
Aaron Greenlee ff52e76f67 Go fmt alt exit addition 2016-06-24 10:24:56 -04:00
Aaron Greenlee a7755c5c03 Enhanced fatal calls so exit handlers can be invoked
While GO offers the ability to recover from panic there is no way to intercept an os.Exit event. To allow graceful shutdown and clean-up or programs which use Logrus to Fatal out I've borrowed ideas from the `atexit` package and enhanced Logrus.

Usage:
* When setting up the logger one call `RegisterExitHandler( func() {...} )` to add a handler that will be invoked for any `Fatal` call to the logger.
2016-06-24 10:23:56 -04:00
Simon Eskildsen f3cfb454f4 readme: fix example 2016-06-01 07:32:10 -04:00
Simon Eskildsen 6d9ae300aa Merge pull request #360 from morenoh149/patch-1
change 'log' -> 'logrus'
2016-05-24 09:24:53 -04:00
Harry Moreno 67fb1f35e6 change 'log' -> 'logrus'
Change 'log' -> 'logrus' to fix syntax error
2016-05-23 19:54:24 -04:00
Guillaume Polaert 93985e4b77 Add new hook for Logmatic.io
Logmatic.io is Saas-based log management solution.
We develop a simple hook in order to send your Logrus logs straight to Logmatic.io
2016-05-17 17:38:10 +02:00
Tevin Zhang 18073362a7 Fix comment 2016-05-06 11:48:51 +08:00
Antoine Grondin cd7d1bbe41 Merge pull request #350 from apriendeau/update-travis
add go 1.6 to travis
2016-04-25 11:32:37 +02:00
Austin Riendeau 621d3983b3 add go 1.6 to travis 2016-04-18 11:14:45 -06:00
Antoine Grondin 081307d9bc Merge pull request #348 from Sirupsen/semantic-match-func-name
match name to semantic in `needsQuoting`
2016-04-16 13:51:37 +05:30
Antoine Grondin 754bfa9e83 match name to semantic in `needsQuoting`
fixes #196
2016-04-16 13:48:56 +05:30
Antoine Grondin 2b673abc1e Merge pull request #347 from Sirupsen/level-writer
Add WriterLevel() function to the logger
2016-04-16 12:50:33 +05:30
Antoine Grondin 218981bef4 use constant InfoLevel instead of 255 2016-04-16 12:46:11 +05:30
Damien Radtke 1d1fd2d9ce Add WriterLevel() function to the logger
This commit adds a variant of the logger's Writer() function that
accepts a log level. When the variant is used, any messages written to
the returned pipe will be written with the provided level. The original
Writer() function uses the logger's Print() method as it always has.
2016-04-16 12:45:27 +05:30
Antoine Grondin 870c1fc2ca add logstash hook to readme
closes #339
2016-04-16 12:42:53 +05:30
Antoine Grondin 881c9d3328 Merge pull request #298 from dolmen/refactor-prefixFieldClashes
formatter.go: simplify prefixFieldClashes(Fields)
2016-04-16 12:23:30 +05:30
Antoine Grondin ed4b7af3d4 Merge pull request #346 from e-max/logstash_formatter_race
race in logstashformatter.go
2016-04-16 11:52:44 +05:30
Max Lavrenov 8a870e4f7b fix race 2016-04-15 18:22:24 +03:00
Simon Eskildsen 7e6f976580 Merge pull request #341 from doublefree/add_smologic_hook_readme
Sumorus - SumoLogic Hook url added to readme
2016-04-13 08:47:20 -04:00
Simon Eskildsen 889e5d7019 Merge pull request #344 from vlad-doru/vlad-doru-logrusus-readme
Add a new InfluxDB Hook
2016-04-13 08:47:11 -04:00
Vlad-Doru Ion 0d667bc0c7 Added a new hook to the README.md
Added the logrusus hook.
2016-04-04 10:58:58 +03:00
Shintaro Kaneko dfb0e1d797 terminal: Include appengine tag to compile for GAE 2016-04-02 05:06:31 +00:00
takuya.watabe 5eb315cfd5 Sumorus - SumoLogic Hook url added to readme 2016-03-31 00:20:06 +09:00
Simon Eskildsen 4b6ea7319e changelog: update for 0.10.0 2016-03-17 14:11:10 +00:00
Simon Eskildsen 897f3dddf1 Rename LogrusLogger interface to FieldLogger 2016-03-17 14:07:00 +00:00
Simon Eskildsen bb78923f27 Merge pull request #320 from little-arhat/feature-logrus-interface
Add LogrusLogger interface for Entry and Logger
2016-03-17 10:01:39 -04:00
Simon Eskildsen e110284865 Merge pull request #325 from spicydog/patch-1
Update README.md
2016-03-17 10:01:08 -04:00
Simon Eskildsen 9ccfbde280 Merge pull request #335 from dim/master
Avoid re-allocations
2016-03-17 09:56:47 -04:00
Simon Eskildsen 70d89df0fa Merge pull request #336 from sohlich/master
Hook for ElasticSearch.
2016-03-17 09:55:16 -04:00
Radomír Sohlich b8b6593e80 Hook for ElasticSearch.
Added a link for ElasticSearch hook.
2016-03-16 20:34:53 +01:00
Dimitrij Denissenko b81f34e70a Avoid re-allocations 2016-03-15 07:39:40 +00:00
Simon Eskildsen a26f43589d Merge pull request #333 from dragon3/add-typetalk-hook
Add Typetalk hook
2016-03-11 15:57:46 -05:00
dragon3 ea350e0221 Add Typetalk hook 2016-03-09 23:13:41 -05:00
Simon Eskildsen 219c8cb75c Merge pull request #330 from f2prateek/patch-1
Fix Godoc link
2016-02-24 16:10:30 -05:00
Prateek Srivastava 6d7aacc216 Fix Godoc link
Previous https://cloudup.com/cZnP3-jZ9O8
2016-02-24 14:05:45 -07:00
Simon Eskildsen 74bde9ea4c Merge pull request #180 from yawn/testhook
Added test hook
2016-02-23 08:49:20 -05:00
Joern Barthel 95190bb5ae Added testing section to README. 2016-02-22 10:51:00 +01:00
Joern Barthel 0143a90f6e Extended test. 2016-02-22 10:46:52 +01:00
Joern Barthel be4b44b806 Added test hook. 2016-02-22 10:46:52 +01:00
Joern Barthel 088ac1380d Expose all levels (for hooks). 2016-02-22 10:46:52 +01:00
Simon Eskildsen 57cce1ed61 readme: fix kafka link 2016-02-18 19:38:25 -05:00
Simon Eskildsen 840e99181e Merge pull request #326 from alde/make-parselevel-case-insensitive
Make ParseLevel case-insensitive
2016-02-18 11:43:37 -05:00
Simon Eskildsen 70e056d42b Merge pull request #327 from harshadptl/kafka_hook_link
adding link for the kafka hook
2016-02-18 11:43:21 -05:00
harshad 4ccde140c3 adding link for the kafka hook 2016-02-17 13:07:32 +05:30
Rickard Dybeck 03ba213b8a Make ParseLevel case-insensitive
Coming from an environment where loglevels are always specified in
uppercase, having ParseLevel be case-insensitive is a nice to have.
2016-02-16 16:27:38 +01:00
spicydog d5a509ac3f Update README.md
Update set  log formatter command to "log.SetFormatter()" since "log.Formatter = new()" does not work.
2016-02-16 13:41:25 +10:00
Simon Eskildsen 3455d89ac9 Merge pull request #323 from vladoatanasov/master
Link to amqp hook added in readme
2016-02-12 06:24:02 -05:00
Vlado Atanasov 6e0b3a3397 Update README.md 2016-02-12 10:30:56 +00:00
Vlado Atanasov 2be7bf5c9c Update README.md 2016-02-12 10:30:30 +00:00
Simon Eskildsen be52937128 changelog: bump to 0.9.0 2016-02-02 21:14:36 +00:00
Roma Sokolov 1196d67b47 Add LogrusLogger interface for Entry and Logger
This make it possible for client code to accept either Logger or Entry.
For example, utility function may accept logger object to inform fatal
errors and it is job of the calling code to provide either generic
top-level logger, or request-bound Entry created using .WithFields.

(fixes #308)
2016-02-02 17:38:49 +00:00
Simon Eskildsen 433488c23f Merge pull request #315 from ronnylt/feature/race
Sample to test race conditions
2016-02-02 09:30:34 -05:00
Simon Eskildsen 92e7983a9e Merge pull request #317 from rogierlommers/master
add link to logrus-redis-hook
2016-02-02 09:29:02 -05:00
Rogier Lommers 255c37f4ba add link to logrus-redis-hook 2016-01-26 21:01:36 +01:00
Ronny López 6094714616 Run tests with -race 2016-01-23 11:18:45 +01:00
Antoine Grondin f7f79f729e Merge pull request #314 from vend/logstash-immutable-event
avoid modifying the entry by copying all fields and values
2016-01-18 19:00:32 -05:00
Brad Brown 7700add084 avoid modifying the entry by copying all fields and values 2016-01-18 17:26:47 +13:00
Simon Eskildsen 446d1c146f Merge pull request #302 from mttrs/fix-typo
Fix typo
2015-12-04 09:14:43 -05:00
Simon Eskildsen 6a35758b8b Merge pull request #303 from illicitonion/ttydetection
Detect TTY based on stderr, not stdout
2015-12-04 09:14:34 -05:00
Daniel Wagner-Hall 05f9567ba3 Detect TTY based on stderr, not stdout
We actually write to stderr by default, so:

bin >/dev/null

currently weirdly prints non-colorized output, whereas:

bin 2>log

weirdly prints colorized output to a file.
2015-12-03 12:58:31 +00:00
mitsuteru sawa fd860ab61d Fix typo
“ should be ".
2015-11-30 16:22:28 +09:00
Simon Eskildsen cdaedc68f2 Merge pull request #299 from gchpaco/master
Support `WithError` on `Logger`.
2015-11-23 03:15:15 -05:00
Graham Hughes 42c9c25263 Support `WithError` on `Logger`.
Should address #277.
2015-11-21 22:59:38 -08:00
Olivier Mengué 12ea3a4f8e formatter.go: simplify prefixFieldClashes(Fields) 2015-11-21 17:51:52 +01:00
Simon Eskildsen a22723f16e Merge pull request #289 from 0xE0F/crosscompiling-solaris
Cross compiling for Solaris-like OS
2015-11-17 10:41:36 +01:00
Simon Eskildsen ba3ef66951 Merge pull request #292 from xujinzheng/master
fix tools of logrus_mate link address
2015-11-16 17:45:18 +01:00
Zeal 8b27a242e4 fix logrus_mate link address 2015-11-16 18:22:07 +08:00
Simon Eskildsen eb61880d00 Merge pull request #282 from x-cray/master
Add ref to third-party prefixed log formatter
2015-11-16 08:27:26 +01:00
Simon Eskildsen ac496616c9 Merge pull request #275 from xujinzheng/master
add tools section for logrus mate
2015-11-16 08:23:49 +01:00
Simon Eskildsen c734b5594a Merge pull request #285 from GJRTimmer/patch-1
Added Build Rule
2015-11-16 08:23:33 +01:00
Simon Eskildsen 6f383a9b29 Merge pull request #290 from eyberg/master
adding deferpanic hook in readme
2015-11-09 12:36:32 +01:00
Ian Eyberg ac099ab45a adding deferpanic hook in readme 2015-11-06 14:47:10 -08:00
0xe0f 8cdd4b39f5 Added implementation of function "IsTerminal" for Solaris 2015-11-03 12:21:35 +03:00
Denis Parchenko eb1e36217e Add ref to third-party prefixed log formatter 2015-10-29 09:43:41 +02:00
Simon Eskildsen fe6f2b0312 Merge pull request #286 from dorajistyle/master
Add octokit hook.
2015-10-27 08:58:02 -04:00
JoongSeob Vito Kim 2308b7ee63 Add octokit hook.
Add octokit(github) hook.
2015-10-27 18:58:00 +09:00
Gert-Jan Timmer 6815eb0dbd Added Build Rule
Add build rule, to only build on Linux platform, reason for adding this: This allows the usage of the logrus package within a Windows based project. Current logrus project fails to import due to the fact the syslog is missing on Windows.
2015-10-27 10:18:45 +01:00
Simon Eskildsen 62f85a2e39 changelog: remove bugsnag 2015-10-22 17:52:25 -04:00
Simon Eskildsen 8013927d1b Merge pull request #280 from csfrancis/remove_bugsnag
Remove Bugsnag
2015-10-22 17:51:46 -04:00
Scott Francis faac3ea969 Remove Bugsnag 2015-10-22 20:20:45 +00:00
Zeal e958fd6f37 add tools section for logrus mate 2015-10-19 14:19:45 +08:00
Simon Eskildsen 4643a7efec Merge pull request #271 from Abramovic/master
Include InfluxDB hook
2015-10-12 17:08:13 -04:00
Abramovic 68064ae592 include InfluxDB hook 2015-10-12 10:41:37 -07:00
Simon Eskildsen df78bc75a3 changelog: move papertrail 2015-10-09 17:37:08 +00:00
Simon Eskildsen 1855cb72bb Merge pull request #270 from polds/master
Split out Papertrail hook to its own repo
2015-10-09 13:35:41 -04:00
Peter Olds 3240988909 Split out Papertrail hook to its own repo
Signed-off-by: Peter Olds <polds@kyanicorp.com>
2015-10-09 10:28:56 -06:00
Simon Eskildsen 23521f1364 changelog: moved sentry 2015-10-07 14:14:16 +00:00
Simon Eskildsen b2fcfe237c Merge pull request #269 from evalphobia/feature/move-sentry-hook
Move sentry hook to external repository
2015-10-07 10:13:26 -04:00
evalphobia ba64a98b3b Move sentry hook to external repository 2015-10-07 12:59:33 +09:00
Simon Eskildsen b673bf363d Merge pull request #268 from apriendeau/patch-3
hook.go matches their gopkg.in/gemnasium/logrus-airbrake-hook.v2 now
2015-10-06 16:33:52 -04:00
Austin Riendeau f811ea43df hook.go matches their gopkg.in/gemnasium/logrus-airbrake-hook.v2 now 2015-10-06 14:24:51 -06:00
Simon Eskildsen 8ae9297f36 Merge pull request #266 from apriendeau/patch-1
Fixes breaking change with removing airbrake.
2015-10-06 16:10:30 -04:00
Austin Riendeau 457a009ed7 Fixes breaking change with removing airbrake. 2015-10-06 14:01:18 -06:00
Simon Eskildsen d7f23545ad Merge pull request #264 from gemnasium/move-graylog
Move graylog to dedicated repo
2015-10-06 14:48:13 -04:00
Philippe Lafoucrière 1d8ceff9b2 Move graylog to dedicated repo
The new repo uses gopkg.in for versioning.
This is the continuation of logrus moving hooks to dedicated repos.

The old repo
https://github.com/gemnasium/logrus-hooks/tree/master/graylog
is deprecated, as we don't want to share things there too (import
airbrake while I just want graylog). It will be removed in a few
months.
2015-10-06 14:43:55 -04:00
Simon Eskildsen 46358df244 changelog: add airbrake note 2015-10-06 18:39:39 +00:00
Simon Eskildsen aeb0c6c39e Merge pull request #261 from gemnasium/move-airbrake
Move airbrake
2015-10-06 14:38:26 -04:00
Philippe Lafoucrière 86739cfcf4 Remove leftover 2015-10-06 14:33:31 -04:00
Philippe Lafoucrière 297ec6fcaa Move airbrake to dedicated repo(s)
The current implementation is using an old implementation of the
Airbrake v2 api. Airbrake has release v2 since, but this package is
necessary for in-house solutions like errbit.
The number of errbit users don't justify to continue pushing this
version as the official.
That's why the official hook is now using gobrake (the official
package), and the one coming from logrus is now named "legacy".
2015-10-06 14:28:31 -04:00
Simon Eskildsen fe72f59ae7 travis: remove support for 1.2 2015-10-05 12:05:41 +00:00
Simon Eskildsen 1b01a2b2fc changelog: add for 0.9.0 2015-10-05 12:05:41 +00:00
Simon Eskildsen 8445ae196b Merge pull request #259 from Sirupsen/revert-254-master
Revert "TextMarshaler and TextUnmarshaler implementation for Level"
2015-10-05 07:46:03 -04:00
Simon Eskildsen 4197a1bbd5 Revert "TextMarshaler and TextUnmarshaler implementation for Level" 2015-10-05 07:45:58 -04:00
Simon Eskildsen 5a15866bba Merge pull request #254 from fromYukki/master
TextMarshaler and TextUnmarshaler implementation for Level
2015-10-05 07:45:56 -04:00
Simon Eskildsen c69e603431 Merge pull request #200 from freeformz/no_msg
Don't emit a msg if there is none to emit
2015-10-05 07:44:10 -04:00
Simon Eskildsen 05fe8fb917 Merge pull request #256 from flowlo/patch-1
Improve references to Logstash formatter
2015-10-05 07:35:44 -04:00
Simon Eskildsen a6a8245fd7 Merge pull request #251 from devopstaku/syslog_hook_doc
Add description about how to connect to local syslog with syslog hook
2015-10-05 07:35:05 -04:00
Lorenz Leutgeb 005f65e9b5 Improve references to Logstash formatter 2015-09-17 19:37:51 +02:00
Maksim Naumov 277d0cb562 `TextMarshaler` and `TextUnmarshaler` implementation for `Level` 2015-09-12 22:41:06 +02:00
devopstaku 4805f72c39 Add description about how to connect to local syslog with syslog hook 2015-09-09 21:55:50 +08:00
Edward Muller 9ca7d9fefe Don't emit a msg if there is none to emit 2015-09-08 14:21:17 -07:00
Simon Eskildsen 418b41d23a v0.8.7 2015-09-08 20:46:18 +00:00
Antoine Grondin 82c8de1af2 Merge pull request #247 from Sirupsen/revert-api-change
Revert api change
2015-09-07 21:03:30 -04:00
Antoine Grondin 66db2df1ef Merge pull request #245 from wallclockbuilder/documentation
Godoc Documentation
2015-09-07 20:52:27 -04:00
Antoine Grondin a2f80bd9c0 add compile checks for StdLogger interface 2015-09-07 20:49:55 -04:00
Antoine Grondin 38c9fd2510 Revert "Implement casting of *Entry to error."
This reverts commit 756db3cd2d.
2015-09-07 20:47:46 -04:00
Mawuli Adzoe 4b9a646039 Add sample code 2015-09-07 09:14:34 +00:00
Mawuli Adzoe 107695ee50 Add godoc summary 2015-09-07 09:14:15 +00:00
Antoine Grondin c639bedcac Merge pull request #179 from yawn/with-error
Added WithError(err)
2015-09-06 01:34:49 -04:00
Simon Eskildsen b17e718325 Merge pull request #242 from ineiti/patch-1
Update README.md
2015-09-04 09:54:25 -04:00
Linus Gasser 8c8be89501 Update README.md
Typo it seems - except if I still didn't understand go...
2015-09-04 12:02:14 +02:00
Simon Eskildsen 84b968cb9f Merge pull request #238 from weekface/master
Include mgorus(a mongodb hook) in list of available hooks
2015-08-26 22:14:34 -04:00
weekface c2efb40312 Add hook for logging to mongodb 2015-08-27 09:54:11 +08:00
Simon Eskildsen 44512f0d08 Merge pull request #237 from ryanfaerman/patch-1
Fixes typo in WithField documentation
2015-08-23 13:59:52 -04:00
Ryan Faerman 2612e8496d Fixes typo in WithField documentation 2015-08-20 00:07:28 -04:00
Simon Eskildsen 27b713cfd2 Merge pull request #235 from diegobernardes/patch-1
Update README.md
2015-08-18 20:11:02 -04:00
Diego Bernardes de Sousa Pinto 1d47e5ed0e Update README.md
Just a small fix.
2015-08-18 15:15:02 -03:00
Antoine Grondin 9c060de643 Merge pull request #226 from marcosnils/entry_race
Fix race condition in entry log method
2015-08-08 03:20:48 -04:00
Antoine Grondin eb98773bde Merge pull request #228 from hvnsweeting/patch-1
Update logger.go
2015-08-08 03:06:26 -04:00
Marcos Lilljedahl 8280b8b9a6 Add comment to log function
Signed-off-by: Marcos Lilljedahl <marcosnils@gmail.com>
2015-08-07 18:29:20 -03:00
Viet Hung Nguyen d5580f082a Update logger.go 2015-08-07 15:56:08 +07:00
Marcos Lilljedahl 35f12fb760 Fix panic return type
Signed-off-by: Marcos Lilljedahl <marcosnils@gmail.com>
2015-08-06 21:19:22 -03:00
Marcos Lilljedahl 396f8eefaa Make log method receive a copy of Entry structure to avoid race
conditions

Fixes #216

Signed-off-by: Marcos Lilljedahl <marcosnils@gmail.com>
2015-08-06 21:01:47 -03:00
Simon Eskildsen 8bca266407 changelog: update with raven change 2015-07-28 08:10:54 -04:00
Simon Eskildsen 5701be89e7 Merge pull request #215 from underarmour/sentry-hook-with-client
Allow sentry hook to be created using an initialized raven client
2015-07-28 08:09:19 -04:00
Simon Eskildsen 11538ee688 changelog: reformat for proper markdown 2015-07-23 21:49:03 -04:00
Simon Eskildsen aa3ad346e4 Revert "Merge pull request #208 from Arkan/master"
This reverts commit 7eac5879a5, reversing
changes made to c79ccaf8c2.
2015-07-23 21:45:57 -04:00
Simon Eskildsen 3cb248e9df changelog: add 218 2015-07-22 11:29:28 -04:00
Simon Eskildsen 86d90b1593 Merge pull request #218 from pengzhai/master
Fix data race issue in TextFormatter. Fix for https://github.com/Siru
2015-07-22 11:27:57 -04:00
Peng Zhai a8127fd485 Change printColored to use timestampFormat. 2015-07-20 10:43:12 -07:00
Peng Zhai 570db1b0b9 Fix data race issue in TextFormatter. Fix for https://github.com/Sirupsen/logrus/issues/217. 2015-07-16 21:02:46 -04:00
Allan Glen 1b73323cd0 Allow sentry hook to be created using an initialized raven client 2015-07-09 10:12:05 -06:00
Simon Eskildsen 07d998d174 Merge pull request #213 from awonak/sentry-tags
Sentry tags
2015-07-07 14:07:35 -04:00
Adam Wonak afe474b84f updated readme with usage example 2015-07-07 11:52:22 -05:00
Adam Wonak 8ce3556d31 Added a new SentryHook initialization func for setting tags. 2015-07-07 11:41:45 -05:00
Simon Eskildsen cd321ca94d version: bump to 0.8.3 2015-07-07 05:31:24 -04:00
Simon Eskildsen e815b55144 Merge pull request #209 from pdf/print_struct_keys
Print struct keys for struct log fields
2015-07-07 05:30:21 -04:00
Simon Eskildsen b9685ff27b Merge pull request #212 from evalphobia/master
Include fluentd in list of available hooks
2015-07-07 05:30:07 -04:00
Simon Eskildsen 7eac5879a5 Merge pull request #208 from Arkan/master
Fix Entry log level
2015-07-07 05:29:58 -04:00
evalphobia 9846c73426 Include fluentd in list of available hooks 2015-07-07 12:54:35 +09:00
Arkan 0be4ee004c Fix Entry log level 2015-07-06 22:27:39 +02:00
Simon Eskildsen c79ccaf8c2 changelog: add BSD note 2015-07-06 11:44:55 -04:00
Simon Eskildsen d2045717c6 Merge pull request #203 from cgag/master
Add support for DragonflyBSD and NetBSD.
2015-07-06 11:44:22 -04:00
Peter Fern 62bd221be1 Print struct keys for struct log fields
Can be useful for debugging, saves a lot of referring to source code
2015-07-05 17:08:19 +10:00
Simon Eskildsen ab7e46ea22 Merge pull request #206 from moul/patch-1
fix typo
2015-06-30 22:57:07 -04:00
Manfred Touron 3b1ecc5fdd fix typo 2015-06-30 23:20:00 +02:00
Simon Eskildsen b34dae6f7a changelog: add entries for unreleased 2015-06-30 11:11:41 -04:00
Simon Eskildsen cddce4b0ad Merge pull request #204 from noxiouz/speedup_textformatter
[TextFormatter] Speed up (~40%). Fprintf is changed to buffer.Write*
2015-06-30 11:10:09 -04:00
Anton Tiurin e35e0e00b8 [TextFormatter] Speed up. Fprintf is changed to buffer.Write*
As Fprintf is slower than buffer.WriteString, it's replaced
to faster call.

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
2015-06-28 12:36:14 +03:00
Curtis Gagliardi 8a5c13cf78 Add support for DragonflyBSD and NetBSD.
This merges all the bsd (including darwin) support into terminal_bsd.go

Cross-compiling works for all of them in that they all compile, but I've only
actually tested the binary on dragonflybsd.  I don't see why this wouldn't
work on the others since I don't think they'd compile if they didn't
support TIOCGETA and Termios, but just a heads up.
2015-06-26 12:31:17 -07:00
Simon Eskildsen 93a1736895 hooks: expose LevelHooks type 2015-06-26 11:42:49 -04:00
Simon Eskildsen d3e4ea4032 Merge pull request #201 from tail/add-sentry-link
Added link to Sentry hook.
2015-06-25 08:38:08 -07:00
Jason Yan 4ab59a94fa Added link to Sentry hook. 2015-06-24 19:37:01 -07:00
Simon Eskildsen 2a0a9a12ae Merge pull request #199 from freeformz/rollrus
Link to the rollbar notification hook that we use.
2015-06-19 21:53:44 -04:00
Edward Muller 7eed28eea2 Link to the rollbar notification hook that we use. 2015-06-19 16:04:35 -07:00
Simon Eskildsen 21d4508646 Merge pull request #194 from zbindenren/master
add link to mail hook
2015-06-10 05:38:35 -04:00
Rene Zbinden dc834b8b47 add link to mail hook 2015-06-10 11:09:59 +02:00
Simon Eskildsen e3eccfaeb5 readme: fix typo in honeybadger 2015-06-08 19:55:33 -04:00
Simon Eskildsen c8ed7a5eae readme: add @agonzalezro honeybadger hook to readme 2015-06-08 19:54:53 -04:00
Simon Eskildsen 0f2a4955b1 Merge pull request #191 from rifflock/include-lfshook
Include lfshook in list of available hooks
2015-06-02 19:14:05 -04:00
rifflock 245d8971d1 Include lfshook in list of available hooks 2015-06-02 15:36:57 -07:00
Simon Eskildsen c370730052 Merge pull request #190 from rebill/master
fix SetFormatter error
2015-06-02 08:34:42 -04:00
Rebill.Ruan e86319aa1d fix error 2015-06-02 15:33:32 +08:00
Simon Eskildsen 6ba91e24c4 Merge pull request #189 from jondot/patch-1
logrus_syslog / syslog - example should now be valid
2015-05-27 09:39:09 -04:00
Dotan J. Nahum 05d18c70e2 logrus_syslog / syslog - example should now be valid 2015-05-27 14:29:11 +03:00
Simon Eskildsen 98a1428efc version: 0.8.2 2015-05-26 20:10:50 -04:00
Simon Eskildsen b8e090955a Merge pull request #188 from mapuri/master
Fix Fatal*() functions of `logger` to match the behavior of Fatal*() functions of `entry`
2015-05-26 20:09:48 -04:00
Madhav Puri fdae7ddaf5 Fix Fatal*() function of logger to match the behavior of Fatal*() functions of entry
Signed-off-by: Madhav Puri <madhav.puri@gmail.com>
2015-05-26 16:47:13 -07:00
Simon Eskildsen 27857424f9 version: bump to 0.8.1 2015-05-26 18:46:05 -04:00
Simon Eskildsen 488629fea7 Merge pull request #187 from mapuri/master
Fix Fatalf() and Fataln() to exit irrespective of log level
2015-05-26 17:15:44 -04:00
Madhav Puri c0ea7891cd Fix Fatalf() and Fatalln() to exit irrespective of log level
Signed-off-by: Madhav Puri <madhav.puri@gmail.com>
2015-05-26 14:01:33 -07:00
Simon Eskildsen 386ccca031 version: bump to 0.8 2015-05-26 09:04:23 -04:00
Simon Eskildsen f1addc2972 formatter/json: fix possible race 2015-05-26 09:02:45 -04:00
Simon Eskildsen 092eda23b5 Merge pull request #183 from evalphobia/feature/sentry-http-request
Added special field for *http.Request to Sentry hook
2015-05-22 10:03:26 -04:00
evalphobia 5939a6cbf9 Added special field for *http.Request to Sentry hook 2015-05-22 21:14:51 +09:00
Simon Eskildsen 81e2611f37 Merge pull request #168 from squirkle/master
Added Raygun hook.
2015-05-21 10:47:48 -04:00
Philip Allen 099b1bffe0 removing raygun hook from hooks dir, adding reference in hooks table of main README.md 2015-05-21 10:28:14 -04:00
Philip Allen 25bb6a1099 Merge branch 'master' of https://github.com/Sirupsen/logrus 2015-05-21 10:25:46 -04:00
Joern Barthel 756db3cd2d Implement casting of *Entry to error. 2015-05-19 20:20:59 +02:00
Joern Barthel e3e5de11c4 Implement WithError(err) in exported, fixed doco. 2015-05-19 19:50:55 +02:00
Joern Barthel c24d0555d7 Added WithError(err). 2015-05-13 13:35:03 +02:00
Simon Eskildsen 52919f182f Merge pull request #178 from mattbaird/patch-1
proper use of TextFormatter in documentation
2015-05-12 12:38:02 -05:00
Matthew Baird f1d275b800 proper use of TextFormatter in documentation 2015-05-12 09:27:20 -07:00
Simon Eskildsen 48c21bc05c Merge pull request #177 from xyproto/master
Terminals on Windows may not have colors
2015-05-12 09:45:09 -05:00
Alexander F Rødseth 29d30d9f63 Terminals on Windows may not have colors 2015-05-12 16:31:17 +02:00
Philip Allen d97bbff05e Moving raygun hook to its own repositiroy at github.com/squirkle/logrus-raygun-hook 2015-05-01 10:48:23 -04:00
Simon Eskildsen aaf92c9571 Merge pull request #170 from aybabtme/log-to-stderr
default logs to stderr
2015-04-30 08:58:29 +02:00
Antoine Grondin f8f08842cc default logs to stderr 2015-04-22 22:53:40 -04:00
Antoine Grondin 26709e2714 Merge pull request #171 from Sirupsen/fix/broken-test-by-json-unmarshal
assertify changed behavior and consider float64 != int
2015-04-22 22:53:12 -04:00
Antoine Grondin 9561fcd7d6 assertify changed behavior and consider float64 != int 2015-04-22 22:48:32 -04:00
Philip Allen 6b18d5cf62 Added Raygun hook. 2015-04-17 10:51:07 -04:00
Simon Eskildsen 55eb11d21d changelog: 0.7.3 2015-04-09 19:08:25 -04:00
Simon Eskildsen 7ca50a32cd Merge pull request #162 from stevvooe/timestamp-layout
Allow configuration of timestamp layout
2015-04-09 19:07:39 -04:00
Stephen J Day e14471f8f2 Allow configuration of timestamp layout
Signed-off-by: Stephen J Day <stephen.day@docker.com>
2015-04-06 12:18:40 -07:00
Simon Eskildsen cdd90c38c6 readme: fix comment on context logger 2015-03-31 11:30:41 -04:00
Simon Eskildsen 7586a697f6 readme: add example of context logging 2015-03-31 11:24:31 -04:00
Simon Eskildsen e00ed35f54 Merge pull request #160 from gravis/patch-1
Add graylog hook to Readme
2015-03-27 10:00:09 -04:00
Philippe Lafoucrière 034cc50c51 Add graylog hook to Readme
I have updated the list to be a table instead. It's much more readable than the previous version, and used in a lot of places (ex: https://github.com/codegangsta/negroni/blob/master/README.md)
2015-03-27 09:43:24 -04:00
Simon Eskildsen ae9ba8a88e changelog: add #158 2015-03-26 22:48:30 -04:00
Simon Eskildsen 35d5aa8f70 Merge pull request #158 from scarletmeow/custom_text_format
allow custom time format string in TextFormatter
2015-03-26 22:09:19 -04:00
Tiffany Low 3e3e87a165 allow custom time format string in TextFormatter
- fixes examples in README.md that incorrectly state usage of
  RFC3339Nano format instead of RFC3339
2015-03-26 16:15:06 -07:00
Simon Eskildsen 3fc34d061b Merge pull request #154 from imkira/master
improved: move level check from entry to logger and bail out faster
2015-03-20 00:05:19 -04:00
Mário Freitas a4a5df2c1f improved: move level check from entry to logger and bail out faster 2015-03-20 12:05:33 +09:00
Simon Eskildsen fa58c5d59f readme: move bugsnag docs 2015-03-19 11:26:39 -04:00
Simon Eskildsen 8be81604a8 Merge pull request #152 from burke/bugsnag-hook
hooks: Add BugSnag hook
2015-03-19 11:23:38 -04:00
Burke Libbey d96cee72fa
Code review changes 2015-03-19 11:21:40 -04:00
Simon Eskildsen 2471adf231 Merge pull request #148 from noxiouz/remove_unused_regexp
Remove unused regexp
2015-03-19 10:06:15 -04:00
Simon Eskildsen cf302ffdee Merge branch 'logstash' 2015-03-19 10:05:18 -04:00
Simon Eskildsen e178ef4efd formatter/logstash: style 2015-03-19 10:04:53 -04:00
Simon Eskildsen 2d359740a4 text_formatter: remove unneeded regexp 2015-03-19 10:03:27 -04:00
Matt Bostock bc1129f48e Remove outdated version of Airbrake hook
It seems unnecessary to duplicate the code (which is now outdated) in
the README. Instead, link to the built-in hooks where a user can see
the code.
2015-03-19 10:03:27 -04:00
Matt Bostock 7ba71bd357 Rework the Airbrake hook
Rework the Airbrake hook to:

a) change the interface so that the Airbrake credentials are stored in
an unexported struct, `airbrakeHook`, which is instantiated using the
`NewHook()` method

b) send log entries where no 'error' field is set to Airbrake, using the
`entry.Message` string as the message sent to Airbrake but continue to
allow the passing of error types using the 'error' field

Update the tests accordingly, assuring that the correct message is
received by the Airbrake server.

Also update the examples in the README, which would not have worked with
the previous implementation of the Airbrake hook.
2015-03-19 10:03:27 -04:00
Matt Bostock 4fcb55c734 Rename package from logrus_airbrake to airbrake
Using underscores in package names in discouraged:
https://golang.org/doc/effective_go.html#package-names

Given that this package is in a subdirectory of the logrus package,
the name `airbrake` should be sufficiently descriptive.
2015-03-19 10:03:27 -04:00
Simon Eskildsen 9cc13fab16 examples/basic: add debug level 2015-03-19 10:03:27 -04:00
Simon Eskildsen 566a97d868 json_formatter: add tests for field clashes and newline 2015-03-19 10:03:27 -04:00
Simon Eskildsen 0fa54be10f text_formatter: add field to disable sorting 2015-03-19 10:03:27 -04:00
Simon Eskildsen 0dd045932f json_formatter: always cast errors to strings
Fixes #137
2015-03-19 10:03:27 -04:00
Matt Bostock 31897e2db5 Remove misleading comment in Airbrake hook
As far as I can tell, exceptions are always sent regardless of what
`airbrake.Environment` is set to.
2015-03-19 10:03:27 -04:00
Matt Bostock e803eeed62 Add integration test to Airbrake hook
Add a test for the Airbrake hook to:

a) document how the hook is intended to work

b) test that an XML payload is received with the expected message
2015-03-19 10:03:27 -04:00
Henrik Hodne ff5ba169e8 text-formatter: do not quote 9 2015-03-19 10:03:26 -04:00
Nikolay Kirsh 9aea821200 fix Second const 2015-03-19 10:03:26 -04:00
Simon Eskildsen 5be851d706 text_formatter: improve comments 2015-03-19 10:03:26 -04:00
Steeve Lennmark 115ae7564e Add option to show full timestamp in TextFormatter
Sometimes elapsed seconds just aren't enough.
2015-03-19 10:03:26 -04:00
Lorenzo Villani 9c9013ac4f Change DebugLevel color to gray 2015-03-19 10:03:26 -04:00
Simon Eskildsen 7495181ab1 text_formatter: remove unneeded regexp 2015-03-19 09:59:55 -04:00
Simon Eskildsen 347abac2ab Merge pull request #149 from alphagov/improve_airbrake_hook
[#76800642] Rework the Airbrake hook
2015-03-19 09:56:24 -04:00
Burke Libbey 83752ed3c5
hooks: Add BugSnag hook 2015-03-16 15:34:10 -04:00
Alexander Demidov 2ec723cd5b add logstash formatter test 2015-03-15 23:34:19 +06:00
Matt Bostock ecc16b3b2a Remove outdated version of Airbrake hook
It seems unnecessary to duplicate the code (which is now outdated) in
the README. Instead, link to the built-in hooks where a user can see
the code.
2015-03-15 16:26:24 +00:00
Matt Bostock 83a820d91e Rework the Airbrake hook
Rework the Airbrake hook to:

a) change the interface so that the Airbrake credentials are stored in
an unexported struct, `airbrakeHook`, which is instantiated using the
`NewHook()` method

b) send log entries where no 'error' field is set to Airbrake, using the
`entry.Message` string as the message sent to Airbrake but continue to
allow the passing of error types using the 'error' field

Update the tests accordingly, assuring that the correct message is
received by the Airbrake server.

Also update the examples in the README, which would not have worked with
the previous implementation of the Airbrake hook.
2015-03-15 16:26:24 +00:00
Matt Bostock 78dee3c0ba Rename package from logrus_airbrake to airbrake
Using underscores in package names in discouraged:
https://golang.org/doc/effective_go.html#package-names

Given that this package is in a subdirectory of the logrus package,
the name `airbrake` should be sufficiently descriptive.
2015-03-10 17:45:12 +00:00
Anton Tiurin a8b793a1fc gofmt 2015-03-10 19:06:14 +03:00
Anton Tiurin 98fd21de2c [JSON] Use type-switch for error field 2015-03-10 19:04:57 +03:00
Anton Tiurin 7498110889 Remove unused regexp 2015-03-10 18:56:16 +03:00
Antoine Grondin 2cea0f0d14 Merge pull request #145 from alphagov/add_tests_to_airbrake_hook
Add integration test to Airbrake hook
2015-03-09 11:58:39 -04:00
Simon Eskildsen 79d043289e examples/basic: add debug level 2015-03-09 15:40:44 +00:00
Simon Eskildsen bbf1b22f08 Merge pull request #131 from lvillani/debuglevel-gray
Change DebugLevel color to gray
2015-03-09 11:39:16 -04:00
Simon Eskildsen ab83faccde json_formatter: add tests for field clashes and newline 2015-03-09 15:30:43 +00:00
Simon Eskildsen 538395b333 text_formatter: add field to disable sorting 2015-03-09 15:19:51 +00:00
Simon Eskildsen 8287db7934 json_formatter: always cast errors to strings
Fixes #137
2015-03-09 15:15:08 +00:00
Antoine Grondin 53adda1d3e Merge pull request #147 from mattbostock/remove_misleading_comment
Remove misleading comment in Airbrake hook
2015-03-06 17:22:14 -05:00
Matt Bostock 8ba09b1c21 Remove misleading comment in Airbrake hook
As far as I can tell, exceptions are always sent regardless of what
`airbrake.Environment` is set to.
2015-03-06 16:32:18 +00:00
Matt Bostock 26ea5be9c3 Add integration test to Airbrake hook
Add a test for the Airbrake hook to:

a) document how the hook is intended to work

b) test that an XML payload is received with the expected message
2015-03-06 16:18:47 +00:00
Alexander Demidov 3cc6fcc521 use formatters directory 2015-03-05 23:31:39 +06:00
Simon Eskildsen a020ac2471 Merge pull request #143 from henrikhodne/do-not-quote-9
Text formatter: Do not quote 9
2015-03-04 10:45:58 -05:00
Henrik Hodne cd4266df0e text-formatter: do not quote 9 2015-03-04 14:04:50 +00:00
Simon Eskildsen eb84da520d Merge pull request #140 from xboston/patch-1
fix Second const
2015-03-02 07:28:43 -05:00
Nikolay Kirsh c810928262 fix Second const 2015-03-02 16:25:19 +05:00
Simon Eskildsen c0f7e35ed2 text_formatter: improve comments 2015-02-25 19:01:02 +00:00
Simon Eskildsen b0279da492 Merge pull request #134 from steevel/master
Add option to show full timestamp in TextFormatter
2015-02-25 13:59:30 -05:00
Steeve Lennmark ccaf6983d9 Add option to show full timestamp in TextFormatter
Sometimes elapsed seconds just aren't enough.
2015-02-20 18:43:24 +02:00
Alex Demidov 75cc3dd51a Update README.md 2015-02-20 21:02:11 +05:00
Alexander Demidov e15d51fef3 formatter for logstash (http://logstash.net) 2015-02-20 21:52:53 +06:00
Lorenzo Villani 4fbdf5948e Change DebugLevel color to gray 2015-02-20 16:32:47 +01:00
Simon Eskildsen 6dcec6ed3b Merge pull request #127 from noxiouz/fix_datarace_GetLevel
[Race] Fix datarace in GetLevel
2015-02-19 19:52:08 -05:00
Anton Tiurin f08673d24a [Race] Fix datarace in GetLevel
`std.Level` is protected by mutex in setter (SetLevel),
so it must be protected in geetter (GetLevel) too.

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
2015-02-20 01:14:10 +03:00
Simon Eskildsen 4d9b4f0c83 readme: add journal hook 2015-02-19 18:51:13 +00:00
Simon Eskildsen 273bd5984c Merge pull request #129 from noxiouz/speedup_text_formatter
[TextFormatter] Preallocate enough memory for keys to speedup
2015-02-17 12:42:44 -05:00
Anton Tiurin d1dfe8db73 [TextFormatter] Preallocate enough memory for keys to speedup.
Benchmarks:
 * BenchmarkSmallTextFormatter: 6140/5943 ~3%
 * BenchmarkLargeTextFormatter: 28050/24789 ~11%

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
2015-02-17 19:08:32 +03:00
Simon Eskildsen 38a6ff9215 readme: add note on stability to avoid confusion 2015-02-12 17:48:35 +00:00
Simon Eskildsen 7f14e05c6c Merge pull request #126 from x1022as/master
fix typo
2015-02-10 12:38:07 -05:00
d00221763 6383fe40c5 fix typo
Signed-off-by: Deng Guangxing <denguangxing@huawei.com>
2015-02-10 16:49:34 +08:00
Simon Eskildsen 0b189e019a terminal: don't exclude appengine 2015-02-07 20:27:43 +00:00
Simon Eskildsen 467d9d55c2 Merge pull request #119 from Sirupsen/add-writer-to-readme
add an entry about writer in README
2015-02-03 15:19:37 -05:00
Antoine Grondin 896e5e5d4d add an entry about logger.Writer() in README 2015-01-31 14:08:25 -05:00
Simon Eskildsen 89efc1fea9 Merge pull request #103 from phemmer/writer_interface
add io.Writer interface compatability
2015-01-30 13:07:08 -05:00
Patrick Hemmer 51cbf81dde rename InputWriter -> Writer 2015-01-26 09:59:50 -05:00
Simon Eskildsen 539d4dc034 Merge pull request #117 from mattyw/patch-1
drive by fix to README.md
2015-01-23 12:37:18 -05:00
Matt Williams 2bc78c204b drive by fix to README.md 2015-01-23 01:57:51 +00:00
Simon Eskildsen 844911ce07 test: remove deprecated #len calls 2015-01-20 20:38:13 -05:00
Patrick Hemmer ed888975b3 close inputReader on error instead of using Fatal() 2015-01-20 10:22:30 -05:00
Antoine Grondin bdb64b3f9f Merge pull request #115 from rasky/go14
Add testing for go 1.4
2015-01-19 19:00:36 -05:00
Giovanni Bajo 0009c01b31 Simplify dep install 2015-01-18 02:19:28 +01:00
Giovanni Bajo 0e4c360cd8 Test with go 1.4 2015-01-18 02:06:44 +01:00
Simon Eskildsen 58f778a886 Revert "Merge pull request #100 from crquan/patch-1"
This reverts commit c6a969a0de, reversing
changes made to 3c5b048a9d.
2015-01-15 13:40:48 -05:00
Simon Eskildsen c6a969a0de Merge pull request #100 from crquan/patch-1
make sure no trailing spaces
2015-01-15 07:12:29 -05:00
Simon Eskildsen 3c5b048a9d Merge pull request #104 from freeformz/doc-fix
The format is logfmt, not l2met, although that is where it "originated"
2015-01-14 20:05:07 -05:00
Simon Eskildsen 35ade18898 Merge pull request #101 from vbatts/termios_openbsd
openbsd Termios
2015-01-14 18:31:36 -05:00
Simon Eskildsen 2f722357d3 Merge pull request #105 from phemmer/entry_warning
Add missing `(*Entry) Warning()` function
2015-01-14 18:30:43 -05:00
Patrick Hemmer e2dff63faa Add missing `(*Entry) Warning()` function 2015-01-12 17:20:11 -05:00
Patrick Hemmer 299ee95277 add io.Writer interface compatability 2015-01-12 03:24:19 -05:00
Edward Muller 8c09acde89 The format is logfmt, not l2met, although that is where it "originated" 2015-01-09 16:13:34 -08:00
Vincent Batts 97e951044c terminal: openbsd Termios
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2015-01-06 08:44:32 -05:00
Derek Che dcbe8d66af make sure no leading or trailing spaces
This changed printColored and printKeyValue to print in same way
with prefix space instead of trailing space, to make it easier
to slice out when returning in Format;
The test cases are to make sure msg formartting doesn't include
leading or trailing spaces;

Closes #99

Signed-off-by: Derek Che <drc@yahoo-inc.com>
2015-01-04 00:19:36 -08:00
Derek Che a243bbaa0b share common calling path in printKeyValue
Signed-off-by: Derek Che <drc@yahoo-inc.com>
2015-01-04 00:01:49 -08:00
Derek Che 03377c6168 rename f.appendKeyValue to printKeyValue
printKeyValue is working similar like printColored, not using
any fields of TextFormatter, should be a util func instead of
a method of TextFormatter.

Signed-off-by: Derek Che <drc@yahoo-inc.com>
2015-01-03 23:56:39 -08:00
Simon Eskildsen d2f9ffa1d9 readme: add slack hook
closes #98
2014-12-30 14:02:50 +01:00
Simon Eskildsen a51c6e4ce2 Merge pull request #96 from msabramo/patch-4
README.md: Add Godoc badge to top
2014-12-29 11:10:33 +01:00
Marc Abramowitz 82c427c515 README.md: Add Godoc badge to top 2014-12-28 12:56:20 -08:00
Simon Eskildsen fd90de6570 Merge pull request #93 from xi3/master
Update papertrail.go
2014-12-26 20:55:22 +01:00
xi3 60d80a6d2c Update papertrail.go
Report all log data,  not just the entry.message.
2014-12-19 21:11:58 +08:00
76 changed files with 5332 additions and 1026 deletions

61
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,61 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
lint:
name: Golang-CI Lint
timeout-minutes: 10
strategy:
matrix:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: golangci/golangci-lint-action@v2
with:
# must be specified without patch version
version: v1.46
cross:
name: Cross
timeout-minutes: 10
strategy:
matrix:
go-version: [1.17.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Cross
working-directory: ci
run: go run mage.go -v -w ../ crossBuild
test:
name: Unit test
timeout-minutes: 10
strategy:
matrix:
go-version: [1.17.x]
platform: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test -race -v ./...

22
.github/workflows/stale.yaml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v3
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@ -1 +1,4 @@
logrus logrus
vendor
.idea/

40
.golangci.yml Normal file
View File

@ -0,0 +1,40 @@
run:
# do not run on test files yet
tests: false
# all available settings of specific linters
linters-settings:
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: false
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: false
lll:
line-length: 100
tab-width: 4
prealloc:
simple: false
range-loops: false
for-loops: false
whitespace:
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
linters:
enable:
- megacheck
- govet
disable:
- maligned
- prealloc
disable-all: false
presets:
- bugs
- unused
fast: false

View File

@ -1,10 +1,15 @@
language: go language: go
go: go_import_path: git.internal/re/logrus
- 1.2 git:
- 1.3 depth: 1
- tip env:
- GO111MODULE=on
go: 1.15.x
os: linux
install: install:
- go get github.com/stretchr/testify - ./travis/install.sh
- go get github.com/stvp/go-udp-testing script:
- go get github.com/tobi/airbrake-go - cd ci
- go get github.com/getsentry/raven-go - go run mage.go -v -w ../ crossBuild
- go run mage.go -v -w ../ lint
- go run mage.go -v -w ../ test

259
CHANGELOG.md Normal file
View File

@ -0,0 +1,259 @@
# 1.8.1
Code quality:
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
* improve timestamp format documentation
Fixes:
* fix race condition on logger hooks
# 1.8.0
Correct versioning number replacing v1.7.1.
# 1.7.1
Beware this release has introduced a new public API and its semver is therefore incorrect.
Code quality:
* use go 1.15 in travis
* use magefile as task runner
Fixes:
* small fixes about new go 1.13 error formatting system
* Fix for long time race condiction with mutating data hooks
Features:
* build support for zos
# 1.7.0
Fixes:
* the dependency toward a windows terminal library has been removed
Features:
* a new buffer pool management API has been added
* a set of `<LogLevel>Fn()` functions have been added
# 1.6.0
Fixes:
* end of line cleanup
* revert the entry concurrency bug fix whic 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
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
* Fixes build break for plan9, nacl, solaris
# 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
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
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)
* Remove coloured output on Windows (#862)
* Pointer to func as field in JSONFormatter (#870)
* Properly marshal Levels (#873)
# 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
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
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
* the extra new line at the end of message in text formatter has been removed
* a new global public API to check if a level is activated: IsLevelEnabled
* the following methods have been added to the Logger object
* IsLevelEnabled
* SetFormatter
* SetOutput
* ReplaceHooks
* introduction of go module
* an indent configuration for the json formatter
* output colour support for windows
* the field sort function is now configurable for text formatter
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
# 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
a new configuration field of the json formatter in order to put all the fields
in a nested dictionnary
* a new SetOutput method in the Logger
* 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
* Fix hooks race (#707)
* Fix panic deadlock (#695)
# 1.0.4
* Fix race when adding hooks (#612)
* Fix terminal check in AppEngine (#635)
# 1.0.3
* Replace example files with testable examples
# 1.0.2
* bug: quote non-string values in text formatter (#583)
* Make (*Logger) SetLevel a public method
# 1.0.1
* bug: fix escaping in text formatter (#575)
# 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
* feature: add writer and writerlevel to entry (#372)
# 0.11.4
* bug: fix undefined variable on solaris (#493)
# 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
* bug: fix windows terminal detection (#476)
# 0.11.1
* bug: fix tty detection with custom out (#471)
# 0.11.0
* performance: Use bufferpool to allocate (#370)
* terminal: terminal detection for app-engine (#343)
* feature: exit handler (#375)
# 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
* logrus/text_formatter: don't emit empty msg
* logrus/hooks/airbrake: move out of main repository
* logrus/hooks/sentry: move out of main repository
* logrus/hooks/papertrail: move out of main repository
* logrus/hooks/bugsnag: move out of main repository
* logrus/core: run tests with `-race`
* logrus/core: detect TTY based on `stderr`
* logrus/core: support `WithError` on logger
* logrus/core: Solaris support
# 0.8.7
* logrus/core: fix possible race (#216)
* logrus/doc: small typo fixes and doc improvements
# 0.8.6
* hooks/raven: allow passing an initialized client
# 0.8.5
* logrus/core: revert #208
# 0.8.4
* formatter/text: fix data race (#218)
# 0.8.3
* logrus/core: fix entry log level (#208)
* logrus/core: improve performance of text formatter by 40%
* logrus/core: expose `LevelHooks` type
* logrus/core: add support for DragonflyBSD and NetBSD
* formatter/text: print structs more verbosely
# 0.8.2
* logrus: fix more Fatal family functions
# 0.8.1
* logrus: fix not exiting on `Fatalf` and `Fatalln`
# 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
* formatter/\*: allow configuration of timestamp layout
# 0.7.2
* formatter/text: Add configuration option for time format (#158)

347
README.md
View File

@ -1,17 +1,46 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) # Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://git.internal/re/logrus/workflows/CI/badge.svg)](https://git.internal/re/logrus/actions?query=workflow%3ACI) [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![Go Reference](https://pkg.go.dev/badge/git.internal/re/logrus.svg)](https://pkg.go.dev/git.internal/re/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not the standard library logger.
yet stable (pre 1.0), the core API is unlikely change much but please version
control your Logrus to make sure you aren't fetching latest `master` on every **Logrus is in maintenance-mode.** We will not be introducing new features. It's
build.** 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).
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
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
community has built those independently. Many fantastic alternatives have sprung
up. Logrus would look like those, had it been re-designed with what we know
about structured logging in Go today. Check out, for example,
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
[zerolog]: https://github.com/rs/zerolog
[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:
`git.internal/re/logrus`. Any package that isn't, should be changed.
To fix Glide, see [these
comments](https://git.internal/re/logrus/issues/553#issuecomment-306591437).
For an in-depth explanation of the casing issue, see [this
comment](https://git.internal/re/logrus/issues/570#issuecomment-313933276).
Nicely color-coded in development (when a TTY is attached, otherwise just Nicely color-coded in development (when a TTY is attached, otherwise just
plain text): plain text):
![Colored](http://i.imgur.com/PY7qMwd.png) ![Colored](http://i.imgur.com/PY7qMwd.png)
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
or Splunk: or Splunk:
```json ```json
@ -31,17 +60,56 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} "time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
``` ```
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
attached, the output is compatible with the attached, the output is compatible with the
[l2met](http://r.32k.io/l2met-introduction) format: [logfmt](http://godoc.org/github.com/kr/logfmt) format:
```text ```text
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" 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="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 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
``` ```
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
```go
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
```
#### Logging Method Name
If you wish to add the calling method as a field, instruct the logger via:
```go
log.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"}
```
```text
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:
```
go test -bench=.*CallerTracing
```
#### 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: `git.internal/re/logrus`.
#### Example #### Example
@ -51,7 +119,7 @@ The simplest way to use Logrus is simply the package-level exported logger:
package main package main
import ( import (
log "github.com/Sirupsen/logrus" log "git.internal/re/logrus"
) )
func main() { func main() {
@ -62,7 +130,7 @@ func main() {
``` ```
Note that it's completely api-compatible with the stdlib logger, so you can Note that it's completely api-compatible with the stdlib logger, so you can
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"` replace your `log` imports everywhere with `log "git.internal/re/logrus"`
and you'll now have the flexibility of Logrus. You can customize it all you and you'll now have the flexibility of Logrus. You can customize it all you
want: want:
@ -71,20 +139,16 @@ package main
import ( import (
"os" "os"
log "github.com/Sirupsen/logrus" log "git.internal/re/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
) )
func init() { func init() {
// Log as JSON instead of the default ASCII formatter. // Log as JSON instead of the default ASCII formatter.
log.SetFormatter(&log.JSONFormatter{}) log.SetFormatter(&log.JSONFormatter{})
// Use the Airbrake hook to report errors that have Error severity or above to // Output to stdout instead of the default stderr
// an exception tracker. You can create custom hooks, see the Hooks section. // Can be any io.Writer, see below for File example
log.AddHook(&logrus_airbrake.AirbrakeHook{}) log.SetOutput(os.Stdout)
// Output to stderr instead of stdout, could also be a file.
log.SetOutput(os.Stderr)
// Only log the warning severity or above. // Only log the warning severity or above.
log.SetLevel(log.WarnLevel) log.SetLevel(log.WarnLevel)
@ -105,6 +169,16 @@ func main() {
"omg": true, "omg": true,
"number": 100, "number": 100,
}).Fatal("The ice breaks!") }).Fatal("The ice breaks!")
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})
contextLogger.Info("I'll be logged with common and other field")
contextLogger.Info("Me too")
} }
``` ```
@ -115,7 +189,8 @@ application, you can also create an instance of the `logrus` Logger:
package main package main
import ( import (
"github.com/Sirupsen/logrus" "os"
"git.internal/re/logrus"
) )
// Create a new instance of the logger. You can have any number of instances. // Create a new instance of the logger. You can have any number of instances.
@ -124,7 +199,15 @@ var log = logrus.New()
func main() { func main() {
// The API for setting attributes is a little different than the package level // The API for setting attributes is a little different than the package level
// exported logger. See Godoc. // exported logger. See Godoc.
log.Out = os.Stderr log.Out = os.Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"animal": "walrus", "animal": "walrus",
@ -135,7 +218,7 @@ func main() {
#### Fields #### Fields
Logrus encourages careful, structured logging though logging fields instead of Logrus encourages careful, structured logging through logging fields instead of
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
to send event %s to topic %s with key %d")`, you should log the much more to send event %s to topic %s with key %d")`, you should log the much more
discoverable: discoverable:
@ -157,60 +240,42 @@ In general, with Logrus using any of the `printf`-family functions should be
seen as a hint you should add a field, however, you can still use the seen as a hint you should add a field, however, you can still use the
`printf`-family functions with Logrus. `printf`-family functions with Logrus.
#### Default Fields
Often it's helpful to have fields _always_ attached to log statements in an
application or parts of one. For example, you may want to always log the
`request_id` and `user_ip` in the context of a request. Instead of writing
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
every line, you can create a `logrus.Entry` to pass around instead:
```go
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
```
#### Hooks #### Hooks
You can add hooks for logging levels. For example to send errors to an exception You can add hooks for logging levels. For example to send errors to an exception
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
multiple places simultaneously, e.g. syslog. multiple places simultaneously, e.g. syslog.
```go Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
// Not the real implementation of the Airbrake hook. Just a simple sample. `init`:
import (
log "github.com/Sirupsen/logrus"
)
func init() {
log.AddHook(new(AirbrakeHook))
}
type AirbrakeHook struct{}
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
// the fields for the entry. See the Fields section of the README.
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
err := airbrake.Notify(entry.Data["error"].(error))
if err != nil {
log.WithFields(log.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Info("Failed to send error to Airbrake")
}
return nil
}
// `Levels()` returns a slice of `Levels` the hook is fired for.
func (hook *AirbrakeHook) Levels() []log.Level {
return []log.Level{
log.ErrorLevel,
log.FatalLevel,
log.PanicLevel,
}
}
```
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
```go ```go
import ( import (
log "github.com/Sirupsen/logrus" log "git.internal/re/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake" "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
"github.com/Sirupsen/logrus/hooks/syslog" logrus_syslog "git.internal/re/logrus/hooks/syslog"
"log/syslog" "log/syslog"
) )
func init() { func init() {
log.AddHook(new(logrus_airbrake.AirbrakeHook))
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil { if err != nil {
@ -220,29 +285,17 @@ func init() {
} }
} }
``` ```
Note: Syslog hook 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).
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) A list of currently known service hooks can be found in this wiki [page](https://git.internal/re/logrus/wiki/Hooks)
Send errors to an exception tracking service compatible with the Airbrake API.
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
Send errors to the Papertrail hosted logging service via UDP.
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
Send errors to remote syslog server.
Uses standard library `log/syslog` behind the scenes.
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
Send errors to a channel in hipchat.
* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
Send logs to Loggly (https://www.loggly.com/)
#### Level logging #### Level logging
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
```go ```go
log.Trace("Something very low level.")
log.Debug("Useful debugging information.") log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!") log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.") log.Warn("You should probably take a look at this.")
@ -285,17 +338,17 @@ could do:
```go ```go
import ( import (
log "github.com/Sirupsen/logrus" log "git.internal/re/logrus"
) )
init() { func init() {
// do something here to set environment depending on an environment variable // do something here to set environment depending on an environment variable
// or command-line flag // or command-line flag
if Environment == "production" { if Environment == "production" {
log.SetFormatter(logrus.JSONFormatter) log.SetFormatter(&log.JSONFormatter{})
} else { } else {
// The TextFormatter is default, you don't actually have to do this. // The TextFormatter is default, you don't actually have to do this.
log.SetFormatter(logrus.TextFormatter) log.SetFormatter(&log.TextFormatter{})
} }
} }
``` ```
@ -312,12 +365,25 @@ The built-in logging formatters are:
without colors. without colors.
* *Note:* to force colored output when there is no TTY, set the `ForceColors` * *Note:* 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 field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true` `DisableColors` field to `true`. For Windows, see
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
* 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://godoc.org/git.internal/re/logrus#TextFormatter).
* `logrus.JSONFormatter`. Logs fields as JSON. * `logrus.JSONFormatter`. Logs fields as JSON.
* All options are listed in the [generated docs](https://godoc.org/git.internal/re/logrus#JSONFormatter).
Third party logging formatters: Third party logging formatters:
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. * [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
* [`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; Sava log to files.
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
You can define your formatter by implementing the `Formatter` interface, You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
@ -330,23 +396,118 @@ type MyJSONFormatter struct {
log.SetFormatter(new(MyJSONFormatter)) log.SetFormatter(new(MyJSONFormatter))
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on // Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the // the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers. // source of the official loggers.
serialized, err := json.Marshal(entry.Data) serialized, err := json.Marshal(entry.Data)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
} }
return append(serialized, '\n'), nil return append(serialized, '\n'), nil
} }
``` ```
#### Logger as an `io.Writer`
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
```go
w := logger.Writer()
defer w.Close()
srv := http.Server{
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog: log.New(w, "", 0),
}
```
Each line written to that writer will be printed the usual way, using formatters
and hooks. The level for those entries is `info`.
This means that we can override the standard library logger easily:
```go
logger := logrus.New()
logger.Formatter = &logrus.JSONFormatter{}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log.SetOutput(logger.Writer())
```
#### Rotation #### Rotation
Log rotation is not provided with Logrus. Log rotation should be done by an Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotated(8)`) that can compress and delete old log external program (like `logrotate(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger. entries. It should not be a feature of the application-level logger.
#### Tools
[godoc]: https://godoc.org/github.com/Sirupsen/logrus | Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
#### Testing
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
```go
import(
"git.internal/re/logrus"
"git.internal/re/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSomething(t*testing.T){
logger, hook := test.NewNullLogger()
logger.Error("Helloerror")
assert.Equal(t, 1, len(hook.Entries))
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
hook.Reset()
assert.Nil(t, hook.LastEntry())
}
```
#### Fatal handlers
Logrus can register one or more functions that will be called when any `fatal`
level message is logged. The registered handlers will be executed before
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
```
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
```
#### Thread safety
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
Situation when locking is not needed includes:
* You have no hooks registered, or hooks calling is already thread-safe.
* Writing to logger.Out is already thread-safe, for example:
1) logger.Out is protected by locks.
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/)

76
alt_exit.go Normal file
View File

@ -0,0 +1,76 @@
package logrus
// The following code was sourced and modified from the
// https://github.com/tebeka/atexit package governed by the following license:
//
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import (
"fmt"
"os"
)
var handlers = []func(){}
func runHandler(handler func()) {
defer func() {
if err := recover(); err != nil {
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
}
}()
handler()
}
func runHandlers() {
for _, handler := range handlers {
runHandler(handler)
}
}
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
func Exit(code int) {
runHandlers()
os.Exit(code)
}
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
// any Fatal log entry is made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
// closing database connections, or sending a alert that the application is
// closing.
func RegisterExitHandler(handler func()) {
handlers = append(handlers, handler)
}
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
// any Fatal log entry is made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
// closing database connections, or sending a alert that the application is
// closing.
func DeferExitHandler(handler func()) {
handlers = append([]func(){handler}, handlers...)
}

151
alt_exit_test.go Normal file
View File

@ -0,0 +1,151 @@
package logrus
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
func TestRegister(t *testing.T) {
current := len(handlers)
var results []string
h1 := func() { results = append(results, "first") }
h2 := func() { results = append(results, "second") }
RegisterExitHandler(h1)
RegisterExitHandler(h2)
if len(handlers) != current+2 {
t.Fatalf("expected %d handlers, got %d", current+2, len(handlers))
}
runHandlers()
if len(results) != 2 {
t.Fatalf("expected 2 handlers to be run, ran %d", len(results))
}
if results[0] != "first" {
t.Fatal("expected handler h1 to be run first, but it wasn't")
}
if results[1] != "second" {
t.Fatal("expected handler h2 to be run second, but it wasn't")
}
}
func TestDefer(t *testing.T) {
current := len(handlers)
var results []string
h1 := func() { results = append(results, "first") }
h2 := func() { results = append(results, "second") }
DeferExitHandler(h1)
DeferExitHandler(h2)
if len(handlers) != current+2 {
t.Fatalf("expected %d handlers, got %d", current+2, len(handlers))
}
runHandlers()
if len(results) != 2 {
t.Fatalf("expected 2 handlers to be run, ran %d", len(results))
}
if results[0] != "second" {
t.Fatal("expected handler h2 to be run first, but it wasn't")
}
if results[1] != "first" {
t.Fatal("expected handler h1 to be run second, but it wasn't")
}
}
func TestHandler(t *testing.T) {
testprog := testprogleader
testprog = append(testprog, getPackage()...)
testprog = append(testprog, testprogtrailer...)
tempDir, err := ioutil.TempDir("", "test_handler")
if err != nil {
log.Fatalf("can't create temp dir. %q", err)
}
defer os.RemoveAll(tempDir)
gofile := filepath.Join(tempDir, "gofile.go")
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
t.Fatalf("can't create go file. %q", err)
}
outfile := filepath.Join(tempDir, "outfile.out")
arg := time.Now().UTC().String()
err = exec.Command("go", "run", gofile, outfile, arg).Run()
if err == nil {
t.Fatalf("completed normally, should have failed")
}
data, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatalf("can't read output file %s. %q", outfile, err)
}
if string(data) != arg {
t.Fatalf("bad data. Expected %q, got %q", data, arg)
}
}
// getPackage returns the name of the current package, which makes running this
// test in a fork simpler
func getPackage() []byte {
pc, _, _, _ := runtime.Caller(0)
fullFuncName := runtime.FuncForPC(pc).Name()
idx := strings.LastIndex(fullFuncName, ".")
return []byte(fullFuncName[:idx]) // trim off function details
}
var testprogleader = []byte(`
// Test program for atexit, gets output file and data as arguments and writes
// data to output file in atexit handler.
package main
import (
"`)
var testprogtrailer = []byte(
`"
"flag"
"fmt"
"io/ioutil"
)
var outfile = ""
var data = ""
func handler() {
ioutil.WriteFile(outfile, []byte(data), 0666)
}
func badHandler() {
n := 0
fmt.Println(1/n)
}
func main() {
flag.Parse()
outfile = flag.Arg(0)
data = flag.Arg(1)
logrus.RegisterExitHandler(handler)
logrus.RegisterExitHandler(badHandler)
logrus.Fatal("Bye bye")
}
`)

14
appveyor.yml Normal file
View File

@ -0,0 +1,14 @@
version: "{build}"
platform: x64
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
environment:
GOPATH: c:\gopath
branches:
only:
- master
install:
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
build_script:
- go get -t
- go test

43
buffer_pool.go Normal file
View File

@ -0,0 +1,43 @@
package logrus
import (
"bytes"
"sync"
)
var (
bufferPool BufferPool
)
type BufferPool interface {
Put(*bytes.Buffer)
Get() *bytes.Buffer
}
type defaultPool struct {
pool *sync.Pool
}
func (p *defaultPool) Put(buf *bytes.Buffer) {
p.pool.Put(buf)
}
func (p *defaultPool) Get() *bytes.Buffer {
return p.pool.Get().(*bytes.Buffer)
}
// SetBufferPool allows to replace the default logrus buffer pool
// to better meets 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)
},
},
})
}

5
ci/go.mod Normal file
View File

@ -0,0 +1,5 @@
module git.internal/re/logrus/ci
go 1.15
require github.com/magefile/mage v1.11.0

2
ci/go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=

10
ci/mage.go Normal file
View File

@ -0,0 +1,10 @@
// +build ignore
package main
import (
"github.com/magefile/mage/mage"
"os"
)
func main() { os.Exit(mage.Main()) }

123
ci/magefile.go Normal file
View File

@ -0,0 +1,123 @@
//go:build mage
package main
import (
"encoding/json"
"fmt"
"os"
"path"
"sort"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
func intersect(a, b []string) []string {
sort.Strings(a)
sort.Strings(b)
res := make([]string, 0, func() int {
if len(a) < len(b) {
return len(a)
}
return len(b)
}())
for _, v := range a {
idx := sort.SearchStrings(b, v)
if idx < len(b) && b[idx] == v {
res = append(res, v)
}
}
return res
}
// getBuildMatrix returns the build matrix from the current version of the go compiler
func getFullBuildMatrix() (map[string][]string, error) {
jsonData, err := sh.Output("go", "tool", "dist", "list", "-json")
if err != nil {
return nil, err
}
var data []struct {
Goos string
Goarch string
}
if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
return nil, err
}
matrix := map[string][]string{}
for _, v := range data {
if val, ok := matrix[v.Goos]; ok {
matrix[v.Goos] = append(val, v.Goarch)
} else {
matrix[v.Goos] = []string{v.Goarch}
}
}
return matrix, nil
}
func getBuildMatrix() (map[string][]string, error) {
minimalMatrix := map[string][]string{
"linux": []string{"amd64"},
"darwin": []string{"amd64", "arm64"},
"freebsd": []string{"amd64"},
"js": []string{"wasm"},
"solaris": []string{"amd64"},
"windows": []string{"amd64", "arm64"},
}
fullMatrix, err := getFullBuildMatrix()
if err != nil {
return nil, err
}
for os, arches := range minimalMatrix {
if fullV, ok := fullMatrix[os]; !ok {
delete(minimalMatrix, os)
} else {
minimalMatrix[os] = intersect(arches, fullV)
}
}
return minimalMatrix, nil
}
func CrossBuild() error {
matrix, err := getBuildMatrix()
if err != nil {
return err
}
for os, arches := range matrix {
for _, arch := range arches {
env := map[string]string{
"GOOS": os,
"GOARCH": arch,
}
if mg.Verbose() {
fmt.Printf("Building for GOOS=%s GOARCH=%s\n", os, arch)
}
if err := sh.RunWith(env, "go", "build", "./..."); err != nil {
return err
}
}
}
return nil
}
func Lint() error {
gopath := os.Getenv("GOPATH")
if gopath == "" {
return fmt.Errorf("cannot retrieve GOPATH")
}
return sh.Run(path.Join(gopath, "bin", "golangci-lint"), "run", "./...")
}
// Run the test suite
func Test() error {
return sh.RunWith(map[string]string{"GORACE": "halt_on_error=1"},
"go", "test", "-race", "-v", "./...")
}

26
doc.go Normal file
View File

@ -0,0 +1,26 @@
/*
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
import (
log "git.internal/re/logrus"
)
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
For a full guide visit https://git.internal/re/logrus
*/
package logrus

378
entry.go
View File

@ -2,16 +2,45 @@ package logrus
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io"
"os" "os"
"reflect"
"runtime"
"strings"
"sync"
"time" "time"
) )
var (
// qualified package name, cached at first use
logrusPackage string
// Positions in the call stack when tracing to report the calling method
minimumCallerDepth int
// Used for caller information initialisation
callerInitOnce sync.Once
)
const (
maximumCallerDepth int = 25
knownLogrusFrames int = 4
)
func init() {
// start at the bottom of the stack before the package-name cache is primed
minimumCallerDepth = 1
}
// Defines the key when adding errors using WithError.
var ErrorKey = "error"
// An entry is the final or intermediate Logrus logging entry. It contains all // An entry is the final or intermediate Logrus logging entry. It contains all
// the fields passed with WithField{,s}. It's finally logged when Debug, Info, // the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and // Info, Warn, Error, Fatal or Panic is called on it. These objects can be
// passed around as much as you wish to avoid field duplication. // reused and passed around as much as you wish to avoid field duplication.
type Entry struct { type Entry struct {
Logger *Logger Logger *Logger
@ -21,36 +50,70 @@ type Entry struct {
// Time at which the log entry was created // Time at which the log entry was created
Time time.Time Time time.Time
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic // 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 Level Level Level
// Message passed to Debug, Info, Warn, Error, Fatal or Panic // Calling method, with package name
Caller *runtime.Frame
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
Message string Message string
// When formatter is called in entry.log(), a Buffer may be set to entry
Buffer *bytes.Buffer
// Contains the context set by the user. Useful for hook processing etc.
Context context.Context
// err may contain a field formatting error
err string
} }
func NewEntry(logger *Logger) *Entry { func NewEntry(logger *Logger) *Entry {
return &Entry{ return &Entry{
Logger: logger, Logger: logger,
// Default is three fields, give a little extra room // Default is three fields, plus one optional. Give a little extra room.
Data: make(Fields, 5), Data: make(Fields, 6),
} }
} }
// Returns a reader for the entry, which is a proxy to the formatter. func (entry *Entry) Dup() *Entry {
func (entry *Entry) Reader() (*bytes.Buffer, error) { data := make(Fields, len(entry.Data))
serialized, err := entry.Logger.Formatter.Format(entry) for k, v := range entry.Data {
return bytes.NewBuffer(serialized), err data[k] = v
}
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
}
// Returns the bytes representation of this entry from the formatter.
func (entry *Entry) Bytes() ([]byte, error) {
return entry.Logger.Formatter.Format(entry)
} }
// Returns the string representation from the reader and ultimately the // Returns the string representation from the reader and ultimately the
// formatter. // formatter.
func (entry *Entry) String() (string, error) { func (entry *Entry) String() (string, error) {
reader, err := entry.Reader() serialized, err := entry.Bytes()
if err != nil { if err != nil {
return "", err return "", err
} }
str := string(serialized)
return str, nil
}
return reader.String(), err // Add an error as single field (using the key defined in ErrorKey) to the Entry.
func (entry *Entry) WithError(err error) *Entry {
return entry.WithField(ErrorKey, err)
}
// Add a context to the Entry.
func (entry *Entry) WithContext(ctx context.Context) *Entry {
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}
} }
// Add a single field to the Entry. // Add a single field to the Entry.
@ -60,54 +123,194 @@ func (entry *Entry) WithField(key string, value interface{}) *Entry {
// Add a map of fields to the Entry. // Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry { func (entry *Entry) WithFields(fields Fields) *Entry {
data := Fields{} data := make(Fields, len(entry.Data)+len(fields))
for k, v := range entry.Data { for k, v := range entry.Data {
data[k] = v data[k] = v
} }
fieldErr := entry.err
for k, v := range fields { for k, v := range fields {
data[k] = v 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} return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
}
// Overrides the time of the Entry.
func (entry *Entry) WithTime(t time.Time) *Entry {
dataCopy := make(Fields, len(entry.Data))
for k, v := range entry.Data {
dataCopy[k] = v
}
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
}
// getPackageName reduces a fully qualified function name to the package name
// There really ought to be to be a better way...
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}
return f
}
// getCaller retrieves the name of the first non-logrus calling function
func getCaller() *runtime.Frame {
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
pcs := make([]uintptr, maximumCallerDepth)
_ = runtime.Callers(0, pcs)
// dynamic get the package name and the minimum caller depth
for i := 0; i < maximumCallerDepth; i++ {
funcName := runtime.FuncForPC(pcs[i]).Name()
if strings.Contains(funcName, "getCaller") {
logrusPackage = getPackageName(funcName)
break
}
}
minimumCallerDepth = knownLogrusFrames
})
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)
// If the caller isn't part of this package, we're done
if pkg != logrusPackage {
return &f //nolint:scopelint
}
}
// if we got here, we failed to find the caller's context
return nil
}
func (entry Entry) HasCaller() (has bool) {
return entry.Logger != nil &&
entry.Logger.ReportCaller &&
entry.Caller != nil
} }
func (entry *Entry) log(level Level, msg string) { func (entry *Entry) log(level Level, msg string) {
entry.Time = time.Now() var buffer *bytes.Buffer
entry.Level = level
entry.Message = msg
if err := entry.Logger.Hooks.Fire(level, entry); err != nil { newEntry := entry.Dup()
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) if newEntry.Time.IsZero() {
entry.Logger.mu.Unlock() newEntry.Time = time.Now()
} }
reader, err := entry.Reader() newEntry.Level = level
if err != nil { newEntry.Message = msg
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) newEntry.Logger.mu.Lock()
entry.Logger.mu.Unlock() reportCaller := newEntry.Logger.ReportCaller
bufPool := newEntry.getBufferPool()
newEntry.Logger.mu.Unlock()
if reportCaller {
newEntry.Caller = getCaller()
} }
entry.Logger.mu.Lock() newEntry.fireHooks()
defer entry.Logger.mu.Unlock() buffer = bufPool.Get()
defer func() {
newEntry.Buffer = nil
buffer.Reset()
bufPool.Put(buffer)
}()
buffer.Reset()
newEntry.Buffer = buffer
_, err = io.Copy(entry.Logger.Out, reader) newEntry.write()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) newEntry.Buffer = nil
}
// To avoid Entry#log() returning a value that only would make sense for // 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 // panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here. // directly here.
if level <= PanicLevel { if level <= PanicLevel {
panic(entry) panic(newEntry)
} }
} }
func (entry *Entry) Debug(args ...interface{}) { func (entry *Entry) getBufferPool() (pool BufferPool) {
if entry.Logger.Level >= DebugLevel { if entry.Logger.BufferPool != nil {
entry.log(DebugLevel, fmt.Sprint(args...)) return entry.Logger.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) write() {
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
serialized, err := entry.Logger.Formatter.Format(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
return
}
if _, err := entry.Logger.Out.Write(serialized); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
// Log will log a message at the level given as parameter.
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
// For this behaviour Entry.Panic or Entry.Fatal should be used instead.
func (entry *Entry) Log(level Level, args ...interface{}) {
if entry.Logger.IsLevelEnabled(level) {
entry.log(level, fmt.Sprint(args...))
}
}
func (entry *Entry) Trace(args ...interface{}) {
entry.Log(TraceLevel, args...)
}
func (entry *Entry) Debug(args ...interface{}) {
entry.Log(DebugLevel, args...)
} }
func (entry *Entry) Print(args ...interface{}) { func (entry *Entry) Print(args ...interface{}) {
@ -115,49 +318,48 @@ func (entry *Entry) Print(args ...interface{}) {
} }
func (entry *Entry) Info(args ...interface{}) { func (entry *Entry) Info(args ...interface{}) {
if entry.Logger.Level >= InfoLevel { entry.Log(InfoLevel, args...)
entry.log(InfoLevel, fmt.Sprint(args...))
}
} }
func (entry *Entry) Warn(args ...interface{}) { func (entry *Entry) Warn(args ...interface{}) {
if entry.Logger.Level >= WarnLevel { entry.Log(WarnLevel, args...)
entry.log(WarnLevel, fmt.Sprint(args...)) }
}
func (entry *Entry) Warning(args ...interface{}) {
entry.Warn(args...)
} }
func (entry *Entry) Error(args ...interface{}) { func (entry *Entry) Error(args ...interface{}) {
if entry.Logger.Level >= ErrorLevel { entry.Log(ErrorLevel, args...)
entry.log(ErrorLevel, fmt.Sprint(args...))
}
} }
func (entry *Entry) Fatal(args ...interface{}) { func (entry *Entry) Fatal(args ...interface{}) {
if entry.Logger.Level >= FatalLevel { entry.Log(FatalLevel, args...)
entry.log(FatalLevel, fmt.Sprint(args...)) entry.Logger.Exit(1)
}
os.Exit(1)
} }
func (entry *Entry) Panic(args ...interface{}) { func (entry *Entry) Panic(args ...interface{}) {
if entry.Logger.Level >= PanicLevel { entry.Log(PanicLevel, args...)
entry.log(PanicLevel, fmt.Sprint(args...))
}
panic(fmt.Sprint(args...))
} }
// Entry Printf family functions // Entry Printf family functions
func (entry *Entry) Debugf(format string, args ...interface{}) { func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
if entry.Logger.Level >= DebugLevel { if entry.Logger.IsLevelEnabled(level) {
entry.Debug(fmt.Sprintf(format, args...)) entry.Log(level, fmt.Sprintf(format, args...))
} }
} }
func (entry *Entry) Tracef(format string, args ...interface{}) {
entry.Logf(TraceLevel, format, args...)
}
func (entry *Entry) Debugf(format string, args ...interface{}) {
entry.Logf(DebugLevel, format, args...)
}
func (entry *Entry) Infof(format string, args ...interface{}) { func (entry *Entry) Infof(format string, args ...interface{}) {
if entry.Logger.Level >= InfoLevel { entry.Logf(InfoLevel, format, args...)
entry.Info(fmt.Sprintf(format, args...))
}
} }
func (entry *Entry) Printf(format string, args ...interface{}) { func (entry *Entry) Printf(format string, args ...interface{}) {
@ -165,9 +367,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
} }
func (entry *Entry) Warnf(format string, args ...interface{}) { func (entry *Entry) Warnf(format string, args ...interface{}) {
if entry.Logger.Level >= WarnLevel { entry.Logf(WarnLevel, format, args...)
entry.Warn(fmt.Sprintf(format, args...))
}
} }
func (entry *Entry) Warningf(format string, args ...interface{}) { func (entry *Entry) Warningf(format string, args ...interface{}) {
@ -175,35 +375,36 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
} }
func (entry *Entry) Errorf(format string, args ...interface{}) { func (entry *Entry) Errorf(format string, args ...interface{}) {
if entry.Logger.Level >= ErrorLevel { entry.Logf(ErrorLevel, format, args...)
entry.Error(fmt.Sprintf(format, args...))
}
} }
func (entry *Entry) Fatalf(format string, args ...interface{}) { func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.Level >= FatalLevel { entry.Logf(FatalLevel, format, args...)
entry.Fatal(fmt.Sprintf(format, args...)) entry.Logger.Exit(1)
}
} }
func (entry *Entry) Panicf(format string, args ...interface{}) { func (entry *Entry) Panicf(format string, args ...interface{}) {
if entry.Logger.Level >= PanicLevel { entry.Logf(PanicLevel, format, args...)
entry.Panic(fmt.Sprintf(format, args...))
}
} }
// Entry Println family functions // Entry Println family functions
func (entry *Entry) Debugln(args ...interface{}) { func (entry *Entry) Logln(level Level, args ...interface{}) {
if entry.Logger.Level >= DebugLevel { if entry.Logger.IsLevelEnabled(level) {
entry.Debug(entry.sprintlnn(args...)) entry.Log(level, entry.sprintlnn(args...))
} }
} }
func (entry *Entry) Traceln(args ...interface{}) {
entry.Logln(TraceLevel, args...)
}
func (entry *Entry) Debugln(args ...interface{}) {
entry.Logln(DebugLevel, args...)
}
func (entry *Entry) Infoln(args ...interface{}) { func (entry *Entry) Infoln(args ...interface{}) {
if entry.Logger.Level >= InfoLevel { entry.Logln(InfoLevel, args...)
entry.Info(entry.sprintlnn(args...))
}
} }
func (entry *Entry) Println(args ...interface{}) { func (entry *Entry) Println(args ...interface{}) {
@ -211,9 +412,7 @@ func (entry *Entry) Println(args ...interface{}) {
} }
func (entry *Entry) Warnln(args ...interface{}) { func (entry *Entry) Warnln(args ...interface{}) {
if entry.Logger.Level >= WarnLevel { entry.Logln(WarnLevel, args...)
entry.Warn(entry.sprintlnn(args...))
}
} }
func (entry *Entry) Warningln(args ...interface{}) { func (entry *Entry) Warningln(args ...interface{}) {
@ -221,21 +420,16 @@ func (entry *Entry) Warningln(args ...interface{}) {
} }
func (entry *Entry) Errorln(args ...interface{}) { func (entry *Entry) Errorln(args ...interface{}) {
if entry.Logger.Level >= ErrorLevel { entry.Logln(ErrorLevel, args...)
entry.Error(entry.sprintlnn(args...))
}
} }
func (entry *Entry) Fatalln(args ...interface{}) { func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.Level >= FatalLevel { entry.Logln(FatalLevel, args...)
entry.Fatal(entry.sprintlnn(args...)) entry.Logger.Exit(1)
}
} }
func (entry *Entry) Panicln(args ...interface{}) { func (entry *Entry) Panicln(args ...interface{}) {
if entry.Logger.Level >= PanicLevel { entry.Logln(PanicLevel, args...)
entry.Panic(entry.sprintlnn(args...))
}
} }
// Sprintlnn => Sprint no newline. This is to get the behavior of how // Sprintlnn => Sprint no newline. This is to get the behavior of how

View File

@ -2,12 +2,127 @@ package logrus
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEntryWithError(t *testing.T) {
assert := assert.New(t)
defer func() {
ErrorKey = "error"
}()
err := fmt.Errorf("kaboom at layer %d", 4711)
assert.Equal(err, WithError(err).Data["error"])
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
assert.Equal(err, entry.WithError(err).Data["error"])
ErrorKey = "err"
assert.Equal(err, entry.WithError(err).Data["err"])
}
func TestEntryWithContext(t *testing.T) {
assert := assert.New(t)
ctx := context.WithValue(context.Background(), "foo", "bar")
assert.Equal(ctx, WithContext(ctx).Context)
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
assert.Equal(ctx, entry.WithContext(ctx).Context)
}
func TestEntryWithContextCopiesData(t *testing.T) {
assert := assert.New(t)
// Initialize a parent Entry object with a key/value set in its Data map
logger := New()
logger.Out = &bytes.Buffer{}
parentEntry := NewEntry(logger).WithField("parentKey", "parentValue")
// Create two children Entry objects from the parent in different contexts
ctx1 := context.WithValue(context.Background(), "foo", "bar")
childEntry1 := parentEntry.WithContext(ctx1)
assert.Equal(ctx1, childEntry1.Context)
ctx2 := context.WithValue(context.Background(), "bar", "baz")
childEntry2 := parentEntry.WithContext(ctx2)
assert.Equal(ctx2, childEntry2.Context)
assert.NotEqual(ctx1, ctx2)
// Ensure that data set in the parent Entry are preserved to both children
assert.Equal("parentValue", childEntry1.Data["parentKey"])
assert.Equal("parentValue", childEntry2.Data["parentKey"])
// Modify data stored in the child entry
childEntry1.Data["childKey"] = "childValue"
// Verify that data is successfully stored in the child it was set on
val, exists := childEntry1.Data["childKey"]
assert.True(exists)
assert.Equal("childValue", val)
// Verify that the data change to child 1 has not affected its sibling
val, exists = childEntry2.Data["childKey"]
assert.False(exists)
assert.Empty(val)
// Verify that the data change to child 1 has not affected its parent
val, exists = parentEntry.Data["childKey"]
assert.False(exists)
assert.Empty(val)
}
func TestEntryWithTimeCopiesData(t *testing.T) {
assert := assert.New(t)
// Initialize a parent Entry object with a key/value set in its Data map
logger := New()
logger.Out = &bytes.Buffer{}
parentEntry := NewEntry(logger).WithField("parentKey", "parentValue")
// Create two children Entry objects from the parent with two different times
childEntry1 := parentEntry.WithTime(time.Now().AddDate(0, 0, 1))
childEntry2 := parentEntry.WithTime(time.Now().AddDate(0, 0, 2))
// Ensure that data set in the parent Entry are preserved to both children
assert.Equal("parentValue", childEntry1.Data["parentKey"])
assert.Equal("parentValue", childEntry2.Data["parentKey"])
// Modify data stored in the child entry
childEntry1.Data["childKey"] = "childValue"
// Verify that data is successfully stored in the child it was set on
val, exists := childEntry1.Data["childKey"]
assert.True(exists)
assert.Equal("childValue", val)
// Verify that the data change to child 1 has not affected its sibling
val, exists = childEntry2.Data["childKey"]
assert.False(exists)
assert.Empty(val)
// Verify that the data change to child 1 has not affected its parent
val, exists = parentEntry.Data["childKey"]
assert.False(exists)
assert.Empty(val)
}
func TestEntryPanicln(t *testing.T) { func TestEntryPanicln(t *testing.T) {
errBoom := fmt.Errorf("boom time") errBoom := fmt.Errorf("boom time")
@ -51,3 +166,136 @@ func TestEntryPanicf(t *testing.T) {
entry := NewEntry(logger) entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicf("kaboom %v", true) entry.WithField("err", errBoom).Panicf("kaboom %v", true)
} }
func TestEntryPanic(t *testing.T) {
errBoom := fmt.Errorf("boom again")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panic("kaboom")
}
const (
badMessage = "this is going to panic"
panicMessage = "this is broken"
)
type panickyHook struct{}
func (p *panickyHook) Levels() []Level {
return []Level{InfoLevel}
}
func (p *panickyHook) Fire(entry *Entry) error {
if entry.Message == badMessage {
panic(panicMessage)
}
return nil
}
func TestEntryHooksPanic(t *testing.T) {
logger := New()
logger.Out = &bytes.Buffer{}
logger.Level = InfoLevel
logger.Hooks.Add(&panickyHook{})
defer func() {
p := recover()
assert.NotNil(t, p)
assert.Equal(t, panicMessage, p)
entry := NewEntry(logger)
entry.Info("another message")
}()
entry := NewEntry(logger)
entry.Info(badMessage)
}
func TestEntryWithIncorrectField(t *testing.T) {
assert := assert.New(t)
fn := func() {}
e := Entry{Logger: New()}
eWithFunc := e.WithFields(Fields{"func": fn})
eWithFuncPtr := e.WithFields(Fields{"funcPtr": &fn})
assert.Equal(eWithFunc.err, `can not add field "func"`)
assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
eWithFunc = eWithFunc.WithField("not_a_func", "it is a string")
eWithFuncPtr = eWithFuncPtr.WithField("not_a_func", "it is a string")
assert.Equal(eWithFunc.err, `can not add field "func"`)
assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
eWithFunc = eWithFunc.WithTime(time.Now())
eWithFuncPtr = eWithFuncPtr.WithTime(time.Now())
assert.Equal(eWithFunc.err, `can not add field "func"`)
assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
}
func TestEntryLogfLevel(t *testing.T) {
logger := New()
buffer := &bytes.Buffer{}
logger.Out = buffer
logger.SetLevel(InfoLevel)
entry := NewEntry(logger)
entry.Logf(DebugLevel, "%s", "debug")
assert.NotContains(t, buffer.String(), "debug")
entry.Logf(WarnLevel, "%s", "warn")
assert.Contains(t, buffer.String(), "warn")
}
func TestEntryReportCallerRace(t *testing.T) {
logger := New()
entry := NewEntry(logger)
// logging before SetReportCaller has the highest chance of causing a race condition
// to be detected, but doing it twice just to increase the likelyhood of detecting the race
go func() {
entry.Info("should not race")
}()
go func() {
logger.SetReportCaller(true)
}()
go func() {
entry.Info("should not race")
}()
}
func TestEntryFormatterRace(t *testing.T) {
logger := New()
entry := NewEntry(logger)
// logging before SetReportCaller has the highest chance of causing a race condition
// to be detected, but doing it twice just to increase the likelyhood of detecting the race
go func() {
entry.Info("should not race")
}()
go func() {
logger.SetFormatter(&TextFormatter{})
}()
go func() {
entry.Info("should not race")
}()
}

77
example_basic_test.go Normal file
View File

@ -0,0 +1,77 @@
package logrus_test
import (
"os"
"git.internal/re/logrus"
)
func Example_basic() {
log := logrus.New()
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
log.Level = logrus.TraceLevel
log.Out = os.Stdout
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
defer func() {
err := recover()
if err != nil {
entry := err.(*logrus.Entry)
log.WithFields(logrus.Fields{
"omg": true,
"err_animal": entry.Data["animal"],
"err_size": entry.Data["size"],
"err_level": entry.Level,
"err_message": entry.Message,
"number": 100,
}).Error("The ice breaks!") // or use Fatal() to force the process to exit with a nonzero code
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 0,
}).Trace("Went to the beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 8,
}).Debug("Started observing beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"temperature": -4,
}).Debug("Temperature changes")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
// Output:
// level=trace msg="Went to the beach" animal=walrus number=0
// level=debug msg="Started observing beach" animal=walrus number=8
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
// level=debug msg="Temperature changes" temperature=-4
// level=panic msg="It's over 9000!" animal=orca size=9009
// level=error msg="The ice breaks!" err_animal=orca err_level=panic err_message="It's over 9000!" err_size=9009 number=100 omg=true
}

View File

@ -0,0 +1,28 @@
package logrus_test
import (
"os"
"path"
"runtime"
"strings"
"git.internal/re/logrus"
)
func ExampleJSONFormatter_CallerPrettyfier() {
l := logrus.New()
l.SetReportCaller(true)
l.Out = os.Stdout
l.Formatter = &logrus.JSONFormatter{
DisableTimestamp: true,
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
s := strings.Split(f.Function, ".")
funcname := s[len(s)-1]
_, filename := path.Split(f.File)
return funcname, filename
},
}
l.Info("example of custom format caller")
// Output:
// {"file":"example_custom_caller_test.go","func":"ExampleJSONFormatter_CallerPrettyfier","level":"info","msg":"example of custom format caller"}
}

View File

@ -0,0 +1,31 @@
package logrus_test
import (
"os"
"git.internal/re/logrus"
)
type DefaultFieldHook struct {
GetValue func() string
}
func (h *DefaultFieldHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h *DefaultFieldHook) Fire(e *logrus.Entry) error {
e.Data["aDefaultField"] = h.GetValue()
return nil
}
func ExampleDefaultFieldHook() {
l := logrus.New()
l.Out = os.Stdout
l.Formatter = &logrus.TextFormatter{DisableTimestamp: true, DisableColors: true}
l.AddHook(&DefaultFieldHook{GetValue: func() string { return "with its default value" }})
l.Info("first log")
// Output:
// level=info msg="first log" aDefaultField="with its default value"
}

31
example_function_test.go Normal file
View File

@ -0,0 +1,31 @@
package logrus_test
import (
"testing"
log "git.internal/re/logrus"
"github.com/stretchr/testify/assert"
)
func TestLogger_LogFn(t *testing.T) {
log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.WarnLevel)
notCalled := 0
log.InfoFn(func() []interface{} {
notCalled++
return []interface{}{
"Hello",
}
})
assert.Equal(t, 0, notCalled)
called := 0
log.ErrorFn(func() []interface{} {
called++
return []interface{}{
"Oopsi",
}
})
assert.Equal(t, 1, called)
}

View File

@ -0,0 +1,34 @@
package logrus_test
import (
"os"
"git.internal/re/logrus"
)
var mystring string
type GlobalHook struct{}
func (h *GlobalHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h *GlobalHook) Fire(e *logrus.Entry) error {
e.Data["mystring"] = mystring
return nil
}
func ExampleGlobalHook() {
l := logrus.New()
l.Out = os.Stdout
l.Formatter = &logrus.TextFormatter{DisableTimestamp: true, DisableColors: true}
l.AddHook(&GlobalHook{})
mystring = "first value"
l.Info("first log")
mystring = "another value"
l.Info("second log")
// Output:
// level=info msg="first log" mystring="first value"
// level=info msg="second log" mystring="another value"
}

44
example_hook_test.go Normal file
View File

@ -0,0 +1,44 @@
//go:build !windows
// +build !windows
package logrus_test
import (
"log/syslog"
"os"
"git.internal/re/logrus"
slhooks "git.internal/re/logrus/hooks/syslog"
)
// An example on how to use a hook
func Example_hook() {
log := logrus.New()
log.Formatter = new(logrus.TextFormatter) // default
log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
if sl, err := slhooks.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""); err == nil {
log.Hooks.Add(sl)
}
log.Out = os.Stdout
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Error("The ice breaks!")
// Output:
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
// level=error msg="The ice breaks!" number=100 omg=true
}

View File

@ -1,40 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
}

View File

@ -1,35 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
"github.com/tobi/airbrake-go"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
}
func main() {
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
airbrake.ApiKey = "whatever"
airbrake.Environment = "production"
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View File

@ -1,7 +1,9 @@
package logrus package logrus
import ( import (
"context"
"io" "io"
"time"
) )
var ( var (
@ -9,37 +11,54 @@ var (
std = New() std = New()
) )
func StandardLogger() *Logger {
return std
}
// SetOutput sets the standard logger output. // SetOutput sets the standard logger output.
func SetOutput(out io.Writer) { func SetOutput(out io.Writer) {
std.mu.Lock() std.SetOutput(out)
defer std.mu.Unlock()
std.Out = out
} }
// SetFormatter sets the standard logger formatter. // SetFormatter sets the standard logger formatter.
func SetFormatter(formatter Formatter) { func SetFormatter(formatter Formatter) {
std.mu.Lock() std.SetFormatter(formatter)
defer std.mu.Unlock() }
std.Formatter = formatter
// SetReportCaller sets whether the standard logger will include the calling
// method as a field.
func SetReportCaller(include bool) {
std.SetReportCaller(include)
} }
// SetLevel sets the standard logger level. // SetLevel sets the standard logger level.
func SetLevel(level Level) { func SetLevel(level Level) {
std.mu.Lock() std.SetLevel(level)
defer std.mu.Unlock()
std.Level = level
} }
// GetLevel returns the standard logger level. // GetLevel returns the standard logger level.
func GetLevel() Level { func GetLevel() Level {
return std.Level return std.GetLevel()
}
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
func IsLevelEnabled(level Level) bool {
return std.IsLevelEnabled(level)
} }
// AddHook adds a hook to the standard logger hooks. // AddHook adds a hook to the standard logger hooks.
func AddHook(hook Hook) { func AddHook(hook Hook) {
std.mu.Lock() std.AddHook(hook)
defer std.mu.Unlock() }
std.Hooks.Add(hook)
// 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)
}
// WithContext creates an entry from the standard logger and adds a context to it.
func WithContext(ctx context.Context) *Entry {
return std.WithContext(ctx)
} }
// WithField creates an entry from the standard logger and adds a field to // WithField creates an entry from the standard logger and adds a field to
@ -61,6 +80,20 @@ func WithFields(fields Fields) *Entry {
return std.WithFields(fields) 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.
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{}) {
std.Trace(args...)
}
// Debug logs a message at level Debug on the standard logger. // Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) { func Debug(args ...interface{}) {
std.Debug(args...) std.Debug(args...)
@ -96,11 +129,61 @@ func Panic(args ...interface{}) {
std.Panic(args...) std.Panic(args...)
} }
// Fatal logs a message at level Fatal on the standard logger. // 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{}) { func Fatal(args ...interface{}) {
std.Fatal(args...) std.Fatal(args...)
} }
// TraceFn logs a message from a func at level Trace 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.
func DebugFn(fn LogFunction) {
std.DebugFn(fn)
}
// PrintFn logs a message from a func at level Info 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.
func InfoFn(fn LogFunction) {
std.InfoFn(fn)
}
// WarnFn logs a message from a func at level Warn 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.
func WarningFn(fn LogFunction) {
std.WarningFn(fn)
}
// ErrorFn logs a message from a func at level Error 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.
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.
func FatalFn(fn LogFunction) {
std.FatalFn(fn)
}
// Tracef logs a message at level Trace on the standard logger.
func Tracef(format string, args ...interface{}) {
std.Tracef(format, args...)
}
// Debugf logs a message at level Debug on the standard logger. // Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) { func Debugf(format string, args ...interface{}) {
std.Debugf(format, args...) std.Debugf(format, args...)
@ -136,11 +219,16 @@ func Panicf(format string, args ...interface{}) {
std.Panicf(format, args...) std.Panicf(format, args...)
} }
// Fatalf logs a message at level Fatal on the standard logger. // 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{}) { func Fatalf(format string, args ...interface{}) {
std.Fatalf(format, args...) std.Fatalf(format, args...)
} }
// Traceln logs a message at level Trace on the standard logger.
func Traceln(args ...interface{}) {
std.Traceln(args...)
}
// Debugln logs a message at level Debug on the standard logger. // Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) { func Debugln(args ...interface{}) {
std.Debugln(args...) std.Debugln(args...)
@ -176,7 +264,7 @@ func Panicln(args ...interface{}) {
std.Panicln(args...) std.Panicln(args...)
} }
// Fatalln logs a message at level Fatal on the standard logger. // 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{}) { func Fatalln(args ...interface{}) {
std.Fatalln(args...) std.Fatalln(args...)
} }

View File

@ -1,5 +1,18 @@
package logrus package logrus
import "time"
// Default key names for the default fields
const (
defaultTimestampFormat = time.RFC3339
FieldKeyMsg = "msg"
FieldKeyLevel = "level"
FieldKeyTime = "time"
FieldKeyLogrusError = "logrus_error"
FieldKeyFunc = "func"
FieldKeyFile = "file"
)
// The Formatter interface is used to implement a custom Formatter. It takes an // The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones: // `Entry`. It exposes all the fields, including the default ones:
// //
@ -14,7 +27,7 @@ type Formatter interface {
Format(*Entry) ([]byte, error) Format(*Entry) ([]byte, error)
} }
// This is to not silently overwrite `time`, `msg` and `level` fields when // This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
// dumping it. If this code wasn't there doing: // dumping it. If this code wasn't there doing:
// //
// logrus.WithField("level", 1).Info("hello") // logrus.WithField("level", 1).Info("hello")
@ -26,19 +39,40 @@ type Formatter interface {
// //
// It's not exported because it's still using Data in an opinionated way. It's to // 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. // avoid code duplication between the two default formatters.
func prefixFieldClashes(data Fields) { func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
_, ok := data["time"] timeKey := fieldMap.resolve(FieldKeyTime)
if ok { if t, ok := data[timeKey]; ok {
data["fields.time"] = data["time"] data["fields."+timeKey] = t
delete(data, timeKey)
} }
_, ok = data["msg"] msgKey := fieldMap.resolve(FieldKeyMsg)
if ok { if m, ok := data[msgKey]; ok {
data["fields.msg"] = data["msg"] data["fields."+msgKey] = m
delete(data, msgKey)
} }
_, ok = data["level"] levelKey := fieldMap.resolve(FieldKeyLevel)
if ok { if l, ok := data[levelKey]; ok {
data["fields.level"] = data["level"] data["fields."+levelKey] = l
delete(data, levelKey)
}
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
if l, ok := data[logrusErrKey]; ok {
data["fields."+logrusErrKey] = l
delete(data, logrusErrKey)
}
// If reportCaller is not set, 'func' will not conflict.
if reportCaller {
funcKey := fieldMap.resolve(FieldKeyFunc)
if l, ok := data[funcKey]; ok {
data["fields."+funcKey] = l
}
fileKey := fieldMap.resolve(FieldKeyFile)
if l, ok := data[fileKey]; ok {
data["fields."+fileKey] = l
}
} }
} }

View File

@ -1,6 +1,7 @@
package logrus package logrus
import ( import (
"fmt"
"testing" "testing"
"time" "time"
) )
@ -45,6 +46,15 @@ var largeFields = Fields{
"entries": "yeah", "entries": "yeah",
} }
var errorFields = Fields{
"foo": fmt.Errorf("bar"),
"baz": fmt.Errorf("qux"),
}
func BenchmarkErrorTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
}
func BenchmarkSmallTextFormatter(b *testing.B) { func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields) doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
} }
@ -70,11 +80,14 @@ func BenchmarkLargeJSONFormatter(b *testing.B) {
} }
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) { func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
logger := New()
entry := &Entry{ entry := &Entry{
Time: time.Time{}, Time: time.Time{},
Level: InfoLevel, Level: InfoLevel,
Message: "message", Message: "message",
Data: fields, Data: fields,
Logger: logger,
} }
var d []byte var d []byte
var err error var err error

9
go.mod Normal file
View File

@ -0,0 +1,9 @@
module git.internal/re/logrus
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/stretchr/testify v1.7.0
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
)
go 1.13

14
go.sum Normal file
View File

@ -0,0 +1,14 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,9 +1,18 @@
package logrus package logrus_test
import ( import (
"bytes"
"encoding/json"
"fmt"
"sync"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
. "git.internal/re/logrus"
"git.internal/re/logrus/hooks/test"
. "git.internal/re/logrus/internal/testutils"
) )
type TestHook struct { type TestHook struct {
@ -17,6 +26,7 @@ func (hook *TestHook) Fire(entry *Entry) error {
func (hook *TestHook) Levels() []Level { func (hook *TestHook) Levels() []Level {
return []Level{ return []Level{
TraceLevel,
DebugLevel, DebugLevel,
InfoLevel, InfoLevel,
WarnLevel, WarnLevel,
@ -39,8 +49,7 @@ func TestHookFires(t *testing.T) {
}) })
} }
type ModifyHook struct { type ModifyHook struct{}
}
func (hook *ModifyHook) Fire(entry *Entry) error { func (hook *ModifyHook) Fire(entry *Entry) error {
entry.Data["wow"] = "whale" entry.Data["wow"] = "whale"
@ -49,6 +58,7 @@ func (hook *ModifyHook) Fire(entry *Entry) error {
func (hook *ModifyHook) Levels() []Level { func (hook *ModifyHook) Levels() []Level {
return []Level{ return []Level{
TraceLevel,
DebugLevel, DebugLevel,
InfoLevel, InfoLevel,
WarnLevel, WarnLevel,
@ -84,6 +94,46 @@ func TestCanFireMultipleHooks(t *testing.T) {
}) })
} }
type SingleLevelModifyHook struct {
ModifyHook
}
func (h *SingleLevelModifyHook) Levels() []Level {
return []Level{InfoLevel}
}
func TestHookEntryIsPristine(t *testing.T) {
l := New()
b := &bytes.Buffer{}
l.Formatter = &JSONFormatter{}
l.Out = b
l.AddHook(&SingleLevelModifyHook{})
l.Error("error message")
data := map[string]string{}
err := json.Unmarshal(b.Bytes(), &data)
require.NoError(t, err)
_, ok := data["wow"]
require.False(t, ok)
b.Reset()
l.Info("error message")
data = map[string]string{}
err = json.Unmarshal(b.Bytes(), &data)
require.NoError(t, err)
_, ok = data["wow"]
require.True(t, ok)
b.Reset()
l.Error("error message")
data = map[string]string{}
err = json.Unmarshal(b.Bytes(), &data)
require.NoError(t, err)
_, ok = data["wow"]
require.False(t, ok)
b.Reset()
}
type ErrorHook struct { type ErrorHook struct {
Fired bool Fired bool
} }
@ -120,3 +170,64 @@ func TestErrorHookShouldFireOnError(t *testing.T) {
assert.Equal(t, hook.Fired, true) assert.Equal(t, hook.Fired, true)
}) })
} }
func TestAddHookRace(t *testing.T) {
var wg sync.WaitGroup
wg.Add(2)
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
go func() {
defer wg.Done()
log.AddHook(hook)
}()
go func() {
defer wg.Done()
log.Error("test")
}()
wg.Wait()
}, func(fields Fields) {
// the line may have been logged
// before the hook was added, so we can't
// actually assert on the hook
})
}
func TestAddHookRace2(t *testing.T) {
t.Parallel()
for i := 0; i < 3; i++ {
testname := fmt.Sprintf("Test %d", i)
t.Run(testname, func(t *testing.T) {
t.Parallel()
_ = test.NewGlobal()
Info(testname)
})
}
}
type HookCallFunc struct {
F func()
}
func (h *HookCallFunc) Levels() []Level {
return AllLevels
}
func (h *HookCallFunc) Fire(e *Entry) error {
h.F()
return nil
}
func TestHookFireOrder(t *testing.T) {
checkers := []string{}
h := LevelHooks{}
h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "first hook") }})
h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "second hook") }})
h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "third hook") }})
if err := h.Fire(InfoLevel, &Entry{}); err != nil {
t.Error("unexpected error:", err)
}
require.Equal(t, []string{"first hook", "second hook", "third hook"}, checkers)
}

View File

@ -11,11 +11,11 @@ type Hook interface {
} }
// Internal type for storing the hooks on a logger instance. // Internal type for storing the hooks on a logger instance.
type levelHooks map[Level][]Hook type LevelHooks map[Level][]Hook
// Add a hook to an instance of logger. This is called with // Add a hook to an instance of logger. This is called with
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. // `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
func (hooks levelHooks) Add(hook Hook) { func (hooks LevelHooks) Add(hook Hook) {
for _, level := range hook.Levels() { for _, level := range hook.Levels() {
hooks[level] = append(hooks[level], hook) hooks[level] = append(hooks[level], hook)
} }
@ -23,7 +23,7 @@ func (hooks levelHooks) Add(hook Hook) {
// Fire all the hooks for the passed level. Used by `entry.log` to fire // Fire all the hooks for the passed level. Used by `entry.log` to fire
// appropriate hooks for a log entry. // appropriate hooks for a log entry.
func (hooks levelHooks) Fire(level Level, entry *Entry) error { func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
for _, hook := range hooks[level] { for _, hook := range hooks[level] {
if err := hook.Fire(entry); err != nil { if err := hook.Fire(entry); err != nil {
return err return err

View File

@ -1,54 +0,0 @@
package logrus_airbrake
import (
"github.com/Sirupsen/logrus"
"github.com/tobi/airbrake-go"
)
// AirbrakeHook to send exceptions to an exception-tracking service compatible
// with the Airbrake API. You must set:
// * airbrake.Endpoint
// * airbrake.ApiKey
// * airbrake.Environment (only sends exceptions when set to "production")
//
// Before using this hook, to send an error. Entries that trigger an Error,
// Fatal or Panic should now include an "error" field to send to Airbrake.
type AirbrakeHook struct{}
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
if entry.Data["error"] == nil {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
return nil
}
err, ok := entry.Data["error"].(error)
if !ok {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
return nil
}
airErr := airbrake.Notify(err)
if airErr != nil {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
"error": airErr,
}).Warn("Failed to send error to Airbrake")
}
return nil
}
func (hook *AirbrakeHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}

View File

@ -1,28 +0,0 @@
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
## Usage
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/papertrail"
)
func main() {
log := logrus.New()
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
if err == nil {
log.Hooks.Add(hook)
}
}
```

View File

@ -1,54 +0,0 @@
package logrus_papertrail
import (
"fmt"
"net"
"os"
"time"
"github.com/Sirupsen/logrus"
)
const (
format = "Jan 2 15:04:05"
)
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
type PapertrailHook struct {
Host string
Port int
AppName string
UDPConn net.Conn
}
// NewPapertrailHook creates a hook to be added to an instance of logger.
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
return &PapertrailHook{host, port, appName, conn}, err
}
// Fire is called when a log event is fired.
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
date := time.Now().Format(format)
payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Level, entry.Message)
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
return err
}
return nil
}
// Levels returns the available logging levels.
func (hook *PapertrailHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}

View File

@ -1,26 +0,0 @@
package logrus_papertrail
import (
"fmt"
"testing"
"github.com/Sirupsen/logrus"
"github.com/stvp/go-udp-testing"
)
func TestWritingToUDP(t *testing.T) {
port := 16661
udp.SetAddr(fmt.Sprintf(":%d", port))
hook, err := NewPapertrailHook("localhost", port, "test")
if err != nil {
t.Errorf("Unable to connect to local UDP server.")
}
log := logrus.New()
log.Hooks.Add(hook)
udp.ShouldReceive(t, "foo", func() {
log.Info("foo")
})
}

View File

@ -1,61 +0,0 @@
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
[Sentry](https://getsentry.com) provides both self-hosted and hosted
solutions for exception tracking.
Both client and server are
[open source](https://github.com/getsentry/sentry).
## Usage
Every sentry application defined on the server gets a different
[DSN](https://www.getsentry.com/docs/). In the example below replace
`YOUR_DSN` with the one created for your application.
```go
import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/sentry"
)
func main() {
log := logrus.New()
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
})
if err == nil {
log.Hooks.Add(hook)
}
}
```
## Special fields
Some logrus fields have a special meaning in this hook,
these are server_name and logger.
When logs are sent to sentry these fields are treated differently.
- server_name (also known as hostname) is the name of the server which
is logging the event (hostname.example.com)
- logger is the part of the application which is logging the event.
In go this usually means setting it to the name of the package.
## Timeout
`Timeout` is the time the sentry hook will wait for a response
from the sentry server.
If this time elapses with no response from
the server an error will be returned.
If `Timeout` is set to 0 the SentryHook will not wait for a reply
and will assume a correct delivery.
The SentryHook has a default timeout of `100 milliseconds` when created
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
```go
hook, _ := logrus_sentry.NewSentryHook(...)
hook.Timeout = 20*time.Seconds
```

View File

@ -1,100 +0,0 @@
package logrus_sentry
import (
"fmt"
"time"
"github.com/Sirupsen/logrus"
"github.com/getsentry/raven-go"
)
var (
severityMap = map[logrus.Level]raven.Severity{
logrus.DebugLevel: raven.DEBUG,
logrus.InfoLevel: raven.INFO,
logrus.WarnLevel: raven.WARNING,
logrus.ErrorLevel: raven.ERROR,
logrus.FatalLevel: raven.FATAL,
logrus.PanicLevel: raven.FATAL,
}
)
func getAndDel(d logrus.Fields, key string) (string, bool) {
var (
ok bool
v interface{}
val string
)
if v, ok = d[key]; !ok {
return "", false
}
if val, ok = v.(string); !ok {
return "", false
}
delete(d, key)
return val, true
}
// SentryHook delivers logs to a sentry server.
type SentryHook struct {
// Timeout sets the time to wait for a delivery error from the sentry server.
// If this is set to zero the server will not wait for any response and will
// consider the message correctly sent
Timeout time.Duration
client *raven.Client
levels []logrus.Level
}
// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
client, err := raven.NewClient(DSN, nil)
if err != nil {
return nil, err
}
return &SentryHook{100 * time.Millisecond, client, levels}, nil
}
// Called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: logger and server_name
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
packet := &raven.Packet{
Message: entry.Message,
Timestamp: raven.Timestamp(entry.Time),
Level: severityMap[entry.Level],
Platform: "go",
}
d := entry.Data
if logger, ok := getAndDel(d, "logger"); ok {
packet.Logger = logger
}
if serverName, ok := getAndDel(d, "server_name"); ok {
packet.ServerName = serverName
}
packet.Extra = map[string]interface{}(d)
_, errCh := hook.client.Capture(packet, nil)
timeout := hook.Timeout
if timeout != 0 {
timeoutCh := time.After(timeout)
select {
case err := <-errCh:
return err
case <-timeoutCh:
return fmt.Errorf("no response from sentry server in %s", timeout)
}
}
return nil
}
// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
return hook.levels
}

View File

@ -1,97 +0,0 @@
package logrus_sentry
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/Sirupsen/logrus"
"github.com/getsentry/raven-go"
)
const (
message = "error message"
server_name = "testserver.internal"
logger_name = "test.logger"
)
func getTestLogger() *logrus.Logger {
l := logrus.New()
l.Out = ioutil.Discard
return l
}
func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
pch := make(chan *raven.Packet, 1)
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
d := json.NewDecoder(req.Body)
p := &raven.Packet{}
err := d.Decode(p)
if err != nil {
t.Fatal(err.Error())
}
pch <- p
}))
defer s.Close()
fragments := strings.SplitN(s.URL, "://", 2)
dsn := fmt.Sprintf(
"%s://public:secret@%s/sentry/project-id",
fragments[0],
fragments[1],
)
tf(dsn, pch)
}
func TestSpecialFields(t *testing.T) {
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
logger := getTestLogger()
hook, err := NewSentryHook(dsn, []logrus.Level{
logrus.ErrorLevel,
})
if err != nil {
t.Fatal(err.Error())
}
logger.Hooks.Add(hook)
logger.WithFields(logrus.Fields{
"server_name": server_name,
"logger": logger_name,
}).Error(message)
packet := <-pch
if packet.Logger != logger_name {
t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
}
if packet.ServerName != server_name {
t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
}
})
}
func TestSentryHandler(t *testing.T) {
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
logger := getTestLogger()
hook, err := NewSentryHook(dsn, []logrus.Level{
logrus.ErrorLevel,
})
if err != nil {
t.Fatal(err.Error())
}
logger.Hooks.Add(hook)
logger.Error(message)
packet := <-pch
if packet.Message != message {
t.Errorf("message should have been %s, was %s", message, packet.Message)
}
})
}

View File

@ -5,13 +5,32 @@
```go ```go
import ( import (
"log/syslog" "log/syslog"
"github.com/Sirupsen/logrus" "git.internal/re/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" lSyslog "git.internal/re/logrus/hooks/syslog"
) )
func main() { func main() {
log := logrus.New() log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
```go
import (
"log/syslog"
"git.internal/re/logrus"
lSyslog "git.internal/re/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
if err == nil { if err == nil {
log.Hooks.Add(hook) log.Hooks.Add(hook)

View File

@ -1,10 +1,14 @@
package logrus_syslog //go:build !windows && !nacl && !plan9
// +build !windows,!nacl,!plan9
package syslog
import ( import (
"fmt" "fmt"
"github.com/Sirupsen/logrus"
"log/syslog" "log/syslog"
"os" "os"
"git.internal/re/logrus"
) )
// SyslogHook to send logs via syslog. // SyslogHook to send logs via syslog.
@ -40,7 +44,7 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
return hook.Writer.Warning(line) return hook.Writer.Warning(line)
case logrus.InfoLevel: case logrus.InfoLevel:
return hook.Writer.Info(line) return hook.Writer.Info(line)
case logrus.DebugLevel: case logrus.DebugLevel, logrus.TraceLevel:
return hook.Writer.Debug(line) return hook.Writer.Debug(line)
default: default:
return nil return nil
@ -48,12 +52,5 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
} }
func (hook *SyslogHook) Levels() []logrus.Level { func (hook *SyslogHook) Levels() []logrus.Level {
return []logrus.Level{ return logrus.AllLevels
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
} }

View File

@ -1,15 +1,18 @@
package logrus_syslog //go:build !windows && !nacl && !plan9
// +build !windows,!nacl,!plan9
package syslog
import ( import (
"github.com/Sirupsen/logrus"
"log/syslog" "log/syslog"
"testing" "testing"
"git.internal/re/logrus"
) )
func TestLocalhostAddAndPrint(t *testing.T) { func TestLocalhostAddAndPrint(t *testing.T) {
log := logrus.New() log := logrus.New()
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil { if err != nil {
t.Errorf("Unable to connect to local syslog.") t.Errorf("Unable to connect to local syslog.")
} }

85
hooks/test/test.go Normal file
View File

@ -0,0 +1,85 @@
// The Test package is used for testing logrus.
// It provides a simple hooks which register logged messages.
package test
import (
"io/ioutil"
"sync"
"git.internal/re/logrus"
)
// Hook is a hook designed for dealing with logs in test scenarios.
type Hook struct {
// Entries is an array of all entries that have been received by this hook.
// For safe access, use the AllEntries() method, rather than reading this
// value directly.
Entries []logrus.Entry
mu sync.RWMutex
}
// NewGlobal installs a test hook for the global logger.
func NewGlobal() *Hook {
hook := new(Hook)
logrus.AddHook(hook)
return hook
}
// NewLocal installs a test hook for a given local logger.
func NewLocal(logger *logrus.Logger) *Hook {
hook := new(Hook)
logger.Hooks.Add(hook)
return hook
}
// NewNullLogger creates a discarding logger and installs the test hook.
func NewNullLogger() (*logrus.Logger, *Hook) {
logger := logrus.New()
logger.Out = ioutil.Discard
return logger, NewLocal(logger)
}
func (t *Hook) Fire(e *logrus.Entry) error {
t.mu.Lock()
defer t.mu.Unlock()
t.Entries = append(t.Entries, *e)
return nil
}
func (t *Hook) Levels() []logrus.Level {
return logrus.AllLevels
}
// LastEntry returns the last entry that was logged or nil.
func (t *Hook) LastEntry() *logrus.Entry {
t.mu.RLock()
defer t.mu.RUnlock()
i := len(t.Entries) - 1
if i < 0 {
return nil
}
return &t.Entries[i]
}
// AllEntries returns all entries that were logged.
func (t *Hook) AllEntries() []*logrus.Entry {
t.mu.RLock()
defer t.mu.RUnlock()
// Make a copy so the returned value won't race with future log requests
entries := make([]*logrus.Entry, len(t.Entries))
for i := 0; i < len(t.Entries); i++ {
// Make a copy, for safety
entries[i] = &t.Entries[i]
}
return entries
}
// Reset removes all Entries from this test hook.
func (t *Hook) Reset() {
t.mu.Lock()
defer t.mu.Unlock()
t.Entries = make([]logrus.Entry, 0)
}

84
hooks/test/test_test.go Normal file
View File

@ -0,0 +1,84 @@
package test
import (
"math/rand"
"sync"
"testing"
"time"
"git.internal/re/logrus"
"github.com/stretchr/testify/assert"
)
func TestAllHooks(t *testing.T) {
assert := assert.New(t)
logger, hook := NewNullLogger()
assert.Nil(hook.LastEntry())
assert.Equal(0, len(hook.Entries))
logger.Error("Hello error")
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal("Hello error", hook.LastEntry().Message)
assert.Equal(1, len(hook.Entries))
logger.Warn("Hello warning")
assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
assert.Equal("Hello warning", hook.LastEntry().Message)
assert.Equal(2, len(hook.Entries))
hook.Reset()
assert.Nil(hook.LastEntry())
assert.Equal(0, len(hook.Entries))
hook = NewGlobal()
logrus.Error("Hello error")
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal("Hello error", hook.LastEntry().Message)
assert.Equal(1, len(hook.Entries))
}
func TestLoggingWithHooksRace(t *testing.T) {
rand.Seed(time.Now().Unix())
unlocker := rand.Int() % 100
assert := assert.New(t)
logger, hook := NewNullLogger()
var wgOne, wgAll sync.WaitGroup
wgOne.Add(1)
wgAll.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
logger.Info("info")
wgAll.Done()
if i == unlocker {
wgOne.Done()
}
}(i)
}
wgOne.Wait()
assert.Equal(logrus.InfoLevel, hook.LastEntry().Level)
assert.Equal("info", hook.LastEntry().Message)
wgAll.Wait()
entries := hook.AllEntries()
assert.Equal(100, len(entries))
}
func TestFatalWithAlternateExit(t *testing.T) {
assert := assert.New(t)
logger, hook := NewNullLogger()
logger.ExitFunc = func(code int) {}
logger.Fatal("something went very wrong")
assert.Equal(logrus.FatalLevel, hook.LastEntry().Level)
assert.Equal("something went very wrong", hook.LastEntry().Message)
assert.Equal(1, len(hook.Entries))
}

43
hooks/writer/README.md Normal file
View File

@ -0,0 +1,43 @@
# Writer Hooks for Logrus
Send logs of given levels to any object with `io.Writer` interface.
## Usage
If you want for example send high level logs to `Stderr` and
logs of normal execution to `Stdout`, you could do it like this:
```go
package main
import (
"io/ioutil"
"os"
log "git.internal/re/logrus"
"git.internal/re/logrus/hooks/writer"
)
func main() {
log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default
log.AddHook(&writer.Hook{ // Send logs with level higher than warning to stderr
Writer: os.Stderr,
LogLevels: []log.Level{
log.PanicLevel,
log.FatalLevel,
log.ErrorLevel,
log.WarnLevel,
},
})
log.AddHook(&writer.Hook{ // Send info and debug logs to stdout
Writer: os.Stdout,
LogLevels: []log.Level{
log.InfoLevel,
log.DebugLevel,
},
})
log.Info("This will go to stdout")
log.Warn("This will go to stderr")
}
```

29
hooks/writer/writer.go Normal file
View File

@ -0,0 +1,29 @@
package writer
import (
"io"
log "git.internal/re/logrus"
)
// Hook is a hook that writes logs of specified LogLevels to specified Writer
type Hook struct {
Writer io.Writer
LogLevels []log.Level
}
// Fire will be called when some logging function is called with current hook
// It will format log entry to string and write it to appropriate writer
func (hook *Hook) Fire(entry *log.Entry) error {
line, err := entry.Bytes()
if err != nil {
return err
}
_, err = hook.Writer.Write(line)
return err
}
// Levels define on which log levels this hook would trigger
func (hook *Hook) Levels() []log.Level {
return hook.LogLevels
}

View File

@ -0,0 +1,38 @@
package writer
import (
"bytes"
"io/ioutil"
"testing"
log "git.internal/re/logrus"
"github.com/stretchr/testify/assert"
)
func TestDifferentLevelsGoToDifferentWriters(t *testing.T) {
var a, b bytes.Buffer
log.SetFormatter(&log.TextFormatter{
DisableTimestamp: true,
DisableColors: true,
})
log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default
log.AddHook(&Hook{
Writer: &a,
LogLevels: []log.Level{
log.WarnLevel,
},
})
log.AddHook(&Hook{ // Send info and debug logs to stdout
Writer: &b,
LogLevels: []log.Level{
log.InfoLevel,
},
})
log.Warn("send to a")
log.Info("send to b")
assert.Equal(t, a.String(), "level=warning msg=\"send to a\"\n")
assert.Equal(t, b.String(), "level=info msg=\"send to b\"\n")
}

View File

@ -0,0 +1,58 @@
package testutils
import (
"bytes"
"encoding/json"
"strconv"
"strings"
"testing"
. "git.internal/re/logrus"
"github.com/stretchr/testify/require"
)
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
log(logger)
err := json.Unmarshal(buffer.Bytes(), &fields)
require.Nil(t, err)
assertions(fields)
}
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
var buffer bytes.Buffer
logger := New()
logger.Out = &buffer
logger.Formatter = &TextFormatter{
DisableColors: true,
}
log(logger)
fields := make(map[string]string)
for _, kv := range strings.Split(strings.TrimRight(buffer.String(), "\n"), " ") {
if !strings.Contains(kv, "=") {
continue
}
kvArr := strings.Split(kv, "=")
key := strings.TrimSpace(kvArr[0])
val := kvArr[1]
if kvArr[1][0] == '"' {
var err error
val, err = strconv.Unquote(val)
require.NoError(t, err)
}
fields[key] = val
}
assertions(fields)
}

View File

@ -1,26 +1,128 @@
package logrus package logrus
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"time" "runtime"
) )
type JSONFormatter struct{} type fieldKey string
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { // FieldMap allows customization of the key names for default fields.
data := make(Fields, len(entry.Data)+3) type FieldMap map[fieldKey]string
for k, v := range entry.Data {
data[k] = v
}
prefixFieldClashes(data)
data["time"] = entry.Time.Format(time.RFC3339)
data["msg"] = entry.Message
data["level"] = entry.Level.String()
serialized, err := json.Marshal(data) func (f FieldMap) resolve(key fieldKey) string {
if err != nil { if k, ok := f[key]; ok {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) return k
} }
return append(serialized, '\n'), nil
return string(key)
}
// JSONFormatter formats logs into parsable json
type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
// The format to use is the same than for time.Format or time.Parse from the standard
// library.
// The standard Library already provides a set of predefined format.
TimestampFormat string
// DisableTimestamp allows disabling automatic timestamps in output
DisableTimestamp bool
// DisableHTMLEscape allows disabling html escaping in output
DisableHTMLEscape bool
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
DataKey string
// FieldMap allows users to customize the names of keys for default fields.
// As an example:
// formatter := &JSONFormatter{
// FieldMap: FieldMap{
// FieldKeyTime: "@timestamp",
// FieldKeyLevel: "@level",
// FieldKeyMsg: "@message",
// FieldKeyFunc: "@caller",
// },
// }
FieldMap FieldMap
// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the json data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from json fields.
CallerPrettyfier func(*runtime.Frame) (function string, file string)
// PrettyPrint will indent all json logs
PrettyPrint bool
}
// Format renders a single log entry
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+4)
for k, v := range entry.Data {
switch v := v.(type) {
case error:
// Otherwise errors are ignored by `encoding/json`
// https://git.internal/re/logrus/issues/137
data[k] = v.Error()
default:
data[k] = v
}
}
if f.DataKey != "" {
newData := make(Fields, 4)
newData[f.DataKey] = data
data = newData
}
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = defaultTimestampFormat
}
if entry.err != "" {
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
}
if !f.DisableTimestamp {
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
}
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 f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
}
if funcVal != "" {
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
}
if fileVal != "" {
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
}
}
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
encoder := json.NewEncoder(b)
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
if f.PrettyPrint {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(data); err != nil {
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
}
return b.Bytes(), nil
} }

372
json_formatter_test.go Normal file
View File

@ -0,0 +1,372 @@
package logrus
import (
"encoding/json"
"errors"
"fmt"
"runtime"
"strings"
"testing"
)
func TestErrorNotLost(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["error"] != "wild walrus" {
t.Fatal("Error field not set")
}
}
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["omg"] != "wild walrus" {
t.Fatal("Error field not set")
}
}
func TestFieldClashWithTime(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("time", "right now!"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.time"] != "right now!" {
t.Fatal("fields.time not set to original time field")
}
if entry["time"] != "0001-01-01T00:00:00Z" {
t.Fatal("time field not set to current time, was: ", entry["time"])
}
}
func TestFieldClashWithMsg(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("msg", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.msg"] != "something" {
t.Fatal("fields.msg not set to original msg field")
}
}
func TestFieldClashWithLevel(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.level"] != "something" {
t.Fatal("fields.level not set to original level field")
}
}
func TestFieldClashWithRemappedFields(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyTime: "@timestamp",
FieldKeyLevel: "@level",
FieldKeyMsg: "@message",
},
}
b, err := formatter.Format(WithFields(Fields{
"@timestamp": "@timestamp",
"@level": "@level",
"@message": "@message",
"timestamp": "timestamp",
"level": "level",
"msg": "msg",
}))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
for _, field := range []string{"timestamp", "level", "msg"} {
if entry[field] != field {
t.Errorf("Expected field %v to be untouched; got %v", field, entry[field])
}
remappedKey := fmt.Sprintf("fields.%s", field)
if remapped, ok := entry[remappedKey]; ok {
t.Errorf("Expected %s to be empty; got %v", remappedKey, remapped)
}
}
for _, field := range []string{"@timestamp", "@level", "@message"} {
if entry[field] == field {
t.Errorf("Expected field %v to be mapped to an Entry value", field)
}
remappedKey := fmt.Sprintf("fields.%s", field)
if remapped, ok := entry[remappedKey]; ok {
if remapped != field {
t.Errorf("Expected field %v to be copied to %s; got %v", field, remappedKey, remapped)
}
} else {
t.Errorf("Expected field %v to be copied to %s; was absent", field, remappedKey)
}
}
}
func TestFieldsInNestedDictionary(t *testing.T) {
formatter := &JSONFormatter{
DataKey: "args",
}
logEntry := WithFields(Fields{
"level": "level",
"test": "test",
})
logEntry.Level = InfoLevel
b, err := formatter.Format(logEntry)
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
args := entry["args"].(map[string]interface{})
for _, field := range []string{"test", "level"} {
if value, present := args[field]; !present || value != field {
t.Errorf("Expected field %v to be present under 'args'; untouched", field)
}
}
for _, field := range []string{"test", "fields.level"} {
if _, present := entry[field]; present {
t.Errorf("Expected field %v not to be present at top level", field)
}
}
// with nested object, "level" shouldn't clash
if entry["level"] != "info" {
t.Errorf("Expected 'level' field to contain 'info'")
}
}
func TestJSONEntryEndsWithNewline(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
if b[len(b)-1] != '\n' {
t.Fatal("Expected JSON log entry to end with a newline")
}
}
func TestJSONMessageKey(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyMsg: "message",
},
}
b, err := formatter.Format(&Entry{Message: "oh hai"})
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !(strings.Contains(s, "message") && strings.Contains(s, "oh hai")) {
t.Fatal("Expected JSON to format message key")
}
}
func TestJSONLevelKey(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyLevel: "somelevel",
},
}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, "somelevel") {
t.Fatal("Expected JSON to format level key")
}
}
func TestJSONTimeKey(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyTime: "timeywimey",
},
}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, "timeywimey") {
t.Fatal("Expected JSON to format time key")
}
}
func TestFieldDoesNotClashWithCaller(t *testing.T) {
SetReportCaller(false)
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("func", "howdy pardner"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["func"] != "howdy pardner" {
t.Fatal("func field replaced when ReportCaller=false")
}
}
func TestFieldClashWithCaller(t *testing.T) {
SetReportCaller(true)
formatter := &JSONFormatter{}
e := WithField("func", "howdy pardner")
e.Caller = &runtime.Frame{Function: "somefunc"}
b, err := formatter.Format(e)
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.func"] != "howdy pardner" {
t.Fatalf("fields.func not set to original func field when ReportCaller=true (got '%s')",
entry["fields.func"])
}
if entry["func"] != "somefunc" {
t.Fatalf("func not set as expected when ReportCaller=true (got '%s')",
entry["func"])
}
SetReportCaller(false) // return to default value
}
func TestJSONDisableTimestamp(t *testing.T) {
formatter := &JSONFormatter{
DisableTimestamp: true,
}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if strings.Contains(s, FieldKeyTime) {
t.Error("Did not prevent timestamp", s)
}
}
func TestJSONEnableTimestamp(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, FieldKeyTime) {
t.Error("Timestamp not present", s)
}
}
func TestJSONDisableHTMLEscape(t *testing.T) {
formatter := &JSONFormatter{DisableHTMLEscape: true}
b, err := formatter.Format(&Entry{Message: "& < >"})
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, "& < >") {
t.Error("Message should not be HTML escaped", s)
}
}
func TestJSONEnableHTMLEscape(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(&Entry{Message: "& < >"})
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !(strings.Contains(s, "u0026") && strings.Contains(s, "u003e") && strings.Contains(s, "u003c")) {
t.Error("Message should be HTML escaped", s)
}
}

62
level_test.go Normal file
View File

@ -0,0 +1,62 @@
package logrus_test
import (
"bytes"
"encoding/json"
"testing"
"git.internal/re/logrus"
"github.com/stretchr/testify/require"
)
func TestLevelJsonEncoding(t *testing.T) {
type X struct {
Level logrus.Level
}
var x X
x.Level = logrus.WarnLevel
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
require.NoError(t, enc.Encode(x))
dec := json.NewDecoder(&buf)
var y X
require.NoError(t, dec.Decode(&y))
}
func TestLevelUnmarshalText(t *testing.T) {
var u logrus.Level
for _, level := range logrus.AllLevels {
t.Run(level.String(), func(t *testing.T) {
require.NoError(t, u.UnmarshalText([]byte(level.String())))
require.Equal(t, level, u)
})
}
t.Run("invalid", func(t *testing.T) {
require.Error(t, u.UnmarshalText([]byte("invalid")))
})
}
func TestLevelMarshalText(t *testing.T) {
levelStrings := []string{
"panic",
"fatal",
"error",
"warning",
"info",
"debug",
"trace",
}
for idx, val := range logrus.AllLevels {
level := val
t.Run(level.String(), func(t *testing.T) {
var cmp logrus.Level
b, err := level.MarshalText()
require.NoError(t, err)
require.Equal(t, levelStrings[idx], string(b))
err = cmp.UnmarshalText(b)
require.NoError(t, err)
require.Equal(t, level, cmp)
})
}
}

340
logger.go
View File

@ -1,20 +1,28 @@
package logrus package logrus
import ( import (
"context"
"io" "io"
"os" "os"
"sync" "sync"
"sync/atomic"
"time"
) )
// 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 Logger struct { type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a // 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.Stdout`. You can also set this to // file, or leave it default which is `os.Stderr`. You can also set this to
// something more adventorous, such as logging to Kafka. // something more adventurous, such as logging to Kafka.
Out io.Writer Out io.Writer
// Hooks for the logger instance. These allow firing events based on logging // 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 // levels and log entries. For example, to send errors to an error tracking
// service, log to StatsD or dump the core on fatal errors. // service, log to StatsD or dump the core on fatal errors.
Hooks levelHooks Hooks LevelHooks
// All log entries pass through the formatter before logged to Out. The // All log entries pass through the formatter before logged to Out. The
// included formatters are `TextFormatter` and `JSONFormatter` for which // included formatters are `TextFormatter` and `JSONFormatter` for which
// TextFormatter is the default. In development (when a TTY is attached) it // TextFormatter is the default. In development (when a TTY is attached) it
@ -22,140 +30,388 @@ type Logger struct {
// own that implements the `Formatter` interface, see the `README` or included // own that implements the `Formatter` interface, see the `README` or included
// formatters for examples. // formatters for examples.
Formatter Formatter Formatter Formatter
// Flag for whether to log caller info (off by default)
ReportCaller bool
// The logging level the logger should log at. This is typically (and defaults // The logging level the logger should log at. This is typically (and defaults
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
// logged. `logrus.Debug` is useful in // logged.
Level Level Level Level
// Used to sync writing to the log. // Used to sync writing to the log. Locking is enabled by Default
mu sync.Mutex mu MutexWrap
// Reusable empty entry
entryPool sync.Pool
// Function to exit the application, defaults to `os.Exit()`
ExitFunc exitFunc
// The buffer pool used to format the log. If it is nil, the default global
// buffer pool will be used.
BufferPool BufferPool
}
type exitFunc func(int)
type MutexWrap struct {
lock sync.Mutex
disabled bool
}
func (mw *MutexWrap) Lock() {
if !mw.disabled {
mw.lock.Lock()
}
}
func (mw *MutexWrap) Unlock() {
if !mw.disabled {
mw.lock.Unlock()
}
}
func (mw *MutexWrap) Disable() {
mw.disabled = true
} }
// Creates a new logger. Configuration should be set by changing `Formatter`, // Creates a new logger. Configuration should be set by changing `Formatter`,
// `Out` and `Hooks` directly on the default logger instance. You can also just // `Out` and `Hooks` directly on the default logger instance. You can also just
// instantiate your own: // instantiate your own:
// //
// var log = &Logger{ // var log = &logrus.Logger{
// Out: os.Stderr, // Out: os.Stderr,
// Formatter: new(JSONFormatter), // Formatter: new(logrus.TextFormatter),
// Hooks: make(levelHooks), // Hooks: make(logrus.LevelHooks),
// Level: logrus.DebugLevel, // Level: logrus.DebugLevel,
// } // }
// //
// It's recommended to make this a global instance called `log`. // It's recommended to make this a global instance called `log`.
func New() *Logger { func New() *Logger {
return &Logger{ return &Logger{
Out: os.Stdout, Out: os.Stderr,
Formatter: new(TextFormatter), Formatter: new(TextFormatter),
Hooks: make(levelHooks), Hooks: make(LevelHooks),
Level: InfoLevel, Level: InfoLevel,
ExitFunc: os.Exit,
ReportCaller: false,
} }
} }
// Adds a field to the log entry, note that you it doesn't log until you call func (logger *Logger) newEntry() *Entry {
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. entry, ok := logger.entryPool.Get().(*Entry)
// Ff you want multiple fields, use `WithFields`. if ok {
return entry
}
return NewEntry(logger)
}
func (logger *Logger) releaseEntry(entry *Entry) {
entry.Data = map[string]interface{}{}
logger.entryPool.Put(entry)
}
// WithField allocates a new entry and adds a field to it.
// 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 interface{}) *Entry {
return NewEntry(logger).WithField(key, value) entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithField(key, value)
} }
// Adds a struct of fields to the log entry. All it does is call `WithField` for // Adds a struct of fields to the log entry. All it does is call `WithField` for
// each `Field`. // each `Field`.
func (logger *Logger) WithFields(fields Fields) *Entry { func (logger *Logger) WithFields(fields Fields) *Entry {
return NewEntry(logger).WithFields(fields) entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithFields(fields)
}
// Add an error as single field to the log entry. All it does is call
// `WithError` for the given `error`.
func (logger *Logger) WithError(err error) *Entry {
entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithError(err)
}
// Add a context to the log entry.
func (logger *Logger) WithContext(ctx context.Context) *Entry {
entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithContext(ctx)
}
// Overrides the time of the log entry.
func (logger *Logger) WithTime(t time.Time) *Entry {
entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithTime(t)
}
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Logf(level, format, args...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) Tracef(format string, args ...interface{}) {
logger.Logf(TraceLevel, format, args...)
} }
func (logger *Logger) Debugf(format string, args ...interface{}) { func (logger *Logger) Debugf(format string, args ...interface{}) {
NewEntry(logger).Debugf(format, args...) logger.Logf(DebugLevel, format, args...)
} }
func (logger *Logger) Infof(format string, args ...interface{}) { func (logger *Logger) Infof(format string, args ...interface{}) {
NewEntry(logger).Infof(format, args...) logger.Logf(InfoLevel, format, args...)
} }
func (logger *Logger) Printf(format string, args ...interface{}) { func (logger *Logger) Printf(format string, args ...interface{}) {
NewEntry(logger).Printf(format, args...) 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 ...interface{}) {
NewEntry(logger).Warnf(format, args...) logger.Logf(WarnLevel, format, args...)
} }
func (logger *Logger) Warningf(format string, args ...interface{}) { func (logger *Logger) Warningf(format string, args ...interface{}) {
NewEntry(logger).Warnf(format, args...) logger.Warnf(format, args...)
} }
func (logger *Logger) Errorf(format string, args ...interface{}) { func (logger *Logger) Errorf(format string, args ...interface{}) {
NewEntry(logger).Errorf(format, args...) logger.Logf(ErrorLevel, format, args...)
} }
func (logger *Logger) Fatalf(format string, args ...interface{}) { func (logger *Logger) Fatalf(format string, args ...interface{}) {
NewEntry(logger).Fatalf(format, args...) logger.Logf(FatalLevel, format, args...)
logger.Exit(1)
} }
func (logger *Logger) Panicf(format string, args ...interface{}) { func (logger *Logger) Panicf(format string, args ...interface{}) {
NewEntry(logger).Panicf(format, args...) logger.Logf(PanicLevel, format, args...)
}
// Log will log a message at the level given as parameter.
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
// For this behaviour Logger.Panic or Logger.Fatal should be used instead.
func (logger *Logger) Log(level Level, args ...interface{}) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Log(level, args...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) LogFn(level Level, fn LogFunction) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Log(level, fn()...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) Trace(args ...interface{}) {
logger.Log(TraceLevel, args...)
} }
func (logger *Logger) Debug(args ...interface{}) { func (logger *Logger) Debug(args ...interface{}) {
NewEntry(logger).Debug(args...) logger.Log(DebugLevel, args...)
} }
func (logger *Logger) Info(args ...interface{}) { func (logger *Logger) Info(args ...interface{}) {
NewEntry(logger).Info(args...) logger.Log(InfoLevel, args...)
} }
func (logger *Logger) Print(args ...interface{}) { func (logger *Logger) Print(args ...interface{}) {
NewEntry(logger).Info(args...) entry := logger.newEntry()
entry.Print(args...)
logger.releaseEntry(entry)
} }
func (logger *Logger) Warn(args ...interface{}) { func (logger *Logger) Warn(args ...interface{}) {
NewEntry(logger).Warn(args...) logger.Log(WarnLevel, args...)
} }
func (logger *Logger) Warning(args ...interface{}) { func (logger *Logger) Warning(args ...interface{}) {
NewEntry(logger).Warn(args...) logger.Warn(args...)
} }
func (logger *Logger) Error(args ...interface{}) { func (logger *Logger) Error(args ...interface{}) {
NewEntry(logger).Error(args...) logger.Log(ErrorLevel, args...)
} }
func (logger *Logger) Fatal(args ...interface{}) { func (logger *Logger) Fatal(args ...interface{}) {
NewEntry(logger).Fatal(args...) logger.Log(FatalLevel, args...)
logger.Exit(1)
} }
func (logger *Logger) Panic(args ...interface{}) { func (logger *Logger) Panic(args ...interface{}) {
NewEntry(logger).Panic(args...) logger.Log(PanicLevel, args...)
}
func (logger *Logger) TraceFn(fn LogFunction) {
logger.LogFn(TraceLevel, fn)
}
func (logger *Logger) DebugFn(fn LogFunction) {
logger.LogFn(DebugLevel, fn)
}
func (logger *Logger) InfoFn(fn LogFunction) {
logger.LogFn(InfoLevel, fn)
}
func (logger *Logger) PrintFn(fn LogFunction) {
entry := logger.newEntry()
entry.Print(fn()...)
logger.releaseEntry(entry)
}
func (logger *Logger) WarnFn(fn LogFunction) {
logger.LogFn(WarnLevel, fn)
}
func (logger *Logger) WarningFn(fn LogFunction) {
logger.WarnFn(fn)
}
func (logger *Logger) ErrorFn(fn LogFunction) {
logger.LogFn(ErrorLevel, fn)
}
func (logger *Logger) FatalFn(fn LogFunction) {
logger.LogFn(FatalLevel, fn)
logger.Exit(1)
}
func (logger *Logger) PanicFn(fn LogFunction) {
logger.LogFn(PanicLevel, fn)
}
func (logger *Logger) Logln(level Level, args ...interface{}) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Logln(level, args...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) Traceln(args ...interface{}) {
logger.Logln(TraceLevel, args...)
} }
func (logger *Logger) Debugln(args ...interface{}) { func (logger *Logger) Debugln(args ...interface{}) {
NewEntry(logger).Debugln(args...) logger.Logln(DebugLevel, args...)
} }
func (logger *Logger) Infoln(args ...interface{}) { func (logger *Logger) Infoln(args ...interface{}) {
NewEntry(logger).Infoln(args...) logger.Logln(InfoLevel, args...)
} }
func (logger *Logger) Println(args ...interface{}) { func (logger *Logger) Println(args ...interface{}) {
NewEntry(logger).Println(args...) entry := logger.newEntry()
entry.Println(args...)
logger.releaseEntry(entry)
} }
func (logger *Logger) Warnln(args ...interface{}) { func (logger *Logger) Warnln(args ...interface{}) {
NewEntry(logger).Warnln(args...) logger.Logln(WarnLevel, args...)
} }
func (logger *Logger) Warningln(args ...interface{}) { func (logger *Logger) Warningln(args ...interface{}) {
NewEntry(logger).Warnln(args...) logger.Warnln(args...)
} }
func (logger *Logger) Errorln(args ...interface{}) { func (logger *Logger) Errorln(args ...interface{}) {
NewEntry(logger).Errorln(args...) logger.Logln(ErrorLevel, args...)
} }
func (logger *Logger) Fatalln(args ...interface{}) { func (logger *Logger) Fatalln(args ...interface{}) {
NewEntry(logger).Fatalln(args...) logger.Logln(FatalLevel, args...)
logger.Exit(1)
} }
func (logger *Logger) Panicln(args ...interface{}) { func (logger *Logger) Panicln(args ...interface{}) {
NewEntry(logger).Panicln(args...) logger.Logln(PanicLevel, args...)
}
func (logger *Logger) Exit(code int) {
runHandlers()
if logger.ExitFunc == nil {
logger.ExitFunc = os.Exit
}
logger.ExitFunc(code)
}
//When file is opened with appending mode, it's safe to
//write concurrently to a file (within 4k message on Linux).
//In these cases user can choose to disable the lock.
func (logger *Logger) SetNoLock() {
logger.mu.Disable()
}
func (logger *Logger) level() Level {
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
}
// SetLevel sets the logger level.
func (logger *Logger) SetLevel(level Level) {
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
}
// GetLevel returns the logger level.
func (logger *Logger) GetLevel() Level {
return logger.level()
}
// AddHook adds a hook to the logger hooks.
func (logger *Logger) AddHook(hook Hook) {
logger.mu.Lock()
defer logger.mu.Unlock()
logger.Hooks.Add(hook)
}
// IsLevelEnabled checks if the log level of the logger is greater than the level param
func (logger *Logger) IsLevelEnabled(level Level) bool {
return logger.level() >= level
}
// SetFormatter sets the logger formatter.
func (logger *Logger) SetFormatter(formatter Formatter) {
logger.mu.Lock()
defer logger.mu.Unlock()
logger.Formatter = formatter
}
// SetOutput sets the logger output.
func (logger *Logger) SetOutput(output io.Writer) {
logger.mu.Lock()
defer logger.mu.Unlock()
logger.Out = output
}
func (logger *Logger) SetReportCaller(reportCaller bool) {
logger.mu.Lock()
defer logger.mu.Unlock()
logger.ReportCaller = reportCaller
}
// ReplaceHooks replaces the logger hooks and returns the old ones
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
logger.mu.Lock()
oldHooks := logger.Hooks
logger.Hooks = hooks
logger.mu.Unlock()
return oldHooks
}
// SetBufferPool sets the logger buffer pool.
func (logger *Logger) SetBufferPool(pool BufferPool) {
logger.mu.Lock()
defer logger.mu.Unlock()
logger.BufferPool = pool
} }

77
logger_bench_test.go Normal file
View File

@ -0,0 +1,77 @@
package logrus
import (
"io/ioutil"
"os"
"testing"
)
func BenchmarkDummyLogger(b *testing.B) {
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY, 0666)
if err != nil {
b.Fatalf("%v", err)
}
defer nullf.Close()
doLoggerBenchmark(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkDummyLoggerNoLock(b *testing.B) {
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
b.Fatalf("%v", err)
}
defer nullf.Close()
doLoggerBenchmarkNoLock(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
}
func doLoggerBenchmark(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
logger := Logger{
Out: out,
Level: InfoLevel,
Formatter: formatter,
}
entry := logger.WithFields(fields)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
entry.Info("aaa")
}
})
}
func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
logger := Logger{
Out: out,
Level: InfoLevel,
Formatter: formatter,
}
logger.SetNoLock()
entry := logger.WithFields(fields)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
entry.Info("aaa")
}
})
}
func BenchmarkLoggerJSONFormatter(b *testing.B) {
doLoggerBenchmarkWithFormatter(b, &JSONFormatter{})
}
func BenchmarkLoggerTextFormatter(b *testing.B) {
doLoggerBenchmarkWithFormatter(b, &TextFormatter{})
}
func doLoggerBenchmarkWithFormatter(b *testing.B, f Formatter) {
b.SetParallelism(100)
log := New()
log.Formatter = f
log.Out = ioutil.Discard
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
log.
WithField("foo1", "bar1").
WithField("foo2", "bar2").
Info("this is a dummy log")
}
})
}

97
logger_test.go Normal file
View File

@ -0,0 +1,97 @@
package logrus
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFieldValueError(t *testing.T) {
buf := &bytes.Buffer{}
l := &Logger{
Out: buf,
Formatter: new(JSONFormatter),
Hooks: make(LevelHooks),
Level: DebugLevel,
}
l.WithField("func", func() {}).Info("test")
fmt.Println(buf.String())
var data map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &data); err != nil {
t.Error("unexpected error", err)
}
_, ok := data[FieldKeyLogrusError]
require.True(t, ok, `cannot found expected "logrus_error" field: %v`, data)
}
func TestNoFieldValueError(t *testing.T) {
buf := &bytes.Buffer{}
l := &Logger{
Out: buf,
Formatter: new(JSONFormatter),
Hooks: make(LevelHooks),
Level: DebugLevel,
}
l.WithField("str", "str").Info("test")
fmt.Println(buf.String())
var data map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &data); err != nil {
t.Error("unexpected error", err)
}
_, ok := data[FieldKeyLogrusError]
require.False(t, ok)
}
func TestWarninglnNotEqualToWarning(t *testing.T) {
buf := &bytes.Buffer{}
bufln := &bytes.Buffer{}
formatter := new(TextFormatter)
formatter.DisableTimestamp = true
formatter.DisableLevelTruncation = true
l := &Logger{
Out: buf,
Formatter: formatter,
Hooks: make(LevelHooks),
Level: DebugLevel,
}
l.Warning("hello,", "world")
l.SetOutput(bufln)
l.Warningln("hello,", "world")
assert.NotEqual(t, buf.String(), bufln.String(), "Warning() and Wantingln() should not be equal")
}
type testBufferPool struct {
buffers []*bytes.Buffer
get int
}
func (p *testBufferPool) Get() *bytes.Buffer {
p.get++
return new(bytes.Buffer)
}
func (p *testBufferPool) Put(buf *bytes.Buffer) {
p.buffers = append(p.buffers, buf)
}
func TestLogger_SetBufferPool(t *testing.T) {
out := &bytes.Buffer{}
l := New()
l.SetOutput(out)
pool := new(testBufferPool)
l.SetBufferPool(pool)
l.Info("test")
assert.Equal(t, pool.get, 1, "Logger.SetBufferPool(): The BufferPool.Get() must be called")
assert.Len(t, pool.buffers, 1, "Logger.SetBufferPool(): The BufferPool.Put() must be called")
}

130
logrus.go
View File

@ -3,37 +3,27 @@ package logrus
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
) )
// Fields type, used to pass to `WithFields`. // Fields type, used to pass to `WithFields`.
type Fields map[string]interface{} type Fields map[string]interface{}
// Level type // Level type
type Level uint8 type Level uint32
// Convert the Level to a string. E.g. PanicLevel becomes "panic". // Convert the Level to a string. E.g. PanicLevel becomes "panic".
func (level Level) String() string { func (level Level) String() string {
switch level { if b, err := level.MarshalText(); err == nil {
case DebugLevel: return string(b)
return "debug" } else {
case InfoLevel: return "unknown"
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
case PanicLevel:
return "panic"
} }
return "unknown"
} }
// ParseLevel takes a string level and returns the Logrus log level constant. // ParseLevel takes a string level and returns the Logrus log level constant.
func ParseLevel(lvl string) (Level, error) { func ParseLevel(lvl string) (Level, error) {
switch lvl { switch strings.ToLower(lvl) {
case "panic": case "panic":
return PanicLevel, nil return PanicLevel, nil
case "fatal": case "fatal":
@ -46,19 +36,65 @@ func ParseLevel(lvl string) (Level, error) {
return InfoLevel, nil return InfoLevel, nil
case "debug": case "debug":
return DebugLevel, nil return DebugLevel, nil
case "trace":
return TraceLevel, nil
} }
var l Level var l Level
return l, fmt.Errorf("not a valid logrus Level: %q", lvl) return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
} }
// UnmarshalText implements encoding.TextUnmarshaler.
func (level *Level) UnmarshalText(text []byte) error {
l, err := ParseLevel(string(text))
if err != nil {
return err
}
*level = l
return nil
}
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
}
return nil, fmt.Errorf("not a valid logrus level %d", level)
}
// A constant exposing all logging levels
var AllLevels = []Level{
PanicLevel,
FatalLevel,
ErrorLevel,
WarnLevel,
InfoLevel,
DebugLevel,
TraceLevel,
}
// These are the different logging levels. You can set the logging level to log // 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 ( const (
// PanicLevel level, highest level of severity. Logs and then calls panic with the // PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ... // message passed to Debug, Info, ...
PanicLevel Level = iota PanicLevel Level = iota
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level is set to Panic. // logging level is set to Panic.
FatalLevel FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted. // ErrorLevel level. Logs. Used for errors that should definitely be noted.
@ -71,10 +107,16 @@ const (
InfoLevel InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging. // DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel DebugLevel
// TraceLevel level. Designates finer-grained informational events than the Debug.
TraceLevel
) )
// Won't compile if StdLogger can't be realized by a log.Logger // Won't compile if StdLogger can't be realized by a log.Logger
var _ StdLogger = &log.Logger{} var (
_ StdLogger = &log.Logger{}
_ StdLogger = &Entry{}
_ StdLogger = &Logger{}
)
// StdLogger is what your logrus-enabled library should take, that way // StdLogger is what your logrus-enabled library should take, that way
// it'll accept a stdlib logger and a logrus logger. There's no standard // it'll accept a stdlib logger and a logrus logger. There's no standard
@ -92,3 +134,53 @@ type StdLogger interface {
Panicf(string, ...interface{}) Panicf(string, ...interface{})
Panicln(...interface{}) Panicln(...interface{})
} }
// The FieldLogger interface generalizes the Entry and Logger types
type FieldLogger interface {
WithField(key string, value interface{}) *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{})
Debug(args ...interface{})
Info(args ...interface{})
Print(args ...interface{})
Warn(args ...interface{})
Warning(args ...interface{})
Error(args ...interface{})
Fatal(args ...interface{})
Panic(args ...interface{})
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
}
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
// here for consistancy. Do not use. Use Logger or Entry instead.
type Ext1FieldLogger interface {
FieldLogger
Tracef(format string, args ...interface{})
Trace(args ...interface{})
Traceln(args ...interface{})
}

View File

@ -1,66 +1,117 @@
package logrus package logrus_test
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"strconv" "fmt"
"strings" "io/ioutil"
"os"
"path/filepath"
"runtime"
"sync"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
. "git.internal/re/logrus"
. "git.internal/re/logrus/internal/testutils"
) )
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) { // TestReportCaller verifies that when ReportCaller is set, the 'func' field
// is added, and when it is unset it is not set or modified
// Verify that functions within the Logrus package aren't considered when
// discovering the caller.
func TestReportCallerWhenConfigured(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.ReportCaller = false
log.Print("testNoCaller")
}, func(fields Fields) {
assert.Equal(t, "testNoCaller", fields["msg"])
assert.Equal(t, "info", fields["level"])
assert.Equal(t, nil, fields["func"])
})
LogAndAssertJSON(t, func(log *Logger) {
log.ReportCaller = true
log.Print("testWithCaller")
}, func(fields Fields) {
assert.Equal(t, "testWithCaller", fields["msg"])
assert.Equal(t, "info", fields["level"])
assert.Equal(t,
"git.internal/re/logrus_test.TestReportCallerWhenConfigured.func3", fields[FieldKeyFunc])
})
LogAndAssertJSON(t, func(log *Logger) {
log.ReportCaller = true
log.Formatter.(*JSONFormatter).CallerPrettyfier = func(f *runtime.Frame) (string, string) {
return "somekindoffunc", "thisisafilename"
}
log.Print("testWithCallerPrettyfier")
}, func(fields Fields) {
assert.Equal(t, "somekindoffunc", fields[FieldKeyFunc])
assert.Equal(t, "thisisafilename", fields[FieldKeyFile])
})
LogAndAssertText(t, func(log *Logger) {
log.ReportCaller = true
log.Formatter.(*TextFormatter).CallerPrettyfier = func(f *runtime.Frame) (string, string) {
return "somekindoffunc", "thisisafilename"
}
log.Print("testWithCallerPrettyfier")
}, func(fields map[string]string) {
assert.Equal(t, "somekindoffunc", fields[FieldKeyFunc])
assert.Equal(t, "thisisafilename", fields[FieldKeyFile])
})
}
func logSomething(t *testing.T, message string) Fields {
var buffer bytes.Buffer var buffer bytes.Buffer
var fields Fields var fields Fields
logger := New() logger := New()
logger.Out = &buffer logger.Out = &buffer
logger.Formatter = new(JSONFormatter) logger.Formatter = new(JSONFormatter)
logger.ReportCaller = true
log(logger) entry := logger.WithFields(Fields{
"foo": "bar",
})
entry.Info(message)
err := json.Unmarshal(buffer.Bytes(), &fields) err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err) assert.Nil(t, err)
assertions(fields) return fields
} }
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) { // TestReportCallerHelperDirect - verify reference when logging from a regular function
var buffer bytes.Buffer func TestReportCallerHelperDirect(t *testing.T) {
fields := logSomething(t, "direct")
logger := New() assert.Equal(t, "direct", fields["msg"])
logger.Out = &buffer assert.Equal(t, "info", fields["level"])
logger.Formatter = &TextFormatter{ assert.Regexp(t, "github.com/.*/logrus_test.logSomething", fields["func"])
DisableColors: true, }
}
log(logger) // TestReportCallerHelperDirect - verify reference when logging from a function called via pointer
func TestReportCallerHelperViaPointer(t *testing.T) {
fptr := logSomething
fields := fptr(t, "via pointer")
fields := make(map[string]string) assert.Equal(t, "via pointer", fields["msg"])
for _, kv := range strings.Split(buffer.String(), " ") { assert.Equal(t, "info", fields["level"])
if !strings.Contains(kv, "=") { assert.Regexp(t, "github.com/.*/logrus_test.logSomething", fields["func"])
continue
}
kvArr := strings.Split(kv, "=")
key := strings.TrimSpace(kvArr[0])
val := kvArr[1]
if kvArr[1][0] == '"' {
var err error
val, err = strconv.Unquote(val)
assert.NoError(t, err)
}
fields[key] = val
}
assertions(fields)
} }
func TestPrint(t *testing.T) { func TestPrint(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Print("test") log.Print("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test") assert.Equal(t, "test", fields["msg"])
assert.Equal(t, fields["level"], "info") assert.Equal(t, "info", fields["level"])
}) })
} }
@ -68,8 +119,8 @@ func TestInfo(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Info("test") log.Info("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test") assert.Equal(t, "test", fields["msg"])
assert.Equal(t, fields["level"], "info") assert.Equal(t, "info", fields["level"])
}) })
} }
@ -77,8 +128,17 @@ func TestWarn(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Warn("test") log.Warn("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test") assert.Equal(t, "test", fields["msg"])
assert.Equal(t, fields["level"], "warning") assert.Equal(t, "warning", fields["level"])
})
}
func TestLog(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Log(WarnLevel, "test")
}, func(fields Fields) {
assert.Equal(t, "test", fields["msg"])
assert.Equal(t, "warning", fields["level"])
}) })
} }
@ -86,7 +146,7 @@ func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", "test") log.Infoln("test", "test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test test") assert.Equal(t, "test test", fields["msg"])
}) })
} }
@ -94,7 +154,7 @@ func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", 10) log.Infoln("test", 10)
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test 10") assert.Equal(t, "test 10", fields["msg"])
}) })
} }
@ -102,7 +162,7 @@ func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10) log.Infoln(10, 10)
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10") assert.Equal(t, "10 10", fields["msg"])
}) })
} }
@ -110,7 +170,7 @@ func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10) log.Infoln(10, 10)
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10") assert.Equal(t, "10 10", fields["msg"])
}) })
} }
@ -118,7 +178,7 @@ func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", 10) log.Info("test", 10)
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test10") assert.Equal(t, "test10", fields["msg"])
}) })
} }
@ -126,7 +186,7 @@ func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", "test") log.Info("test", "test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "testtest") assert.Equal(t, "testtest", fields["msg"])
}) })
} }
@ -164,7 +224,7 @@ func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test") log.WithField("msg", "hello").Info("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test") assert.Equal(t, "test", fields["msg"])
}) })
} }
@ -172,8 +232,8 @@ func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test") log.WithField("msg", "hello").Info("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["msg"], "test") assert.Equal(t, "test", fields["msg"])
assert.Equal(t, fields["fields.msg"], "hello") assert.Equal(t, "hello", fields["fields.msg"])
}) })
} }
@ -181,7 +241,7 @@ func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.WithField("time", "hello").Info("test") log.WithField("time", "hello").Info("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["fields.time"], "hello") assert.Equal(t, "hello", fields["fields.time"])
}) })
} }
@ -189,8 +249,8 @@ func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) { LogAndAssertJSON(t, func(log *Logger) {
log.WithField("level", 1).Info("test") log.WithField("level", 1).Info("test")
}, func(fields Fields) { }, func(fields Fields) {
assert.Equal(t, fields["level"], "info") assert.Equal(t, "info", fields["level"])
assert.Equal(t, fields["fields.level"], 1) assert.Equal(t, 1.0, fields["fields.level"]) // JSON has floats only
}) })
} }
@ -208,8 +268,66 @@ func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
}) })
} }
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { func TestWithTimeShouldOverrideTime(t *testing.T) {
now := time.Now().Add(24 * time.Hour)
LogAndAssertJSON(t, func(log *Logger) {
log.WithTime(now).Info("foobar")
}, func(fields Fields) {
assert.Equal(t, fields["time"], now.Format(time.RFC3339))
})
}
func TestWithTimeShouldNotOverrideFields(t *testing.T) {
now := time.Now().Add(24 * time.Hour)
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("herp", "derp").WithTime(now).Info("blah")
}, func(fields Fields) {
assert.Equal(t, fields["time"], now.Format(time.RFC3339))
assert.Equal(t, fields["herp"], "derp")
})
}
func TestWithFieldShouldNotOverrideTime(t *testing.T) {
now := time.Now().Add(24 * time.Hour)
LogAndAssertJSON(t, func(log *Logger) {
log.WithTime(now).WithField("herp", "derp").Info("blah")
}, func(fields Fields) {
assert.Equal(t, fields["time"], now.Format(time.RFC3339))
assert.Equal(t, fields["herp"], "derp")
})
}
func TestTimeOverrideMultipleLogs(t *testing.T) {
var buffer bytes.Buffer
var firstFields, secondFields Fields
logger := New()
logger.Out = &buffer
formatter := new(JSONFormatter)
formatter.TimestampFormat = time.StampMilli
logger.Formatter = formatter
llog := logger.WithField("herp", "derp")
llog.Info("foo")
err := json.Unmarshal(buffer.Bytes(), &firstFields)
assert.NoError(t, err, "should have decoded first message")
buffer.Reset()
time.Sleep(10 * time.Millisecond)
llog.Info("bar")
err = json.Unmarshal(buffer.Bytes(), &secondFields)
assert.NoError(t, err, "should have decoded second message")
assert.NotEqual(t, firstFields["time"], secondFields["time"], "timestamps should not be equal")
}
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
var fields Fields var fields Fields
@ -223,7 +341,7 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
err := json.Unmarshal(buffer.Bytes(), &fields) err := json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded first message") assert.NoError(t, err, "should have decoded first message")
assert.Len(t, fields, 4, "should only have msg/time/level/context fields") assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "looks delicious") assert.Equal(t, fields["msg"], "looks delicious")
assert.Equal(t, fields["context"], "eating raw fish") assert.Equal(t, fields["context"], "eating raw fish")
@ -233,14 +351,119 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
err = json.Unmarshal(buffer.Bytes(), &fields) err = json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded second message") assert.NoError(t, err, "should have decoded second message")
assert.Len(t, fields, 4, "should only have msg/time/level/context fields") assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "omg it is!") assert.Equal(t, "omg it is!", fields["msg"])
assert.Equal(t, fields["context"], "eating raw fish") assert.Equal(t, "eating raw fish", fields["context"])
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
}
func TestNestedLoggingReportsCorrectCaller(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
logger.ReportCaller = true
llog := logger.WithField("context", "eating raw fish")
llog.Info("looks delicious")
_, _, line, _ := runtime.Caller(0)
err := json.Unmarshal(buffer.Bytes(), &fields)
require.NoError(t, err, "should have decoded first message")
assert.Equal(t, 6, len(fields), "should have msg/time/level/func/context fields")
assert.Equal(t, "looks delicious", fields["msg"])
assert.Equal(t, "eating raw fish", fields["context"])
assert.Equal(t,
"git.internal/re/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"])
cwd, err := os.Getwd()
require.NoError(t, err)
assert.Equal(t, filepath.ToSlash(fmt.Sprintf("%s/logrus_test.go:%d", cwd, line-1)), filepath.ToSlash(fields["file"].(string)))
buffer.Reset()
logger.WithFields(Fields{
"Clyde": "Stubblefield",
}).WithFields(Fields{
"Jab'o": "Starks",
}).WithFields(Fields{
"uri": "https://www.youtube.com/watch?v=V5DTznu-9v0",
}).WithFields(Fields{
"func": "y drummer",
}).WithFields(Fields{
"James": "Brown",
}).Print("The hardest workin' man in show business")
_, _, line, _ = runtime.Caller(0)
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded second message")
assert.Equal(t, 11, len(fields), "should have all builtin fields plus foo,bar,baz,...")
assert.Equal(t, "Stubblefield", fields["Clyde"])
assert.Equal(t, "Starks", fields["Jab'o"])
assert.Equal(t, "https://www.youtube.com/watch?v=V5DTznu-9v0", fields["uri"])
assert.Equal(t, "y drummer", fields["fields.func"])
assert.Equal(t, "Brown", fields["James"])
assert.Equal(t, "The hardest workin' man in show business", fields["msg"])
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
assert.Equal(t,
"git.internal/re/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"])
require.NoError(t, err)
assert.Equal(t, filepath.ToSlash(fmt.Sprintf("%s/logrus_test.go:%d", cwd, line-1)), filepath.ToSlash(fields["file"].(string)))
logger.ReportCaller = false // return to default value
}
func logLoop(iterations int, reportCaller bool) {
var buffer bytes.Buffer
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
logger.ReportCaller = reportCaller
for i := 0; i < iterations; i++ {
logger.Infof("round %d of %d", i, iterations)
}
}
// Assertions for upper bounds to reporting overhead
func TestCallerReportingOverhead(t *testing.T) {
iterations := 5000
before := time.Now()
logLoop(iterations, false)
during := time.Now()
logLoop(iterations, true)
after := time.Now()
elapsedNotReporting := during.Sub(before).Nanoseconds()
elapsedReporting := after.Sub(during).Nanoseconds()
maxDelta := 1 * time.Second
assert.WithinDuration(t, during, before, maxDelta,
"%d log calls without caller name lookup takes less than %d second(s) (was %d nanoseconds)",
iterations, maxDelta.Seconds(), elapsedNotReporting)
assert.WithinDuration(t, after, during, maxDelta,
"%d log calls without caller name lookup takes less than %d second(s) (was %d nanoseconds)",
iterations, maxDelta.Seconds(), elapsedReporting)
}
// benchmarks for both with and without caller-function reporting
func BenchmarkWithoutCallerTracing(b *testing.B) {
for i := 0; i < b.N; i++ {
logLoop(1000, false)
}
}
func BenchmarkWithCallerTracing(b *testing.B) {
for i := 0; i < b.N; i++ {
logLoop(1000, true)
}
} }
func TestConvertLevelToString(t *testing.T) { func TestConvertLevelToString(t *testing.T) {
assert.Equal(t, "trace", TraceLevel.String())
assert.Equal(t, "debug", DebugLevel.String()) assert.Equal(t, "debug", DebugLevel.String())
assert.Equal(t, "info", InfoLevel.String()) assert.Equal(t, "info", InfoLevel.String())
assert.Equal(t, "warning", WarnLevel.String()) assert.Equal(t, "warning", WarnLevel.String())
@ -254,30 +477,320 @@ func TestParseLevel(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, PanicLevel, l) assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("PANIC")
assert.Nil(t, err)
assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("fatal") l, err = ParseLevel("fatal")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, FatalLevel, l) assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("FATAL")
assert.Nil(t, err)
assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("error") l, err = ParseLevel("error")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l) assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("ERROR")
assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("warn") l, err = ParseLevel("warn")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, WarnLevel, l) assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("WARN")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("warning") l, err = ParseLevel("warning")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, WarnLevel, l) assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("WARNING")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("info") l, err = ParseLevel("info")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, InfoLevel, l) assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("INFO")
assert.Nil(t, err)
assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("debug") l, err = ParseLevel("debug")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, DebugLevel, l) assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("invalid") l, err = ParseLevel("DEBUG")
assert.Nil(t, err)
assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("trace")
assert.Nil(t, err)
assert.Equal(t, TraceLevel, l)
l, err = ParseLevel("TRACE")
assert.Nil(t, err)
assert.Equal(t, TraceLevel, l)
_, err = ParseLevel("invalid")
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
} }
func TestLevelString(t *testing.T) {
var loggerlevel Level
loggerlevel = 32000
_ = loggerlevel.String()
}
func TestGetSetLevelRace(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
if i%2 == 0 {
SetLevel(InfoLevel)
} else {
GetLevel()
}
}(i)
}
wg.Wait()
}
func TestLoggingRace(t *testing.T) {
logger := New()
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
logger.Info("info")
wg.Done()
}()
}
wg.Wait()
}
func TestLoggingRaceWithHooksOnEntry(t *testing.T) {
logger := New()
hook := new(ModifyHook)
logger.AddHook(hook)
entry := logger.WithField("context", "clue")
var (
wg sync.WaitGroup
mtx sync.Mutex
start bool
)
cond := sync.NewCond(&mtx)
wg.Add(100)
for i := 0; i < 50; i++ {
go func() {
cond.L.Lock()
for !start {
cond.Wait()
}
cond.L.Unlock()
for j := 0; j < 100; j++ {
entry.Info("info")
}
wg.Done()
}()
}
for i := 0; i < 50; i++ {
go func() {
cond.L.Lock()
for !start {
cond.Wait()
}
cond.L.Unlock()
for j := 0; j < 100; j++ {
entry.WithField("another field", "with some data").Info("info")
}
wg.Done()
}()
}
cond.L.Lock()
start = true
cond.L.Unlock()
cond.Broadcast()
wg.Wait()
}
func TestReplaceHooks(t *testing.T) {
old, cur := &TestHook{}, &TestHook{}
logger := New()
logger.SetOutput(ioutil.Discard)
logger.AddHook(old)
hooks := make(LevelHooks)
hooks.Add(cur)
replaced := logger.ReplaceHooks(hooks)
logger.Info("test")
assert.Equal(t, old.Fired, false)
assert.Equal(t, cur.Fired, true)
logger.ReplaceHooks(replaced)
logger.Info("test")
assert.Equal(t, old.Fired, true)
}
// Compile test
func TestLogrusInterfaces(t *testing.T) {
var buffer bytes.Buffer
// This verifies FieldLogger and Ext1FieldLogger work as designed.
// Please don't use them. Use Logger and Entry directly.
fn := func(xl Ext1FieldLogger) {
var l FieldLogger = xl
b := l.WithField("key", "value")
b.Debug("Test")
}
// test logger
logger := New()
logger.Out = &buffer
fn(logger)
// test Entry
e := logger.WithField("another", "value")
fn(e)
}
// Implements io.Writer using channels for synchronization, so we can wait on
// the Entry.Writer goroutine to write in a non-racey way. This does assume that
// there is a single call to Logger.Out for each message.
type channelWriter chan []byte
func (cw channelWriter) Write(p []byte) (int, error) {
cw <- p
return len(p), nil
}
func TestEntryWriter(t *testing.T) {
cw := channelWriter(make(chan []byte, 1))
log := New()
log.Out = cw
log.Formatter = new(JSONFormatter)
_, err := log.WithField("foo", "bar").WriterLevel(WarnLevel).Write([]byte("hello\n"))
if err != nil {
t.Error("unexecpted error", err)
}
bs := <-cw
var fields Fields
err = json.Unmarshal(bs, &fields)
assert.Nil(t, err)
assert.Equal(t, fields["foo"], "bar")
assert.Equal(t, fields["level"], "warning")
}
func TestLogLevelEnabled(t *testing.T) {
log := New()
log.SetLevel(PanicLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, false, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, false, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
log.SetLevel(FatalLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, false, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
log.SetLevel(ErrorLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
log.SetLevel(WarnLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
log.SetLevel(InfoLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
log.SetLevel(DebugLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, true, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
log.SetLevel(TraceLevel)
assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
assert.Equal(t, true, log.IsLevelEnabled(DebugLevel))
assert.Equal(t, true, log.IsLevelEnabled(TraceLevel))
}
func TestReportCallerOnTextFormatter(t *testing.T) {
l := New()
l.Formatter.(*TextFormatter).ForceColors = true
l.Formatter.(*TextFormatter).DisableColors = false
l.WithFields(Fields{"func": "func", "file": "file"}).Info("test")
l.Formatter.(*TextFormatter).ForceColors = false
l.Formatter.(*TextFormatter).DisableColors = true
l.WithFields(Fields{"func": "func", "file": "file"}).Info("test")
}
func TestSetReportCallerRace(t *testing.T) {
l := New()
l.Out = ioutil.Discard
l.SetReportCaller(true)
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
l.Error("Some Error")
wg.Done()
}()
}
wg.Wait()
}

View File

@ -0,0 +1,11 @@
// +build appengine
package logrus
import (
"io"
)
func checkIfTerminal(w io.Writer) bool {
return true
}

13
terminal_check_bsd.go Normal file
View File

@ -0,0 +1,13 @@
// +build darwin dragonfly freebsd netbsd openbsd
// +build !js
package logrus
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TIOCGETA
func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}

7
terminal_check_js.go Normal file
View File

@ -0,0 +1,7 @@
// +build js
package logrus
func isTerminal(fd int) bool {
return false
}

View File

@ -0,0 +1,11 @@
// +build js nacl plan9
package logrus
import (
"io"
)
func checkIfTerminal(w io.Writer) bool {
return false
}

View File

@ -0,0 +1,17 @@
// +build !appengine,!js,!windows,!nacl,!plan9
package logrus
import (
"io"
"os"
)
func checkIfTerminal(w io.Writer) bool {
switch v := w.(type) {
case *os.File:
return isTerminal(int(v.Fd()))
default:
return false
}
}

11
terminal_check_solaris.go Normal file
View File

@ -0,0 +1,11 @@
package logrus
import (
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
return err == nil
}

13
terminal_check_unix.go Normal file
View File

@ -0,0 +1,13 @@
// +build linux aix zos
// +build !js
package logrus
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TCGETS
func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}

27
terminal_check_windows.go Normal file
View File

@ -0,0 +1,27 @@
// +build !appengine,!js,windows
package logrus
import (
"io"
"os"
"golang.org/x/sys/windows"
)
func checkIfTerminal(w io.Writer) bool {
switch v := w.(type) {
case *os.File:
handle := windows.Handle(v.Fd())
var mode uint32
if err := windows.GetConsoleMode(handle, &mode); err != nil {
return false
}
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
if err := windows.SetConsoleMode(handle, mode); err != nil {
return false
}
return true
}
return false
}

View File

@ -1,12 +0,0 @@
// Based on ssh/terminal:
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logrus
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
type Termios syscall.Termios

View File

@ -1,20 +0,0 @@
/*
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
*/
package logrus
import (
"syscall"
)
const ioctlReadTermios = syscall.TIOCGETA
type Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]uint8
Ispeed uint32
Ospeed uint32
}

View File

@ -1,12 +0,0 @@
// Based on ssh/terminal:
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logrus
import "syscall"
const ioctlReadTermios = syscall.TCGETS
type Termios syscall.Termios

View File

@ -1,21 +0,0 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux,!appengine darwin freebsd
package logrus
import (
"syscall"
"unsafe"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
fd := syscall.Stdout
var termios Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}

View File

@ -1,27 +0,0 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package logrus
import (
"syscall"
"unsafe"
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
fd := syscall.Stdout
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}

View File

@ -3,68 +3,225 @@ package logrus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"regexp" "os"
"runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"sync"
"time" "time"
"unicode/utf8"
) )
const ( const (
nocolor = 0 red = 31
red = 31 yellow = 33
green = 32 blue = 36
yellow = 33 gray = 37
blue = 34
) )
var ( var baseTimestamp time.Time
baseTimestamp time.Time
isTerminal bool
noQuoteNeeded *regexp.Regexp
)
func init() { func init() {
baseTimestamp = time.Now() baseTimestamp = time.Now()
isTerminal = IsTerminal()
}
func miniTS() int {
return int(time.Since(baseTimestamp) / time.Second)
} }
// TextFormatter formats logs into text
type TextFormatter struct { type TextFormatter struct {
// Set to true to bypass checking for a TTY before outputting colors. // Set to true to bypass checking for a TTY before outputting colors.
ForceColors bool ForceColors bool
// Force disabling colors.
DisableColors bool DisableColors bool
// Set to true to disable timestamp logging (useful when the output
// is redirected to a logging system already adding a timestamp) // Force quoting of all values
ForceQuote bool
// DisableQuote disables quoting for all values.
// DisableQuote will have a lower priority than ForceQuote.
// If both of them are set to true, quote will be forced on all values.
DisableQuote bool
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
EnvironmentOverrideColors bool
// Disable timestamp logging. useful when output is redirected to logging
// system that already adds timestamps.
DisableTimestamp bool DisableTimestamp bool
// Enable logging the full timestamp when a TTY is attached instead of just
// the time passed since beginning of execution.
FullTimestamp bool
// TimestampFormat to use for display when a full timestamp is printed.
// The format to use is the same than for time.Format or time.Parse from the standard
// library.
// The standard Library already provides a set of predefined format.
TimestampFormat string
// The fields are sorted by default for a consistent output. For applications
// that log extremely frequently and don't use the JSON formatter this may not
// be desired.
DisableSorting bool
// The keys sorting function, when uninitialized it uses sort.Strings.
SortingFunc func([]string)
// Disables the truncation of the level text to 4 characters.
DisableLevelTruncation bool
// PadLevelText Adds padding the level text so that all the levels output at the same length
// PadLevelText is a superset of the DisableLevelTruncation option
PadLevelText bool
// QuoteEmptyFields will wrap empty fields in quotes if true
QuoteEmptyFields bool
// Whether the logger's out is to a terminal
isTerminal bool
// FieldMap allows users to customize the names of keys for default fields.
// As an example:
// formatter := &TextFormatter{
// FieldMap: FieldMap{
// FieldKeyTime: "@timestamp",
// FieldKeyLevel: "@level",
// FieldKeyMsg: "@message"}}
FieldMap FieldMap
// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from fields.
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) Format(entry *Entry) ([]byte, error) { 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
}
}
}
var keys []string func (f *TextFormatter) isColored() bool {
for k := range entry.Data { 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
}
}
return isColored && !f.DisableColors
}
// 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())
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys)
b := &bytes.Buffer{} var funcVal, fileVal string
prefixFieldClashes(entry.Data) fixedKeys := make([]string, 0, 4+len(data))
if !f.DisableTimestamp {
isColored := (f.ForceColors || isTerminal) && !f.DisableColors fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
}
if isColored { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
printColored(b, entry, keys) if entry.Message != "" {
} else { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
if !f.DisableTimestamp { }
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) if entry.err != "" {
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
}
if entry.HasCaller() {
if f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
} else {
funcVal = entry.Caller.Function
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
} }
f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "msg", entry.Message) if funcVal != "" {
for _, key := range keys { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
f.appendKeyValue(b, key, entry.Data[key]) }
if fileVal != "" {
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
}
}
if !f.DisableSorting {
if f.SortingFunc == nil {
sort.Strings(keys)
fixedKeys = append(fixedKeys, keys...)
} else {
if !f.isColored() {
fixedKeys = append(fixedKeys, keys...)
f.SortingFunc(fixedKeys)
} else {
f.SortingFunc(keys)
}
}
} 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]
}
f.appendKeyValue(b, key, value)
} }
} }
@ -72,53 +229,111 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
return b.Bytes(), nil return b.Bytes(), nil
} }
func printColored(b *bytes.Buffer, entry *Entry, keys []string) { func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
var levelColor int var levelColor int
switch entry.Level { switch entry.Level {
case DebugLevel, TraceLevel:
levelColor = gray
case WarnLevel: case WarnLevel:
levelColor = yellow levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel: case ErrorLevel, FatalLevel, PanicLevel:
levelColor = red levelColor = red
case InfoLevel:
levelColor = blue
default: default:
levelColor = blue levelColor = blue
} }
levelText := strings.ToUpper(entry.Level.String())[0:4] 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)
}
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) // 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)
if f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
}
if fileVal == "" {
caller = funcVal
} else if funcVal == "" {
caller = fileVal
} else {
caller = fileVal + " " + funcVal
}
}
switch {
case f.DisableTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, 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)
default:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
}
for _, k := range keys { for _, k := range keys {
v := entry.Data[k] v := data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
f.appendValue(b, v)
} }
} }
func needsQuoting(text string) bool { 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 { for _, ch := range text {
if !((ch >= 'a' && ch <= 'z') || if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') || (ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch < '9') || (ch >= '0' && ch <= '9') ||
ch == '-' || ch == '.') { ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
return false return true
} }
} }
return true return false
} }
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) { func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
switch value.(type) { if b.Len() > 0 {
case string: b.WriteByte(' ')
if needsQuoting(value.(string)) { }
fmt.Fprintf(b, "%v=%s ", key, value) b.WriteString(key)
} else { b.WriteByte('=')
fmt.Fprintf(b, "%v=%q ", key, value) f.appendValue(b, value)
} }
case error:
if needsQuoting(value.(error).Error()) { func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
fmt.Fprintf(b, "%v=%s ", key, value) stringVal, ok := value.(string)
} else { if !ok {
fmt.Fprintf(b, "%v=%q ", key, value) stringVal = fmt.Sprint(value)
} }
default:
fmt.Fprintf(b, "%v=%v ", key, value) if !f.needsQuoting(stringVal) {
b.WriteString(stringVal)
} else {
b.WriteString(fmt.Sprintf("%q", stringVal))
} }
} }

View File

@ -3,17 +3,44 @@ package logrus
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"os"
"runtime"
"sort"
"strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestFormatting(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
testCases := []struct {
value string
expected string
}{
{`foo`, "time=\"0001-01-01T00:00:00Z\" level=panic test=foo\n"},
}
for _, tc := range testCases {
b, _ := tf.Format(WithField("test", tc.value))
if string(b) != tc.expected {
t.Errorf("formatting expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
}
}
}
func TestQuoting(t *testing.T) { func TestQuoting(t *testing.T) {
tf := &TextFormatter{DisableColors: true} tf := &TextFormatter{DisableColors: true}
checkQuoting := func(q bool, value interface{}) { checkQuoting := func(q bool, value interface{}) {
b, _ := tf.Format(WithField("test", value)) b, _ := tf.Format(WithField("test", value))
idx := bytes.Index(b, ([]byte)("test=")) idx := bytes.Index(b, ([]byte)("test="))
cont := bytes.Contains(b[idx+5:], []byte{'"'}) cont := bytes.Contains(b[idx+5:], []byte("\""))
if cont != q { if cont != q {
if q { if q {
t.Errorf("quoting expected for: %#v", value) t.Errorf("quoting expected for: %#v", value)
@ -23,11 +50,551 @@ func TestQuoting(t *testing.T) {
} }
} }
checkQuoting(false, "")
checkQuoting(false, "abcd") checkQuoting(false, "abcd")
checkQuoting(false, "v1.0") checkQuoting(false, "v1.0")
checkQuoting(true, "/foobar") checkQuoting(false, "1234567890")
checkQuoting(false, "/foobar")
checkQuoting(false, "foo_bar")
checkQuoting(false, "foo@bar")
checkQuoting(false, "foobar^")
checkQuoting(false, "+/-_^@f.oobar")
checkQuoting(true, "foo\n\rbar")
checkQuoting(true, "foobar$")
checkQuoting(true, "&foobar")
checkQuoting(true, "x y") checkQuoting(true, "x y")
checkQuoting(true, "x,y") checkQuoting(true, "x,y")
checkQuoting(false, errors.New("invalid")) checkQuoting(false, errors.New("invalid"))
checkQuoting(true, errors.New("invalid argument")) checkQuoting(true, errors.New("invalid argument"))
// Test for quoting empty fields.
tf.QuoteEmptyFields = true
checkQuoting(true, "")
checkQuoting(false, "abcd")
checkQuoting(true, "foo\n\rbar")
checkQuoting(true, errors.New("invalid argument"))
// Test forcing quotes.
tf.ForceQuote = true
checkQuoting(true, "")
checkQuoting(true, "abcd")
checkQuoting(true, "foo\n\rbar")
checkQuoting(true, errors.New("invalid argument"))
// Test forcing quotes when also disabling them.
tf.DisableQuote = true
checkQuoting(true, "")
checkQuoting(true, "abcd")
checkQuoting(true, "foo\n\rbar")
checkQuoting(true, errors.New("invalid argument"))
// Test disabling quotes
tf.ForceQuote = false
tf.QuoteEmptyFields = false
checkQuoting(false, "")
checkQuoting(false, "abcd")
checkQuoting(false, "foo\n\rbar")
checkQuoting(false, errors.New("invalid argument"))
}
func TestEscaping(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
testCases := []struct {
value string
expected string
}{
{`ba"r`, `ba\"r`},
{`ba'r`, `ba'r`},
}
for _, tc := range testCases {
b, _ := tf.Format(WithField("test", tc.value))
if !bytes.Contains(b, []byte(tc.expected)) {
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
}
}
}
func TestEscaping_Interface(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
ts := time.Now()
testCases := []struct {
value interface{}
expected string
}{
{ts, fmt.Sprintf("\"%s\"", ts.String())},
{errors.New("error: something went wrong"), "\"error: something went wrong\""},
}
for _, tc := range testCases {
b, _ := tf.Format(WithField("test", tc.value))
if !bytes.Contains(b, []byte(tc.expected)) {
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
}
}
}
func TestTimestampFormat(t *testing.T) {
checkTimeStr := func(format string) {
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
customStr, _ := customFormatter.Format(WithField("test", "test"))
timeStart := bytes.Index(customStr, ([]byte)("time="))
timeEnd := bytes.Index(customStr, ([]byte)("level="))
timeStr := customStr[timeStart+5+len("\"") : timeEnd-1-len("\"")]
if format == "" {
format = time.RFC3339
}
_, e := time.Parse(format, (string)(timeStr))
if e != nil {
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
}
}
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
checkTimeStr("Mon Jan _2 15:04:05 2006")
checkTimeStr("")
}
func TestDisableLevelTruncation(t *testing.T) {
entry := &Entry{
Time: time.Now(),
Message: "testing",
}
keys := []string{}
timestampFormat := "Mon Jan 2 15:04:05 -0700 MST 2006"
checkDisableTruncation := func(disabled bool, level Level) {
tf := &TextFormatter{DisableLevelTruncation: disabled}
var b bytes.Buffer
entry.Level = level
tf.printColored(&b, entry, keys, nil, timestampFormat)
logLine := (&b).String()
if disabled {
expected := strings.ToUpper(level.String())
if !strings.Contains(logLine, expected) {
t.Errorf("level string expected to be %s when truncation disabled", expected)
}
} else {
expected := strings.ToUpper(level.String())
if len(level.String()) > 4 {
if strings.Contains(logLine, expected) {
t.Errorf("level string %s expected to be truncated to %s when truncation is enabled", expected, expected[0:4])
}
} else {
if !strings.Contains(logLine, expected) {
t.Errorf("level string expected to be %s when truncation is enabled and level string is below truncation threshold", expected)
}
}
}
}
checkDisableTruncation(true, DebugLevel)
checkDisableTruncation(true, InfoLevel)
checkDisableTruncation(false, ErrorLevel)
checkDisableTruncation(false, InfoLevel)
}
func TestPadLevelText(t *testing.T) {
// A note for future maintainers / committers:
//
// This test denormalizes the level text as a part of its assertions.
// Because of that, its not really a "unit test" of the PadLevelText functionality.
// So! Many apologies to the potential future person who has to rewrite this test
// when they are changing some completely unrelated functionality.
params := []struct {
name string
level Level
paddedLevelText string
}{
{
name: "PanicLevel",
level: PanicLevel,
paddedLevelText: "PANIC ", // 2 extra spaces
},
{
name: "FatalLevel",
level: FatalLevel,
paddedLevelText: "FATAL ", // 2 extra spaces
},
{
name: "ErrorLevel",
level: ErrorLevel,
paddedLevelText: "ERROR ", // 2 extra spaces
},
{
name: "WarnLevel",
level: WarnLevel,
// WARNING is already the max length, so we don't need to assert a paddedLevelText
},
{
name: "DebugLevel",
level: DebugLevel,
paddedLevelText: "DEBUG ", // 2 extra spaces
},
{
name: "TraceLevel",
level: TraceLevel,
paddedLevelText: "TRACE ", // 2 extra spaces
},
{
name: "InfoLevel",
level: InfoLevel,
paddedLevelText: "INFO ", // 3 extra spaces
},
}
// We create a "default" TextFormatter to do a control test.
// We also create a TextFormatter with PadLevelText, which is the parameter we want to do our most relevant assertions against.
tfDefault := TextFormatter{}
tfWithPadding := TextFormatter{PadLevelText: true}
for _, val := range params {
t.Run(val.name, func(t *testing.T) {
// TextFormatter writes into these bytes.Buffers, and we make assertions about their contents later
var bytesDefault bytes.Buffer
var bytesWithPadding bytes.Buffer
// The TextFormatter instance and the bytes.Buffer instance are different here
// all the other arguments are the same. We also initialize them so that they
// fill in the value of levelTextMaxLength.
tfDefault.init(&Entry{})
tfDefault.printColored(&bytesDefault, &Entry{Level: val.level}, []string{}, nil, "")
tfWithPadding.init(&Entry{})
tfWithPadding.printColored(&bytesWithPadding, &Entry{Level: val.level}, []string{}, nil, "")
// turn the bytes back into a string so that we can actually work with the data
logLineDefault := (&bytesDefault).String()
logLineWithPadding := (&bytesWithPadding).String()
// Control: the level text should not be padded by default
if val.paddedLevelText != "" && strings.Contains(logLineDefault, val.paddedLevelText) {
t.Errorf("log line %q should not contain the padded level text %q by default", logLineDefault, val.paddedLevelText)
}
// Assertion: the level text should still contain the string representation of the level
if !strings.Contains(strings.ToLower(logLineWithPadding), val.level.String()) {
t.Errorf("log line %q should contain the level text %q when padding is enabled", logLineWithPadding, val.level.String())
}
// Assertion: the level text should be in its padded form now
if val.paddedLevelText != "" && !strings.Contains(logLineWithPadding, val.paddedLevelText) {
t.Errorf("log line %q should contain the padded level text %q when padding is enabled", logLineWithPadding, val.paddedLevelText)
}
})
}
}
func TestDisableTimestampWithColoredOutput(t *testing.T) {
tf := &TextFormatter{DisableTimestamp: true, ForceColors: true}
b, _ := tf.Format(WithField("test", "test"))
if strings.Contains(string(b), "[0000]") {
t.Error("timestamp not expected when DisableTimestamp is true")
}
}
func TestNewlineBehavior(t *testing.T) {
tf := &TextFormatter{ForceColors: true}
// Ensure a single new line is removed as per stdlib log
e := NewEntry(StandardLogger())
e.Message = "test message\n"
b, _ := tf.Format(e)
if bytes.Contains(b, []byte("test message\n")) {
t.Error("first newline at end of Entry.Message resulted in unexpected 2 newlines in output. Expected newline to be removed.")
}
// Ensure a double new line is reduced to a single new line
e = NewEntry(StandardLogger())
e.Message = "test message\n\n"
b, _ = tf.Format(e)
if bytes.Contains(b, []byte("test message\n\n")) {
t.Error("Double newline at end of Entry.Message resulted in unexpected 2 newlines in output. Expected single newline")
}
if !bytes.Contains(b, []byte("test message\n")) {
t.Error("Double newline at end of Entry.Message did not result in a single newline after formatting")
}
}
func TestTextFormatterFieldMap(t *testing.T) {
formatter := &TextFormatter{
DisableColors: true,
FieldMap: FieldMap{
FieldKeyMsg: "message",
FieldKeyLevel: "somelevel",
FieldKeyTime: "timeywimey",
},
}
entry := &Entry{
Message: "oh hi",
Level: WarnLevel,
Time: time.Date(1981, time.February, 24, 4, 28, 3, 100, time.UTC),
Data: Fields{
"field1": "f1",
"message": "messagefield",
"somelevel": "levelfield",
"timeywimey": "timeywimeyfield",
},
}
b, err := formatter.Format(entry)
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
assert.Equal(t,
`timeywimey="1981-02-24T04:28:03Z" `+
`somelevel=warning `+
`message="oh hi" `+
`field1=f1 `+
`fields.message=messagefield `+
`fields.somelevel=levelfield `+
`fields.timeywimey=timeywimeyfield`+"\n",
string(b),
"Formatted output doesn't respect FieldMap")
}
func TestTextFormatterIsColored(t *testing.T) {
params := []struct {
name string
expectedResult bool
isTerminal bool
disableColor bool
forceColor bool
envColor bool
clicolorIsSet bool
clicolorForceIsSet bool
clicolorVal string
clicolorForceVal string
}{
// Default values
{
name: "testcase1",
expectedResult: false,
isTerminal: false,
disableColor: false,
forceColor: false,
envColor: false,
clicolorIsSet: false,
clicolorForceIsSet: false,
},
// Output on terminal
{
name: "testcase2",
expectedResult: true,
isTerminal: true,
disableColor: false,
forceColor: false,
envColor: false,
clicolorIsSet: false,
clicolorForceIsSet: false,
},
// Output on terminal with color disabled
{
name: "testcase3",
expectedResult: false,
isTerminal: true,
disableColor: true,
forceColor: false,
envColor: false,
clicolorIsSet: false,
clicolorForceIsSet: false,
},
// Output not on terminal with color disabled
{
name: "testcase4",
expectedResult: false,
isTerminal: false,
disableColor: true,
forceColor: false,
envColor: false,
clicolorIsSet: false,
clicolorForceIsSet: false,
},
// Output not on terminal with color forced
{
name: "testcase5",
expectedResult: true,
isTerminal: false,
disableColor: false,
forceColor: true,
envColor: false,
clicolorIsSet: false,
clicolorForceIsSet: false,
},
// Output on terminal with clicolor set to "0"
{
name: "testcase6",
expectedResult: false,
isTerminal: true,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: true,
clicolorForceIsSet: false,
clicolorVal: "0",
},
// Output on terminal with clicolor set to "1"
{
name: "testcase7",
expectedResult: true,
isTerminal: true,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: true,
clicolorForceIsSet: false,
clicolorVal: "1",
},
// Output not on terminal with clicolor set to "0"
{
name: "testcase8",
expectedResult: false,
isTerminal: false,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: true,
clicolorForceIsSet: false,
clicolorVal: "0",
},
// Output not on terminal with clicolor set to "1"
{
name: "testcase9",
expectedResult: false,
isTerminal: false,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: true,
clicolorForceIsSet: false,
clicolorVal: "1",
},
// Output not on terminal with clicolor set to "1" and force color
{
name: "testcase10",
expectedResult: true,
isTerminal: false,
disableColor: false,
forceColor: true,
envColor: true,
clicolorIsSet: true,
clicolorForceIsSet: false,
clicolorVal: "1",
},
// Output not on terminal with clicolor set to "0" and force color
{
name: "testcase11",
expectedResult: false,
isTerminal: false,
disableColor: false,
forceColor: true,
envColor: true,
clicolorIsSet: true,
clicolorForceIsSet: false,
clicolorVal: "0",
},
// Output not on terminal with clicolor_force set to "1"
{
name: "testcase12",
expectedResult: true,
isTerminal: false,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: false,
clicolorForceIsSet: true,
clicolorForceVal: "1",
},
// Output not on terminal with clicolor_force set to "0"
{
name: "testcase13",
expectedResult: false,
isTerminal: false,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: false,
clicolorForceIsSet: true,
clicolorForceVal: "0",
},
// Output on terminal with clicolor_force set to "0"
{
name: "testcase14",
expectedResult: false,
isTerminal: true,
disableColor: false,
forceColor: false,
envColor: true,
clicolorIsSet: false,
clicolorForceIsSet: true,
clicolorForceVal: "0",
},
}
cleanenv := func() {
os.Unsetenv("CLICOLOR")
os.Unsetenv("CLICOLOR_FORCE")
}
defer cleanenv()
for _, val := range params {
t.Run("textformatter_"+val.name, func(subT *testing.T) {
tf := TextFormatter{
isTerminal: val.isTerminal,
DisableColors: val.disableColor,
ForceColors: val.forceColor,
EnvironmentOverrideColors: val.envColor,
}
cleanenv()
if val.clicolorIsSet {
os.Setenv("CLICOLOR", val.clicolorVal)
}
if val.clicolorForceIsSet {
os.Setenv("CLICOLOR_FORCE", val.clicolorForceVal)
}
res := tf.isColored()
if runtime.GOOS == "windows" && !tf.ForceColors && !val.clicolorForceIsSet {
assert.Equal(subT, false, res)
} else {
assert.Equal(subT, val.expectedResult, res)
}
})
}
}
func TestCustomSorting(t *testing.T) {
formatter := &TextFormatter{
DisableColors: true,
SortingFunc: func(keys []string) {
sort.Slice(keys, func(i, j int) bool {
if keys[j] == "prefix" {
return false
}
if keys[i] == "prefix" {
return true
}
return strings.Compare(keys[i], keys[j]) == -1
})
},
}
entry := &Entry{
Message: "Testing custom sort function",
Time: time.Now(),
Level: InfoLevel,
Data: Fields{
"test": "testvalue",
"prefix": "the application prefix",
"blablabla": "blablabla",
},
}
b, err := formatter.Format(entry)
require.NoError(t, err)
require.True(t, strings.HasPrefix(string(b), "prefix="), "format output is %q", string(b))
} }

5
travis/cross_build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.13\. ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$GO111MODULE" == "on" ]]; then
$(go env GOPATH)/bin/gox -build-lib
fi

8
travis/install.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
# Install golanci 1.32.2
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.15\. ]]; then
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.32.2
fi

70
writer.go Normal file
View File

@ -0,0 +1,70 @@
package logrus
import (
"bufio"
"io"
"runtime"
)
// Writer at INFO level. See WriterLevel for details.
func (logger *Logger) Writer() *io.PipeWriter {
return logger.WriterLevel(InfoLevel)
}
// WriterLevel returns an io.Writer that can be used to write arbitrary text to
// the logger at the given log level. Each line written to the writer will be
// printed in the usual way using formatters and hooks. The writer is part of an
// io.Pipe and it is the callers responsibility to close the writer when done.
// This can be used to override the standard library logger easily.
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
return NewEntry(logger).WriterLevel(level)
}
func (entry *Entry) Writer() *io.PipeWriter {
return entry.WriterLevel(InfoLevel)
}
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
reader, writer := io.Pipe()
var printFunc func(args ...interface{})
switch level {
case TraceLevel:
printFunc = entry.Trace
case DebugLevel:
printFunc = entry.Debug
case InfoLevel:
printFunc = entry.Info
case WarnLevel:
printFunc = entry.Warn
case ErrorLevel:
printFunc = entry.Error
case FatalLevel:
printFunc = entry.Fatal
case PanicLevel:
printFunc = entry.Panic
default:
printFunc = entry.Print
}
go entry.writerScanner(reader, printFunc)
runtime.SetFinalizer(writer, writerFinalizer)
return writer
}
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
printFunc(scanner.Text())
}
if err := scanner.Err(); err != nil {
entry.Errorf("Error while reading from Writer: %s", err)
}
reader.Close()
}
func writerFinalizer(writer *io.PipeWriter) {
writer.Close()
}

34
writer_test.go Normal file
View File

@ -0,0 +1,34 @@
package logrus_test
import (
"log"
"net/http"
"git.internal/re/logrus"
)
func ExampleLogger_Writer_httpServer() {
logger := logrus.New()
w := logger.Writer()
defer w.Close()
srv := http.Server{
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog: log.New(w, "", 0),
}
if err := srv.ListenAndServe(); err != nil {
logger.Fatal(err)
}
}
func ExampleLogger_Writer_stdlib() {
logger := logrus.New()
logger.Formatter = &logrus.JSONFormatter{}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log.SetOutput(logger.Writer())
}