mirror of https://github.com/sirupsen/logrus.git
Merge branch 'master' of https://github.com/sirupsen/logrus
This commit is contained in:
commit
eab1019f63
15
.travis.yml
15
.travis.yml
|
@ -1,8 +1,15 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.6
|
- 1.6.x
|
||||||
- 1.7
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
- tip
|
- tip
|
||||||
|
env:
|
||||||
|
- GOMAXPROCS=4 GORACE=halt_on_error=1
|
||||||
install:
|
install:
|
||||||
- go get -t ./...
|
- go get github.com/stretchr/testify/assert
|
||||||
script: GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...
|
- go get gopkg.in/gemnasium/logrus-airbrake-hook.v2
|
||||||
|
- go get golang.org/x/sys/unix
|
||||||
|
- go get golang.org/x/sys/windows
|
||||||
|
script:
|
||||||
|
- go test -race -v ./...
|
||||||
|
|
48
CHANGELOG.md
48
CHANGELOG.md
|
@ -1,6 +1,52 @@
|
||||||
# 0.11.0
|
# 1.0.4
|
||||||
|
|
||||||
* feature: Add optional logging of caller method
|
* feature: Add optional logging of caller method
|
||||||
|
# 1.0.3
|
||||||
|
|
||||||
|
* Replace example files with testable examples
|
||||||
|
|
||||||
|
# 1.0.2
|
||||||
|
|
||||||
|
* bug: quote non-string values in text formatter (#583)
|
||||||
|
* Make (*Logger) SetLevel a public method
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
|
||||||
|
* bug: fix escaping in text formatter (#575)
|
||||||
|
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
* Officially changed name to lower-case
|
||||||
|
* bug: colors on Windows 10 (#541)
|
||||||
|
* bug: fix race in accessing level (#512)
|
||||||
|
|
||||||
|
# 0.11.5
|
||||||
|
|
||||||
|
* feature: add writer and writerlevel to entry (#372)
|
||||||
|
|
||||||
|
# 0.11.4
|
||||||
|
|
||||||
|
* bug: fix undefined variable on solaris (#493)
|
||||||
|
|
||||||
|
# 0.11.3
|
||||||
|
|
||||||
|
* formatter: configure quoting of empty values (#484)
|
||||||
|
* formatter: configure quoting character (default is `"`) (#484)
|
||||||
|
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||||
|
|
||||||
|
# 0.11.2
|
||||||
|
|
||||||
|
* bug: fix windows terminal detection (#476)
|
||||||
|
|
||||||
|
# 0.11.1
|
||||||
|
|
||||||
|
* bug: fix tty detection with custom out (#471)
|
||||||
|
|
||||||
|
# 0.11.0
|
||||||
|
|
||||||
|
* performance: Use bufferpool to allocate (#370)
|
||||||
|
* terminal: terminal detection for app-engine (#343)
|
||||||
|
* feature: exit handler (#375)
|
||||||
|
|
||||||
# 0.10.0
|
# 0.10.0
|
||||||
|
|
||||||
|
|
200
README.md
200
README.md
|
@ -1,17 +1,24 @@
|
||||||
# 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://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)
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** See [this
|
|
||||||
issue](https://github.com/sirupsen/logrus/issues/451#issuecomment-264332021).
|
|
||||||
This change has been reverted. I apologize for causing this. I greatly
|
|
||||||
underestimated the impact this would have. Logrus strives for stability and
|
|
||||||
backwards compatibility and failed to provide that.
|
|
||||||
|
|
||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
the standard library logger.
|
||||||
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
|
||||||
many large deployments. The core API is unlikely to change much but please
|
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||||
version control your Logrus to make sure you aren't fetching latest `master` on
|
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||||
every build.**
|
this caused issues in the community and we needed a standard. Some environments
|
||||||
|
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||||
|
Everything using `logrus` will need to use the lower-case:
|
||||||
|
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||||
|
|
||||||
|
To fix Glide, see [these
|
||||||
|
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||||
|
For an in-depth explanation of the casing issue, see [this
|
||||||
|
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||||
|
|
||||||
|
**Are you interested in assisting in maintaining Logrus?** Currently I have a
|
||||||
|
lot of obligations, and I am unable to provide Logrus with the maintainership it
|
||||||
|
needs. If you'd like to help, please reach out to me at `simon at author's
|
||||||
|
username dot com`.
|
||||||
|
|
||||||
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):
|
||||||
|
@ -81,6 +88,12 @@ go test -bench=.*CallerTracing
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Case-sensitivity
|
||||||
|
|
||||||
|
The organization's name was changed to lower-case--and this will not be changed
|
||||||
|
back. If you are getting import conflicts due to case sensitivity, please use
|
||||||
|
the lower-case import: `github.com/sirupsen/logrus`.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
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:
|
||||||
|
@ -89,7 +102,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 "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -100,7 +113,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 "github.com/sirupsen/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:
|
||||||
|
|
||||||
|
@ -109,14 +122,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr, could also be a file.
|
// Output to stdout instead of the default stderr
|
||||||
|
// Can be any io.Writer, see below for File example
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
// Only log the warning severity or above.
|
||||||
|
@ -158,7 +172,8 @@ application, you can also create an instance of the `logrus` Logger:
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
"os"
|
||||||
|
"github.com/sirupsen/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.
|
||||||
|
@ -167,7 +182,15 @@ var log = logrus.New()
|
||||||
func main() {
|
func main() {
|
||||||
// The API for setting attributes is a little different than the package level
|
// The API for setting attributes is a little different than the package level
|
||||||
// exported logger. See Godoc.
|
// exported logger. See Godoc.
|
||||||
log.Out = os.Stderr
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
// You could set this to any `io.Writer` such as a file
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"animal": "walrus",
|
"animal": "walrus",
|
||||||
|
@ -178,7 +201,7 @@ func main() {
|
||||||
|
|
||||||
#### Fields
|
#### Fields
|
||||||
|
|
||||||
Logrus encourages careful, structured logging though logging fields instead of
|
Logrus encourages careful, structured logging through logging fields instead of
|
||||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||||
to send event %s to topic %s with key %d")`, you should log the much more
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
discoverable:
|
discoverable:
|
||||||
|
@ -200,6 +223,20 @@ In general, with Logrus using any of the `printf`-family functions should be
|
||||||
seen as a hint you should add a field, however, you can still use the
|
seen as a hint you should add a field, however, you can still use the
|
||||||
`printf`-family functions with Logrus.
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Default Fields
|
||||||
|
|
||||||
|
Often it's helpful to have fields _always_ attached to log statements in an
|
||||||
|
application or parts of one. For example, you may want to always log the
|
||||||
|
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||||
|
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||||
|
every line, you can create a `logrus.Entry` to pass around instead:
|
||||||
|
|
||||||
|
```go
|
||||||
|
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||||
|
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||||
|
requestLogger.Warn("something not great happened")
|
||||||
|
```
|
||||||
|
|
||||||
#### Hooks
|
#### Hooks
|
||||||
|
|
||||||
You can add hooks for logging levels. For example to send errors to an exception
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
@ -211,9 +248,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 "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -235,42 +272,54 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
||||||
|
|
||||||
| Hook | Description |
|
| Hook | Description |
|
||||||
| ----- | ----------- |
|
| ----- | ----------- |
|
||||||
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
|
||||||
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||||
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
|
||||||
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
|
||||||
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
|
||||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
|
||||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
|
||||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
|
||||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
|
||||||
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
|
||||||
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
|
||||||
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
|
||||||
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
|
||||||
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
|
||||||
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
|
||||||
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
|
||||||
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
|
||||||
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
|
||||||
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
|
||||||
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
|
||||||
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
|
||||||
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
|
||||||
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||||
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
| [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage|
|
||||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||||
|
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||||
|
| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
|
||||||
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
|
||||||
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
|
||||||
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||||
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||||
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||||
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||||
|
| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) |
|
||||||
|
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||||
|
| [KafkaLogrus](https://github.com/tracer0tong/kafkalogrus) | Hook for logging to Kafka |
|
||||||
|
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||||
|
| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||||
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||||
|
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
||||||
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
|
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||||
|
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||||
|
| [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) |
|
||||||
|
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||||
|
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
||||||
|
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||||
|
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||||
|
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
||||||
|
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
||||||
|
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||||
|
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||||
|
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||||
|
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
||||||
|
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||||
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
|
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
||||||
|
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||||
|
| [Syslog](https://github.com/sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||||
|
| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
|
||||||
|
| [Telegram](https://github.com/rossmcdonald/telegram_hook) | Hook for logging errors to [Telegram](https://telegram.org/) |
|
||||||
|
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||||
|
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||||
|
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
||||||
|
| [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) |
|
||||||
|
|
||||||
#### Level logging
|
#### Level logging
|
||||||
|
|
||||||
|
@ -319,7 +368,7 @@ could do:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -346,11 +395,15 @@ The built-in logging formatters are:
|
||||||
without colors.
|
without colors.
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||||
field to `true`. To force no colored output even if there is a TTY set the
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
`DisableColors` field to `true`
|
`DisableColors` field to `true`. For Windows, see
|
||||||
|
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/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).
|
||||||
|
|
||||||
Third party logging formatters:
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can by parsed by Kubernetes and Google Container Engine.
|
||||||
* [`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 P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
@ -396,6 +449,18 @@ srv := http.Server{
|
||||||
Each line written to that writer will be printed the usual way, using formatters
|
Each line written to that writer will be printed the usual way, using formatters
|
||||||
and hooks. The level for those entries is `info`.
|
and hooks. The level for those entries is `info`.
|
||||||
|
|
||||||
|
This means that we can override the standard library logger easily:
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Formatter = &logrus.JSONFormatter{}
|
||||||
|
|
||||||
|
// Use logrus for standard log output
|
||||||
|
// Note that `log` here references stdlib's log
|
||||||
|
// Not logrus imported under the name `log`.
|
||||||
|
log.SetOutput(logger.Writer())
|
||||||
|
```
|
||||||
|
|
||||||
#### Rotation
|
#### Rotation
|
||||||
|
|
||||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
@ -407,7 +472,7 @@ entries. It should not be a feature of the application-level logger.
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
| ---- | ----------- |
|
| ---- | ----------- |
|
||||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
||||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper arround Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||||
|
|
||||||
#### Testing
|
#### Testing
|
||||||
|
|
||||||
|
@ -417,15 +482,24 @@ Logrus has a built in facility for asserting the presence of log messages. This
|
||||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
logger, hook := NewNullLogger()
|
import(
|
||||||
logger.Error("Hello error")
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
assert.Equal(1, len(hook.Entries))
|
func TestSomething(t*testing.T){
|
||||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
logger, hook := test.NewNullLogger()
|
||||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
logger.Error("Helloerror")
|
||||||
|
|
||||||
hook.Reset()
|
assert.Equal(t, 1, len(hook.Entries))
|
||||||
assert.Nil(hook.LastEntry())
|
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(t, hook.LastEntry())
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fatal handlers
|
#### Fatal handlers
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
// The following code was sourced and modified from the
|
// The following code was sourced and modified from the
|
||||||
// https://bitbucket.org/tebeka/atexit package governed by the following license:
|
// https://github.com/tebeka/atexit package governed by the following license:
|
||||||
//
|
//
|
||||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||||
//
|
//
|
||||||
|
|
|
@ -2,7 +2,10 @@ package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -13,33 +16,39 @@ func TestRegister(t *testing.T) {
|
||||||
current := len(handlers)
|
current := len(handlers)
|
||||||
RegisterExitHandler(func() {})
|
RegisterExitHandler(func() {})
|
||||||
if len(handlers) != current+1 {
|
if len(handlers) != current+1 {
|
||||||
t.Fatalf("can't add handler")
|
t.Fatalf("expected %d handlers, got %d", current+1, len(handlers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
gofile := "/tmp/testprog.go"
|
|
||||||
testprog := testprogleader
|
testprog := testprogleader
|
||||||
testprog = append(testprog, getPackage()...)
|
testprog = append(testprog, getPackage()...)
|
||||||
testprog = append(testprog, testprogtrailer...)
|
testprog = append(testprog, testprogtrailer...)
|
||||||
|
tempDir, err := ioutil.TempDir("", "test_handler")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can't create temp dir. %q", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
gofile := filepath.Join(tempDir, "gofile.go")
|
||||||
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
||||||
t.Fatalf("can't create go file")
|
t.Fatalf("can't create go file. %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outfile := "/tmp/testprog.out"
|
outfile := filepath.Join(tempDir, "outfile.out")
|
||||||
arg := time.Now().UTC().String()
|
arg := time.Now().UTC().String()
|
||||||
err := exec.Command("go", "run", gofile, outfile, arg).Run()
|
err = exec.Command("go", "run", gofile, outfile, arg).Run()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("completed normally, should have failed")
|
t.Fatalf("completed normally, should have failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(outfile)
|
data, err := ioutil.ReadFile(outfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't read output file %s", outfile)
|
t.Fatalf("can't read output file %s. %q", outfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(data) != arg {
|
if string(data) != arg {
|
||||||
t.Fatalf("bad data")
|
t.Fatalf("bad data. Expected %q, got %q", data, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
version: "{build}"
|
||||||
|
platform: x64
|
||||||
|
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
install:
|
||||||
|
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
|
- go version
|
||||||
|
build_script:
|
||||||
|
- go get -t
|
||||||
|
- go test
|
4
doc.go
4
doc.go
|
@ -7,7 +7,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 "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -21,6 +21,6 @@ The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
Output:
|
Output:
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||||
|
|
||||||
For a full guide visit https://github.com/Sirupsen/logrus
|
For a full guide visit https://github.com/sirupsen/logrus
|
||||||
*/
|
*/
|
||||||
package logrus
|
package logrus
|
||||||
|
|
43
entry.go
43
entry.go
|
@ -49,6 +49,7 @@ type Entry struct {
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
// Calling method, with package name
|
// Calling method, with package name
|
||||||
|
@ -162,7 +163,11 @@ func (entry Entry) log(level Level, msg string) {
|
||||||
if entry.Logger.ReportCaller {
|
if entry.Logger.ReportCaller {
|
||||||
entry.Caller = getCaller()
|
entry.Caller = getCaller()
|
||||||
}
|
}
|
||||||
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
err := entry.Logger.Hooks.Fire(level, &entry)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
entry.Logger.mu.Lock()
|
entry.Logger.mu.Lock()
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
entry.Logger.mu.Unlock()
|
entry.Logger.mu.Unlock()
|
||||||
|
@ -195,7 +200,7 @@ func (entry Entry) log(level Level, msg string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
if entry.Logger.Level >= DebugLevel {
|
if entry.Logger.level() >= DebugLevel {
|
||||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,13 +210,13 @@ func (entry *Entry) Print(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
if entry.Logger.Level >= InfoLevel {
|
if entry.Logger.level() >= InfoLevel {
|
||||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
if entry.Logger.Level >= WarnLevel {
|
if entry.Logger.level() >= WarnLevel {
|
||||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,20 +226,20 @@ func (entry *Entry) Warning(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
if entry.Logger.level() >= ErrorLevel {
|
||||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
if entry.Logger.Level >= FatalLevel {
|
if entry.Logger.level() >= FatalLevel {
|
||||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
if entry.Logger.Level >= PanicLevel {
|
if entry.Logger.level() >= PanicLevel {
|
||||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
panic(fmt.Sprint(args...))
|
panic(fmt.Sprint(args...))
|
||||||
|
@ -243,13 +248,13 @@ func (entry *Entry) Panic(args ...interface{}) {
|
||||||
// Entry Printf family functions
|
// Entry Printf family functions
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= DebugLevel {
|
if entry.Logger.level() >= DebugLevel {
|
||||||
entry.Debug(fmt.Sprintf(format, args...))
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= InfoLevel {
|
if entry.Logger.level() >= InfoLevel {
|
||||||
entry.Info(fmt.Sprintf(format, args...))
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,7 +264,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= WarnLevel {
|
if entry.Logger.level() >= WarnLevel {
|
||||||
entry.Warn(fmt.Sprintf(format, args...))
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,20 +274,20 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
if entry.Logger.level() >= ErrorLevel {
|
||||||
entry.Error(fmt.Sprintf(format, args...))
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= FatalLevel {
|
if entry.Logger.level() >= FatalLevel {
|
||||||
entry.Fatal(fmt.Sprintf(format, args...))
|
entry.Fatal(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= PanicLevel {
|
if entry.Logger.level() >= PanicLevel {
|
||||||
entry.Panic(fmt.Sprintf(format, args...))
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,13 +295,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
// Entry Println family functions
|
// Entry Println family functions
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= DebugLevel {
|
if entry.Logger.level() >= DebugLevel {
|
||||||
entry.Debug(entry.sprintlnn(args...))
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= InfoLevel {
|
if entry.Logger.level() >= InfoLevel {
|
||||||
entry.Info(entry.sprintlnn(args...))
|
entry.Info(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,7 +311,7 @@ func (entry *Entry) Println(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= WarnLevel {
|
if entry.Logger.level() >= WarnLevel {
|
||||||
entry.Warn(entry.sprintlnn(args...))
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,20 +321,20 @@ func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
if entry.Logger.level() >= ErrorLevel {
|
||||||
entry.Error(entry.sprintlnn(args...))
|
entry.Error(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= FatalLevel {
|
if entry.Logger.level() >= FatalLevel {
|
||||||
entry.Fatal(entry.sprintlnn(args...))
|
entry.Fatal(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= PanicLevel {
|
if entry.Logger.level() >= PanicLevel {
|
||||||
entry.Panic(entry.sprintlnn(args...))
|
entry.Panic(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package logrus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_basic() {
|
||||||
|
var log = logrus.New()
|
||||||
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
|
log.Formatter = new(logrus.TextFormatter) //default
|
||||||
|
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||||
|
log.Level = logrus.DebugLevel
|
||||||
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
entry := err.(*logrus.Entry)
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"err_animal": entry.Data["animal"],
|
||||||
|
"err_size": entry.Data["size"],
|
||||||
|
"err_level": entry.Level,
|
||||||
|
"err_message": entry.Message,
|
||||||
|
"number": 100,
|
||||||
|
}).Error("The ice breaks!") // or use Fatal() to force the process to exit with a nonzero code
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"number": 8,
|
||||||
|
}).Debug("Started observing beach")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"temperature": -4,
|
||||||
|
}).Debug("Temperature changes")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "orca",
|
||||||
|
"size": 9009,
|
||||||
|
}).Panic("It's over 9000!")
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// level=debug msg="Started observing beach" animal=walrus number=8
|
||||||
|
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
// level=debug msg="Temperature changes" temperature=-4
|
||||||
|
// level=panic msg="It's over 9000!" animal=orca size=9009
|
||||||
|
// level=error msg="The ice breaks!" err_animal=orca err_level=panic err_message="It's over 9000!" err_size=9009 number=100 omg=true
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package logrus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_hook() {
|
||||||
|
var log = logrus.New()
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||||
|
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||||
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Error("The ice breaks!")
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
// level=error msg="The ice breaks!" number=100 omg=true
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Formatter = new(logrus.JSONFormatter)
|
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
|
||||||
log.Level = logrus.DebugLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
defer func() {
|
|
||||||
err := recover()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"err": err,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"number": 8,
|
|
||||||
}).Debug("Started observing beach")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"temperature": -4,
|
|
||||||
}).Debug("Temperature changes")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "orca",
|
|
||||||
"size": 9009,
|
|
||||||
}).Panic("It's over 9000!")
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
|
||||||
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
}
|
|
|
@ -39,14 +39,14 @@ func SetReportCaller(include bool) {
|
||||||
func SetLevel(level Level) {
|
func SetLevel(level Level) {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
std.Level = level
|
std.SetLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
// GetLevel returns the standard logger level.
|
||||||
func GetLevel() Level {
|
func GetLevel() Level {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
return std.Level
|
return std.level()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
|
|
@ -4,8 +4,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
// DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ
|
// DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ
|
||||||
const DefaultTimestampFormat = time.RFC3339
|
const DefaultTimestampFormat = time.RFC3339
|
||||||
|
=======
|
||||||
|
const defaultTimestampFormat = time.RFC3339
|
||||||
|
>>>>>>> 89742aefa4b206dcf400792f3bd35b542998eb3b
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
|
|
@ -80,11 +80,14 @@ func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||||
|
logger := New()
|
||||||
|
|
||||||
entry := &Entry{
|
entry := &Entry{
|
||||||
Time: time.Time{},
|
Time: time.Time{},
|
||||||
Level: InfoLevel,
|
Level: InfoLevel,
|
||||||
Message: "message",
|
Message: "message",
|
||||||
Data: fields,
|
Data: fields,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
var d []byte
|
var d []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
22
hook_test.go
22
hook_test.go
|
@ -1,6 +1,7 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -120,3 +121,24 @@ func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||||
assert.Equal(t, hook.Fired, true)
|
assert.Equal(t, hook.Fired, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddHookRace(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
hook := new(ErrorHook)
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
log.AddHook(hook)
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
log.Error("test")
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}, func(fields Fields) {
|
||||||
|
// the line may have been logged
|
||||||
|
// before the hook was added, so we can't
|
||||||
|
// actually assert on the hook
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log := logrus.New()
|
log := logrus.New()
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Hooks.Add(hook)
|
log.Hooks.Add(hook)
|
||||||
|
@ -24,16 +24,16 @@ 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"
|
"github.com/sirupsen/logrus"
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log := logrus.New()
|
log := logrus.New()
|
||||||
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Hooks.Add(hook)
|
log.Hooks.Add(hook)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// +build !windows,!nacl,!plan9
|
// +build !windows,!nacl,!plan9
|
||||||
|
|
||||||
package logrus_syslog
|
package syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyslogHook to send logs via syslog.
|
// SyslogHook to send logs via syslog.
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package logrus_syslog
|
package syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalhostAddAndPrint(t *testing.T) {
|
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
|
// The Test package is used for testing logrus. It is here for backwards
|
||||||
|
// compatibility from when logrus' organization was upper-case. Please use
|
||||||
|
// lower-case logrus and the `null` package instead of this one.
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// test.Hook is a hook designed for dealing with logs in test scenarios.
|
// Hook is a hook designed for dealing with logs in test scenarios.
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
|
// Entries is an array of all entries that have been received by this hook.
|
||||||
|
// For safe access, use the AllEntries() method, rather than reading this
|
||||||
|
// value directly.
|
||||||
Entries []*logrus.Entry
|
Entries []*logrus.Entry
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -21,7 +29,7 @@ func NewGlobal() *Hook {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -31,7 +39,7 @@ func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
|
@ -42,6 +50,8 @@ func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Hook) Fire(e *logrus.Entry) error {
|
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
t.Entries = append(t.Entries, e)
|
t.Entries = append(t.Entries, e)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,17 +61,35 @@ func (t *Hook) Levels() []logrus.Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastEntry returns the last entry that was logged or nil.
|
// LastEntry returns the last entry that was logged or nil.
|
||||||
func (t *Hook) LastEntry() (l *logrus.Entry) {
|
func (t *Hook) LastEntry() *logrus.Entry {
|
||||||
|
t.mu.RLock()
|
||||||
if i := len(t.Entries) - 1; i < 0 {
|
defer t.mu.RUnlock()
|
||||||
|
i := len(t.Entries) - 1
|
||||||
|
if i < 0 {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
return t.Entries[i]
|
|
||||||
}
|
}
|
||||||
|
// Make a copy, for safety
|
||||||
|
e := *t.Entries[i]
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllEntries returns all entries that were logged.
|
||||||
|
func (t *Hook) AllEntries() []*logrus.Entry {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
// Make a copy so the returned value won't race with future log requests
|
||||||
|
entries := make([]*logrus.Entry, len(t.Entries))
|
||||||
|
for i, entry := range t.Entries {
|
||||||
|
// Make a copy, for safety
|
||||||
|
e := *entry
|
||||||
|
entries[i] = &e
|
||||||
|
}
|
||||||
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset removes all Entries from this test hook.
|
// Reset removes all Entries from this test hook.
|
||||||
func (t *Hook) Reset() {
|
func (t *Hook) Reset() {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
t.Entries = make([]*logrus.Entry, 0)
|
t.Entries = make([]*logrus.Entry, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type fieldKey string
|
type fieldKey string
|
||||||
|
|
||||||
|
// FieldMap allows customization of the key names for default fields.
|
||||||
type FieldMap map[fieldKey]string
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
|
// Default key names for the default fields
|
||||||
const (
|
const (
|
||||||
FieldKeyMsg = "msg"
|
FieldKeyMsg = "msg"
|
||||||
FieldKeyLevel = "level"
|
FieldKeyLevel = "level"
|
||||||
|
@ -23,6 +26,7 @@ func (f FieldMap) resolve(key fieldKey) string {
|
||||||
return string(key)
|
return string(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
TimestampFormat string
|
TimestampFormat string
|
||||||
|
@ -30,26 +34,31 @@ type JSONFormatter struct {
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
DisableTimestamp bool
|
DisableTimestamp bool
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for various fields.
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
// As an example:
|
// As an example:
|
||||||
// formatter := &JSONFormatter{
|
// formatter := &JSONFormatter{
|
||||||
// FieldMap: FieldMap{
|
// FieldMap: FieldMap{
|
||||||
// FieldKeyTime: "@timestamp",
|
// FieldKeyTime: "@timestamp",
|
||||||
// FieldKeyLevel: "@level",
|
// FieldKeyLevel: "@level",
|
||||||
|
<<<<<<< HEAD
|
||||||
// FieldKeyMsg: "@message",
|
// FieldKeyMsg: "@message",
|
||||||
// FieldKeyFunc: "@caller",
|
// FieldKeyFunc: "@caller",
|
||||||
|
=======
|
||||||
|
// FieldKeyMsg: "@message",
|
||||||
|
>>>>>>> 89742aefa4b206dcf400792f3bd35b542998eb3b
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
FieldMap FieldMap
|
FieldMap FieldMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields, len(entry.Data)+4)
|
data := make(Fields, len(entry.Data)+4)
|
||||||
for k, v := range entry.Data {
|
for k, v := range entry.Data {
|
||||||
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://github.com/sirupsen/logrus/issues/137
|
||||||
data[k] = v.Error()
|
data[k] = v.Error()
|
||||||
default:
|
default:
|
||||||
data[k] = v
|
data[k] = v
|
||||||
|
@ -60,7 +69,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = DefaultTimestampFormat
|
timestampFormat = defaultTimestampFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.DisableTimestamp {
|
if !f.DisableTimestamp {
|
||||||
|
|
59
logger.go
59
logger.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
|
@ -28,7 +29,7 @@ type Logger struct {
|
||||||
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
// logged. `logrus.Debug` is useful in
|
// logged.
|
||||||
Level Level
|
Level Level
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
mu MutexWrap
|
mu MutexWrap
|
||||||
|
@ -117,7 +118,7 @@ func (logger *Logger) WithError(err error) *Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
if logger.Level >= DebugLevel {
|
if logger.level() >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debugf(format, args...)
|
entry.Debugf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -125,7 +126,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
if logger.Level >= InfoLevel {
|
if logger.level() >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Infof(format, args...)
|
entry.Infof(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -139,7 +140,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -147,7 +148,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -155,7 +156,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
if logger.Level >= ErrorLevel {
|
if logger.level() >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Errorf(format, args...)
|
entry.Errorf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -163,7 +164,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
if logger.Level >= FatalLevel {
|
if logger.level() >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatalf(format, args...)
|
entry.Fatalf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -172,7 +173,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
if logger.Level >= PanicLevel {
|
if logger.level() >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panicf(format, args...)
|
entry.Panicf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -180,7 +181,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
if logger.Level >= DebugLevel {
|
if logger.level() >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debug(args...)
|
entry.Debug(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -188,7 +189,7 @@ func (logger *Logger) Debug(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
if logger.Level >= InfoLevel {
|
if logger.level() >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Info(args...)
|
entry.Info(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -202,7 +203,7 @@ func (logger *Logger) Print(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -210,7 +211,7 @@ func (logger *Logger) Warn(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -218,7 +219,7 @@ func (logger *Logger) Warning(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
if logger.Level >= ErrorLevel {
|
if logger.level() >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Error(args...)
|
entry.Error(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -226,7 +227,7 @@ func (logger *Logger) Error(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
if logger.Level >= FatalLevel {
|
if logger.level() >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatal(args...)
|
entry.Fatal(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -235,7 +236,7 @@ func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
if logger.Level >= PanicLevel {
|
if logger.level() >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panic(args...)
|
entry.Panic(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -243,7 +244,7 @@ func (logger *Logger) Panic(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
if logger.Level >= DebugLevel {
|
if logger.level() >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debugln(args...)
|
entry.Debugln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -251,7 +252,7 @@ func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
if logger.Level >= InfoLevel {
|
if logger.level() >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Infoln(args...)
|
entry.Infoln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -265,7 +266,7 @@ func (logger *Logger) Println(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -273,7 +274,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -281,7 +282,7 @@ func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
if logger.Level >= ErrorLevel {
|
if logger.level() >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Errorln(args...)
|
entry.Errorln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -289,7 +290,7 @@ func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
if logger.Level >= FatalLevel {
|
if logger.level() >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatalln(args...)
|
entry.Fatalln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -298,7 +299,7 @@ func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
if logger.Level >= PanicLevel {
|
if logger.level() >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panicln(args...)
|
entry.Panicln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -311,3 +312,17 @@ func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
func (logger *Logger) SetNoLock() {
|
func (logger *Logger) SetNoLock() {
|
||||||
logger.mu.Disable()
|
logger.mu.Disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) level() Level {
|
||||||
|
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) SetLevel(level Level) {
|
||||||
|
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) AddHook(hook Hook) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
type Fields map[string]interface{}
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
// Level type
|
// Level type
|
||||||
type Level uint8
|
type Level uint32
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
func (level Level) String() string {
|
func (level Level) String() string {
|
||||||
|
|
|
@ -478,3 +478,28 @@ func TestLogrusInterface(t *testing.T) {
|
||||||
e := logger.WithField("another", "value")
|
e := logger.WithField("another", "value")
|
||||||
fn(e)
|
fn(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements io.Writer using channels for synchronization, so we can wait on
|
||||||
|
// the Entry.Writer goroutine to write in a non-racey way. This does assume that
|
||||||
|
// there is a single call to Logger.Out for each message.
|
||||||
|
type channelWriter chan []byte
|
||||||
|
|
||||||
|
func (cw channelWriter) Write(p []byte) (int, error) {
|
||||||
|
cw <- p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryWriter(t *testing.T) {
|
||||||
|
cw := channelWriter(make(chan []byte, 1))
|
||||||
|
log := New()
|
||||||
|
log.Out = cw
|
||||||
|
log.Formatter = new(JSONFormatter)
|
||||||
|
log.WithField("foo", "bar").WriterLevel(WarnLevel).Write([]byte("hello\n"))
|
||||||
|
|
||||||
|
bs := <-cw
|
||||||
|
var fields Fields
|
||||||
|
err := json.Unmarshal(bs, &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, fields["foo"], "bar")
|
||||||
|
assert.Equal(t, fields["level"], "warning")
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "syscall"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
|
||||||
type Termios syscall.Termios
|
type Termios unix.Termios
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "syscall"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TCGETS
|
const ioctlReadTermios = unix.TCGETS
|
||||||
|
|
||||||
type Termios syscall.Termios
|
type Termios unix.Termios
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var termios Termios
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
|
||||||
return err == 0
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// +build solaris,!appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build windows,!appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
var (
|
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var st uint32
|
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
|
||||||
return r != 0 && e == 0
|
|
||||||
}
|
|
|
@ -3,10 +3,14 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"io"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -14,20 +18,19 @@ const (
|
||||||
red = 31
|
red = 31
|
||||||
green = 32
|
green = 32
|
||||||
yellow = 33
|
yellow = 33
|
||||||
blue = 34
|
blue = 36
|
||||||
gray = 37
|
gray = 37
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
baseTimestamp time.Time
|
baseTimestamp time.Time
|
||||||
isTerminal bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
baseTimestamp = time.Now()
|
baseTimestamp = time.Now()
|
||||||
isTerminal = IsTerminal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextFormatter formats logs into text
|
||||||
type TextFormatter struct {
|
type TextFormatter struct {
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
ForceColors bool
|
ForceColors bool
|
||||||
|
@ -50,8 +53,32 @@ type TextFormatter struct {
|
||||||
// that log extremely frequently and don't use the JSON formatter this may not
|
// that log extremely frequently and don't use the JSON formatter this may not
|
||||||
// be desired.
|
// be desired.
|
||||||
DisableSorting bool
|
DisableSorting bool
|
||||||
|
|
||||||
|
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||||
|
QuoteEmptyFields bool
|
||||||
|
|
||||||
|
// Whether the logger's out is to a terminal
|
||||||
|
isTerminal bool
|
||||||
|
|
||||||
|
sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) init(entry *Entry) {
|
||||||
|
if entry.Logger != nil {
|
||||||
|
f.isTerminal = f.checkIfTerminal(entry.Logger.Out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) checkIfTerminal(w io.Writer) bool {
|
||||||
|
switch v := w.(type) {
|
||||||
|
case *os.File:
|
||||||
|
return terminal.IsTerminal(int(v.Fd()))
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
var b *bytes.Buffer
|
var b *bytes.Buffer
|
||||||
keys := make([]string, 0, len(entry.Data))
|
keys := make([]string, 0, len(entry.Data))
|
||||||
|
@ -70,12 +97,13 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
prefixFieldClashes(entry.Data, entry.HasCaller())
|
prefixFieldClashes(entry.Data, entry.HasCaller())
|
||||||
|
|
||||||
isColorTerminal := isTerminal && (runtime.GOOS != "windows")
|
f.Do(func() { f.init(entry) })
|
||||||
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
|
|
||||||
|
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = DefaultTimestampFormat
|
timestampFormat = defaultTimestampFormat
|
||||||
}
|
}
|
||||||
if isColored {
|
if isColored {
|
||||||
f.printColored(b, entry, keys, timestampFormat)
|
f.printColored(b, entry, keys, timestampFormat)
|
||||||
|
@ -134,12 +162,15 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func needsQuoting(text string) bool {
|
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||||
|
if f.QuoteEmptyFields && len(text) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
for _, ch := range text {
|
for _, ch := range text {
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
if !((ch >= 'a' && ch <= 'z') ||
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
(ch >= '0' && ch <= '9') ||
|
(ch >= '0' && ch <= '9') ||
|
||||||
ch == '-' || ch == '.') {
|
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,29 +178,23 @@ func needsQuoting(text string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
b.WriteString(key)
|
b.WriteString(key)
|
||||||
b.WriteByte('=')
|
b.WriteByte('=')
|
||||||
f.appendValue(b, value)
|
f.appendValue(b, value)
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||||
switch value := value.(type) {
|
stringVal, ok := value.(string)
|
||||||
case string:
|
if !ok {
|
||||||
if !needsQuoting(value) {
|
stringVal = fmt.Sprint(value)
|
||||||
b.WriteString(value)
|
}
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%q", value)
|
if !f.needsQuoting(stringVal) {
|
||||||
}
|
b.WriteString(stringVal)
|
||||||
case error:
|
} else {
|
||||||
errmsg := value.Error()
|
b.WriteString(fmt.Sprintf("%q", stringVal))
|
||||||
if !needsQuoting(errmsg) {
|
|
||||||
b.WriteString(errmsg)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%q", errmsg)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Fprint(b, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,38 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFormatting(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{`foo`, "time=\"0001-01-01T00:00:00Z\" level=panic test=foo\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
b, _ := tf.Format(WithField("test", tc.value))
|
||||||
|
|
||||||
|
if string(b) != tc.expected {
|
||||||
|
t.Errorf("formatting expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQuoting(t *testing.T) {
|
func TestQuoting(t *testing.T) {
|
||||||
tf := &TextFormatter{DisableColors: true}
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
checkQuoting := func(q bool, value interface{}) {
|
checkQuoting := func(q bool, value interface{}) {
|
||||||
b, _ := tf.Format(WithField("test", value))
|
b, _ := tf.Format(WithField("test", value))
|
||||||
idx := bytes.Index(b, ([]byte)("test="))
|
idx := bytes.Index(b, ([]byte)("test="))
|
||||||
cont := bytes.Contains(b[idx+5:], []byte{'"'})
|
cont := bytes.Contains(b[idx+5:], []byte("\""))
|
||||||
if cont != q {
|
if cont != q {
|
||||||
if q {
|
if q {
|
||||||
t.Errorf("quoting expected for: %#v", value)
|
t.Errorf("quoting expected for: %#v", value)
|
||||||
|
@ -24,14 +44,67 @@ func TestQuoting(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkQuoting(false, "")
|
||||||
checkQuoting(false, "abcd")
|
checkQuoting(false, "abcd")
|
||||||
checkQuoting(false, "v1.0")
|
checkQuoting(false, "v1.0")
|
||||||
checkQuoting(false, "1234567890")
|
checkQuoting(false, "1234567890")
|
||||||
checkQuoting(true, "/foobar")
|
checkQuoting(false, "/foobar")
|
||||||
|
checkQuoting(false, "foo_bar")
|
||||||
|
checkQuoting(false, "foo@bar")
|
||||||
|
checkQuoting(false, "foobar^")
|
||||||
|
checkQuoting(false, "+/-_^@f.oobar")
|
||||||
|
checkQuoting(true, "foobar$")
|
||||||
|
checkQuoting(true, "&foobar")
|
||||||
checkQuoting(true, "x y")
|
checkQuoting(true, "x y")
|
||||||
checkQuoting(true, "x,y")
|
checkQuoting(true, "x,y")
|
||||||
checkQuoting(false, errors.New("invalid"))
|
checkQuoting(false, errors.New("invalid"))
|
||||||
checkQuoting(true, errors.New("invalid argument"))
|
checkQuoting(true, errors.New("invalid argument"))
|
||||||
|
|
||||||
|
// Test for quoting empty fields.
|
||||||
|
tf.QuoteEmptyFields = true
|
||||||
|
checkQuoting(true, "")
|
||||||
|
checkQuoting(false, "abcd")
|
||||||
|
checkQuoting(true, errors.New("invalid argument"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscaping(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{`ba"r`, `ba\"r`},
|
||||||
|
{`ba'r`, `ba'r`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
b, _ := tf.Format(WithField("test", tc.value))
|
||||||
|
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||||
|
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscaping_Interface(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
value interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{ts, fmt.Sprintf("\"%s\"", ts.String())},
|
||||||
|
{errors.New("error: something went wrong"), "\"error: something went wrong\""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
b, _ := tf.Format(WithField("test", tc.value))
|
||||||
|
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||||
|
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimestampFormat(t *testing.T) {
|
func TestTimestampFormat(t *testing.T) {
|
||||||
|
@ -40,10 +113,7 @@ func TestTimestampFormat(t *testing.T) {
|
||||||
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
||||||
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
||||||
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
||||||
timeStr := customStr[timeStart+5 : timeEnd-1]
|
timeStr := customStr[timeStart+5+len("\"") : timeEnd-1-len("\"")]
|
||||||
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
|
|
||||||
timeStr = timeStr[1 : len(timeStr)-1]
|
|
||||||
}
|
|
||||||
if format == "" {
|
if format == "" {
|
||||||
format = time.RFC3339
|
format = time.RFC3339
|
||||||
}
|
}
|
||||||
|
|
29
writer.go
29
writer.go
|
@ -11,39 +11,48 @@ func (logger *Logger) Writer() *io.PipeWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||||
|
return NewEntry(logger).WriterLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Writer() *io.PipeWriter {
|
||||||
|
return entry.WriterLevel(InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
var printFunc func(args ...interface{})
|
||||||
|
|
||||||
switch level {
|
switch level {
|
||||||
case DebugLevel:
|
case DebugLevel:
|
||||||
printFunc = logger.Debug
|
printFunc = entry.Debug
|
||||||
case InfoLevel:
|
case InfoLevel:
|
||||||
printFunc = logger.Info
|
printFunc = entry.Info
|
||||||
case WarnLevel:
|
case WarnLevel:
|
||||||
printFunc = logger.Warn
|
printFunc = entry.Warn
|
||||||
case ErrorLevel:
|
case ErrorLevel:
|
||||||
printFunc = logger.Error
|
printFunc = entry.Error
|
||||||
case FatalLevel:
|
case FatalLevel:
|
||||||
printFunc = logger.Fatal
|
printFunc = entry.Fatal
|
||||||
case PanicLevel:
|
case PanicLevel:
|
||||||
printFunc = logger.Panic
|
printFunc = entry.Panic
|
||||||
default:
|
default:
|
||||||
printFunc = logger.Print
|
printFunc = entry.Print
|
||||||
}
|
}
|
||||||
|
|
||||||
go logger.writerScanner(reader, printFunc)
|
go entry.writerScanner(reader, printFunc)
|
||||||
runtime.SetFinalizer(writer, writerFinalizer)
|
runtime.SetFinalizer(writer, writerFinalizer)
|
||||||
|
|
||||||
return writer
|
return writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
printFunc(scanner.Text())
|
printFunc(scanner.Text())
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
logger.Errorf("Error while reading from Writer: %s", err)
|
entry.Errorf("Error while reading from Writer: %s", err)
|
||||||
}
|
}
|
||||||
reader.Close()
|
reader.Close()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue