forked from mirror/logrus
Compare commits
161 Commits
Author | SHA1 | Date |
---|---|---|
re | 22f067d83b | |
re | b06ab7d9aa | |
re | e451092dba | |
David Bariod | f8bf7650dc | |
David Bariod | ebc9029252 | |
Simon Eskildsen | 56c843c73d | |
izhakmo | 41b4ee686d | |
David Bariod | f98ed3eb76 | |
Nathan Johnson | 2b8f60a012 | |
Nathan Johnson | 0db10ef84a | |
Simon Eskildsen | 85981c0459 | |
David Bariod | 79c5ab66aa | |
David Bariod | 5f8c666a13 | |
David Bariod | 5418b6e7a4 | |
David Bariod | 25e89b7d23 | |
David Bariod | f25cd754cf | |
David Bariod | 51f2599bdd | |
David Bariod | accc7da667 | |
anajavi | 0926db15e5 | |
David Bariod | 22d63b740b | |
David Bariod | 526e535580 | |
David Bariod | b53d94c8ad | |
David Bariod | de2d2027ff | |
David Bariod | dff9872c76 | |
anajavi | 15b98b1d72 | |
heui | f5f6a033d3 | |
Ruben de Vries | 78f838918d | |
David Bariod | b50299cfaa | |
Qingshan Luo | 1818363d79 | |
David Bariod | fdf1618bf7 | |
Billy Zaelani Malik | b1c1cea8f6 | |
David Bariod | bde44a27f3 | |
Sebastiaan van Stijn | 9b555f4fd7 | |
David Bariod | fe9e9fcbba | |
David Bariod | bdc0db8ead | |
David Bariod | 1bfef4b986 | |
David Bariod | 7a997b9285 | |
Sebastiaan van Stijn | cbd14ede4d | |
Sebastiaan van Stijn | bdb7d4c531 | |
David Bariod | f104497f2b | |
Sebastiaan van Stijn | 1d8091a7e9 | |
Sebastiaan van Stijn | feebf74e97 | |
David Bariod | 6cff360233 | |
David Bariod | d172886045 | |
David Bariod | d59e5619da | |
David Bariod | 35ab8d8fef | |
David Bariod | 5cb4bf65c6 | |
David Bariod | 15ca3c0694 | |
David Bariod | 67c117ceb0 | |
David Bariod | 88d56b69b5 | |
David Bariod | ac6e35b4c2 | |
David Bariod | f513f99c15 | |
David Bariod | 003c63ac69 | |
David Bariod | 6a61186a64 | |
David Bariod | c6da0523dd | |
David Bariod | 3986c92379 | |
David Bariod | cd4bf4ef8d | |
David Bariod | 44d983dbc2 | |
David Bariod | b02b418f8f | |
Alec Benzer | 02fcb16005 | |
David Bariod | 4b818a50d4 | |
David Bariod | 8ae478eb8a | |
David Bariod | 6121f5c019 | |
David Bariod | e3e79b6306 | |
David Bariod | a752a62f5e | |
l-lindsay | be16a81096 | |
David Bariod | 89b92b94dd | |
David Bariod | e328a4e3f4 | |
David Bariod | 581900062e | |
David Bariod | 0d28e29335 | |
CreativeCactus | c81a54c5aa | |
David Bariod | d131c24e23 | |
Ichinose Shogo | c6b865f1d2 | |
David Bariod | 6699a89a23 | |
David Bariod | 64a59449f3 | |
David Bariod | 42baed85eb | |
Sohel | 630ea450e6 | |
David Bariod | 20dcf91051 | |
David Bariod | ba4da53cff | |
David Bariod | d7edea4451 | |
David Bariod | e8fa988410 | |
David Bariod | 0de04f1584 | |
Tobias Klauser | 86657918d4 | |
David Bariod | 60c74ad9be | |
David Bariod | e8e563a823 | |
David Bariod | 0fd458a22e | |
David Bariod | 4d96c600d9 | |
David Bariod | a5b02471f8 | |
David Bariod | 163c051d4a | |
David Bariod | e79215d3d5 | |
ialidzhikov | 4989a3fd5d | |
Thomas Lacroix | aff00feb0a | |
Thomas Lacroix | c7455de10a | |
Mark Phelps | 91ef3ab5d5 | |
Mark Phelps | 03155c5499 | |
Ariel Simulevski | 7d248fa1b1 | |
Mark Phelps | d417be0fe6 | |
Mark Phelps | 32fd107816 | |
Mark Phelps | ddb57a2a54 | |
Mark Phelps | a635f0489d | |
Mark Phelps | 4ddc9cf62e | |
Alisdair MacLeod | e3e40605a2 | |
Alisdair MacLeod | ba670baee1 | |
Alisdair MacLeod | b28acda22d | |
Alisdair MacLeod | e76a5c4450 | |
David Raleigh | 0fb945b034 | |
Deep Datta | 0882384258 | |
Fabrizio Cirelli | fa25593b15 | |
Fabrizio Cirelli | af6ac8cee6 | |
David Bariod | 7ea96a3284 | |
Mark Phelps | 53000c4c0f | |
Mark Phelps | 334dd7729c | |
Mark Phelps | 1e936e2d75 | |
David Bariod | ab4d0e6ead | |
Alex S | 86a84a9d18 | |
Mark Phelps | 77ab282a06 | |
Mark Phelps | 7fab003954 | |
Mark Phelps | 494ec951d1 | |
Sébastien Lavoie | 24566a3fc4 | |
Mark Phelps | f5d95b63a6 | |
Mark Phelps | eef122e96e | |
Mark Phelps | 2f069ddd45 | |
Mark Phelps | 63d9911443 | |
Simon Eskildsen | 4ecd9a62bd | |
Simon Eskildsen | 947831125f | |
Mikolaj Holysz | 6895a36b17 | |
nolleh | 779e0e214d | |
Alex Shi | b70d15e202 | |
Dmitri Goutnik | 28e212178a | |
Taylor Wrobel | 8fbaf3dbd0 | |
Taylor Wrobel | bcc146f96b | |
David Bariod | 4fd274e0b8 | |
David Bariod | 67a7fdcf74 | |
David Bariod | 9746113fa8 | |
David Bariod | f4ece9c82f | |
David Bariod | 12176f2f72 | |
David Bariod | 9df6f6aa0b | |
David Bariod | 88d44306be | |
David Bariod | b77b626665 | |
lwsanty | c7278b2d7a | |
Edward Muller | d5d4df1108 | |
Edward Muller | b6a9e5632b | |
Edward Muller | 007cacdd34 | |
Edward Muller | fb62dbe2f2 | |
Edward Muller | 843e0aaa75 | |
Edward Muller | 68e6dbbcb7 | |
Edward Muller | 08cf62cb80 | |
Edward Muller | ad9f41a0cd | |
Edward Muller | 75440f2ebe | |
Edward Muller | 8ec9a493ec | |
Edward Muller | d30efdb30d | |
Edward Muller | 7b6c0d11ad | |
Edward Muller | 60320cbc2c | |
tbunyk | 8b0b8a88f2 | |
tbunyk | c88f8de1fe | |
tbunyk | 470f2e08fc | |
tbunyk | d8e3add56f | |
Kirill Motkov | 1487633cc3 | |
Albert Nigmatzianov | 6137e6b13d | |
Albert Nigmatzianov | e59e5eaa92 | |
Albert Nigmatzianov | 1fb8c53680 |
|
@ -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 ./...
|
|
@ -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 }}
|
|
@ -1,2 +1,4 @@
|
||||||
logrus
|
logrus
|
||||||
vendor
|
vendor
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
|
@ -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
|
24
.travis.yml
24
.travis.yml
|
@ -1,25 +1,15 @@
|
||||||
language: go
|
language: go
|
||||||
go_import_path: github.com/sirupsen/logrus
|
go_import_path: git.internal/re/logrus
|
||||||
git:
|
git:
|
||||||
depth: 1
|
depth: 1
|
||||||
env:
|
env:
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
- GO111MODULE=off
|
go: 1.15.x
|
||||||
go: [ 1.11.x, 1.12.x, 1.13.x ]
|
os: linux
|
||||||
os: [ linux, osx ]
|
|
||||||
matrix:
|
|
||||||
exclude:
|
|
||||||
- go: 1.12.x
|
|
||||||
env: GO111MODULE=off
|
|
||||||
- go: 1.11.x
|
|
||||||
os: osx
|
|
||||||
install:
|
install:
|
||||||
- ./travis/install.sh
|
- ./travis/install.sh
|
||||||
- if [[ "$GO111MODULE" == "on" ]]; then go mod download; fi
|
|
||||||
- if [[ "$GO111MODULE" == "off" ]]; then go get github.com/stretchr/testify/assert golang.org/x/sys/unix github.com/konsorten/go-windows-terminal-sequences; fi
|
|
||||||
script:
|
script:
|
||||||
- ./travis/cross_build.sh
|
- cd ci
|
||||||
- export GOMAXPROCS=4
|
- go run mage.go -v -w ../ crossBuild
|
||||||
- export GORACE=halt_on_error=1
|
- go run mage.go -v -w ../ lint
|
||||||
- go test -race -v ./...
|
- go run mage.go -v -w ../ test
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi
|
|
||||||
|
|
63
CHANGELOG.md
63
CHANGELOG.md
|
@ -1,9 +1,68 @@
|
||||||
|
# 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
|
# 1.4.2
|
||||||
* Fixes build break for plan9, nacl, solaris
|
* Fixes build break for plan9, nacl, solaris
|
||||||
# 1.4.1
|
# 1.4.1
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||||
|
@ -11,7 +70,7 @@ Fixes:
|
||||||
# 1.4.0
|
# 1.4.0
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
* 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 `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).
|
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
61
README.md
61
README.md
|
@ -1,19 +1,39 @@
|
||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![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.
|
the standard library logger.
|
||||||
|
|
||||||
|
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
||||||
|
simply too hard to do in a way that won't break many people's projects, which is
|
||||||
|
the last thing you want from your Logging library (again...).
|
||||||
|
|
||||||
|
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
||||||
|
security, (backwards compatible) bug fixes, and performance (where we are
|
||||||
|
limited by the interface).
|
||||||
|
|
||||||
|
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
|
**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,
|
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
|
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.
|
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||||
Everything using `logrus` will need to use the lower-case:
|
Everything using `logrus` will need to use the lower-case:
|
||||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
`git.internal/re/logrus`. Any package that isn't, should be changed.
|
||||||
|
|
||||||
To fix Glide, see [these
|
To fix Glide, see [these
|
||||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
comments](https://git.internal/re/logrus/issues/553#issuecomment-306591437).
|
||||||
For an in-depth explanation of the casing issue, see [this
|
For an in-depth explanation of the casing issue, see [this
|
||||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
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):
|
||||||
|
@ -89,7 +109,7 @@ go test -bench=.*CallerTracing
|
||||||
|
|
||||||
The organization's name was changed to lower-case--and this will not be changed
|
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
|
back. If you are getting import conflicts due to case sensitivity, please use
|
||||||
the lower-case import: `github.com/sirupsen/logrus`.
|
the lower-case import: `git.internal/re/logrus`.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
@ -99,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() {
|
||||||
|
@ -110,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:
|
||||||
|
|
||||||
|
@ -119,7 +139,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
log "github.com/sirupsen/logrus"
|
log "git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -170,7 +190,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"github.com/sirupsen/logrus"
|
"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.
|
||||||
|
@ -245,9 +265,9 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "git.internal/re/logrus"
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
||||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
logrus_syslog "git.internal/re/logrus/hooks/syslog"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -267,7 +287,7 @@ 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).
|
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).
|
||||||
|
|
||||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
A list of currently known service hooks can be found in this wiki [page](https://git.internal/re/logrus/wiki/Hooks)
|
||||||
|
|
||||||
|
|
||||||
#### Level logging
|
#### Level logging
|
||||||
|
@ -318,10 +338,10 @@ 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" {
|
||||||
|
@ -350,9 +370,9 @@ The built-in logging formatters are:
|
||||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||||
truncation set the `DisableLevelTruncation` field to `true`.
|
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.
|
* 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/github.com/sirupsen/logrus#TextFormatter).
|
* 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/github.com/sirupsen/logrus#JSONFormatter).
|
* All options are listed in the [generated docs](https://godoc.org/git.internal/re/logrus#JSONFormatter).
|
||||||
|
|
||||||
Third party logging formatters:
|
Third party logging formatters:
|
||||||
|
|
||||||
|
@ -360,9 +380,10 @@ Third party logging formatters:
|
||||||
* [`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).
|
* [`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.
|
* [`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.
|
* [`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 P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
* [`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.
|
* [`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.
|
* [`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
|
||||||
|
@ -381,7 +402,7 @@ func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
@ -439,8 +460,8 @@ Logrus has a built in facility for asserting the presence of log messages. This
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import(
|
import(
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
"git.internal/re/logrus/hooks/test"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
28
appveyor.yml
28
appveyor.yml
|
@ -1,14 +1,14 @@
|
||||||
version: "{build}"
|
version: "{build}"
|
||||||
platform: x64
|
platform: x64
|
||||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||||
environment:
|
environment:
|
||||||
GOPATH: c:\gopath
|
GOPATH: c:\gopath
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
install:
|
install:
|
||||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
- go version
|
- go version
|
||||||
build_script:
|
build_script:
|
||||||
- go get -t
|
- go get -t
|
||||||
- go test
|
- go test
|
||||||
|
|
|
@ -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)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module git.internal/re/logrus/ci
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require github.com/magefile/mage v1.11.0
|
|
@ -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=
|
|
@ -0,0 +1,10 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/magefile/mage/mage"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() { os.Exit(mage.Main()) }
|
|
@ -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", "./...")
|
||||||
|
}
|
28
doc.go
28
doc.go
|
@ -1,26 +1,26 @@
|
||||||
/*
|
/*
|
||||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
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:
|
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() {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"animal": "walrus",
|
"animal": "walrus",
|
||||||
"number": 1,
|
"number": 1,
|
||||||
"size": 10,
|
"size": 10,
|
||||||
}).Info("A walrus appears")
|
}).Info("A walrus appears")
|
||||||
}
|
}
|
||||||
|
|
||||||
Output:
|
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://github.com/sirupsen/logrus
|
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
|
package logrus
|
||||||
|
|
135
entry.go
135
entry.go
|
@ -13,7 +13,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bufferPool *sync.Pool
|
|
||||||
|
|
||||||
// qualified package name, cached at first use
|
// qualified package name, cached at first use
|
||||||
logrusPackage string
|
logrusPackage string
|
||||||
|
@ -31,12 +30,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
bufferPool = &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// start at the bottom of the stack before the package-name cache is primed
|
// start at the bottom of the stack before the package-name cache is primed
|
||||||
minimumCallerDepth = 1
|
minimumCallerDepth = 1
|
||||||
}
|
}
|
||||||
|
@ -85,10 +78,23 @@ func NewEntry(logger *Logger) *Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Dup() *Entry {
|
||||||
|
data := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the bytes representation of this entry from the formatter.
|
||||||
|
func (entry *Entry) Bytes() ([]byte, error) {
|
||||||
|
return entry.Logger.Formatter.Format(entry)
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
serialized, err := entry.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -103,7 +109,11 @@ func (entry *Entry) WithError(err error) *Entry {
|
||||||
|
|
||||||
// Add a context to the Entry.
|
// Add a context to the Entry.
|
||||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
|
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.
|
||||||
|
@ -121,11 +131,9 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
for k, v := range fields {
|
for k, v := range fields {
|
||||||
isErrField := false
|
isErrField := false
|
||||||
if t := reflect.TypeOf(v); t != nil {
|
if t := reflect.TypeOf(v); t != nil {
|
||||||
switch t.Kind() {
|
switch {
|
||||||
case reflect.Func:
|
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
||||||
isErrField = true
|
isErrField = true
|
||||||
case reflect.Ptr:
|
|
||||||
isErrField = t.Elem().Kind() == reflect.Func
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isErrField {
|
if isErrField {
|
||||||
|
@ -144,7 +152,11 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
|
|
||||||
// Overrides the time of the Entry.
|
// Overrides the time of the Entry.
|
||||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
|
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
|
// getPackageName reduces a fully qualified function name to the package name
|
||||||
|
@ -165,15 +177,20 @@ func getPackageName(f string) string {
|
||||||
|
|
||||||
// getCaller retrieves the name of the first non-logrus calling function
|
// getCaller retrieves the name of the first non-logrus calling function
|
||||||
func getCaller() *runtime.Frame {
|
func getCaller() *runtime.Frame {
|
||||||
|
|
||||||
// cache this package's fully-qualified name
|
// cache this package's fully-qualified name
|
||||||
callerInitOnce.Do(func() {
|
callerInitOnce.Do(func() {
|
||||||
pcs := make([]uintptr, 2)
|
pcs := make([]uintptr, maximumCallerDepth)
|
||||||
_ = runtime.Callers(0, pcs)
|
_ = runtime.Callers(0, pcs)
|
||||||
logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
|
|
||||||
|
|
||||||
// now that we have the cache, we can skip a minimum count of known-logrus functions
|
// dynamic get the package name and the minimum caller depth
|
||||||
// XXX this is dubious, the number of frames may vary
|
for i := 0; i < maximumCallerDepth; i++ {
|
||||||
|
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||||
|
if strings.Contains(funcName, "getCaller") {
|
||||||
|
logrusPackage = getPackageName(funcName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
minimumCallerDepth = knownLogrusFrames
|
minimumCallerDepth = knownLogrusFrames
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -187,7 +204,7 @@ func getCaller() *runtime.Frame {
|
||||||
|
|
||||||
// If the caller isn't part of this package, we're done
|
// If the caller isn't part of this package, we're done
|
||||||
if pkg != logrusPackage {
|
if pkg != logrusPackage {
|
||||||
return &f
|
return &f //nolint:scopelint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,49 +218,66 @@ func (entry Entry) HasCaller() (has bool) {
|
||||||
entry.Caller != nil
|
entry.Caller != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is not declared with a pointer value because otherwise
|
func (entry *Entry) log(level Level, msg string) {
|
||||||
// race conditions will occur when using multiple goroutines
|
|
||||||
func (entry Entry) log(level Level, msg string) {
|
|
||||||
var buffer *bytes.Buffer
|
var buffer *bytes.Buffer
|
||||||
|
|
||||||
// Default to now, but allow users to override if they want.
|
newEntry := entry.Dup()
|
||||||
//
|
|
||||||
// We don't have to worry about polluting future calls to Entry#log()
|
if newEntry.Time.IsZero() {
|
||||||
// with this assignment because this function is declared with a
|
newEntry.Time = time.Now()
|
||||||
// non-pointer receiver.
|
|
||||||
if entry.Time.IsZero() {
|
|
||||||
entry.Time = time.Now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Level = level
|
newEntry.Level = level
|
||||||
entry.Message = msg
|
newEntry.Message = msg
|
||||||
if entry.Logger.ReportCaller {
|
|
||||||
entry.Caller = getCaller()
|
newEntry.Logger.mu.Lock()
|
||||||
|
reportCaller := newEntry.Logger.ReportCaller
|
||||||
|
bufPool := newEntry.getBufferPool()
|
||||||
|
newEntry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
if reportCaller {
|
||||||
|
newEntry.Caller = getCaller()
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.fireHooks()
|
newEntry.fireHooks()
|
||||||
|
buffer = bufPool.Get()
|
||||||
buffer = bufferPool.Get().(*bytes.Buffer)
|
defer func() {
|
||||||
|
newEntry.Buffer = nil
|
||||||
|
buffer.Reset()
|
||||||
|
bufPool.Put(buffer)
|
||||||
|
}()
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
defer bufferPool.Put(buffer)
|
newEntry.Buffer = buffer
|
||||||
entry.Buffer = buffer
|
|
||||||
|
|
||||||
entry.write()
|
newEntry.write()
|
||||||
|
|
||||||
entry.Buffer = nil
|
newEntry.Buffer = nil
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
// 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) getBufferPool() (pool BufferPool) {
|
||||||
|
if entry.Logger.BufferPool != nil {
|
||||||
|
return entry.Logger.BufferPool
|
||||||
|
}
|
||||||
|
return bufferPool
|
||||||
|
}
|
||||||
|
|
||||||
func (entry *Entry) fireHooks() {
|
func (entry *Entry) fireHooks() {
|
||||||
|
var tmpHooks LevelHooks
|
||||||
entry.Logger.mu.Lock()
|
entry.Logger.mu.Lock()
|
||||||
defer entry.Logger.mu.Unlock()
|
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
|
||||||
err := entry.Logger.Hooks.Fire(entry.Level, entry)
|
for k, v := range entry.Logger.Hooks {
|
||||||
|
tmpHooks[k] = v
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
err := tmpHooks.Fire(entry.Level, entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
}
|
}
|
||||||
|
@ -255,14 +289,16 @@ func (entry *Entry) write() {
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
} else {
|
return
|
||||||
_, err = entry.Logger.Out.Write(serialized)
|
}
|
||||||
if err != nil {
|
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
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{}) {
|
func (entry *Entry) Log(level Level, args ...interface{}) {
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
entry.log(level, fmt.Sprint(args...))
|
entry.log(level, fmt.Sprint(args...))
|
||||||
|
@ -304,7 +340,6 @@ func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
entry.Log(PanicLevel, args...)
|
entry.Log(PanicLevel, args...)
|
||||||
panic(fmt.Sprint(args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry Printf family functions
|
// Entry Printf family functions
|
||||||
|
|
140
entry_test.go
140
entry_test.go
|
@ -47,6 +47,82 @@ func TestEntryWithContext(t *testing.T) {
|
||||||
assert.Equal(ctx, entry.WithContext(ctx).Context)
|
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")
|
||||||
|
|
||||||
|
@ -91,6 +167,28 @@ func TestEntryPanicf(t *testing.T) {
|
||||||
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 (
|
const (
|
||||||
badMessage = "this is going to panic"
|
badMessage = "this is going to panic"
|
||||||
panicMessage = "this is broken"
|
panicMessage = "this is broken"
|
||||||
|
@ -134,7 +232,7 @@ func TestEntryWithIncorrectField(t *testing.T) {
|
||||||
|
|
||||||
fn := func() {}
|
fn := func() {}
|
||||||
|
|
||||||
e := Entry{}
|
e := Entry{Logger: New()}
|
||||||
eWithFunc := e.WithFields(Fields{"func": fn})
|
eWithFunc := e.WithFields(Fields{"func": fn})
|
||||||
eWithFuncPtr := e.WithFields(Fields{"funcPtr": &fn})
|
eWithFuncPtr := e.WithFields(Fields{"funcPtr": &fn})
|
||||||
|
|
||||||
|
@ -162,8 +260,42 @@ func TestEntryLogfLevel(t *testing.T) {
|
||||||
entry := NewEntry(logger)
|
entry := NewEntry(logger)
|
||||||
|
|
||||||
entry.Logf(DebugLevel, "%s", "debug")
|
entry.Logf(DebugLevel, "%s", "debug")
|
||||||
assert.NotContains(t, buffer.String(), "debug", )
|
assert.NotContains(t, buffer.String(), "debug")
|
||||||
|
|
||||||
entry.Logf(WarnLevel, "%s", "warn")
|
entry.Logf(WarnLevel, "%s", "warn")
|
||||||
assert.Contains(t, buffer.String(), "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")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@ package logrus_test
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Example_basic() {
|
func Example_basic() {
|
||||||
var log = logrus.New()
|
log := logrus.New()
|
||||||
log.Formatter = new(logrus.JSONFormatter)
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
log.Formatter = new(logrus.TextFormatter) //default
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors
|
log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors
|
||||||
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||||
log.Level = logrus.TraceLevel
|
log.Level = logrus.TraceLevel
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleJSONFormatter_CallerPrettyfier() {
|
func ExampleJSONFormatter_CallerPrettyfier() {
|
||||||
|
@ -24,5 +24,5 @@ func ExampleJSONFormatter_CallerPrettyfier() {
|
||||||
}
|
}
|
||||||
l.Info("example of custom format caller")
|
l.Info("example of custom format caller")
|
||||||
// Output:
|
// Output:
|
||||||
// {"file":"example_custom_caller_test.go","func":"ExampleCustomFormatter","level":"info","msg":"example of custom format caller"}
|
// {"file":"example_custom_caller_test.go","func":"ExampleJSONFormatter_CallerPrettyfier","level":"info","msg":"example of custom format caller"}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package logrus_test
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultFieldHook struct {
|
type DefaultFieldHook struct {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -3,15 +3,12 @@ package logrus_test
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var mystring string
|
||||||
mystring string
|
|
||||||
)
|
|
||||||
|
|
||||||
type GlobalHook struct {
|
type GlobalHook struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (h *GlobalHook) Levels() []logrus.Level {
|
func (h *GlobalHook) Levels() []logrus.Level {
|
||||||
return logrus.AllLevels
|
return logrus.AllLevels
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package logrus_test
|
package logrus_test
|
||||||
|
@ -6,13 +7,13 @@ import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
slhooks "github.com/sirupsen/logrus/hooks/syslog"
|
slhooks "git.internal/re/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An example on how to use a hook
|
// An example on how to use a hook
|
||||||
func Example_hook() {
|
func Example_hook() {
|
||||||
var log = logrus.New()
|
log := logrus.New()
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors
|
log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors
|
||||||
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||||
|
|
47
exported.go
47
exported.go
|
@ -80,7 +80,7 @@ func WithFields(fields Fields) *Entry {
|
||||||
return std.WithFields(fields)
|
return std.WithFields(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTime creats an entry from the standard logger and overrides the time of
|
// WithTime creates an entry from the standard logger and overrides the time of
|
||||||
// logs generated with it.
|
// logs generated with it.
|
||||||
//
|
//
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
@ -134,6 +134,51 @@ 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.
|
// Tracef logs a message at level Trace on the standard logger.
|
||||||
func Tracef(format string, args ...interface{}) {
|
func Tracef(format string, args ...interface{}) {
|
||||||
std.Tracef(format, args...)
|
std.Tracef(format, args...)
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -1,10 +1,9 @@
|
||||||
module github.com/sirupsen/logrus
|
module git.internal/re/logrus
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
|
||||||
github.com/stretchr/objx v0.1.1 // indirect
|
|
||||||
github.com/stretchr/testify v1.2.2
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
22
go.sum
22
go.sum
|
@ -1,16 +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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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=
|
||||||
|
|
23
hook_test.go
23
hook_test.go
|
@ -3,14 +3,16 @@ package logrus_test
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
. "github.com/sirupsen/logrus"
|
. "git.internal/re/logrus"
|
||||||
. "github.com/sirupsen/logrus/internal/testutils"
|
"git.internal/re/logrus/hooks/test"
|
||||||
|
. "git.internal/re/logrus/internal/testutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestHook struct {
|
type TestHook struct {
|
||||||
|
@ -47,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"
|
||||||
|
@ -191,6 +192,20 @@ func TestAddHookRace(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddHookRace2(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
testname := fmt.Sprintf("Test %d", i)
|
||||||
|
t.Run(testname, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_ = test.NewGlobal()
|
||||||
|
Info(testname)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type HookCallFunc struct {
|
type HookCallFunc struct {
|
||||||
F func()
|
F func()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
lSyslog "git.internal/re/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -24,8 +24,8 @@ If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
lSyslog "git.internal/re/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !windows && !nacl && !plan9
|
||||||
// +build !windows,!nacl,!plan9
|
// +build !windows,!nacl,!plan9
|
||||||
|
|
||||||
package syslog
|
package syslog
|
||||||
|
@ -7,7 +8,7 @@ import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyslogHook to send logs via syslog.
|
// SyslogHook to send logs via syslog.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !windows && !nacl && !plan9
|
||||||
// +build !windows,!nacl,!plan9
|
// +build !windows,!nacl,!plan9
|
||||||
|
|
||||||
package syslog
|
package syslog
|
||||||
|
@ -6,13 +7,12 @@ import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"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.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
// The Test package is used for testing logrus. It is here for backwards
|
// The Test package is used for testing logrus.
|
||||||
// compatibility from when logrus' organization was upper-case. Please use
|
// It provides a simple hooks which register logged messages.
|
||||||
// lower-case logrus and the `null` package instead of this one.
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hook is a hook designed for dealing with logs in test scenarios.
|
// Hook is a hook designed for dealing with logs in test scenarios.
|
||||||
|
@ -21,32 +20,26 @@ type Hook struct {
|
||||||
|
|
||||||
// NewGlobal installs a test hook for the global logger.
|
// NewGlobal installs a test hook for the global logger.
|
||||||
func NewGlobal() *Hook {
|
func NewGlobal() *Hook {
|
||||||
|
|
||||||
hook := new(Hook)
|
hook := new(Hook)
|
||||||
logrus.AddHook(hook)
|
logrus.AddHook(hook)
|
||||||
|
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocal installs a test hook for a given local logger.
|
// NewLocal installs a test hook for a given local logger.
|
||||||
func NewLocal(logger *logrus.Logger) *Hook {
|
func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
hook := new(Hook)
|
hook := new(Hook)
|
||||||
logger.Hooks.Add(hook)
|
logger.Hooks.Add(hook)
|
||||||
|
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNullLogger creates a discarding logger and installs the test hook.
|
// NewNullLogger creates a discarding logger and installs the test hook.
|
||||||
func NewNullLogger() (*logrus.Logger, *Hook) {
|
func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
|
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
logger.Out = ioutil.Discard
|
logger.Out = ioutil.Discard
|
||||||
|
|
||||||
return logger, NewLocal(logger)
|
return logger, NewLocal(logger)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Hook) Fire(e *logrus.Entry) error {
|
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ func TestAllHooks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggingWithHooksRace(t *testing.T) {
|
func TestLoggingWithHooksRace(t *testing.T) {
|
||||||
|
|
||||||
rand.Seed(time.Now().Unix())
|
rand.Seed(time.Now().Unix())
|
||||||
unlocker := rand.Int() % 100
|
unlocker := rand.Int() % 100
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
```
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/sirupsen/logrus"
|
. "git.internal/re/logrus"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,11 +23,17 @@ func (f FieldMap) resolve(key fieldKey) string {
|
||||||
// JSONFormatter formats logs into parsable json
|
// JSONFormatter formats logs into parsable json
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
// 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
|
TimestampFormat string
|
||||||
|
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
DisableTimestamp bool
|
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 allows users to put all the log entry parameters into a nested dictionary at a given key.
|
||||||
DataKey string
|
DataKey string
|
||||||
|
|
||||||
|
@ -60,7 +66,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case error:
|
case error:
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
// Otherwise errors are ignored by `encoding/json`
|
||||||
// https://github.com/sirupsen/logrus/issues/137
|
// https://git.internal/re/logrus/issues/137
|
||||||
data[k] = v.Error()
|
data[k] = v.Error()
|
||||||
default:
|
default:
|
||||||
data[k] = v
|
data[k] = v
|
||||||
|
@ -110,11 +116,12 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder := json.NewEncoder(b)
|
encoder := json.NewEncoder(b)
|
||||||
|
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
||||||
if f.PrettyPrint {
|
if f.PrettyPrint {
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
}
|
}
|
||||||
if err := encoder.Encode(data); err != nil {
|
if err := encoder.Encode(data); err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
|
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.Bytes(), nil
|
return b.Bytes(), nil
|
||||||
|
|
|
@ -344,3 +344,29 @@ func TestJSONEnableTimestamp(t *testing.T) {
|
||||||
t.Error("Timestamp not present", s)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
76
logger.go
76
logger.go
|
@ -9,6 +9,11 @@ import (
|
||||||
"time"
|
"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.Stderr`. You can also set this to
|
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||||
|
@ -39,6 +44,9 @@ type Logger struct {
|
||||||
entryPool sync.Pool
|
entryPool sync.Pool
|
||||||
// Function to exit the application, defaults to `os.Exit()`
|
// Function to exit the application, defaults to `os.Exit()`
|
||||||
ExitFunc exitFunc
|
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 exitFunc func(int)
|
||||||
|
@ -68,10 +76,10 @@ func (mw *MutexWrap) Disable() {
|
||||||
// `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,
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -100,8 +108,9 @@ func (logger *Logger) releaseEntry(entry *Entry) {
|
||||||
logger.entryPool.Put(entry)
|
logger.entryPool.Put(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a field to the log entry, note that it doesn't log until you call
|
// WithField allocates a new entry and adds a field to it.
|
||||||
// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
|
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||||
|
// this new returned entry.
|
||||||
// If you want multiple fields, use `WithFields`.
|
// 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 {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
|
@ -186,6 +195,9 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
logger.Logf(PanicLevel, 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{}) {
|
func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||||
if logger.IsLevelEnabled(level) {
|
if logger.IsLevelEnabled(level) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
|
@ -194,6 +206,14 @@ func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{}) {
|
func (logger *Logger) Trace(args ...interface{}) {
|
||||||
logger.Log(TraceLevel, args...)
|
logger.Log(TraceLevel, args...)
|
||||||
}
|
}
|
||||||
|
@ -233,6 +253,45 @@ func (logger *Logger) Panic(args ...interface{}) {
|
||||||
logger.Log(PanicLevel, 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{}) {
|
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||||
if logger.IsLevelEnabled(level) {
|
if logger.IsLevelEnabled(level) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
|
@ -349,3 +408,10 @@ func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||||
logger.mu.Unlock()
|
logger.mu.Unlock()
|
||||||
return oldHooks
|
return oldHooks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBufferPool sets the logger buffer pool.
|
||||||
|
func (logger *Logger) SetBufferPool(pool BufferPool) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.BufferPool = pool
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ func TestFieldValueError(t *testing.T) {
|
||||||
t.Error("unexpected error", err)
|
t.Error("unexpected error", err)
|
||||||
}
|
}
|
||||||
_, ok := data[FieldKeyLogrusError]
|
_, ok := data[FieldKeyLogrusError]
|
||||||
require.True(t, ok)
|
require.True(t, ok, `cannot found expected "logrus_error" field: %v`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoFieldValueError(t *testing.T) {
|
func TestNoFieldValueError(t *testing.T) {
|
||||||
|
@ -67,3 +67,31 @@ func TestWarninglnNotEqualToWarning(t *testing.T) {
|
||||||
|
|
||||||
assert.NotEqual(t, buf.String(), bufln.String(), "Warning() and Wantingln() should not be equal")
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
. "github.com/sirupsen/logrus"
|
. "git.internal/re/logrus"
|
||||||
. "github.com/sirupsen/logrus/internal/testutils"
|
. "git.internal/re/logrus/internal/testutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestReportCaller verifies that when ReportCaller is set, the 'func' field
|
// TestReportCaller verifies that when ReportCaller is set, the 'func' field
|
||||||
|
@ -40,7 +40,7 @@ func TestReportCallerWhenConfigured(t *testing.T) {
|
||||||
assert.Equal(t, "testWithCaller", fields["msg"])
|
assert.Equal(t, "testWithCaller", fields["msg"])
|
||||||
assert.Equal(t, "info", fields["level"])
|
assert.Equal(t, "info", fields["level"])
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"github.com/sirupsen/logrus_test.TestReportCallerWhenConfigured.func3", fields[FieldKeyFunc])
|
"git.internal/re/logrus_test.TestReportCallerWhenConfigured.func3", fields[FieldKeyFunc])
|
||||||
})
|
})
|
||||||
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
@ -328,7 +328,6 @@ func TestTimeOverrideMultipleLogs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
var fields Fields
|
var fields Fields
|
||||||
|
|
||||||
|
@ -356,7 +355,6 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||||
assert.Equal(t, "omg it is!", fields["msg"])
|
assert.Equal(t, "omg it is!", fields["msg"])
|
||||||
assert.Equal(t, "eating raw fish", fields["context"])
|
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) {
|
func TestNestedLoggingReportsCorrectCaller(t *testing.T) {
|
||||||
|
@ -379,7 +377,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) {
|
||||||
assert.Equal(t, "looks delicious", fields["msg"])
|
assert.Equal(t, "looks delicious", fields["msg"])
|
||||||
assert.Equal(t, "eating raw fish", fields["context"])
|
assert.Equal(t, "eating raw fish", fields["context"])
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"])
|
"git.internal/re/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"])
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, filepath.ToSlash(fmt.Sprintf("%s/logrus_test.go:%d", cwd, line-1)), filepath.ToSlash(fields["file"].(string)))
|
assert.Equal(t, filepath.ToSlash(fmt.Sprintf("%s/logrus_test.go:%d", cwd, line-1)), filepath.ToSlash(fields["file"].(string)))
|
||||||
|
@ -410,7 +408,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) {
|
||||||
assert.Equal(t, "The hardest workin' man in show business", fields["msg"])
|
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.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"])
|
"git.internal/re/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, filepath.ToSlash(fmt.Sprintf("%s/logrus_test.go:%d", cwd, line-1)), filepath.ToSlash(fields["file"].(string)))
|
assert.Equal(t, filepath.ToSlash(fmt.Sprintf("%s/logrus_test.go:%d", cwd, line-1)), filepath.ToSlash(fields["file"].(string)))
|
||||||
|
|
||||||
|
@ -588,15 +586,48 @@ func TestLoggingRaceWithHooksOnEntry(t *testing.T) {
|
||||||
logger.AddHook(hook)
|
logger.AddHook(hook)
|
||||||
entry := logger.WithField("context", "clue")
|
entry := logger.WithField("context", "clue")
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
mtx sync.Mutex
|
||||||
|
start bool
|
||||||
|
)
|
||||||
|
|
||||||
|
cond := sync.NewCond(&mtx)
|
||||||
|
|
||||||
wg.Add(100)
|
wg.Add(100)
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
entry.Info("info")
|
cond.L.Lock()
|
||||||
|
for !start {
|
||||||
|
cond.Wait()
|
||||||
|
}
|
||||||
|
cond.L.Unlock()
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
entry.Info("info")
|
||||||
|
}
|
||||||
wg.Done()
|
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()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build linux aix
|
// +build linux aix zos
|
||||||
// +build !js
|
// +build !js
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
|
@ -5,30 +5,23 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
sequences "github.com/konsorten/go-windows-terminal-sequences"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initTerminal(w io.Writer) {
|
|
||||||
switch v := w.(type) {
|
|
||||||
case *os.File:
|
|
||||||
sequences.EnableVirtualTerminalProcessing(syscall.Handle(v.Fd()), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
var ret bool
|
|
||||||
switch v := w.(type) {
|
switch v := w.(type) {
|
||||||
case *os.File:
|
case *os.File:
|
||||||
|
handle := windows.Handle(v.Fd())
|
||||||
var mode uint32
|
var mode uint32
|
||||||
err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode)
|
if err := windows.GetConsoleMode(handle, &mode); err != nil {
|
||||||
ret = (err == nil)
|
return false
|
||||||
default:
|
}
|
||||||
ret = false
|
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
if err := windows.SetConsoleMode(handle, mode); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if ret {
|
return false
|
||||||
initTerminal(w)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,14 @@ type TextFormatter struct {
|
||||||
// Force disabling colors.
|
// Force disabling colors.
|
||||||
DisableColors bool
|
DisableColors bool
|
||||||
|
|
||||||
|
// 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/
|
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
|
||||||
EnvironmentOverrideColors bool
|
EnvironmentOverrideColors bool
|
||||||
|
|
||||||
|
@ -45,7 +53,10 @@ type TextFormatter struct {
|
||||||
// the time passed since beginning of execution.
|
// the time passed since beginning of execution.
|
||||||
FullTimestamp bool
|
FullTimestamp bool
|
||||||
|
|
||||||
// TimestampFormat to use for display when a full timestamp is printed
|
// TimestampFormat to use for display when a full timestamp is printed.
|
||||||
|
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||||
|
// library.
|
||||||
|
// The standard Library already provides a set of predefined format.
|
||||||
TimestampFormat string
|
TimestampFormat string
|
||||||
|
|
||||||
// The fields are sorted by default for a consistent output. For applications
|
// The fields are sorted by default for a consistent output. For applications
|
||||||
|
@ -107,11 +118,10 @@ func (f *TextFormatter) isColored() bool {
|
||||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
||||||
|
|
||||||
if f.EnvironmentOverrideColors {
|
if f.EnvironmentOverrideColors {
|
||||||
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
|
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
||||||
|
case ok && force != "0":
|
||||||
isColored = true
|
isColored = true
|
||||||
} else if ok && force == "0" {
|
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
||||||
isColored = false
|
|
||||||
} else if os.Getenv("CLICOLOR") == "0" {
|
|
||||||
isColored = false
|
isColored = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +238,8 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -268,11 +280,12 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.DisableTimestamp {
|
switch {
|
||||||
|
case f.DisableTimestamp:
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
||||||
} else if !f.FullTimestamp {
|
case !f.FullTimestamp:
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
||||||
} else {
|
default:
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
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 {
|
||||||
|
@ -283,9 +296,15 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||||
|
if f.ForceQuote {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if f.QuoteEmptyFields && len(text) == 0 {
|
if f.QuoteEmptyFields && len(text) == 0 {
|
||||||
return true
|
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') ||
|
||||||
|
|
|
@ -59,6 +59,7 @@ func TestQuoting(t *testing.T) {
|
||||||
checkQuoting(false, "foo@bar")
|
checkQuoting(false, "foo@bar")
|
||||||
checkQuoting(false, "foobar^")
|
checkQuoting(false, "foobar^")
|
||||||
checkQuoting(false, "+/-_^@f.oobar")
|
checkQuoting(false, "+/-_^@f.oobar")
|
||||||
|
checkQuoting(true, "foo\n\rbar")
|
||||||
checkQuoting(true, "foobar$")
|
checkQuoting(true, "foobar$")
|
||||||
checkQuoting(true, "&foobar")
|
checkQuoting(true, "&foobar")
|
||||||
checkQuoting(true, "x y")
|
checkQuoting(true, "x y")
|
||||||
|
@ -70,7 +71,30 @@ func TestQuoting(t *testing.T) {
|
||||||
tf.QuoteEmptyFields = true
|
tf.QuoteEmptyFields = true
|
||||||
checkQuoting(true, "")
|
checkQuoting(true, "")
|
||||||
checkQuoting(false, "abcd")
|
checkQuoting(false, "abcd")
|
||||||
|
checkQuoting(true, "foo\n\rbar")
|
||||||
checkQuoting(true, errors.New("invalid argument"))
|
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) {
|
func TestEscaping(t *testing.T) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [[ "$TRAVIS_GO_VERSION" =~ ^1.\12\. ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.13\. ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$GO111MODULE" == "on" ]]; then
|
||||||
/tmp/gox/gox -build-lib -all
|
$(go env GOPATH)/bin/gox -build-lib
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ "$TRAVIS_GO_VERSION" =~ ^1.\12\. ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
# Install golanci 1.32.2
|
||||||
git clone https://github.com/dgsb/gox.git /tmp/gox
|
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.15\. ]]; then
|
||||||
pushd /tmp/gox
|
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.32.2
|
||||||
git checkout new_master
|
|
||||||
go build ./
|
|
||||||
popd
|
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"git.internal/re/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleLogger_Writer_httpServer() {
|
func ExampleLogger_Writer_httpServer() {
|
||||||
|
|
Loading…
Reference in New Issue