diff --git a/.travis.yml b/.travis.yml
index 804c569..a23296a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,15 @@
language: go
go:
- - 1.6
- - 1.7
+ - 1.6.x
+ - 1.7.x
+ - 1.8.x
- tip
+env:
+ - GOMAXPROCS=4 GORACE=halt_on_error=1
install:
- - go get -t ./...
-script: GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...
+ - go get github.com/stretchr/testify/assert
+ - 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 ./...
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5af988d..dccf33a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,32 @@
+# 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)
diff --git a/README.md b/README.md
index c1e01e9..3c22360 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,24 @@
-# Logrus [![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 [![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 is a structured logger for Go (golang), completely API compatible with
-the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
-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
-version control your Logrus to make sure you aren't fetching latest `master` on
-every build.**
+the standard library logger.
+
+**Seeing weird case-sensitive problems?** It's in the past been possible to
+import Logrus as both upper- and lower-case. Due to the Go package environment,
+this caused issues in the community and we needed a standard. Some environments
+experienced problems with the upper-case variant, so the lower-case was decided.
+Everything using `logrus` will need to use the lower-case:
+`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
+
+To fix Glide, see [these
+comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
+For an in-depth explanation of the casing issue, see [this
+comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
+
+**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
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
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
import (
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
)
func main() {
@@ -100,7 +113,7 @@ func main() {
```
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
want:
@@ -109,7 +122,7 @@ package main
import (
"os"
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
)
func init() {
@@ -159,7 +172,8 @@ application, you can also create an instance of the `logrus` Logger:
package main
import (
- "github.com/Sirupsen/logrus"
+ "os"
+ "github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
@@ -187,7 +201,7 @@ func main() {
#### 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
to send event %s to topic %s with key %d")`, you should log the much more
discoverable:
@@ -218,7 +232,7 @@ application or parts of one. For example, you may want to always log the
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 := 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")
```
@@ -234,9 +248,9 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
```go
import (
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"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"
)
@@ -264,14 +278,16 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
| [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|
+| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
| [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/) |
+| [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/goibibo/KafkaLogrus) | Hook for logging to kafka |
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
@@ -281,6 +297,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
| [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 |
@@ -295,10 +312,12 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
| [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](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. |
| [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
@@ -347,7 +366,7 @@ could do:
```go
import (
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
)
init() {
@@ -382,6 +401,7 @@ The built-in logging formatters are:
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.
* [`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̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
@@ -450,7 +470,7 @@ entries. It should not be a feature of the application-level logger.
| 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 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
@@ -460,15 +480,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):
```go
-logger, hook := NewNullLogger()
-logger.Error("Hello error")
+import(
+ "github.com/sirupsen/logrus"
+ "github.com/sirupsen/logrus/hooks/test"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
-assert.Equal(1, len(hook.Entries))
-assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
-assert.Equal("Hello error", hook.LastEntry().Message)
+func TestSomething(t*testing.T){
+ logger, hook := test.NewNullLogger()
+ logger.Error("Helloerror")
-hook.Reset()
-assert.Nil(hook.LastEntry())
+ assert.Equal(t, 1, len(hook.Entries))
+ assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
+ assert.Equal(t, "Helloerror", hook.LastEntry().Message)
+
+ hook.Reset()
+ assert.Nil(t, hook.LastEntry())
+}
```
#### Fatal handlers
diff --git a/alt_exit.go b/alt_exit.go
index b4c9e84..8af9063 100644
--- a/alt_exit.go
+++ b/alt_exit.go
@@ -1,7 +1,7 @@
package logrus
// 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 .
//
diff --git a/alt_exit_test.go b/alt_exit_test.go
index 5763c60..0a2ff56 100644
--- a/alt_exit_test.go
+++ b/alt_exit_test.go
@@ -2,7 +2,10 @@ package logrus
import (
"io/ioutil"
+ "log"
+ "os"
"os/exec"
+ "path/filepath"
"runtime"
"strings"
"testing"
@@ -13,33 +16,39 @@ func TestRegister(t *testing.T) {
current := len(handlers)
RegisterExitHandler(func() {})
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) {
- gofile := "/tmp/testprog.go"
testprog := testprogleader
testprog = append(testprog, getPackage()...)
testprog = append(testprog, testprogtrailer...)
+ tempDir, err := ioutil.TempDir("", "test_handler")
+ if err != nil {
+ log.Fatalf("can't create temp dir. %q", err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ gofile := filepath.Join(tempDir, "gofile.go")
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
- t.Fatalf("can't create go file")
+ t.Fatalf("can't create go file. %q", err)
}
- outfile := "/tmp/testprog.out"
+ outfile := filepath.Join(tempDir, "outfile.out")
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 {
t.Fatalf("completed normally, should have failed")
}
data, err := ioutil.ReadFile(outfile)
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 {
- t.Fatalf("bad data")
+ t.Fatalf("bad data. Expected %q, got %q", data, arg)
}
}
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..96c2ce1
--- /dev/null
+++ b/appveyor.yml
@@ -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
diff --git a/doc.go b/doc.go
index dddd5f8..da67aba 100644
--- a/doc.go
+++ b/doc.go
@@ -7,7 +7,7 @@ The simplest way to use Logrus is simply the package-level exported logger:
package main
import (
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
)
func main() {
@@ -21,6 +21,6 @@ The simplest way to use Logrus is simply the package-level exported logger:
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
+For a full guide visit https://github.com/sirupsen/logrus
*/
package logrus
diff --git a/entry.go b/entry.go
index 50ca795..11f79c9 100644
--- a/entry.go
+++ b/entry.go
@@ -49,6 +49,7 @@ type Entry struct {
Time time.Time
// 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
// Calling method, with package name
@@ -195,7 +196,7 @@ func (entry Entry) log(level Level, msg string) {
}
func (entry *Entry) Debug(args ...interface{}) {
- if entry.Logger.Level >= DebugLevel {
+ if entry.Logger.level() >= DebugLevel {
entry.log(DebugLevel, fmt.Sprint(args...))
}
}
@@ -205,13 +206,13 @@ func (entry *Entry) Print(args ...interface{}) {
}
func (entry *Entry) Info(args ...interface{}) {
- if entry.Logger.Level >= InfoLevel {
+ if entry.Logger.level() >= InfoLevel {
entry.log(InfoLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Warn(args ...interface{}) {
- if entry.Logger.Level >= WarnLevel {
+ if entry.Logger.level() >= WarnLevel {
entry.log(WarnLevel, fmt.Sprint(args...))
}
}
@@ -221,20 +222,20 @@ func (entry *Entry) Warning(args ...interface{}) {
}
func (entry *Entry) Error(args ...interface{}) {
- if entry.Logger.Level >= ErrorLevel {
+ if entry.Logger.level() >= ErrorLevel {
entry.log(ErrorLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Fatal(args ...interface{}) {
- if entry.Logger.Level >= FatalLevel {
+ if entry.Logger.level() >= FatalLevel {
entry.log(FatalLevel, fmt.Sprint(args...))
}
Exit(1)
}
func (entry *Entry) Panic(args ...interface{}) {
- if entry.Logger.Level >= PanicLevel {
+ if entry.Logger.level() >= PanicLevel {
entry.log(PanicLevel, fmt.Sprint(args...))
}
panic(fmt.Sprint(args...))
@@ -243,13 +244,13 @@ func (entry *Entry) Panic(args ...interface{}) {
// Entry Printf family functions
func (entry *Entry) Debugf(format string, args ...interface{}) {
- if entry.Logger.Level >= DebugLevel {
+ if entry.Logger.level() >= DebugLevel {
entry.Debug(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Infof(format string, args ...interface{}) {
- if entry.Logger.Level >= InfoLevel {
+ if entry.Logger.level() >= InfoLevel {
entry.Info(fmt.Sprintf(format, args...))
}
}
@@ -259,7 +260,7 @@ func (entry *Entry) Printf(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...))
}
}
@@ -269,20 +270,20 @@ func (entry *Entry) Warningf(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...))
}
}
func (entry *Entry) Fatalf(format string, args ...interface{}) {
- if entry.Logger.Level >= FatalLevel {
+ if entry.Logger.level() >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...))
}
Exit(1)
}
func (entry *Entry) Panicf(format string, args ...interface{}) {
- if entry.Logger.Level >= PanicLevel {
+ if entry.Logger.level() >= PanicLevel {
entry.Panic(fmt.Sprintf(format, args...))
}
}
@@ -290,13 +291,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
// Entry Println family functions
func (entry *Entry) Debugln(args ...interface{}) {
- if entry.Logger.Level >= DebugLevel {
+ if entry.Logger.level() >= DebugLevel {
entry.Debug(entry.sprintlnn(args...))
}
}
func (entry *Entry) Infoln(args ...interface{}) {
- if entry.Logger.Level >= InfoLevel {
+ if entry.Logger.level() >= InfoLevel {
entry.Info(entry.sprintlnn(args...))
}
}
@@ -306,7 +307,7 @@ func (entry *Entry) Println(args ...interface{}) {
}
func (entry *Entry) Warnln(args ...interface{}) {
- if entry.Logger.Level >= WarnLevel {
+ if entry.Logger.level() >= WarnLevel {
entry.Warn(entry.sprintlnn(args...))
}
}
@@ -316,20 +317,20 @@ func (entry *Entry) Warningln(args ...interface{}) {
}
func (entry *Entry) Errorln(args ...interface{}) {
- if entry.Logger.Level >= ErrorLevel {
+ if entry.Logger.level() >= ErrorLevel {
entry.Error(entry.sprintlnn(args...))
}
}
func (entry *Entry) Fatalln(args ...interface{}) {
- if entry.Logger.Level >= FatalLevel {
+ if entry.Logger.level() >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...))
}
Exit(1)
}
func (entry *Entry) Panicln(args ...interface{}) {
- if entry.Logger.Level >= PanicLevel {
+ if entry.Logger.level() >= PanicLevel {
entry.Panic(entry.sprintlnn(args...))
}
}
diff --git a/examples/basic/basic.go b/examples/basic/basic.go
index ad703fc..3e112b4 100644
--- a/examples/basic/basic.go
+++ b/examples/basic/basic.go
@@ -1,7 +1,7 @@
package main
import (
- "github.com/Sirupsen/logrus"
+ "github.com/sirupsen/logrus"
// "os"
)
diff --git a/examples/hook/hook.go b/examples/hook/hook.go
index 3187f6d..c8470c3 100644
--- a/examples/hook/hook.go
+++ b/examples/hook/hook.go
@@ -1,7 +1,7 @@
package main
import (
- "github.com/Sirupsen/logrus"
+ "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
)
diff --git a/exported.go b/exported.go
index 3585f94..b2728a9 100644
--- a/exported.go
+++ b/exported.go
@@ -39,14 +39,14 @@ func SetReportCaller(include bool) {
func SetLevel(level Level) {
std.mu.Lock()
defer std.mu.Unlock()
- std.Level = level
+ std.SetLevel(level)
}
// GetLevel returns the standard logger level.
func GetLevel() Level {
std.mu.Lock()
defer std.mu.Unlock()
- return std.Level
+ return std.level()
}
// AddHook adds a hook to the standard logger hooks.
diff --git a/formatter.go b/formatter.go
index ae319c3..dc6631d 100644
--- a/formatter.go
+++ b/formatter.go
@@ -1,8 +1,6 @@
package logrus
-import (
- "time"
-)
+import "time"
// DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ
const DefaultTimestampFormat = time.RFC3339
diff --git a/hooks/syslog/README.md b/hooks/syslog/README.md
index 066704b..1bbc0f7 100644
--- a/hooks/syslog/README.md
+++ b/hooks/syslog/README.md
@@ -5,13 +5,13 @@
```go
import (
"log/syslog"
- "github.com/Sirupsen/logrus"
- logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
+ "github.com/sirupsen/logrus"
+ lSyslog "github.com/sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
- hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
+ hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
@@ -24,16 +24,16 @@ If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "
```go
import (
"log/syslog"
- "github.com/Sirupsen/logrus"
- logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
+ "github.com/sirupsen/logrus"
+ lSyslog "github.com/sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
- hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
+ hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
-```
\ No newline at end of file
+```
diff --git a/hooks/syslog/syslog.go b/hooks/syslog/syslog.go
index a36e200..329ce0d 100644
--- a/hooks/syslog/syslog.go
+++ b/hooks/syslog/syslog.go
@@ -1,12 +1,13 @@
// +build !windows,!nacl,!plan9
-package logrus_syslog
+package syslog
import (
"fmt"
- "github.com/Sirupsen/logrus"
"log/syslog"
"os"
+
+ "github.com/sirupsen/logrus"
)
// SyslogHook to send logs via syslog.
diff --git a/hooks/syslog/syslog_test.go b/hooks/syslog/syslog_test.go
index 42762dc..5ec3a44 100644
--- a/hooks/syslog/syslog_test.go
+++ b/hooks/syslog/syslog_test.go
@@ -1,9 +1,10 @@
-package logrus_syslog
+package syslog
import (
- "github.com/Sirupsen/logrus"
"log/syslog"
"testing"
+
+ "github.com/sirupsen/logrus"
)
func TestLocalhostAddAndPrint(t *testing.T) {
diff --git a/hooks/test/test.go b/hooks/test/test.go
index 0688125..62c4845 100644
--- a/hooks/test/test.go
+++ b/hooks/test/test.go
@@ -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
import (
"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 {
+ // Entries is an array of all entries that have been received by this hook.
+ // For safe access, use the AllEntries() method, rather than reading this
+ // value directly.
Entries []*logrus.Entry
+ mu sync.RWMutex
}
-// Installs a test hook for the global logger.
+// NewGlobal installs a test hook for the global logger.
func NewGlobal() *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 {
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) {
logger := logrus.New()
@@ -42,6 +50,8 @@ func NewNullLogger() (*logrus.Logger, *Hook) {
}
func (t *Hook) Fire(e *logrus.Entry) error {
+ t.mu.Lock()
+ defer t.mu.Unlock()
t.Entries = append(t.Entries, e)
return nil
}
@@ -51,17 +61,35 @@ func (t *Hook) Levels() []logrus.Level {
}
// LastEntry returns the last entry that was logged or nil.
-func (t *Hook) LastEntry() (l *logrus.Entry) {
-
- if i := len(t.Entries) - 1; i < 0 {
+func (t *Hook) LastEntry() *logrus.Entry {
+ t.mu.RLock()
+ defer t.mu.RUnlock()
+ i := len(t.Entries) - 1
+ if i < 0 {
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.
func (t *Hook) Reset() {
+ t.mu.Lock()
+ defer t.mu.Unlock()
t.Entries = make([]*logrus.Entry, 0)
}
diff --git a/hooks/test/test_test.go b/hooks/test/test_test.go
index d69455b..3f55cfe 100644
--- a/hooks/test/test_test.go
+++ b/hooks/test/test_test.go
@@ -3,7 +3,7 @@ package test
import (
"testing"
- "github.com/Sirupsen/logrus"
+ "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
diff --git a/json_formatter.go b/json_formatter.go
index 8bdac89..15a64e4 100644
--- a/json_formatter.go
+++ b/json_formatter.go
@@ -6,8 +6,11 @@ import (
)
type fieldKey string
+
+// FieldMap allows customization of the key names for default fields.
type FieldMap map[fieldKey]string
+// Default key names for the default fields
const (
FieldKeyMsg = "msg"
FieldKeyLevel = "level"
@@ -23,6 +26,7 @@ func (f FieldMap) resolve(key fieldKey) string {
return string(key)
}
+// JSONFormatter formats logs into parsable json
type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
TimestampFormat string
@@ -30,7 +34,7 @@ type JSONFormatter struct {
// DisableTimestamp allows disabling automatic timestamps in output
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:
// formatter := &JSONFormatter{
// FieldMap: FieldMap{
@@ -43,13 +47,14 @@ type JSONFormatter struct {
FieldMap FieldMap
}
+// Format renders a single log entry
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+4)
for k, v := range entry.Data {
switch v := v.(type) {
case error:
// Otherwise errors are ignored by `encoding/json`
- // https://github.com/Sirupsen/logrus/issues/137
+ // https://github.com/sirupsen/logrus/issues/137
data[k] = v.Error()
default:
data[k] = v
@@ -60,7 +65,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
- timestampFormat = DefaultTimestampFormat
+ timestampFormat = defaultTimestampFormat
}
if !f.DisableTimestamp {
diff --git a/logger.go b/logger.go
index ddab68a..880d574 100644
--- a/logger.go
+++ b/logger.go
@@ -4,6 +4,7 @@ import (
"io"
"os"
"sync"
+ "sync/atomic"
)
type Logger struct {
@@ -28,7 +29,7 @@ type Logger struct {
// 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
- // logged. `logrus.Debug` is useful in
+ // logged.
Level Level
// Used to sync writing to the log. Locking is enabled by Default
mu MutexWrap
@@ -117,7 +118,7 @@ func (logger *Logger) WithError(err error) *Entry {
}
func (logger *Logger) Debugf(format string, args ...interface{}) {
- if logger.Level >= DebugLevel {
+ if logger.level() >= DebugLevel {
entry := logger.newEntry()
entry.Debugf(format, args...)
logger.releaseEntry(entry)
@@ -125,7 +126,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) {
}
func (logger *Logger) Infof(format string, args ...interface{}) {
- if logger.Level >= InfoLevel {
+ if logger.level() >= InfoLevel {
entry := logger.newEntry()
entry.Infof(format, args...)
logger.releaseEntry(entry)
@@ -139,7 +140,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
}
func (logger *Logger) Warnf(format string, args ...interface{}) {
- if logger.Level >= WarnLevel {
+ if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnf(format, args...)
logger.releaseEntry(entry)
@@ -147,7 +148,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) {
}
func (logger *Logger) Warningf(format string, args ...interface{}) {
- if logger.Level >= WarnLevel {
+ if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnf(format, args...)
logger.releaseEntry(entry)
@@ -155,7 +156,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) {
}
func (logger *Logger) Errorf(format string, args ...interface{}) {
- if logger.Level >= ErrorLevel {
+ if logger.level() >= ErrorLevel {
entry := logger.newEntry()
entry.Errorf(format, args...)
logger.releaseEntry(entry)
@@ -163,7 +164,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) {
}
func (logger *Logger) Fatalf(format string, args ...interface{}) {
- if logger.Level >= FatalLevel {
+ if logger.level() >= FatalLevel {
entry := logger.newEntry()
entry.Fatalf(format, args...)
logger.releaseEntry(entry)
@@ -172,7 +173,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) {
}
func (logger *Logger) Panicf(format string, args ...interface{}) {
- if logger.Level >= PanicLevel {
+ if logger.level() >= PanicLevel {
entry := logger.newEntry()
entry.Panicf(format, args...)
logger.releaseEntry(entry)
@@ -180,7 +181,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
}
func (logger *Logger) Debug(args ...interface{}) {
- if logger.Level >= DebugLevel {
+ if logger.level() >= DebugLevel {
entry := logger.newEntry()
entry.Debug(args...)
logger.releaseEntry(entry)
@@ -188,7 +189,7 @@ func (logger *Logger) Debug(args ...interface{}) {
}
func (logger *Logger) Info(args ...interface{}) {
- if logger.Level >= InfoLevel {
+ if logger.level() >= InfoLevel {
entry := logger.newEntry()
entry.Info(args...)
logger.releaseEntry(entry)
@@ -202,7 +203,7 @@ func (logger *Logger) Print(args ...interface{}) {
}
func (logger *Logger) Warn(args ...interface{}) {
- if logger.Level >= WarnLevel {
+ if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warn(args...)
logger.releaseEntry(entry)
@@ -210,7 +211,7 @@ func (logger *Logger) Warn(args ...interface{}) {
}
func (logger *Logger) Warning(args ...interface{}) {
- if logger.Level >= WarnLevel {
+ if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warn(args...)
logger.releaseEntry(entry)
@@ -218,7 +219,7 @@ func (logger *Logger) Warning(args ...interface{}) {
}
func (logger *Logger) Error(args ...interface{}) {
- if logger.Level >= ErrorLevel {
+ if logger.level() >= ErrorLevel {
entry := logger.newEntry()
entry.Error(args...)
logger.releaseEntry(entry)
@@ -226,7 +227,7 @@ func (logger *Logger) Error(args ...interface{}) {
}
func (logger *Logger) Fatal(args ...interface{}) {
- if logger.Level >= FatalLevel {
+ if logger.level() >= FatalLevel {
entry := logger.newEntry()
entry.Fatal(args...)
logger.releaseEntry(entry)
@@ -235,7 +236,7 @@ func (logger *Logger) Fatal(args ...interface{}) {
}
func (logger *Logger) Panic(args ...interface{}) {
- if logger.Level >= PanicLevel {
+ if logger.level() >= PanicLevel {
entry := logger.newEntry()
entry.Panic(args...)
logger.releaseEntry(entry)
@@ -243,7 +244,7 @@ func (logger *Logger) Panic(args ...interface{}) {
}
func (logger *Logger) Debugln(args ...interface{}) {
- if logger.Level >= DebugLevel {
+ if logger.level() >= DebugLevel {
entry := logger.newEntry()
entry.Debugln(args...)
logger.releaseEntry(entry)
@@ -251,7 +252,7 @@ func (logger *Logger) Debugln(args ...interface{}) {
}
func (logger *Logger) Infoln(args ...interface{}) {
- if logger.Level >= InfoLevel {
+ if logger.level() >= InfoLevel {
entry := logger.newEntry()
entry.Infoln(args...)
logger.releaseEntry(entry)
@@ -265,7 +266,7 @@ func (logger *Logger) Println(args ...interface{}) {
}
func (logger *Logger) Warnln(args ...interface{}) {
- if logger.Level >= WarnLevel {
+ if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnln(args...)
logger.releaseEntry(entry)
@@ -273,7 +274,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
}
func (logger *Logger) Warningln(args ...interface{}) {
- if logger.Level >= WarnLevel {
+ if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnln(args...)
logger.releaseEntry(entry)
@@ -281,7 +282,7 @@ func (logger *Logger) Warningln(args ...interface{}) {
}
func (logger *Logger) Errorln(args ...interface{}) {
- if logger.Level >= ErrorLevel {
+ if logger.level() >= ErrorLevel {
entry := logger.newEntry()
entry.Errorln(args...)
logger.releaseEntry(entry)
@@ -289,7 +290,7 @@ func (logger *Logger) Errorln(args ...interface{}) {
}
func (logger *Logger) Fatalln(args ...interface{}) {
- if logger.Level >= FatalLevel {
+ if logger.level() >= FatalLevel {
entry := logger.newEntry()
entry.Fatalln(args...)
logger.releaseEntry(entry)
@@ -298,7 +299,7 @@ func (logger *Logger) Fatalln(args ...interface{}) {
}
func (logger *Logger) Panicln(args ...interface{}) {
- if logger.Level >= PanicLevel {
+ if logger.level() >= PanicLevel {
entry := logger.newEntry()
entry.Panicln(args...)
logger.releaseEntry(entry)
@@ -311,3 +312,11 @@ func (logger *Logger) Panicln(args ...interface{}) {
func (logger *Logger) SetNoLock() {
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))
+}
diff --git a/logrus.go b/logrus.go
index e596691..dd38999 100644
--- a/logrus.go
+++ b/logrus.go
@@ -10,7 +10,7 @@ import (
type Fields map[string]interface{}
// Level type
-type Level uint8
+type Level uint32
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
func (level Level) String() string {
diff --git a/logrus_test.go b/logrus_test.go
index 7b5c2c4..e724cc8 100644
--- a/logrus_test.go
+++ b/logrus_test.go
@@ -478,3 +478,28 @@ func TestLogrusInterface(t *testing.T) {
e := logger.WithField("another", "value")
fn(e)
}
+
+// Implements io.Writer using channels for synchronization, so we can wait on
+// the Entry.Writer goroutine to write in a non-racey way. This does assume that
+// there is a single call to Logger.Out for each message.
+type channelWriter chan []byte
+
+func (cw channelWriter) Write(p []byte) (int, error) {
+ cw <- p
+ return len(p), nil
+}
+
+func TestEntryWriter(t *testing.T) {
+ cw := channelWriter(make(chan []byte, 1))
+ log := New()
+ log.Out = cw
+ log.Formatter = new(JSONFormatter)
+ 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")
+}
diff --git a/terminal_appengine.go b/terminal_appengine.go
deleted file mode 100644
index 632ecbe..0000000
--- a/terminal_appengine.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// +build appengine
-
-package logrus
-
-// IsTerminal returns true if stderr's file descriptor is a terminal.
-func IsTerminal(f io.Writer) bool {
- return true
-}
diff --git a/terminal_bsd.go b/terminal_bsd.go
index 5f6be4d..d7b3893 100644
--- a/terminal_bsd.go
+++ b/terminal_bsd.go
@@ -3,8 +3,8 @@
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
diff --git a/terminal_linux.go b/terminal_linux.go
index 308160c..88d7298 100644
--- a/terminal_linux.go
+++ b/terminal_linux.go
@@ -7,8 +7,8 @@
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
diff --git a/terminal_notwindows.go b/terminal_notwindows.go
deleted file mode 100644
index 190297a..0000000
--- a/terminal_notwindows.go
+++ /dev/null
@@ -1,28 +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 (
- "io"
- "os"
- "syscall"
- "unsafe"
-)
-
-// IsTerminal returns true if stderr's file descriptor is a terminal.
-func IsTerminal(f io.Writer) bool {
- var termios Termios
- switch v := f.(type) {
- case *os.File:
- _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
- return err == 0
- default:
- return false
- }
-}
diff --git a/terminal_solaris.go b/terminal_solaris.go
deleted file mode 100644
index f3d6f96..0000000
--- a/terminal_solaris.go
+++ /dev/null
@@ -1,21 +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(f io.Writer) bool {
- var termios Termios
- switch v := f.(type) {
- case *os.File:
- _, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
- return err == nil
- default:
- return false
- }
-}
diff --git a/terminal_windows.go b/terminal_windows.go
deleted file mode 100644
index 97ad4e0..0000000
--- a/terminal_windows.go
+++ /dev/null
@@ -1,33 +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 (
- "io"
- "os"
- "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(f io.Writer) bool {
- switch v := f.(type) {
- case *os.File:
- var st uint32
- r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
- return r != 0 && e == 0
- default:
- return false
- }
-}
diff --git a/text_formatter.go b/text_formatter.go
index 96dd354..2537bf2 100644
--- a/text_formatter.go
+++ b/text_formatter.go
@@ -3,10 +3,14 @@ package logrus
import (
"bytes"
"fmt"
+ "io"
+ "os"
"sort"
"strings"
"sync"
"time"
+
+ "golang.org/x/crypto/ssh/terminal"
)
const (
@@ -14,7 +18,7 @@ const (
red = 31
green = 32
yellow = 33
- blue = 34
+ blue = 36
gray = 37
)
@@ -26,6 +30,7 @@ func init() {
baseTimestamp = time.Now()
}
+// TextFormatter formats logs into text
type TextFormatter struct {
// Set to true to bypass checking for a TTY before outputting colors.
ForceColors bool
@@ -49,11 +54,31 @@ type TextFormatter struct {
// be desired.
DisableSorting bool
+ // QuoteEmptyFields will wrap empty fields in quotes if true
+ QuoteEmptyFields bool
+
// Whether the logger's out is to a terminal
- isTerminal bool
- terminalOnce sync.Once
+ 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) {
var b *bytes.Buffer
keys := make([]string, 0, len(entry.Data))
@@ -72,17 +97,13 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
prefixFieldClashes(entry.Data, entry.HasCaller())
- f.terminalOnce.Do(func() {
- if entry.Logger != nil {
- f.isTerminal = IsTerminal(entry.Logger.Out)
- }
- })
+ f.Do(func() { f.init(entry) })
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
- timestampFormat = DefaultTimestampFormat
+ timestampFormat = defaultTimestampFormat
}
if isColored {
f.printColored(b, entry, keys, timestampFormat)
@@ -141,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 {
if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
- ch == '-' || ch == '.') {
+ ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
return true
}
}
@@ -154,29 +178,23 @@ func needsQuoting(text string) bool {
}
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
-
+ if b.Len() > 0 {
+ b.WriteByte(' ')
+ }
b.WriteString(key)
b.WriteByte('=')
f.appendValue(b, value)
- b.WriteByte(' ')
}
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
- switch value := value.(type) {
- case string:
- if !needsQuoting(value) {
- b.WriteString(value)
- } else {
- fmt.Fprintf(b, "%q", value)
- }
- case error:
- errmsg := value.Error()
- if !needsQuoting(errmsg) {
- b.WriteString(errmsg)
- } else {
- fmt.Fprintf(b, "%q", errmsg)
- }
- default:
- fmt.Fprint(b, value)
+ stringVal, ok := value.(string)
+ if !ok {
+ stringVal = fmt.Sprint(value)
+ }
+
+ if !f.needsQuoting(stringVal) {
+ b.WriteString(stringVal)
+ } else {
+ b.WriteString(fmt.Sprintf("%q", stringVal))
}
}
diff --git a/text_formatter_test.go b/text_formatter_test.go
index 107703f..d93b931 100644
--- a/text_formatter_test.go
+++ b/text_formatter_test.go
@@ -3,18 +3,38 @@ package logrus
import (
"bytes"
"errors"
+ "fmt"
+ "strings"
"testing"
"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) {
tf := &TextFormatter{DisableColors: true}
checkQuoting := func(q bool, value interface{}) {
b, _ := tf.Format(WithField("test", value))
idx := bytes.Index(b, ([]byte)("test="))
- cont := bytes.Contains(b[idx+5:], []byte{'"'})
+ cont := bytes.Contains(b[idx+5:], []byte("\""))
if cont != q {
if q {
t.Errorf("quoting expected for: %#v", value)
@@ -24,14 +44,67 @@ func TestQuoting(t *testing.T) {
}
}
+ checkQuoting(false, "")
checkQuoting(false, "abcd")
checkQuoting(false, "v1.0")
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(false, errors.New("invalid"))
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) {
@@ -40,10 +113,7 @@ func TestTimestampFormat(t *testing.T) {
customStr, _ := customFormatter.Format(WithField("test", "test"))
timeStart := bytes.Index(customStr, ([]byte)("time="))
timeEnd := bytes.Index(customStr, ([]byte)("level="))
- timeStr := customStr[timeStart+5 : timeEnd-1]
- if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
- timeStr = timeStr[1 : len(timeStr)-1]
- }
+ timeStr := customStr[timeStart+5+len("\"") : timeEnd-1-len("\"")]
if format == "" {
format = time.RFC3339
}
diff --git a/writer.go b/writer.go
index f74d2aa..7bdebed 100644
--- a/writer.go
+++ b/writer.go
@@ -11,39 +11,48 @@ func (logger *Logger) Writer() *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()
var printFunc func(args ...interface{})
+
switch level {
case DebugLevel:
- printFunc = logger.Debug
+ printFunc = entry.Debug
case InfoLevel:
- printFunc = logger.Info
+ printFunc = entry.Info
case WarnLevel:
- printFunc = logger.Warn
+ printFunc = entry.Warn
case ErrorLevel:
- printFunc = logger.Error
+ printFunc = entry.Error
case FatalLevel:
- printFunc = logger.Fatal
+ printFunc = entry.Fatal
case PanicLevel:
- printFunc = logger.Panic
+ printFunc = entry.Panic
default:
- printFunc = logger.Print
+ printFunc = entry.Print
}
- go logger.writerScanner(reader, printFunc)
+ go entry.writerScanner(reader, printFunc)
runtime.SetFinalizer(writer, writerFinalizer)
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)
for scanner.Scan() {
printFunc(scanner.Text())
}
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()
}