| ... | ... |
@@ -23,6 +23,7 @@ import ( |
| 23 | 23 |
"text/template" |
| 24 | 24 |
"time" |
| 25 | 25 |
|
| 26 |
+ log "github.com/Sirupsen/logrus" |
|
| 26 | 27 |
"github.com/docker/docker/api" |
| 27 | 28 |
"github.com/docker/docker/dockerversion" |
| 28 | 29 |
"github.com/docker/docker/engine" |
| ... | ... |
@@ -30,7 +31,6 @@ import ( |
| 30 | 30 |
"github.com/docker/docker/nat" |
| 31 | 31 |
"github.com/docker/docker/opts" |
| 32 | 32 |
"github.com/docker/docker/pkg/archive" |
| 33 |
- "github.com/docker/docker/pkg/log" |
|
| 34 | 33 |
flag "github.com/docker/docker/pkg/mflag" |
| 35 | 34 |
"github.com/docker/docker/pkg/parsers" |
| 36 | 35 |
"github.com/docker/docker/pkg/parsers/filters" |
| ... | ... |
@@ -11,9 +11,9 @@ import ( |
| 11 | 11 |
"runtime" |
| 12 | 12 |
"strings" |
| 13 | 13 |
|
| 14 |
+ log "github.com/Sirupsen/logrus" |
|
| 14 | 15 |
"github.com/docker/docker/api" |
| 15 | 16 |
"github.com/docker/docker/dockerversion" |
| 16 |
- "github.com/docker/docker/pkg/log" |
|
| 17 | 17 |
"github.com/docker/docker/pkg/promise" |
| 18 | 18 |
"github.com/docker/docker/pkg/stdcopy" |
| 19 | 19 |
"github.com/docker/docker/pkg/term" |
| ... | ... |
@@ -16,10 +16,10 @@ import ( |
| 16 | 16 |
"strings" |
| 17 | 17 |
"syscall" |
| 18 | 18 |
|
| 19 |
+ log "github.com/Sirupsen/logrus" |
|
| 19 | 20 |
"github.com/docker/docker/api" |
| 20 | 21 |
"github.com/docker/docker/dockerversion" |
| 21 | 22 |
"github.com/docker/docker/engine" |
| 22 |
- "github.com/docker/docker/pkg/log" |
|
| 23 | 23 |
"github.com/docker/docker/pkg/stdcopy" |
| 24 | 24 |
"github.com/docker/docker/pkg/term" |
| 25 | 25 |
"github.com/docker/docker/registry" |
| ... | ... |
@@ -23,10 +23,10 @@ import ( |
| 23 | 23 |
"github.com/docker/libcontainer/user" |
| 24 | 24 |
"github.com/gorilla/mux" |
| 25 | 25 |
|
| 26 |
+ log "github.com/Sirupsen/logrus" |
|
| 26 | 27 |
"github.com/docker/docker/api" |
| 27 | 28 |
"github.com/docker/docker/engine" |
| 28 | 29 |
"github.com/docker/docker/pkg/listenbuffer" |
| 29 |
- "github.com/docker/docker/pkg/log" |
|
| 30 | 30 |
"github.com/docker/docker/pkg/parsers" |
| 31 | 31 |
"github.com/docker/docker/pkg/stdcopy" |
| 32 | 32 |
"github.com/docker/docker/pkg/systemd" |
| ... | ... |
@@ -27,10 +27,10 @@ import ( |
| 27 | 27 |
"path" |
| 28 | 28 |
"strings" |
| 29 | 29 |
|
| 30 |
+ log "github.com/Sirupsen/logrus" |
|
| 30 | 31 |
"github.com/docker/docker/builder/parser" |
| 31 | 32 |
"github.com/docker/docker/daemon" |
| 32 | 33 |
"github.com/docker/docker/engine" |
| 33 |
- "github.com/docker/docker/pkg/log" |
|
| 34 | 34 |
"github.com/docker/docker/pkg/tarsum" |
| 35 | 35 |
"github.com/docker/docker/registry" |
| 36 | 36 |
"github.com/docker/docker/runconfig" |
| ... | ... |
@@ -18,11 +18,11 @@ import ( |
| 18 | 18 |
"syscall" |
| 19 | 19 |
"time" |
| 20 | 20 |
|
| 21 |
+ log "github.com/Sirupsen/logrus" |
|
| 21 | 22 |
"github.com/docker/docker/builder/parser" |
| 22 | 23 |
"github.com/docker/docker/daemon" |
| 23 | 24 |
imagepkg "github.com/docker/docker/image" |
| 24 | 25 |
"github.com/docker/docker/pkg/archive" |
| 25 |
- "github.com/docker/docker/pkg/log" |
|
| 26 | 26 |
"github.com/docker/docker/pkg/parsers" |
| 27 | 27 |
"github.com/docker/docker/pkg/symlink" |
| 28 | 28 |
"github.com/docker/docker/pkg/system" |
| ... | ... |
@@ -6,10 +6,10 @@ import ( |
| 6 | 6 |
"os" |
| 7 | 7 |
"time" |
| 8 | 8 |
|
| 9 |
+ log "github.com/Sirupsen/logrus" |
|
| 9 | 10 |
"github.com/docker/docker/engine" |
| 10 | 11 |
"github.com/docker/docker/pkg/ioutils" |
| 11 | 12 |
"github.com/docker/docker/pkg/jsonlog" |
| 12 |
- "github.com/docker/docker/pkg/log" |
|
| 13 | 13 |
"github.com/docker/docker/pkg/promise" |
| 14 | 14 |
"github.com/docker/docker/utils" |
| 15 | 15 |
) |
| ... | ... |
@@ -17,6 +17,7 @@ import ( |
| 17 | 17 |
"github.com/docker/libcontainer/devices" |
| 18 | 18 |
"github.com/docker/libcontainer/label" |
| 19 | 19 |
|
| 20 |
+ log "github.com/Sirupsen/logrus" |
|
| 20 | 21 |
"github.com/docker/docker/daemon/execdriver" |
| 21 | 22 |
"github.com/docker/docker/engine" |
| 22 | 23 |
"github.com/docker/docker/image" |
| ... | ... |
@@ -25,7 +26,6 @@ import ( |
| 25 | 25 |
"github.com/docker/docker/pkg/archive" |
| 26 | 26 |
"github.com/docker/docker/pkg/broadcastwriter" |
| 27 | 27 |
"github.com/docker/docker/pkg/ioutils" |
| 28 |
- "github.com/docker/docker/pkg/log" |
|
| 29 | 28 |
"github.com/docker/docker/pkg/networkfs/etchosts" |
| 30 | 29 |
"github.com/docker/docker/pkg/networkfs/resolvconf" |
| 31 | 30 |
"github.com/docker/docker/pkg/promise" |
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
|
| 15 | 15 |
"github.com/docker/libcontainer/label" |
| 16 | 16 |
|
| 17 |
+ log "github.com/Sirupsen/logrus" |
|
| 17 | 18 |
"github.com/docker/docker/daemon/execdriver" |
| 18 | 19 |
"github.com/docker/docker/daemon/execdriver/execdrivers" |
| 19 | 20 |
"github.com/docker/docker/daemon/execdriver/lxc" |
| ... | ... |
@@ -29,7 +30,6 @@ import ( |
| 29 | 29 |
"github.com/docker/docker/pkg/broadcastwriter" |
| 30 | 30 |
"github.com/docker/docker/pkg/graphdb" |
| 31 | 31 |
"github.com/docker/docker/pkg/ioutils" |
| 32 |
- "github.com/docker/docker/pkg/log" |
|
| 33 | 32 |
"github.com/docker/docker/pkg/namesgenerator" |
| 34 | 33 |
"github.com/docker/docker/pkg/parsers" |
| 35 | 34 |
"github.com/docker/docker/pkg/parsers/kernel" |
| ... | ... |
@@ -304,7 +304,7 @@ func (daemon *Daemon) restore() error {
|
| 304 | 304 |
) |
| 305 | 305 |
|
| 306 | 306 |
if !debug {
|
| 307 |
- log.Infof("Loading containers: ")
|
|
| 307 |
+ log.Infof("Loading containers: start.")
|
|
| 308 | 308 |
} |
| 309 | 309 |
dir, err := ioutil.ReadDir(daemon.repository) |
| 310 | 310 |
if err != nil {
|
| ... | ... |
@@ -392,7 +392,8 @@ func (daemon *Daemon) restore() error {
|
| 392 | 392 |
} |
| 393 | 393 |
|
| 394 | 394 |
if !debug {
|
| 395 |
- log.Infof(": done.")
|
|
| 395 |
+ fmt.Println() |
|
| 396 |
+ log.Infof("Loading containers: done.")
|
|
| 396 | 397 |
} |
| 397 | 398 |
|
| 398 | 399 |
return nil |
| ... | ... |
@@ -3,10 +3,10 @@ |
| 3 | 3 |
package daemon |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ log "github.com/Sirupsen/logrus" |
|
| 6 | 7 |
"github.com/docker/docker/daemon/graphdriver" |
| 7 | 8 |
"github.com/docker/docker/daemon/graphdriver/aufs" |
| 8 | 9 |
"github.com/docker/docker/graph" |
| 9 |
- "github.com/docker/docker/pkg/log" |
|
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
// Given the graphdriver ad, if it is aufs, then migrate it. |
| ... | ... |
@@ -9,12 +9,12 @@ import ( |
| 9 | 9 |
"strings" |
| 10 | 10 |
"sync" |
| 11 | 11 |
|
| 12 |
+ log "github.com/Sirupsen/logrus" |
|
| 12 | 13 |
"github.com/docker/docker/daemon/execdriver" |
| 13 | 14 |
"github.com/docker/docker/daemon/execdriver/lxc" |
| 14 | 15 |
"github.com/docker/docker/engine" |
| 15 | 16 |
"github.com/docker/docker/pkg/broadcastwriter" |
| 16 | 17 |
"github.com/docker/docker/pkg/ioutils" |
| 17 |
- "github.com/docker/docker/pkg/log" |
|
| 18 | 18 |
"github.com/docker/docker/pkg/promise" |
| 19 | 19 |
"github.com/docker/docker/runconfig" |
| 20 | 20 |
"github.com/docker/docker/utils" |
| ... | ... |
@@ -17,8 +17,8 @@ import ( |
| 17 | 17 |
|
| 18 | 18 |
"github.com/kr/pty" |
| 19 | 19 |
|
| 20 |
+ log "github.com/Sirupsen/logrus" |
|
| 20 | 21 |
"github.com/docker/docker/daemon/execdriver" |
| 21 |
- "github.com/docker/docker/pkg/log" |
|
| 22 | 22 |
"github.com/docker/docker/pkg/term" |
| 23 | 23 |
"github.com/docker/docker/utils" |
| 24 | 24 |
"github.com/docker/libcontainer/cgroups" |
| ... | ... |
@@ -30,9 +30,9 @@ import ( |
| 30 | 30 |
"sync" |
| 31 | 31 |
"syscall" |
| 32 | 32 |
|
| 33 |
+ log "github.com/Sirupsen/logrus" |
|
| 33 | 34 |
"github.com/docker/docker/daemon/graphdriver" |
| 34 | 35 |
"github.com/docker/docker/pkg/archive" |
| 35 |
- "github.com/docker/docker/pkg/log" |
|
| 36 | 36 |
mountpk "github.com/docker/docker/pkg/mount" |
| 37 | 37 |
"github.com/docker/docker/utils" |
| 38 | 38 |
"github.com/docker/libcontainer/label" |
| ... | ... |
@@ -18,8 +18,8 @@ import ( |
| 18 | 18 |
"syscall" |
| 19 | 19 |
"time" |
| 20 | 20 |
|
| 21 |
+ log "github.com/Sirupsen/logrus" |
|
| 21 | 22 |
"github.com/docker/docker/daemon/graphdriver" |
| 22 |
- "github.com/docker/docker/pkg/log" |
|
| 23 | 23 |
"github.com/docker/docker/pkg/parsers" |
| 24 | 24 |
"github.com/docker/docker/pkg/units" |
| 25 | 25 |
"github.com/docker/libcontainer/label" |
| ... | ... |
@@ -4,9 +4,9 @@ import ( |
| 4 | 4 |
"os" |
| 5 | 5 |
"runtime" |
| 6 | 6 |
|
| 7 |
+ log "github.com/Sirupsen/logrus" |
|
| 7 | 8 |
"github.com/docker/docker/dockerversion" |
| 8 | 9 |
"github.com/docker/docker/engine" |
| 9 |
- "github.com/docker/docker/pkg/log" |
|
| 10 | 10 |
"github.com/docker/docker/pkg/parsers/kernel" |
| 11 | 11 |
"github.com/docker/docker/pkg/parsers/operatingsystem" |
| 12 | 12 |
"github.com/docker/docker/registry" |
| ... | ... |
@@ -8,9 +8,9 @@ import ( |
| 8 | 8 |
"os" |
| 9 | 9 |
"strconv" |
| 10 | 10 |
|
| 11 |
+ log "github.com/Sirupsen/logrus" |
|
| 11 | 12 |
"github.com/docker/docker/engine" |
| 12 | 13 |
"github.com/docker/docker/pkg/jsonlog" |
| 13 |
- "github.com/docker/docker/pkg/log" |
|
| 14 | 14 |
"github.com/docker/docker/pkg/tailfile" |
| 15 | 15 |
"github.com/docker/docker/pkg/timeutils" |
| 16 | 16 |
) |
| ... | ... |
@@ -8,13 +8,13 @@ import ( |
| 8 | 8 |
"strings" |
| 9 | 9 |
"sync" |
| 10 | 10 |
|
| 11 |
+ log "github.com/Sirupsen/logrus" |
|
| 11 | 12 |
"github.com/docker/docker/daemon/networkdriver" |
| 12 | 13 |
"github.com/docker/docker/daemon/networkdriver/ipallocator" |
| 13 | 14 |
"github.com/docker/docker/daemon/networkdriver/portallocator" |
| 14 | 15 |
"github.com/docker/docker/daemon/networkdriver/portmapper" |
| 15 | 16 |
"github.com/docker/docker/engine" |
| 16 | 17 |
"github.com/docker/docker/pkg/iptables" |
| 17 |
- "github.com/docker/docker/pkg/log" |
|
| 18 | 18 |
"github.com/docker/docker/pkg/networkfs/resolvconf" |
| 19 | 19 |
"github.com/docker/docker/pkg/parsers/kernel" |
| 20 | 20 |
"github.com/docker/libcontainer/netlink" |
| ... | ... |
@@ -10,9 +10,9 @@ import ( |
| 10 | 10 |
"strings" |
| 11 | 11 |
"syscall" |
| 12 | 12 |
|
| 13 |
+ log "github.com/Sirupsen/logrus" |
|
| 13 | 14 |
"github.com/docker/docker/daemon/execdriver" |
| 14 | 15 |
"github.com/docker/docker/pkg/archive" |
| 15 |
- "github.com/docker/docker/pkg/log" |
|
| 16 | 16 |
"github.com/docker/docker/pkg/symlink" |
| 17 | 17 |
"github.com/docker/docker/volumes" |
| 18 | 18 |
) |
| ... | ... |
@@ -3,6 +3,7 @@ |
| 3 | 3 |
package main |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ log "github.com/Sirupsen/logrus" |
|
| 6 | 7 |
"github.com/docker/docker/builder" |
| 7 | 8 |
"github.com/docker/docker/builtins" |
| 8 | 9 |
"github.com/docker/docker/daemon" |
| ... | ... |
@@ -10,7 +11,6 @@ import ( |
| 10 | 10 |
_ "github.com/docker/docker/daemon/execdriver/native" |
| 11 | 11 |
"github.com/docker/docker/dockerversion" |
| 12 | 12 |
"github.com/docker/docker/engine" |
| 13 |
- "github.com/docker/docker/pkg/log" |
|
| 14 | 13 |
flag "github.com/docker/docker/pkg/mflag" |
| 15 | 14 |
"github.com/docker/docker/pkg/signal" |
| 16 | 15 |
) |
| ... | ... |
@@ -28,6 +28,7 @@ func main() {
|
| 28 | 28 |
if reexec.Init() {
|
| 29 | 29 |
return |
| 30 | 30 |
} |
| 31 |
+ |
|
| 31 | 32 |
flag.Parse() |
| 32 | 33 |
// FIXME: validate daemon flags here |
| 33 | 34 |
|
| ... | ... |
@@ -39,6 +40,8 @@ func main() {
|
| 39 | 39 |
os.Setenv("DEBUG", "1")
|
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
+ initLogging(*flDebug) |
|
| 43 |
+ |
|
| 42 | 44 |
if len(flHosts) == 0 {
|
| 43 | 45 |
defaultHost := os.Getenv("DOCKER_HOST")
|
| 44 | 46 |
if defaultHost == "" || *flDaemon {
|
| 45 | 47 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os" |
|
| 4 |
+ |
|
| 5 |
+ log "github.com/Sirupsen/logrus" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func initLogging(debug bool) {
|
|
| 9 |
+ log.SetOutput(os.Stderr) |
|
| 10 |
+ if debug {
|
|
| 11 |
+ log.SetLevel(log.DebugLevel) |
|
| 12 |
+ } else {
|
|
| 13 |
+ log.SetLevel(log.InfoLevel) |
|
| 14 |
+ } |
|
| 15 |
+} |
| ... | ... |
@@ -6,6 +6,8 @@ import ( |
| 6 | 6 |
"io" |
| 7 | 7 |
"strings" |
| 8 | 8 |
"time" |
| 9 |
+ |
|
| 10 |
+ log "github.com/Sirupsen/logrus" |
|
| 9 | 11 |
) |
| 10 | 12 |
|
| 11 | 13 |
// A job is the fundamental unit of work in the docker engine. |
| ... | ... |
@@ -66,10 +68,12 @@ func (job *Job) Run() error {
|
| 66 | 66 |
return fmt.Errorf("%s: job has already completed", job.Name)
|
| 67 | 67 |
} |
| 68 | 68 |
// Log beginning and end of the job |
| 69 |
- job.Eng.Logf("+job %s", job.CallString())
|
|
| 70 |
- defer func() {
|
|
| 71 |
- job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
|
|
| 72 |
- }() |
|
| 69 |
+ if job.Eng.Logging {
|
|
| 70 |
+ log.Infof("+job %s", job.CallString())
|
|
| 71 |
+ defer func() {
|
|
| 72 |
+ log.Infof("-job %s%s", job.CallString(), job.StatusString())
|
|
| 73 |
+ }() |
|
| 74 |
+ } |
|
| 73 | 75 |
var errorMessage = bytes.NewBuffer(nil) |
| 74 | 76 |
job.Stderr.Add(errorMessage) |
| 75 | 77 |
if job.handler == nil {
|
| ... | ... |
@@ -12,11 +12,11 @@ import ( |
| 12 | 12 |
"syscall" |
| 13 | 13 |
"time" |
| 14 | 14 |
|
| 15 |
+ log "github.com/Sirupsen/logrus" |
|
| 15 | 16 |
"github.com/docker/docker/daemon/graphdriver" |
| 16 | 17 |
"github.com/docker/docker/dockerversion" |
| 17 | 18 |
"github.com/docker/docker/image" |
| 18 | 19 |
"github.com/docker/docker/pkg/archive" |
| 19 |
- "github.com/docker/docker/pkg/log" |
|
| 20 | 20 |
"github.com/docker/docker/pkg/truncindex" |
| 21 | 21 |
"github.com/docker/docker/runconfig" |
| 22 | 22 |
"github.com/docker/docker/utils" |
| ... | ... |
@@ -7,10 +7,10 @@ import ( |
| 7 | 7 |
"os" |
| 8 | 8 |
"path" |
| 9 | 9 |
|
| 10 |
+ log "github.com/Sirupsen/logrus" |
|
| 10 | 11 |
"github.com/docker/docker/engine" |
| 11 | 12 |
"github.com/docker/docker/image" |
| 12 | 13 |
"github.com/docker/docker/pkg/archive" |
| 13 |
- "github.com/docker/docker/pkg/log" |
|
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 | 16 |
// Loads a set of images into the repository. This is the complementary of ImageExport. |
| ... | ... |
@@ -12,9 +12,9 @@ import ( |
| 12 | 12 |
"strings" |
| 13 | 13 |
"time" |
| 14 | 14 |
|
| 15 |
+ log "github.com/Sirupsen/logrus" |
|
| 15 | 16 |
"github.com/docker/docker/engine" |
| 16 | 17 |
"github.com/docker/docker/image" |
| 17 |
- "github.com/docker/docker/pkg/log" |
|
| 18 | 18 |
"github.com/docker/docker/registry" |
| 19 | 19 |
"github.com/docker/docker/utils" |
| 20 | 20 |
"github.com/docker/libtrust" |
| ... | ... |
@@ -7,9 +7,9 @@ import ( |
| 7 | 7 |
"os" |
| 8 | 8 |
"path" |
| 9 | 9 |
|
| 10 |
+ log "github.com/Sirupsen/logrus" |
|
| 10 | 11 |
"github.com/docker/docker/engine" |
| 11 | 12 |
"github.com/docker/docker/pkg/archive" |
| 12 |
- "github.com/docker/docker/pkg/log" |
|
| 13 | 13 |
"github.com/docker/docker/registry" |
| 14 | 14 |
"github.com/docker/docker/utils" |
| 15 | 15 |
) |
| ... | ... |
@@ -53,6 +53,8 @@ clone hg code.google.com/p/gosqlite 74691fb6f837 |
| 53 | 53 |
|
| 54 | 54 |
clone git github.com/docker/libtrust d273ef2565ca |
| 55 | 55 |
|
| 56 |
+clone git github.com/Sirupsen/logrus v0.5.1 |
|
| 57 |
+ |
|
| 56 | 58 |
# get Go tip's archive/tar, for xattr support and improved performance |
| 57 | 59 |
# TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep |
| 58 | 60 |
if [ "$1" = '--go' ]; then |
| ... | ... |
@@ -9,9 +9,9 @@ import ( |
| 9 | 9 |
"testing" |
| 10 | 10 |
"time" |
| 11 | 11 |
|
| 12 |
+ log "github.com/Sirupsen/logrus" |
|
| 12 | 13 |
"github.com/docker/docker/api/client" |
| 13 | 14 |
"github.com/docker/docker/daemon" |
| 14 |
- "github.com/docker/docker/pkg/log" |
|
| 15 | 15 |
"github.com/docker/docker/pkg/term" |
| 16 | 16 |
"github.com/docker/docker/utils" |
| 17 | 17 |
"github.com/docker/libtrust" |
| ... | ... |
@@ -16,12 +16,12 @@ import ( |
| 16 | 16 |
"testing" |
| 17 | 17 |
"time" |
| 18 | 18 |
|
| 19 |
+ log "github.com/Sirupsen/logrus" |
|
| 19 | 20 |
"github.com/docker/docker/daemon" |
| 20 | 21 |
"github.com/docker/docker/engine" |
| 21 | 22 |
"github.com/docker/docker/image" |
| 22 | 23 |
"github.com/docker/docker/nat" |
| 23 | 24 |
"github.com/docker/docker/pkg/ioutils" |
| 24 |
- "github.com/docker/docker/pkg/log" |
|
| 25 | 25 |
"github.com/docker/docker/reexec" |
| 26 | 26 |
"github.com/docker/docker/runconfig" |
| 27 | 27 |
"github.com/docker/docker/utils" |
| ... | ... |
@@ -18,20 +18,23 @@ import ( |
| 18 | 18 |
"github.com/docker/docker/builtins" |
| 19 | 19 |
"github.com/docker/docker/daemon" |
| 20 | 20 |
"github.com/docker/docker/engine" |
| 21 |
- "github.com/docker/docker/pkg/log" |
|
| 22 | 21 |
flag "github.com/docker/docker/pkg/mflag" |
| 23 | 22 |
"github.com/docker/docker/pkg/sysinfo" |
| 24 | 23 |
"github.com/docker/docker/runconfig" |
| 25 | 24 |
"github.com/docker/docker/utils" |
| 26 | 25 |
) |
| 27 | 26 |
|
| 27 |
+type Fataler interface {
|
|
| 28 |
+ Fatal(...interface{})
|
|
| 29 |
+} |
|
| 30 |
+ |
|
| 28 | 31 |
// This file contains utility functions for docker's unit test suite. |
| 29 | 32 |
// It has to be named XXX_test.go, apparently, in other to access private functions |
| 30 | 33 |
// from other XXX_test.go functions. |
| 31 | 34 |
|
| 32 | 35 |
// Create a temporary daemon suitable for unit testing. |
| 33 | 36 |
// Call t.Fatal() at the first error. |
| 34 |
-func mkDaemon(f log.Fataler) *daemon.Daemon {
|
|
| 37 |
+func mkDaemon(f Fataler) *daemon.Daemon {
|
|
| 35 | 38 |
eng := newTestEngine(f, false, "") |
| 36 | 39 |
return mkDaemonFromEngine(eng, f) |
| 37 | 40 |
// FIXME: |
| ... | ... |
@@ -40,7 +43,7 @@ func mkDaemon(f log.Fataler) *daemon.Daemon {
|
| 40 | 40 |
// [...] |
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 |
-func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f log.Fataler, name string) (shortId string) {
|
|
| 43 |
+func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) {
|
|
| 44 | 44 |
job := eng.Job("create", name)
|
| 45 | 45 |
if err := job.ImportEnv(config); err != nil {
|
| 46 | 46 |
f.Fatal(err) |
| ... | ... |
@@ -53,23 +56,23 @@ func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f lo |
| 53 | 53 |
return engine.Tail(outputBuffer, 1) |
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 |
-func createTestContainer(eng *engine.Engine, config *runconfig.Config, f log.Fataler) (shortId string) {
|
|
| 56 |
+func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) {
|
|
| 57 | 57 |
return createNamedTestContainer(eng, config, f, "") |
| 58 | 58 |
} |
| 59 | 59 |
|
| 60 |
-func startContainer(eng *engine.Engine, id string, t log.Fataler) {
|
|
| 60 |
+func startContainer(eng *engine.Engine, id string, t Fataler) {
|
|
| 61 | 61 |
job := eng.Job("start", id)
|
| 62 | 62 |
if err := job.Run(); err != nil {
|
| 63 | 63 |
t.Fatal(err) |
| 64 | 64 |
} |
| 65 | 65 |
} |
| 66 | 66 |
|
| 67 |
-func containerRun(eng *engine.Engine, id string, t log.Fataler) {
|
|
| 67 |
+func containerRun(eng *engine.Engine, id string, t Fataler) {
|
|
| 68 | 68 |
startContainer(eng, id, t) |
| 69 | 69 |
containerWait(eng, id, t) |
| 70 | 70 |
} |
| 71 | 71 |
|
| 72 |
-func containerFileExists(eng *engine.Engine, id, dir string, t log.Fataler) bool {
|
|
| 72 |
+func containerFileExists(eng *engine.Engine, id, dir string, t Fataler) bool {
|
|
| 73 | 73 |
c := getContainer(eng, id, t) |
| 74 | 74 |
if err := c.Mount(); err != nil {
|
| 75 | 75 |
t.Fatal(err) |
| ... | ... |
@@ -84,7 +87,7 @@ func containerFileExists(eng *engine.Engine, id, dir string, t log.Fataler) bool |
| 84 | 84 |
return true |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 |
-func containerAttach(eng *engine.Engine, id string, t log.Fataler) (io.WriteCloser, io.ReadCloser) {
|
|
| 87 |
+func containerAttach(eng *engine.Engine, id string, t Fataler) (io.WriteCloser, io.ReadCloser) {
|
|
| 88 | 88 |
c := getContainer(eng, id, t) |
| 89 | 89 |
i, err := c.StdinPipe() |
| 90 | 90 |
if err != nil {
|
| ... | ... |
@@ -97,31 +100,31 @@ func containerAttach(eng *engine.Engine, id string, t log.Fataler) (io.WriteClos |
| 97 | 97 |
return i, o |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 |
-func containerWait(eng *engine.Engine, id string, t log.Fataler) int {
|
|
| 100 |
+func containerWait(eng *engine.Engine, id string, t Fataler) int {
|
|
| 101 | 101 |
ex, _ := getContainer(eng, id, t).WaitStop(-1 * time.Second) |
| 102 | 102 |
return ex |
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 |
-func containerWaitTimeout(eng *engine.Engine, id string, t log.Fataler) error {
|
|
| 105 |
+func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error {
|
|
| 106 | 106 |
_, err := getContainer(eng, id, t).WaitStop(500 * time.Millisecond) |
| 107 | 107 |
return err |
| 108 | 108 |
} |
| 109 | 109 |
|
| 110 |
-func containerKill(eng *engine.Engine, id string, t log.Fataler) {
|
|
| 110 |
+func containerKill(eng *engine.Engine, id string, t Fataler) {
|
|
| 111 | 111 |
if err := eng.Job("kill", id).Run(); err != nil {
|
| 112 | 112 |
t.Fatal(err) |
| 113 | 113 |
} |
| 114 | 114 |
} |
| 115 | 115 |
|
| 116 |
-func containerRunning(eng *engine.Engine, id string, t log.Fataler) bool {
|
|
| 116 |
+func containerRunning(eng *engine.Engine, id string, t Fataler) bool {
|
|
| 117 | 117 |
return getContainer(eng, id, t).IsRunning() |
| 118 | 118 |
} |
| 119 | 119 |
|
| 120 |
-func containerAssertExists(eng *engine.Engine, id string, t log.Fataler) {
|
|
| 120 |
+func containerAssertExists(eng *engine.Engine, id string, t Fataler) {
|
|
| 121 | 121 |
getContainer(eng, id, t) |
| 122 | 122 |
} |
| 123 | 123 |
|
| 124 |
-func containerAssertNotExists(eng *engine.Engine, id string, t log.Fataler) {
|
|
| 124 |
+func containerAssertNotExists(eng *engine.Engine, id string, t Fataler) {
|
|
| 125 | 125 |
daemon := mkDaemonFromEngine(eng, t) |
| 126 | 126 |
if c := daemon.Get(id); c != nil {
|
| 127 | 127 |
t.Fatal(fmt.Errorf("Container %s should not exist", id))
|
| ... | ... |
@@ -130,7 +133,7 @@ func containerAssertNotExists(eng *engine.Engine, id string, t log.Fataler) {
|
| 130 | 130 |
|
| 131 | 131 |
// assertHttpNotError expect the given response to not have an error. |
| 132 | 132 |
// Otherwise the it causes the test to fail. |
| 133 |
-func assertHttpNotError(r *httptest.ResponseRecorder, t log.Fataler) {
|
|
| 133 |
+func assertHttpNotError(r *httptest.ResponseRecorder, t Fataler) {
|
|
| 134 | 134 |
// Non-error http status are [200, 400) |
| 135 | 135 |
if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest {
|
| 136 | 136 |
t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code))
|
| ... | ... |
@@ -139,14 +142,14 @@ func assertHttpNotError(r *httptest.ResponseRecorder, t log.Fataler) {
|
| 139 | 139 |
|
| 140 | 140 |
// assertHttpError expect the given response to have an error. |
| 141 | 141 |
// Otherwise the it causes the test to fail. |
| 142 |
-func assertHttpError(r *httptest.ResponseRecorder, t log.Fataler) {
|
|
| 142 |
+func assertHttpError(r *httptest.ResponseRecorder, t Fataler) {
|
|
| 143 | 143 |
// Non-error http status are [200, 400) |
| 144 | 144 |
if !(r.Code < http.StatusOK || r.Code >= http.StatusBadRequest) {
|
| 145 | 145 |
t.Fatal(fmt.Errorf("Unexpected http success code: %v", r.Code))
|
| 146 | 146 |
} |
| 147 | 147 |
} |
| 148 | 148 |
|
| 149 |
-func getContainer(eng *engine.Engine, id string, t log.Fataler) *daemon.Container {
|
|
| 149 |
+func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container {
|
|
| 150 | 150 |
daemon := mkDaemonFromEngine(eng, t) |
| 151 | 151 |
c := daemon.Get(id) |
| 152 | 152 |
if c == nil {
|
| ... | ... |
@@ -155,7 +158,7 @@ func getContainer(eng *engine.Engine, id string, t log.Fataler) *daemon.Containe |
| 155 | 155 |
return c |
| 156 | 156 |
} |
| 157 | 157 |
|
| 158 |
-func mkDaemonFromEngine(eng *engine.Engine, t log.Fataler) *daemon.Daemon {
|
|
| 158 |
+func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon {
|
|
| 159 | 159 |
iDaemon := eng.Hack_GetGlobalVar("httpapi.daemon")
|
| 160 | 160 |
if iDaemon == nil {
|
| 161 | 161 |
panic("Legacy daemon field not set in engine")
|
| ... | ... |
@@ -167,7 +170,7 @@ func mkDaemonFromEngine(eng *engine.Engine, t log.Fataler) *daemon.Daemon {
|
| 167 | 167 |
return daemon |
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 |
-func newTestEngine(t log.Fataler, autorestart bool, root string) *engine.Engine {
|
|
| 170 |
+func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
|
|
| 171 | 171 |
if root == "" {
|
| 172 | 172 |
if dir, err := newTestDirectory(unitTestStoreBase); err != nil {
|
| 173 | 173 |
t.Fatal(err) |
| ... | ... |
@@ -200,7 +203,7 @@ func newTestEngine(t log.Fataler, autorestart bool, root string) *engine.Engine |
| 200 | 200 |
return eng |
| 201 | 201 |
} |
| 202 | 202 |
|
| 203 |
-func NewTestEngine(t log.Fataler) *engine.Engine {
|
|
| 203 |
+func NewTestEngine(t Fataler) *engine.Engine {
|
|
| 204 | 204 |
return newTestEngine(t, false, "") |
| 205 | 205 |
} |
| 206 | 206 |
|
| ... | ... |
@@ -18,8 +18,8 @@ import ( |
| 18 | 18 |
|
| 19 | 19 |
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
| 20 | 20 |
|
| 21 |
+ log "github.com/Sirupsen/logrus" |
|
| 21 | 22 |
"github.com/docker/docker/pkg/fileutils" |
| 22 |
- "github.com/docker/docker/pkg/log" |
|
| 23 | 23 |
"github.com/docker/docker/pkg/pools" |
| 24 | 24 |
"github.com/docker/docker/pkg/promise" |
| 25 | 25 |
"github.com/docker/docker/pkg/system" |
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
|
| 13 | 13 |
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
| 14 | 14 |
|
| 15 |
- "github.com/docker/docker/pkg/log" |
|
| 15 |
+ log "github.com/Sirupsen/logrus" |
|
| 16 | 16 |
"github.com/docker/docker/pkg/pools" |
| 17 | 17 |
"github.com/docker/docker/pkg/system" |
| 18 | 18 |
) |
| 16 | 16 |
deleted file mode 100644 |
| ... | ... |
@@ -1,114 +0,0 @@ |
| 1 |
-package log |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io" |
|
| 6 |
- "os" |
|
| 7 |
- "runtime" |
|
| 8 |
- "strings" |
|
| 9 |
- "time" |
|
| 10 |
- |
|
| 11 |
- "github.com/docker/docker/pkg/timeutils" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-type priority int |
|
| 15 |
- |
|
| 16 |
-const ( |
|
| 17 |
- errorFormat = "[%s] [%s] %s:%d %s\n" |
|
| 18 |
- logFormat = "[%s] [%s] %s\n" |
|
| 19 |
- |
|
| 20 |
- fatalPriority priority = iota |
|
| 21 |
- errorPriority |
|
| 22 |
- infoPriority |
|
| 23 |
- debugPriority |
|
| 24 |
-) |
|
| 25 |
- |
|
| 26 |
-// A common interface to access the Fatal method of |
|
| 27 |
-// both testing.B and testing.T. |
|
| 28 |
-type Fataler interface {
|
|
| 29 |
- Fatal(args ...interface{})
|
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-func (p priority) String() string {
|
|
| 33 |
- switch p {
|
|
| 34 |
- case fatalPriority: |
|
| 35 |
- return "fatal" |
|
| 36 |
- case errorPriority: |
|
| 37 |
- return "error" |
|
| 38 |
- case infoPriority: |
|
| 39 |
- return "info" |
|
| 40 |
- case debugPriority: |
|
| 41 |
- return "debug" |
|
| 42 |
- } |
|
| 43 |
- |
|
| 44 |
- return "" |
|
| 45 |
-} |
|
| 46 |
- |
|
| 47 |
-var DefaultLogger = Logger{Out: os.Stdout, Err: os.Stderr}
|
|
| 48 |
- |
|
| 49 |
-// Debug function, if the debug flag is set, then display. Do nothing otherwise |
|
| 50 |
-// If Docker is in damon mode, also send the debug info on the socket |
|
| 51 |
-func Debugf(format string, a ...interface{}) (int, error) {
|
|
| 52 |
- return DefaultLogger.Debugf(format, a...) |
|
| 53 |
-} |
|
| 54 |
- |
|
| 55 |
-func Infof(format string, a ...interface{}) (int, error) {
|
|
| 56 |
- return DefaultLogger.Infof(format, a...) |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-func Errorf(format string, a ...interface{}) (int, error) {
|
|
| 60 |
- return DefaultLogger.Errorf(format, a...) |
|
| 61 |
-} |
|
| 62 |
- |
|
| 63 |
-func Fatal(a ...interface{}) {
|
|
| 64 |
- DefaultLogger.Fatalf("%s", a...)
|
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 |
-func Fatalf(format string, a ...interface{}) {
|
|
| 68 |
- DefaultLogger.Fatalf(format, a...) |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-type Logger struct {
|
|
| 72 |
- Err io.Writer |
|
| 73 |
- Out io.Writer |
|
| 74 |
-} |
|
| 75 |
- |
|
| 76 |
-func (l Logger) Debugf(format string, a ...interface{}) (int, error) {
|
|
| 77 |
- if os.Getenv("DEBUG") != "" {
|
|
| 78 |
- return l.logf(l.Err, debugPriority, format, a...) |
|
| 79 |
- } |
|
| 80 |
- return 0, nil |
|
| 81 |
-} |
|
| 82 |
- |
|
| 83 |
-func (l Logger) Infof(format string, a ...interface{}) (int, error) {
|
|
| 84 |
- return l.logf(l.Out, infoPriority, format, a...) |
|
| 85 |
-} |
|
| 86 |
- |
|
| 87 |
-func (l Logger) Errorf(format string, a ...interface{}) (int, error) {
|
|
| 88 |
- return l.logf(l.Err, errorPriority, format, a...) |
|
| 89 |
-} |
|
| 90 |
- |
|
| 91 |
-func (l Logger) Fatalf(format string, a ...interface{}) {
|
|
| 92 |
- l.logf(l.Err, fatalPriority, format, a...) |
|
| 93 |
- os.Exit(1) |
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-func (l Logger) logf(stream io.Writer, level priority, format string, a ...interface{}) (int, error) {
|
|
| 97 |
- var prefix string |
|
| 98 |
- |
|
| 99 |
- if level <= errorPriority || level == debugPriority {
|
|
| 100 |
- // Retrieve the stack infos |
|
| 101 |
- _, file, line, ok := runtime.Caller(2) |
|
| 102 |
- if !ok {
|
|
| 103 |
- file = "<unknown>" |
|
| 104 |
- line = -1 |
|
| 105 |
- } else {
|
|
| 106 |
- file = file[strings.LastIndex(file, "/")+1:] |
|
| 107 |
- } |
|
| 108 |
- prefix = fmt.Sprintf(errorFormat, time.Now().Format(timeutils.RFC3339NanoFixed), level.String(), file, line, format) |
|
| 109 |
- } else {
|
|
| 110 |
- prefix = fmt.Sprintf(logFormat, time.Now().Format(timeutils.RFC3339NanoFixed), level.String(), format) |
|
| 111 |
- } |
|
| 112 |
- |
|
| 113 |
- return fmt.Fprintf(stream, prefix, a...) |
|
| 114 |
-} |
| 115 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,39 +0,0 @@ |
| 1 |
-package log |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "regexp" |
|
| 6 |
- |
|
| 7 |
- "testing" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-var reRFC3339NanoFixed = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{9}.([0-9]{2}:[0-9]{2})?"
|
|
| 11 |
- |
|
| 12 |
-func TestLogFatalf(t *testing.T) {
|
|
| 13 |
- var output *bytes.Buffer |
|
| 14 |
- |
|
| 15 |
- tests := []struct {
|
|
| 16 |
- Level priority |
|
| 17 |
- Format string |
|
| 18 |
- Values []interface{}
|
|
| 19 |
- ExpectedPattern string |
|
| 20 |
- }{
|
|
| 21 |
- {fatalPriority, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[" + reRFC3339NanoFixed + "\\] \\[fatal\\] testing.go:\\d+ 1 \\+ 1 = 2"},
|
|
| 22 |
- {errorPriority, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[" + reRFC3339NanoFixed + "\\] \\[error\\] testing.go:\\d+ 1 \\+ 1 = 2"},
|
|
| 23 |
- {infoPriority, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[" + reRFC3339NanoFixed + "\\] \\[info\\] 1 \\+ 1 = 2"},
|
|
| 24 |
- {debugPriority, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[" + reRFC3339NanoFixed + "\\] \\[debug\\] testing.go:\\d+ 1 \\+ 1 = 2"},
|
|
| 25 |
- } |
|
| 26 |
- |
|
| 27 |
- for i, test := range tests {
|
|
| 28 |
- output = &bytes.Buffer{}
|
|
| 29 |
- DefaultLogger.logf(output, test.Level, test.Format, test.Values...) |
|
| 30 |
- |
|
| 31 |
- expected := regexp.MustCompile(test.ExpectedPattern) |
|
| 32 |
- if !expected.MatchString(output.String()) {
|
|
| 33 |
- t.Errorf("[%d] Log output does not match expected pattern:\n\tExpected: %s\n\tOutput: %s",
|
|
| 34 |
- i, |
|
| 35 |
- expected.String(), |
|
| 36 |
- output.String()) |
|
| 37 |
- } |
|
| 38 |
- } |
|
| 39 |
-} |
| ... | ... |
@@ -20,10 +20,10 @@ import ( |
| 20 | 20 |
"sync" |
| 21 | 21 |
"syscall" |
| 22 | 22 |
|
| 23 |
+ log "github.com/Sirupsen/logrus" |
|
| 23 | 24 |
"github.com/docker/docker/dockerversion" |
| 24 | 25 |
"github.com/docker/docker/pkg/fileutils" |
| 25 | 26 |
"github.com/docker/docker/pkg/ioutils" |
| 26 |
- "github.com/docker/docker/pkg/log" |
|
| 27 | 27 |
) |
| 28 | 28 |
|
| 29 | 29 |
type KeyValuePair struct {
|
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,21 @@ |
| 0 |
+The MIT License (MIT) |
|
| 1 |
+ |
|
| 2 |
+Copyright (c) 2014 Simon Eskildsen |
|
| 3 |
+ |
|
| 4 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
| 5 |
+of this software and associated documentation files (the "Software"), to deal |
|
| 6 |
+in the Software without restriction, including without limitation the rights |
|
| 7 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
| 8 |
+copies of the Software, and to permit persons to whom the Software is |
|
| 9 |
+furnished to do so, subject to the following conditions: |
|
| 10 |
+ |
|
| 11 |
+The above copyright notice and this permission notice shall be included in |
|
| 12 |
+all copies or substantial portions of the Software. |
|
| 13 |
+ |
|
| 14 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 15 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 16 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 17 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 18 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 19 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
| 20 |
+THE SOFTWARE. |
| 0 | 21 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,336 @@ |
| 0 |
+# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus) |
|
| 1 |
+ |
|
| 2 |
+Logrus is a structured logger for Go (golang), completely API compatible with |
|
| 3 |
+the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not |
|
| 4 |
+yet stable (pre 1.0), the core API is unlikely change much but please version |
|
| 5 |
+control your Logrus to make sure you aren't fetching latest `master` on every |
|
| 6 |
+build.** |
|
| 7 |
+ |
|
| 8 |
+Nicely color-coded in development (when a TTY is attached, otherwise just |
|
| 9 |
+plain text): |
|
| 10 |
+ |
|
| 11 |
+ |
|
| 12 |
+ |
|
| 13 |
+With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash |
|
| 14 |
+or Splunk: |
|
| 15 |
+ |
|
| 16 |
+```json |
|
| 17 |
+{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
|
| 18 |
+ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} |
|
| 19 |
+ |
|
| 20 |
+{"level":"warning","msg":"The group's number increased tremendously!",
|
|
| 21 |
+"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} |
|
| 22 |
+ |
|
| 23 |
+{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
|
| 24 |
+"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} |
|
| 25 |
+ |
|
| 26 |
+{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
|
| 27 |
+"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} |
|
| 28 |
+ |
|
| 29 |
+{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
|
| 30 |
+"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} |
|
| 31 |
+``` |
|
| 32 |
+ |
|
| 33 |
+With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not |
|
| 34 |
+attached, the output is compatible with the |
|
| 35 |
+[l2met](http://r.32k.io/l2met-introduction) format: |
|
| 36 |
+ |
|
| 37 |
+```text |
|
| 38 |
+time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 |
|
| 39 |
+time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 |
|
| 40 |
+time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 |
|
| 41 |
+time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 |
|
| 42 |
+time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 |
|
| 43 |
+``` |
|
| 44 |
+ |
|
| 45 |
+#### Example |
|
| 46 |
+ |
|
| 47 |
+The simplest way to use Logrus is simply the package-level exported logger: |
|
| 48 |
+ |
|
| 49 |
+```go |
|
| 50 |
+package main |
|
| 51 |
+ |
|
| 52 |
+import ( |
|
| 53 |
+ log "github.com/Sirupsen/logrus" |
|
| 54 |
+) |
|
| 55 |
+ |
|
| 56 |
+func main() {
|
|
| 57 |
+ log.WithFields(log.Fields{
|
|
| 58 |
+ "animal": "walrus", |
|
| 59 |
+ }).Info("A walrus appears")
|
|
| 60 |
+} |
|
| 61 |
+``` |
|
| 62 |
+ |
|
| 63 |
+Note that it's completely api-compatible with the stdlib logger, so you can |
|
| 64 |
+replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"` |
|
| 65 |
+and you'll now have the flexibility of Logrus. You can customize it all you |
|
| 66 |
+want: |
|
| 67 |
+ |
|
| 68 |
+```go |
|
| 69 |
+package main |
|
| 70 |
+ |
|
| 71 |
+import ( |
|
| 72 |
+ "os" |
|
| 73 |
+ log "github.com/Sirupsen/logrus" |
|
| 74 |
+ "github.com/Sirupsen/logrus/hooks/airbrake" |
|
| 75 |
+) |
|
| 76 |
+ |
|
| 77 |
+func init() {
|
|
| 78 |
+ // Log as JSON instead of the default ASCII formatter. |
|
| 79 |
+ log.SetFormatter(&log.JSONFormatter{})
|
|
| 80 |
+ |
|
| 81 |
+ // Use the Airbrake hook to report errors that have Error severity or above to |
|
| 82 |
+ // an exception tracker. You can create custom hooks, see the Hooks section. |
|
| 83 |
+ log.AddHook(logrus_airbrake.AirbrakeHook) |
|
| 84 |
+ |
|
| 85 |
+ // Output to stderr instead of stdout, could also be a file. |
|
| 86 |
+ log.SetOutput(os.Stderr) |
|
| 87 |
+ |
|
| 88 |
+ // Only log the warning severity or above. |
|
| 89 |
+ log.SetLevel(log.WarnLevel) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func main() {
|
|
| 93 |
+ log.WithFields(log.Fields{
|
|
| 94 |
+ "animal": "walrus", |
|
| 95 |
+ "size": 10, |
|
| 96 |
+ }).Info("A group of walrus emerges from the ocean")
|
|
| 97 |
+ |
|
| 98 |
+ log.WithFields(log.Fields{
|
|
| 99 |
+ "omg": true, |
|
| 100 |
+ "number": 122, |
|
| 101 |
+ }).Warn("The group's number increased tremendously!")
|
|
| 102 |
+ |
|
| 103 |
+ log.WithFields(log.Fields{
|
|
| 104 |
+ "omg": true, |
|
| 105 |
+ "number": 100, |
|
| 106 |
+ }).Fatal("The ice breaks!")
|
|
| 107 |
+} |
|
| 108 |
+``` |
|
| 109 |
+ |
|
| 110 |
+For more advanced usage such as logging to multiple locations from the same |
|
| 111 |
+application, you can also create an instance of the `logrus` Logger: |
|
| 112 |
+ |
|
| 113 |
+```go |
|
| 114 |
+package main |
|
| 115 |
+ |
|
| 116 |
+import ( |
|
| 117 |
+ "github.com/Sirupsen/logrus" |
|
| 118 |
+) |
|
| 119 |
+ |
|
| 120 |
+// Create a new instance of the logger. You can have any number of instances. |
|
| 121 |
+var log = logrus.New() |
|
| 122 |
+ |
|
| 123 |
+func main() {
|
|
| 124 |
+ // The API for setting attributes is a little different than the package level |
|
| 125 |
+ // exported logger. See Godoc. |
|
| 126 |
+ log.Out = os.Stderr |
|
| 127 |
+ |
|
| 128 |
+ log.WithFields(log.Fields{
|
|
| 129 |
+ "animal": "walrus", |
|
| 130 |
+ "size": 10, |
|
| 131 |
+ }).Info("A group of walrus emerges from the ocean")
|
|
| 132 |
+} |
|
| 133 |
+``` |
|
| 134 |
+ |
|
| 135 |
+#### Fields |
|
| 136 |
+ |
|
| 137 |
+Logrus encourages careful, structured logging though logging fields instead of |
|
| 138 |
+long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
|
| 139 |
+to send event %s to topic %s with key %d")`, you should log the much more |
|
| 140 |
+discoverable: |
|
| 141 |
+ |
|
| 142 |
+```go |
|
| 143 |
+log.WithFields(log.Fields{
|
|
| 144 |
+ "event": event, |
|
| 145 |
+ "topic": topic, |
|
| 146 |
+ "key": key, |
|
| 147 |
+}).Fatal("Failed to send event")
|
|
| 148 |
+``` |
|
| 149 |
+ |
|
| 150 |
+We've found this API forces you to think about logging in a way that produces |
|
| 151 |
+much more useful logging messages. We've been in countless situations where just |
|
| 152 |
+a single added field to a log statement that was already there would've saved us |
|
| 153 |
+hours. The `WithFields` call is optional. |
|
| 154 |
+ |
|
| 155 |
+In general, with Logrus using any of the `printf`-family functions should be |
|
| 156 |
+seen as a hint you should add a field, however, you can still use the |
|
| 157 |
+`printf`-family functions with Logrus. |
|
| 158 |
+ |
|
| 159 |
+#### Hooks |
|
| 160 |
+ |
|
| 161 |
+You can add hooks for logging levels. For example to send errors to an exception |
|
| 162 |
+tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to |
|
| 163 |
+multiple places simultaneously, e.g. syslog. |
|
| 164 |
+ |
|
| 165 |
+```go |
|
| 166 |
+// Not the real implementation of the Airbrake hook. Just a simple sample. |
|
| 167 |
+import ( |
|
| 168 |
+ log "github.com/Sirupsen/logrus" |
|
| 169 |
+) |
|
| 170 |
+ |
|
| 171 |
+func init() {
|
|
| 172 |
+ log.AddHook(new(AirbrakeHook)) |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+type AirbrakeHook struct{}
|
|
| 176 |
+ |
|
| 177 |
+// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains |
|
| 178 |
+// the fields for the entry. See the Fields section of the README. |
|
| 179 |
+func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
|
| 180 |
+ err := airbrake.Notify(entry.Data["error"].(error)) |
|
| 181 |
+ if err != nil {
|
|
| 182 |
+ log.WithFields(log.Fields{
|
|
| 183 |
+ "source": "airbrake", |
|
| 184 |
+ "endpoint": airbrake.Endpoint, |
|
| 185 |
+ }).Info("Failed to send error to Airbrake")
|
|
| 186 |
+ } |
|
| 187 |
+ |
|
| 188 |
+ return nil |
|
| 189 |
+} |
|
| 190 |
+ |
|
| 191 |
+// `Levels()` returns a slice of `Levels` the hook is fired for. |
|
| 192 |
+func (hook *AirbrakeHook) Levels() []log.Level {
|
|
| 193 |
+ return []log.Level{
|
|
| 194 |
+ log.ErrorLevel, |
|
| 195 |
+ log.FatalLevel, |
|
| 196 |
+ log.PanicLevel, |
|
| 197 |
+ } |
|
| 198 |
+} |
|
| 199 |
+``` |
|
| 200 |
+ |
|
| 201 |
+Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: |
|
| 202 |
+ |
|
| 203 |
+```go |
|
| 204 |
+import ( |
|
| 205 |
+ log "github.com/Sirupsen/logrus" |
|
| 206 |
+ "github.com/Sirupsen/logrus/hooks/airbrake" |
|
| 207 |
+ "github.com/Sirupsen/logrus/hooks/syslog" |
|
| 208 |
+) |
|
| 209 |
+ |
|
| 210 |
+func init() {
|
|
| 211 |
+ log.AddHook(new(logrus_airbrake.AirbrakeHook)) |
|
| 212 |
+ log.AddHook(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""))
|
|
| 213 |
+} |
|
| 214 |
+``` |
|
| 215 |
+ |
|
| 216 |
+* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go). |
|
| 217 |
+ Send errors to an exception tracking service compatible with the Airbrake API. |
|
| 218 |
+ Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
| 219 |
+ |
|
| 220 |
+* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go). |
|
| 221 |
+ Send errors to remote syslog server. |
|
| 222 |
+ Uses standard library `log/syslog` behind the scenes. |
|
| 223 |
+ |
|
| 224 |
+#### Level logging |
|
| 225 |
+ |
|
| 226 |
+Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. |
|
| 227 |
+ |
|
| 228 |
+```go |
|
| 229 |
+log.Debug("Useful debugging information.")
|
|
| 230 |
+log.Info("Something noteworthy happened!")
|
|
| 231 |
+log.Warn("You should probably take a look at this.")
|
|
| 232 |
+log.Error("Something failed but I'm not quitting.")
|
|
| 233 |
+// Calls os.Exit(1) after logging |
|
| 234 |
+log.Fatal("Bye.")
|
|
| 235 |
+// Calls panic() after logging |
|
| 236 |
+log.Panic("I'm bailing.")
|
|
| 237 |
+``` |
|
| 238 |
+ |
|
| 239 |
+You can set the logging level on a `Logger`, then it will only log entries with |
|
| 240 |
+that severity or anything above it: |
|
| 241 |
+ |
|
| 242 |
+```go |
|
| 243 |
+// Will log anything that is info or above (warn, error, fatal, panic). Default. |
|
| 244 |
+log.SetLevel(log.InfoLevel) |
|
| 245 |
+``` |
|
| 246 |
+ |
|
| 247 |
+It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose |
|
| 248 |
+environment if your application has that. |
|
| 249 |
+ |
|
| 250 |
+#### Entries |
|
| 251 |
+ |
|
| 252 |
+Besides the fields added with `WithField` or `WithFields` some fields are |
|
| 253 |
+automatically added to all logging events: |
|
| 254 |
+ |
|
| 255 |
+1. `time`. The timestamp when the entry was created. |
|
| 256 |
+2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
|
| 257 |
+ the `AddFields` call. E.g. `Failed to send event.` |
|
| 258 |
+3. `level`. The logging level. E.g. `info`. |
|
| 259 |
+ |
|
| 260 |
+#### Environments |
|
| 261 |
+ |
|
| 262 |
+Logrus has no notion of environment. |
|
| 263 |
+ |
|
| 264 |
+If you wish for hooks and formatters to only be used in specific environments, |
|
| 265 |
+you should handle that yourself. For example, if your application has a global |
|
| 266 |
+variable `Environment`, which is a string representation of the environment you |
|
| 267 |
+could do: |
|
| 268 |
+ |
|
| 269 |
+```go |
|
| 270 |
+import ( |
|
| 271 |
+ log "github.com/Sirupsen/logrus" |
|
| 272 |
+) |
|
| 273 |
+ |
|
| 274 |
+init() {
|
|
| 275 |
+ // do something here to set environment depending on an environment variable |
|
| 276 |
+ // or command-line flag |
|
| 277 |
+ if Environment == "production" {
|
|
| 278 |
+ log.SetFormatter(logrus.JSONFormatter) |
|
| 279 |
+ } else {
|
|
| 280 |
+ // The TextFormatter is default, you don't actually have to do this. |
|
| 281 |
+ log.SetFormatter(logrus.TextFormatter) |
|
| 282 |
+ } |
|
| 283 |
+} |
|
| 284 |
+``` |
|
| 285 |
+ |
|
| 286 |
+This configuration is how `logrus` was intended to be used, but JSON in |
|
| 287 |
+production is mostly only useful if you do log aggregation with tools like |
|
| 288 |
+Splunk or Logstash. |
|
| 289 |
+ |
|
| 290 |
+#### Formatters |
|
| 291 |
+ |
|
| 292 |
+The built-in logging formatters are: |
|
| 293 |
+ |
|
| 294 |
+* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise |
|
| 295 |
+ without colors. |
|
| 296 |
+ * *Note:* to force colored output when there is no TTY, set the `ForceColors` |
|
| 297 |
+ field to `true`. To force no colored output even if there is a TTY set the |
|
| 298 |
+ `DisableColors` field to `true` |
|
| 299 |
+* `logrus.JSONFormatter`. Logs fields as JSON. |
|
| 300 |
+ |
|
| 301 |
+Third party logging formatters: |
|
| 302 |
+ |
|
| 303 |
+* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. |
|
| 304 |
+ |
|
| 305 |
+You can define your formatter by implementing the `Formatter` interface, |
|
| 306 |
+requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a |
|
| 307 |
+`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
|
| 308 |
+default ones (see Entries section above): |
|
| 309 |
+ |
|
| 310 |
+```go |
|
| 311 |
+type MyJSONFormatter struct {
|
|
| 312 |
+} |
|
| 313 |
+ |
|
| 314 |
+log.SetFormatter(new(MyJSONFormatter)) |
|
| 315 |
+ |
|
| 316 |
+func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
| 317 |
+ // Note this doesn't include Time, Level and Message which are available on |
|
| 318 |
+ // the Entry. Consult `godoc` on information about those fields or read the |
|
| 319 |
+ // source of the official loggers. |
|
| 320 |
+ serialized, err := json.Marshal(entry.Data) |
|
| 321 |
+ if err != nil {
|
|
| 322 |
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
| 323 |
+ } |
|
| 324 |
+ return append(serialized, '\n'), nil |
|
| 325 |
+} |
|
| 326 |
+``` |
|
| 327 |
+ |
|
| 328 |
+#### Rotation |
|
| 329 |
+ |
|
| 330 |
+Log rotation is not provided with Logrus. Log rotation should be done by an |
|
| 331 |
+external program (like `logrotated(8)`) that can compress and delete old log |
|
| 332 |
+entries. It should not be a feature of the application-level logger. |
|
| 333 |
+ |
|
| 334 |
+ |
|
| 335 |
+[godoc]: https://godoc.org/github.com/Sirupsen/logrus |
| 0 | 336 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,242 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "os" |
|
| 7 |
+ "time" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// An entry is the final or intermediate Logrus logging entry. It containts all |
|
| 11 |
+// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
|
| 12 |
+// Warn, Error, Fatal or Panic is called on it. These objects can be reused and |
|
| 13 |
+// passed around as much as you wish to avoid field duplication. |
|
| 14 |
+type Entry struct {
|
|
| 15 |
+ Logger *Logger |
|
| 16 |
+ |
|
| 17 |
+ // Contains all the fields set by the user. |
|
| 18 |
+ Data Fields |
|
| 19 |
+ |
|
| 20 |
+ // Time at which the log entry was created |
|
| 21 |
+ Time time.Time |
|
| 22 |
+ |
|
| 23 |
+ // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic |
|
| 24 |
+ Level Level |
|
| 25 |
+ |
|
| 26 |
+ // Message passed to Debug, Info, Warn, Error, Fatal or Panic |
|
| 27 |
+ Message string |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+var baseTimestamp time.Time |
|
| 31 |
+ |
|
| 32 |
+func NewEntry(logger *Logger) *Entry {
|
|
| 33 |
+ return &Entry{
|
|
| 34 |
+ Logger: logger, |
|
| 35 |
+ // Default is three fields, give a little extra room |
|
| 36 |
+ Data: make(Fields, 5), |
|
| 37 |
+ } |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+// Returns a reader for the entry, which is a proxy to the formatter. |
|
| 41 |
+func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
|
| 42 |
+ serialized, err := entry.Logger.Formatter.Format(entry) |
|
| 43 |
+ return bytes.NewBuffer(serialized), err |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// Returns the string representation from the reader and ultimately the |
|
| 47 |
+// formatter. |
|
| 48 |
+func (entry *Entry) String() (string, error) {
|
|
| 49 |
+ reader, err := entry.Reader() |
|
| 50 |
+ if err != nil {
|
|
| 51 |
+ return "", err |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ return reader.String(), err |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+// Add a single field to the Entry. |
|
| 58 |
+func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
|
| 59 |
+ return entry.WithFields(Fields{key: value})
|
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+// Add a map of fields to the Entry. |
|
| 63 |
+func (entry *Entry) WithFields(fields Fields) *Entry {
|
|
| 64 |
+ data := Fields{}
|
|
| 65 |
+ for k, v := range entry.Data {
|
|
| 66 |
+ data[k] = v |
|
| 67 |
+ } |
|
| 68 |
+ for k, v := range fields {
|
|
| 69 |
+ data[k] = v |
|
| 70 |
+ } |
|
| 71 |
+ return &Entry{Logger: entry.Logger, Data: data}
|
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+func (entry *Entry) log(level Level, msg string) string {
|
|
| 75 |
+ entry.Time = time.Now() |
|
| 76 |
+ entry.Level = level |
|
| 77 |
+ entry.Message = msg |
|
| 78 |
+ |
|
| 79 |
+ if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
|
| 80 |
+ fmt.Fprintf(os.Stderr, "Failed to fire hook", err) |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ reader, err := entry.Reader() |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v", err) |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ entry.Logger.mu.Lock() |
|
| 89 |
+ defer entry.Logger.mu.Unlock() |
|
| 90 |
+ |
|
| 91 |
+ _, err = io.Copy(entry.Logger.Out, reader) |
|
| 92 |
+ if err != nil {
|
|
| 93 |
+ fmt.Fprintf(os.Stderr, "Failed to write to log, %v", err) |
|
| 94 |
+ } |
|
| 95 |
+ |
|
| 96 |
+ return reader.String() |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func (entry *Entry) Debug(args ...interface{}) {
|
|
| 100 |
+ if entry.Logger.Level >= DebugLevel {
|
|
| 101 |
+ entry.log(DebugLevel, fmt.Sprint(args...)) |
|
| 102 |
+ } |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+func (entry *Entry) Print(args ...interface{}) {
|
|
| 106 |
+ entry.Info(args...) |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+func (entry *Entry) Info(args ...interface{}) {
|
|
| 110 |
+ if entry.Logger.Level >= InfoLevel {
|
|
| 111 |
+ entry.log(InfoLevel, fmt.Sprint(args...)) |
|
| 112 |
+ } |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 115 |
+func (entry *Entry) Warn(args ...interface{}) {
|
|
| 116 |
+ if entry.Logger.Level >= WarnLevel {
|
|
| 117 |
+ entry.log(WarnLevel, fmt.Sprint(args...)) |
|
| 118 |
+ } |
|
| 119 |
+} |
|
| 120 |
+ |
|
| 121 |
+func (entry *Entry) Error(args ...interface{}) {
|
|
| 122 |
+ if entry.Logger.Level >= ErrorLevel {
|
|
| 123 |
+ entry.log(ErrorLevel, fmt.Sprint(args...)) |
|
| 124 |
+ } |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+func (entry *Entry) Fatal(args ...interface{}) {
|
|
| 128 |
+ if entry.Logger.Level >= FatalLevel {
|
|
| 129 |
+ entry.log(FatalLevel, fmt.Sprint(args...)) |
|
| 130 |
+ } |
|
| 131 |
+ os.Exit(1) |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func (entry *Entry) Panic(args ...interface{}) {
|
|
| 135 |
+ if entry.Logger.Level >= PanicLevel {
|
|
| 136 |
+ msg := entry.log(PanicLevel, fmt.Sprint(args...)) |
|
| 137 |
+ panic(msg) |
|
| 138 |
+ } |
|
| 139 |
+ panic(fmt.Sprint(args...)) |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+// Entry Printf family functions |
|
| 143 |
+ |
|
| 144 |
+func (entry *Entry) Debugf(format string, args ...interface{}) {
|
|
| 145 |
+ if entry.Logger.Level >= DebugLevel {
|
|
| 146 |
+ entry.Debug(fmt.Sprintf(format, args...)) |
|
| 147 |
+ } |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func (entry *Entry) Infof(format string, args ...interface{}) {
|
|
| 151 |
+ if entry.Logger.Level >= InfoLevel {
|
|
| 152 |
+ entry.Info(fmt.Sprintf(format, args...)) |
|
| 153 |
+ } |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+func (entry *Entry) Printf(format string, args ...interface{}) {
|
|
| 157 |
+ entry.Infof(format, args...) |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func (entry *Entry) Warnf(format string, args ...interface{}) {
|
|
| 161 |
+ if entry.Logger.Level >= WarnLevel {
|
|
| 162 |
+ entry.Warn(fmt.Sprintf(format, args...)) |
|
| 163 |
+ } |
|
| 164 |
+} |
|
| 165 |
+ |
|
| 166 |
+func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|
| 167 |
+ entry.Warnf(format, args...) |
|
| 168 |
+} |
|
| 169 |
+ |
|
| 170 |
+func (entry *Entry) Errorf(format string, args ...interface{}) {
|
|
| 171 |
+ if entry.Logger.Level >= ErrorLevel {
|
|
| 172 |
+ entry.Error(fmt.Sprintf(format, args...)) |
|
| 173 |
+ } |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
|
| 177 |
+ if entry.Logger.Level >= FatalLevel {
|
|
| 178 |
+ entry.Fatal(fmt.Sprintf(format, args...)) |
|
| 179 |
+ } |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 182 |
+func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|
| 183 |
+ if entry.Logger.Level >= PanicLevel {
|
|
| 184 |
+ entry.Panic(fmt.Sprintf(format, args...)) |
|
| 185 |
+ } |
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+// Entry Println family functions |
|
| 189 |
+ |
|
| 190 |
+func (entry *Entry) Debugln(args ...interface{}) {
|
|
| 191 |
+ if entry.Logger.Level >= DebugLevel {
|
|
| 192 |
+ entry.Debug(entry.sprintlnn(args...)) |
|
| 193 |
+ } |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+func (entry *Entry) Infoln(args ...interface{}) {
|
|
| 197 |
+ if entry.Logger.Level >= InfoLevel {
|
|
| 198 |
+ entry.Info(entry.sprintlnn(args...)) |
|
| 199 |
+ } |
|
| 200 |
+} |
|
| 201 |
+ |
|
| 202 |
+func (entry *Entry) Println(args ...interface{}) {
|
|
| 203 |
+ entry.Infoln(args...) |
|
| 204 |
+} |
|
| 205 |
+ |
|
| 206 |
+func (entry *Entry) Warnln(args ...interface{}) {
|
|
| 207 |
+ if entry.Logger.Level >= WarnLevel {
|
|
| 208 |
+ entry.Warn(entry.sprintlnn(args...)) |
|
| 209 |
+ } |
|
| 210 |
+} |
|
| 211 |
+ |
|
| 212 |
+func (entry *Entry) Warningln(args ...interface{}) {
|
|
| 213 |
+ entry.Warnln(args...) |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func (entry *Entry) Errorln(args ...interface{}) {
|
|
| 217 |
+ if entry.Logger.Level >= ErrorLevel {
|
|
| 218 |
+ entry.Error(entry.sprintlnn(args...)) |
|
| 219 |
+ } |
|
| 220 |
+} |
|
| 221 |
+ |
|
| 222 |
+func (entry *Entry) Fatalln(args ...interface{}) {
|
|
| 223 |
+ if entry.Logger.Level >= FatalLevel {
|
|
| 224 |
+ entry.Fatal(entry.sprintlnn(args...)) |
|
| 225 |
+ } |
|
| 226 |
+} |
|
| 227 |
+ |
|
| 228 |
+func (entry *Entry) Panicln(args ...interface{}) {
|
|
| 229 |
+ if entry.Logger.Level >= PanicLevel {
|
|
| 230 |
+ entry.Panic(entry.sprintlnn(args...)) |
|
| 231 |
+ } |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 234 |
+// Sprintlnn => Sprint no newline. This is to get the behavior of how |
|
| 235 |
+// fmt.Sprintln where spaces are always added between operands, regardless of |
|
| 236 |
+// their type. Instead of vendoring the Sprintln implementation to spare a |
|
| 237 |
+// string allocation, we do the simplest thing. |
|
| 238 |
+func (entry *Entry) sprintlnn(args ...interface{}) string {
|
|
| 239 |
+ msg := fmt.Sprintln(args...) |
|
| 240 |
+ return msg[:len(msg)-1] |
|
| 241 |
+} |
| 0 | 242 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,29 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/Sirupsen/logrus" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+var log = logrus.New() |
|
| 7 |
+ |
|
| 8 |
+func init() {
|
|
| 9 |
+ log.Formatter = new(logrus.JSONFormatter) |
|
| 10 |
+ log.Formatter = new(logrus.TextFormatter) // default |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+func main() {
|
|
| 14 |
+ log.WithFields(logrus.Fields{
|
|
| 15 |
+ "animal": "walrus", |
|
| 16 |
+ "size": 10, |
|
| 17 |
+ }).Info("A group of walrus emerges from the ocean")
|
|
| 18 |
+ |
|
| 19 |
+ log.WithFields(logrus.Fields{
|
|
| 20 |
+ "omg": true, |
|
| 21 |
+ "number": 122, |
|
| 22 |
+ }).Warn("The group's number increased tremendously!")
|
|
| 23 |
+ |
|
| 24 |
+ log.WithFields(logrus.Fields{
|
|
| 25 |
+ "omg": true, |
|
| 26 |
+ "number": 100, |
|
| 27 |
+ }).Fatal("The ice breaks!")
|
|
| 28 |
+} |
| 0 | 29 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,35 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/Sirupsen/logrus" |
|
| 4 |
+ "github.com/Sirupsen/logrus/hooks/airbrake" |
|
| 5 |
+ "github.com/tobi/airbrake-go" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+var log = logrus.New() |
|
| 9 |
+ |
|
| 10 |
+func init() {
|
|
| 11 |
+ log.Formatter = new(logrus.TextFormatter) // default |
|
| 12 |
+ log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func main() {
|
|
| 16 |
+ airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml" |
|
| 17 |
+ airbrake.ApiKey = "whatever" |
|
| 18 |
+ airbrake.Environment = "production" |
|
| 19 |
+ |
|
| 20 |
+ log.WithFields(logrus.Fields{
|
|
| 21 |
+ "animal": "walrus", |
|
| 22 |
+ "size": 10, |
|
| 23 |
+ }).Info("A group of walrus emerges from the ocean")
|
|
| 24 |
+ |
|
| 25 |
+ log.WithFields(logrus.Fields{
|
|
| 26 |
+ "omg": true, |
|
| 27 |
+ "number": 122, |
|
| 28 |
+ }).Warn("The group's number increased tremendously!")
|
|
| 29 |
+ |
|
| 30 |
+ log.WithFields(logrus.Fields{
|
|
| 31 |
+ "omg": true, |
|
| 32 |
+ "number": 100, |
|
| 33 |
+ }).Fatal("The ice breaks!")
|
|
| 34 |
+} |
| 0 | 35 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,177 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+var ( |
|
| 7 |
+ // std is the name of the standard logger in stdlib `log` |
|
| 8 |
+ std = New() |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// SetOutput sets the standard logger output. |
|
| 12 |
+func SetOutput(out io.Writer) {
|
|
| 13 |
+ std.mu.Lock() |
|
| 14 |
+ defer std.mu.Unlock() |
|
| 15 |
+ std.Out = out |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+// SetFormatter sets the standard logger formatter. |
|
| 19 |
+func SetFormatter(formatter Formatter) {
|
|
| 20 |
+ std.mu.Lock() |
|
| 21 |
+ defer std.mu.Unlock() |
|
| 22 |
+ std.Formatter = formatter |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// SetLevel sets the standard logger level. |
|
| 26 |
+func SetLevel(level Level) {
|
|
| 27 |
+ std.mu.Lock() |
|
| 28 |
+ defer std.mu.Unlock() |
|
| 29 |
+ std.Level = level |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// AddHook adds a hook to the standard logger hooks. |
|
| 33 |
+func AddHook(hook Hook) {
|
|
| 34 |
+ std.mu.Lock() |
|
| 35 |
+ defer std.mu.Unlock() |
|
| 36 |
+ std.Hooks.Add(hook) |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+// WithField creates an entry from the standard logger and adds a field to |
|
| 40 |
+// it. If you want multiple fields, use `WithFields`. |
|
| 41 |
+// |
|
| 42 |
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal |
|
| 43 |
+// or Panic on the Entry it returns. |
|
| 44 |
+func WithField(key string, value interface{}) *Entry {
|
|
| 45 |
+ return std.WithField(key, value) |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+// WithFields creates an entry from the standard logger and adds multiple |
|
| 49 |
+// fields to it. This is simply a helper for `WithField`, invoking it |
|
| 50 |
+// once for each field. |
|
| 51 |
+// |
|
| 52 |
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal |
|
| 53 |
+// or Panic on the Entry it returns. |
|
| 54 |
+func WithFields(fields Fields) *Entry {
|
|
| 55 |
+ return std.WithFields(fields) |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// Debug logs a message at level Debug on the standard logger. |
|
| 59 |
+func Debug(args ...interface{}) {
|
|
| 60 |
+ std.Debug(args...) |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// Print logs a message at level Info on the standard logger. |
|
| 64 |
+func Print(args ...interface{}) {
|
|
| 65 |
+ std.Print(args...) |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+// Info logs a message at level Info on the standard logger. |
|
| 69 |
+func Info(args ...interface{}) {
|
|
| 70 |
+ std.Info(args...) |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+// Warn logs a message at level Warn on the standard logger. |
|
| 74 |
+func Warn(args ...interface{}) {
|
|
| 75 |
+ std.Warn(args...) |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+// Warning logs a message at level Warn on the standard logger. |
|
| 79 |
+func Warning(args ...interface{}) {
|
|
| 80 |
+ std.Warning(args...) |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+// Error logs a message at level Error on the standard logger. |
|
| 84 |
+func Error(args ...interface{}) {
|
|
| 85 |
+ std.Error(args...) |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+// Panic logs a message at level Panic on the standard logger. |
|
| 89 |
+func Panic(args ...interface{}) {
|
|
| 90 |
+ std.Panic(args...) |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+// Fatal logs a message at level Fatal on the standard logger. |
|
| 94 |
+func Fatal(args ...interface{}) {
|
|
| 95 |
+ std.Fatal(args...) |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+// Debugf logs a message at level Debugf on the standard logger. |
|
| 99 |
+func Debugf(format string, args ...interface{}) {
|
|
| 100 |
+ std.Debugf(format, args...) |
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+// Printf logs a message at level Info on the standard logger. |
|
| 104 |
+func Printf(format string, args ...interface{}) {
|
|
| 105 |
+ std.Printf(format, args...) |
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+// Infof logs a message at level Info on the standard logger. |
|
| 109 |
+func Infof(format string, args ...interface{}) {
|
|
| 110 |
+ std.Infof(format, args...) |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+// Warnf logs a message at level Warn on the standard logger. |
|
| 114 |
+func Warnf(format string, args ...interface{}) {
|
|
| 115 |
+ std.Warnf(format, args...) |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+// Warningf logs a message at level Warn on the standard logger. |
|
| 119 |
+func Warningf(format string, args ...interface{}) {
|
|
| 120 |
+ std.Warningf(format, args...) |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+// Errorf logs a message at level Error on the standard logger. |
|
| 124 |
+func Errorf(format string, args ...interface{}) {
|
|
| 125 |
+ std.Errorf(format, args...) |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+// Panicf logs a message at level Pancf on the standard logger. |
|
| 129 |
+func Panicf(format string, args ...interface{}) {
|
|
| 130 |
+ std.Panicf(format, args...) |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+// Fatalf logs a message at level Fatal on the standard logger. |
|
| 134 |
+func Fatalf(format string, args ...interface{}) {
|
|
| 135 |
+ std.Fatalf(format, args...) |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+// Debugln logs a message at level Debug on the standard logger. |
|
| 139 |
+func Debugln(args ...interface{}) {
|
|
| 140 |
+ std.Debugln(args...) |
|
| 141 |
+} |
|
| 142 |
+ |
|
| 143 |
+// Println logs a message at level Info on the standard logger. |
|
| 144 |
+func Println(args ...interface{}) {
|
|
| 145 |
+ std.Println(args...) |
|
| 146 |
+} |
|
| 147 |
+ |
|
| 148 |
+// Infoln logs a message at level Info on the standard logger. |
|
| 149 |
+func Infoln(args ...interface{}) {
|
|
| 150 |
+ std.Infoln(args...) |
|
| 151 |
+} |
|
| 152 |
+ |
|
| 153 |
+// Warnln logs a message at level Warn on the standard logger. |
|
| 154 |
+func Warnln(args ...interface{}) {
|
|
| 155 |
+ std.Warnln(args...) |
|
| 156 |
+} |
|
| 157 |
+ |
|
| 158 |
+// Warningln logs a message at level Warn on the standard logger. |
|
| 159 |
+func Warningln(args ...interface{}) {
|
|
| 160 |
+ std.Warningln(args...) |
|
| 161 |
+} |
|
| 162 |
+ |
|
| 163 |
+// Errorln logs a message at level Error on the standard logger. |
|
| 164 |
+func Errorln(args ...interface{}) {
|
|
| 165 |
+ std.Errorln(args...) |
|
| 166 |
+} |
|
| 167 |
+ |
|
| 168 |
+// Panicln logs a message at level Panic on the standard logger. |
|
| 169 |
+func Panicln(args ...interface{}) {
|
|
| 170 |
+ std.Panicln(args...) |
|
| 171 |
+} |
|
| 172 |
+ |
|
| 173 |
+// Fatalln logs a message at level Fatal on the standard logger. |
|
| 174 |
+func Fatalln(args ...interface{}) {
|
|
| 175 |
+ std.Fatalln(args...) |
|
| 176 |
+} |
| 0 | 177 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "time" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// The Formatter interface is used to implement a custom Formatter. It takes an |
|
| 7 |
+// `Entry`. It exposes all the fields, including the default ones: |
|
| 8 |
+// |
|
| 9 |
+// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. |
|
| 10 |
+// * `entry.Data["time"]`. The timestamp. |
|
| 11 |
+// * `entry.Data["level"]. The level the entry was logged at. |
|
| 12 |
+// |
|
| 13 |
+// Any additional fields added with `WithField` or `WithFields` are also in |
|
| 14 |
+// `entry.Data`. Format is expected to return an array of bytes which are then |
|
| 15 |
+// logged to `logger.Out`. |
|
| 16 |
+type Formatter interface {
|
|
| 17 |
+ Format(*Entry) ([]byte, error) |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// This is to not silently overwrite `time`, `msg` and `level` fields when |
|
| 21 |
+// dumping it. If this code wasn't there doing: |
|
| 22 |
+// |
|
| 23 |
+// logrus.WithField("level", 1).Info("hello")
|
|
| 24 |
+// |
|
| 25 |
+// Would just silently drop the user provided level. Instead with this code |
|
| 26 |
+// it'll logged as: |
|
| 27 |
+// |
|
| 28 |
+// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
|
| 29 |
+// |
|
| 30 |
+// It's not exported because it's still using Data in an opionated way. It's to |
|
| 31 |
+// avoid code duplication between the two default formatters. |
|
| 32 |
+func prefixFieldClashes(entry *Entry) {
|
|
| 33 |
+ _, ok := entry.Data["time"] |
|
| 34 |
+ if ok {
|
|
| 35 |
+ entry.Data["fields.time"] = entry.Data["time"] |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ entry.Data["time"] = entry.Time.Format(time.RFC3339) |
|
| 39 |
+ |
|
| 40 |
+ _, ok = entry.Data["msg"] |
|
| 41 |
+ if ok {
|
|
| 42 |
+ entry.Data["fields.msg"] = entry.Data["msg"] |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ entry.Data["msg"] = entry.Message |
|
| 46 |
+ |
|
| 47 |
+ _, ok = entry.Data["level"] |
|
| 48 |
+ if ok {
|
|
| 49 |
+ entry.Data["fields.level"] = entry.Data["level"] |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ entry.Data["level"] = entry.Level.String() |
|
| 53 |
+} |
| 0 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,122 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/stretchr/testify/assert" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type TestHook struct {
|
|
| 9 |
+ Fired bool |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+func (hook *TestHook) Fire(entry *Entry) error {
|
|
| 13 |
+ hook.Fired = true |
|
| 14 |
+ return nil |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+func (hook *TestHook) Levels() []Level {
|
|
| 18 |
+ return []Level{
|
|
| 19 |
+ DebugLevel, |
|
| 20 |
+ InfoLevel, |
|
| 21 |
+ WarnLevel, |
|
| 22 |
+ ErrorLevel, |
|
| 23 |
+ FatalLevel, |
|
| 24 |
+ PanicLevel, |
|
| 25 |
+ } |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+func TestHookFires(t *testing.T) {
|
|
| 29 |
+ hook := new(TestHook) |
|
| 30 |
+ |
|
| 31 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 32 |
+ log.Hooks.Add(hook) |
|
| 33 |
+ assert.Equal(t, hook.Fired, false) |
|
| 34 |
+ |
|
| 35 |
+ log.Print("test")
|
|
| 36 |
+ }, func(fields Fields) {
|
|
| 37 |
+ assert.Equal(t, hook.Fired, true) |
|
| 38 |
+ }) |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+type ModifyHook struct {
|
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func (hook *ModifyHook) Fire(entry *Entry) error {
|
|
| 45 |
+ entry.Data["wow"] = "whale" |
|
| 46 |
+ return nil |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func (hook *ModifyHook) Levels() []Level {
|
|
| 50 |
+ return []Level{
|
|
| 51 |
+ DebugLevel, |
|
| 52 |
+ InfoLevel, |
|
| 53 |
+ WarnLevel, |
|
| 54 |
+ ErrorLevel, |
|
| 55 |
+ FatalLevel, |
|
| 56 |
+ PanicLevel, |
|
| 57 |
+ } |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+func TestHookCanModifyEntry(t *testing.T) {
|
|
| 61 |
+ hook := new(ModifyHook) |
|
| 62 |
+ |
|
| 63 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 64 |
+ log.Hooks.Add(hook) |
|
| 65 |
+ log.WithField("wow", "elephant").Print("test")
|
|
| 66 |
+ }, func(fields Fields) {
|
|
| 67 |
+ assert.Equal(t, fields["wow"], "whale") |
|
| 68 |
+ }) |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+func TestCanFireMultipleHooks(t *testing.T) {
|
|
| 72 |
+ hook1 := new(ModifyHook) |
|
| 73 |
+ hook2 := new(TestHook) |
|
| 74 |
+ |
|
| 75 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 76 |
+ log.Hooks.Add(hook1) |
|
| 77 |
+ log.Hooks.Add(hook2) |
|
| 78 |
+ |
|
| 79 |
+ log.WithField("wow", "elephant").Print("test")
|
|
| 80 |
+ }, func(fields Fields) {
|
|
| 81 |
+ assert.Equal(t, fields["wow"], "whale") |
|
| 82 |
+ assert.Equal(t, hook2.Fired, true) |
|
| 83 |
+ }) |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+type ErrorHook struct {
|
|
| 87 |
+ Fired bool |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func (hook *ErrorHook) Fire(entry *Entry) error {
|
|
| 91 |
+ hook.Fired = true |
|
| 92 |
+ return nil |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func (hook *ErrorHook) Levels() []Level {
|
|
| 96 |
+ return []Level{
|
|
| 97 |
+ ErrorLevel, |
|
| 98 |
+ } |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
|
| 102 |
+ hook := new(ErrorHook) |
|
| 103 |
+ |
|
| 104 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 105 |
+ log.Hooks.Add(hook) |
|
| 106 |
+ log.Info("test")
|
|
| 107 |
+ }, func(fields Fields) {
|
|
| 108 |
+ assert.Equal(t, hook.Fired, false) |
|
| 109 |
+ }) |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 112 |
+func TestErrorHookShouldFireOnError(t *testing.T) {
|
|
| 113 |
+ hook := new(ErrorHook) |
|
| 114 |
+ |
|
| 115 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 116 |
+ log.Hooks.Add(hook) |
|
| 117 |
+ log.Error("test")
|
|
| 118 |
+ }, func(fields Fields) {
|
|
| 119 |
+ assert.Equal(t, hook.Fired, true) |
|
| 120 |
+ }) |
|
| 121 |
+} |
| 0 | 122 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+// A hook to be fired when logging on the logging levels returned from |
|
| 3 |
+// `Levels()` on your implementation of the interface. Note that this is not |
|
| 4 |
+// fired in a goroutine or a channel with workers, you should handle such |
|
| 5 |
+// functionality yourself if your call is non-blocking and you don't wish for |
|
| 6 |
+// the logging calls for levels returned from `Levels()` to block. |
|
| 7 |
+type Hook interface {
|
|
| 8 |
+ Levels() []Level |
|
| 9 |
+ Fire(*Entry) error |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+// Internal type for storing the hooks on a logger instance. |
|
| 13 |
+type levelHooks map[Level][]Hook |
|
| 14 |
+ |
|
| 15 |
+// Add a hook to an instance of logger. This is called with |
|
| 16 |
+// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. |
|
| 17 |
+func (hooks levelHooks) Add(hook Hook) {
|
|
| 18 |
+ for _, level := range hook.Levels() {
|
|
| 19 |
+ hooks[level] = append(hooks[level], hook) |
|
| 20 |
+ } |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// Fire all the hooks for the passed level. Used by `entry.log` to fire |
|
| 24 |
+// appropriate hooks for a log entry. |
|
| 25 |
+func (hooks levelHooks) Fire(level Level, entry *Entry) error {
|
|
| 26 |
+ for _, hook := range hooks[level] {
|
|
| 27 |
+ if err := hook.Fire(entry); err != nil {
|
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ return nil |
|
| 33 |
+} |
| 0 | 34 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package logrus_airbrake |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/Sirupsen/logrus" |
|
| 4 |
+ "github.com/tobi/airbrake-go" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// AirbrakeHook to send exceptions to an exception-tracking service compatible |
|
| 8 |
+// with the Airbrake API. You must set: |
|
| 9 |
+// * airbrake.Endpoint |
|
| 10 |
+// * airbrake.ApiKey |
|
| 11 |
+// * airbrake.Environment (only sends exceptions when set to "production") |
|
| 12 |
+// |
|
| 13 |
+// Before using this hook, to send an error. Entries that trigger an Error, |
|
| 14 |
+// Fatal or Panic should now include an "error" field to send to Airbrake. |
|
| 15 |
+type AirbrakeHook struct{}
|
|
| 16 |
+ |
|
| 17 |
+func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
|
| 18 |
+ if entry.Data["error"] == nil {
|
|
| 19 |
+ entry.Logger.WithFields(logrus.Fields{
|
|
| 20 |
+ "source": "airbrake", |
|
| 21 |
+ "endpoint": airbrake.Endpoint, |
|
| 22 |
+ }).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
|
|
| 23 |
+ return nil |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ err, ok := entry.Data["error"].(error) |
|
| 27 |
+ if !ok {
|
|
| 28 |
+ entry.Logger.WithFields(logrus.Fields{
|
|
| 29 |
+ "source": "airbrake", |
|
| 30 |
+ "endpoint": airbrake.Endpoint, |
|
| 31 |
+ }).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
|
|
| 32 |
+ return nil |
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ airErr := airbrake.Notify(err) |
|
| 36 |
+ if airErr != nil {
|
|
| 37 |
+ entry.Logger.WithFields(logrus.Fields{
|
|
| 38 |
+ "source": "airbrake", |
|
| 39 |
+ "endpoint": airbrake.Endpoint, |
|
| 40 |
+ "error": airErr, |
|
| 41 |
+ }).Warn("Failed to send error to Airbrake")
|
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ return nil |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+func (hook *AirbrakeHook) Levels() []logrus.Level {
|
|
| 48 |
+ return []logrus.Level{
|
|
| 49 |
+ logrus.ErrorLevel, |
|
| 50 |
+ logrus.FatalLevel, |
|
| 51 |
+ logrus.PanicLevel, |
|
| 52 |
+ } |
|
| 53 |
+} |
| 0 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,20 @@ |
| 0 |
+# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> |
|
| 1 |
+ |
|
| 2 |
+## Usage |
|
| 3 |
+ |
|
| 4 |
+```go |
|
| 5 |
+import ( |
|
| 6 |
+ "log/syslog" |
|
| 7 |
+ "github.com/Sirupsen/logrus" |
|
| 8 |
+ "github.com/Sirupsen/logrus/hooks/syslog" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func main() {
|
|
| 12 |
+ log := logrus.New() |
|
| 13 |
+ hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
| 14 |
+ |
|
| 15 |
+ if err == nil {
|
|
| 16 |
+ log.Hooks.Add(hook) |
|
| 17 |
+ } |
|
| 18 |
+} |
|
| 19 |
+``` |
|
| 0 | 20 |
\ No newline at end of file |
| 1 | 21 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,59 @@ |
| 0 |
+package logrus_syslog |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/Sirupsen/logrus" |
|
| 5 |
+ "log/syslog" |
|
| 6 |
+ "os" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// SyslogHook to send logs via syslog. |
|
| 10 |
+type SyslogHook struct {
|
|
| 11 |
+ Writer *syslog.Writer |
|
| 12 |
+ SyslogNetwork string |
|
| 13 |
+ SyslogRaddr string |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+// Creates a hook to be added to an instance of logger. This is called with |
|
| 17 |
+// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
|
| 18 |
+// `if err == nil { log.Hooks.Add(hook) }`
|
|
| 19 |
+func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
|
| 20 |
+ w, err := syslog.Dial(network, raddr, priority, tag) |
|
| 21 |
+ return &SyslogHook{w, network, raddr}, err
|
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
|
| 25 |
+ line, err := entry.String() |
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) |
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ switch entry.Data["level"] {
|
|
| 32 |
+ case "panic": |
|
| 33 |
+ return hook.Writer.Crit(line) |
|
| 34 |
+ case "fatal": |
|
| 35 |
+ return hook.Writer.Crit(line) |
|
| 36 |
+ case "error": |
|
| 37 |
+ return hook.Writer.Err(line) |
|
| 38 |
+ case "warn": |
|
| 39 |
+ return hook.Writer.Warning(line) |
|
| 40 |
+ case "info": |
|
| 41 |
+ return hook.Writer.Info(line) |
|
| 42 |
+ case "debug": |
|
| 43 |
+ return hook.Writer.Debug(line) |
|
| 44 |
+ default: |
|
| 45 |
+ return nil |
|
| 46 |
+ } |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func (hook *SyslogHook) Levels() []logrus.Level {
|
|
| 50 |
+ return []logrus.Level{
|
|
| 51 |
+ logrus.PanicLevel, |
|
| 52 |
+ logrus.FatalLevel, |
|
| 53 |
+ logrus.ErrorLevel, |
|
| 54 |
+ logrus.WarnLevel, |
|
| 55 |
+ logrus.InfoLevel, |
|
| 56 |
+ logrus.DebugLevel, |
|
| 57 |
+ } |
|
| 58 |
+} |
| 0 | 59 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,26 @@ |
| 0 |
+package logrus_syslog |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/Sirupsen/logrus" |
|
| 4 |
+ "log/syslog" |
|
| 5 |
+ "testing" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func TestLocalhostAddAndPrint(t *testing.T) {
|
|
| 9 |
+ log := logrus.New() |
|
| 10 |
+ hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
| 11 |
+ |
|
| 12 |
+ if err != nil {
|
|
| 13 |
+ t.Errorf("Unable to connect to local syslog.")
|
|
| 14 |
+ } |
|
| 15 |
+ |
|
| 16 |
+ log.Hooks.Add(hook) |
|
| 17 |
+ |
|
| 18 |
+ for _, level := range hook.Levels() {
|
|
| 19 |
+ if len(log.Hooks[level]) != 1 {
|
|
| 20 |
+ t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
|
| 21 |
+ } |
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+ log.Info("Congratulations!")
|
|
| 25 |
+} |
| 0 | 26 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+type JSONFormatter struct {
|
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
| 11 |
+ prefixFieldClashes(entry) |
|
| 12 |
+ |
|
| 13 |
+ serialized, err := json.Marshal(entry.Data) |
|
| 14 |
+ if err != nil {
|
|
| 15 |
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
| 16 |
+ } |
|
| 17 |
+ return append(serialized, '\n'), nil |
|
| 18 |
+} |
| 0 | 19 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,161 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "os" |
|
| 5 |
+ "sync" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type Logger struct {
|
|
| 9 |
+ // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a |
|
| 10 |
+ // file, or leave it default which is `os.Stdout`. You can also set this to |
|
| 11 |
+ // something more adventorous, such as logging to Kafka. |
|
| 12 |
+ Out io.Writer |
|
| 13 |
+ // Hooks for the logger instance. These allow firing events based on logging |
|
| 14 |
+ // levels and log entries. For example, to send errors to an error tracking |
|
| 15 |
+ // service, log to StatsD or dump the core on fatal errors. |
|
| 16 |
+ Hooks levelHooks |
|
| 17 |
+ // All log entries pass through the formatter before logged to Out. The |
|
| 18 |
+ // included formatters are `TextFormatter` and `JSONFormatter` for which |
|
| 19 |
+ // TextFormatter is the default. In development (when a TTY is attached) it |
|
| 20 |
+ // logs with colors, but to a file it wouldn't. You can easily implement your |
|
| 21 |
+ // own that implements the `Formatter` interface, see the `README` or included |
|
| 22 |
+ // formatters for examples. |
|
| 23 |
+ Formatter Formatter |
|
| 24 |
+ // The logging level the logger should log at. This is typically (and defaults |
|
| 25 |
+ // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be |
|
| 26 |
+ // logged. `logrus.Debug` is useful in |
|
| 27 |
+ Level Level |
|
| 28 |
+ // Used to sync writing to the log. |
|
| 29 |
+ mu sync.Mutex |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// Creates a new logger. Configuration should be set by changing `Formatter`, |
|
| 33 |
+// `Out` and `Hooks` directly on the default logger instance. You can also just |
|
| 34 |
+// instantiate your own: |
|
| 35 |
+// |
|
| 36 |
+// var log = &Logger{
|
|
| 37 |
+// Out: os.Stderr, |
|
| 38 |
+// Formatter: new(JSONFormatter), |
|
| 39 |
+// Hooks: make(levelHooks), |
|
| 40 |
+// Level: logrus.Debug, |
|
| 41 |
+// } |
|
| 42 |
+// |
|
| 43 |
+// It's recommended to make this a global instance called `log`. |
|
| 44 |
+func New() *Logger {
|
|
| 45 |
+ return &Logger{
|
|
| 46 |
+ Out: os.Stdout, |
|
| 47 |
+ Formatter: new(TextFormatter), |
|
| 48 |
+ Hooks: make(levelHooks), |
|
| 49 |
+ Level: InfoLevel, |
|
| 50 |
+ } |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+// Adds a field to the log entry, note that you it doesn't log until you call |
|
| 54 |
+// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. |
|
| 55 |
+// Ff you want multiple fields, use `WithFields`. |
|
| 56 |
+func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
| 57 |
+ return NewEntry(logger).WithField(key, value) |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+// Adds a struct of fields to the log entry. All it does is call `WithField` for |
|
| 61 |
+// each `Field`. |
|
| 62 |
+func (logger *Logger) WithFields(fields Fields) *Entry {
|
|
| 63 |
+ return NewEntry(logger).WithFields(fields) |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
| 67 |
+ NewEntry(logger).Debugf(format, args...) |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
| 71 |
+ NewEntry(logger).Infof(format, args...) |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
| 75 |
+ NewEntry(logger).Printf(format, args...) |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
| 79 |
+ NewEntry(logger).Warnf(format, args...) |
|
| 80 |
+} |
|
| 81 |
+ |
|
| 82 |
+func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
| 83 |
+ NewEntry(logger).Warnf(format, args...) |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
| 87 |
+ NewEntry(logger).Errorf(format, args...) |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
| 91 |
+ NewEntry(logger).Fatalf(format, args...) |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
| 95 |
+ NewEntry(logger).Panicf(format, args...) |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+func (logger *Logger) Debug(args ...interface{}) {
|
|
| 99 |
+ NewEntry(logger).Debug(args...) |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func (logger *Logger) Info(args ...interface{}) {
|
|
| 103 |
+ NewEntry(logger).Info(args...) |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+func (logger *Logger) Print(args ...interface{}) {
|
|
| 107 |
+ NewEntry(logger).Info(args...) |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func (logger *Logger) Warn(args ...interface{}) {
|
|
| 111 |
+ NewEntry(logger).Warn(args...) |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+func (logger *Logger) Warning(args ...interface{}) {
|
|
| 115 |
+ NewEntry(logger).Warn(args...) |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+func (logger *Logger) Error(args ...interface{}) {
|
|
| 119 |
+ NewEntry(logger).Error(args...) |
|
| 120 |
+} |
|
| 121 |
+ |
|
| 122 |
+func (logger *Logger) Fatal(args ...interface{}) {
|
|
| 123 |
+ NewEntry(logger).Fatal(args...) |
|
| 124 |
+} |
|
| 125 |
+ |
|
| 126 |
+func (logger *Logger) Panic(args ...interface{}) {
|
|
| 127 |
+ NewEntry(logger).Panic(args...) |
|
| 128 |
+} |
|
| 129 |
+ |
|
| 130 |
+func (logger *Logger) Debugln(args ...interface{}) {
|
|
| 131 |
+ NewEntry(logger).Debugln(args...) |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func (logger *Logger) Infoln(args ...interface{}) {
|
|
| 135 |
+ NewEntry(logger).Infoln(args...) |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+func (logger *Logger) Println(args ...interface{}) {
|
|
| 139 |
+ NewEntry(logger).Println(args...) |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func (logger *Logger) Warnln(args ...interface{}) {
|
|
| 143 |
+ NewEntry(logger).Warnln(args...) |
|
| 144 |
+} |
|
| 145 |
+ |
|
| 146 |
+func (logger *Logger) Warningln(args ...interface{}) {
|
|
| 147 |
+ NewEntry(logger).Warnln(args...) |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func (logger *Logger) Errorln(args ...interface{}) {
|
|
| 151 |
+ NewEntry(logger).Errorln(args...) |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+func (logger *Logger) Fatalln(args ...interface{}) {
|
|
| 155 |
+ NewEntry(logger).Fatalln(args...) |
|
| 156 |
+} |
|
| 157 |
+ |
|
| 158 |
+func (logger *Logger) Panicln(args ...interface{}) {
|
|
| 159 |
+ NewEntry(logger).Panicln(args...) |
|
| 160 |
+} |
| 0 | 161 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,72 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "log" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// Fields type, used to pass to `WithFields`. |
|
| 7 |
+type Fields map[string]interface{}
|
|
| 8 |
+ |
|
| 9 |
+// Level type |
|
| 10 |
+type Level uint8 |
|
| 11 |
+ |
|
| 12 |
+// Convert the Level to a string. E.g. PanicLevel becomes "panic". |
|
| 13 |
+func (level Level) String() string {
|
|
| 14 |
+ switch level {
|
|
| 15 |
+ case DebugLevel: |
|
| 16 |
+ return "debug" |
|
| 17 |
+ case InfoLevel: |
|
| 18 |
+ return "info" |
|
| 19 |
+ case WarnLevel: |
|
| 20 |
+ return "warning" |
|
| 21 |
+ case ErrorLevel: |
|
| 22 |
+ return "error" |
|
| 23 |
+ case FatalLevel: |
|
| 24 |
+ return "fatal" |
|
| 25 |
+ case PanicLevel: |
|
| 26 |
+ return "panic" |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ return "unknown" |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// These are the different logging levels. You can set the logging level to log |
|
| 33 |
+// on your instance of logger, obtained with `logrus.New()`. |
|
| 34 |
+const ( |
|
| 35 |
+ // PanicLevel level, highest level of severity. Logs and then calls panic with the |
|
| 36 |
+ // message passed to Debug, Info, ... |
|
| 37 |
+ PanicLevel Level = iota |
|
| 38 |
+ // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the |
|
| 39 |
+ // logging level is set to Panic. |
|
| 40 |
+ FatalLevel |
|
| 41 |
+ // ErrorLevel level. Logs. Used for errors that should definitely be noted. |
|
| 42 |
+ // Commonly used for hooks to send errors to an error tracking service. |
|
| 43 |
+ ErrorLevel |
|
| 44 |
+ // WarnLevel level. Non-critical entries that deserve eyes. |
|
| 45 |
+ WarnLevel |
|
| 46 |
+ // InfoLevel level. General operational entries about what's going on inside the |
|
| 47 |
+ // application. |
|
| 48 |
+ InfoLevel |
|
| 49 |
+ // DebugLevel level. Usually only enabled when debugging. Very verbose logging. |
|
| 50 |
+ DebugLevel |
|
| 51 |
+) |
|
| 52 |
+ |
|
| 53 |
+// Won't compile if StdLogger can't be realized by a log.Logger |
|
| 54 |
+var _ StdLogger = &log.Logger{}
|
|
| 55 |
+ |
|
| 56 |
+// StdLogger is what your logrus-enabled library should take, that way |
|
| 57 |
+// it'll accept a stdlib logger and a logrus logger. There's no standard |
|
| 58 |
+// interface, this is the closest we get, unfortunately. |
|
| 59 |
+type StdLogger interface {
|
|
| 60 |
+ Print(...interface{})
|
|
| 61 |
+ Printf(string, ...interface{})
|
|
| 62 |
+ Println(...interface{})
|
|
| 63 |
+ |
|
| 64 |
+ Fatal(...interface{})
|
|
| 65 |
+ Fatalf(string, ...interface{})
|
|
| 66 |
+ Fatalln(...interface{})
|
|
| 67 |
+ |
|
| 68 |
+ Panic(...interface{})
|
|
| 69 |
+ Panicf(string, ...interface{})
|
|
| 70 |
+ Panicln(...interface{})
|
|
| 71 |
+} |
| 0 | 72 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,173 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "testing" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/stretchr/testify/assert" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
|
| 11 |
+ var buffer bytes.Buffer |
|
| 12 |
+ var fields Fields |
|
| 13 |
+ |
|
| 14 |
+ logger := New() |
|
| 15 |
+ logger.Out = &buffer |
|
| 16 |
+ logger.Formatter = new(JSONFormatter) |
|
| 17 |
+ |
|
| 18 |
+ log(logger) |
|
| 19 |
+ |
|
| 20 |
+ err := json.Unmarshal(buffer.Bytes(), &fields) |
|
| 21 |
+ assert.Nil(t, err) |
|
| 22 |
+ |
|
| 23 |
+ assertions(fields) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func TestPrint(t *testing.T) {
|
|
| 27 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 28 |
+ log.Print("test")
|
|
| 29 |
+ }, func(fields Fields) {
|
|
| 30 |
+ assert.Equal(t, fields["msg"], "test") |
|
| 31 |
+ assert.Equal(t, fields["level"], "info") |
|
| 32 |
+ }) |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func TestInfo(t *testing.T) {
|
|
| 36 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 37 |
+ log.Info("test")
|
|
| 38 |
+ }, func(fields Fields) {
|
|
| 39 |
+ assert.Equal(t, fields["msg"], "test") |
|
| 40 |
+ assert.Equal(t, fields["level"], "info") |
|
| 41 |
+ }) |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func TestWarn(t *testing.T) {
|
|
| 45 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 46 |
+ log.Warn("test")
|
|
| 47 |
+ }, func(fields Fields) {
|
|
| 48 |
+ assert.Equal(t, fields["msg"], "test") |
|
| 49 |
+ assert.Equal(t, fields["level"], "warning") |
|
| 50 |
+ }) |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
|
| 54 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 55 |
+ log.Infoln("test", "test")
|
|
| 56 |
+ }, func(fields Fields) {
|
|
| 57 |
+ assert.Equal(t, fields["msg"], "test test") |
|
| 58 |
+ }) |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
|
| 62 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 63 |
+ log.Infoln("test", 10)
|
|
| 64 |
+ }, func(fields Fields) {
|
|
| 65 |
+ assert.Equal(t, fields["msg"], "test 10") |
|
| 66 |
+ }) |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
|
| 70 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 71 |
+ log.Infoln(10, 10) |
|
| 72 |
+ }, func(fields Fields) {
|
|
| 73 |
+ assert.Equal(t, fields["msg"], "10 10") |
|
| 74 |
+ }) |
|
| 75 |
+} |
|
| 76 |
+ |
|
| 77 |
+func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
|
| 78 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 79 |
+ log.Infoln(10, 10) |
|
| 80 |
+ }, func(fields Fields) {
|
|
| 81 |
+ assert.Equal(t, fields["msg"], "10 10") |
|
| 82 |
+ }) |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
|
| 86 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 87 |
+ log.Info("test", 10)
|
|
| 88 |
+ }, func(fields Fields) {
|
|
| 89 |
+ assert.Equal(t, fields["msg"], "test10") |
|
| 90 |
+ }) |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
|
| 94 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 95 |
+ log.Info("test", "test")
|
|
| 96 |
+ }, func(fields Fields) {
|
|
| 97 |
+ assert.Equal(t, fields["msg"], "testtest") |
|
| 98 |
+ }) |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
|
| 102 |
+ var buffer bytes.Buffer |
|
| 103 |
+ var fields Fields |
|
| 104 |
+ |
|
| 105 |
+ logger := New() |
|
| 106 |
+ logger.Out = &buffer |
|
| 107 |
+ logger.Formatter = new(JSONFormatter) |
|
| 108 |
+ |
|
| 109 |
+ localLog := logger.WithFields(Fields{
|
|
| 110 |
+ "key1": "value1", |
|
| 111 |
+ }) |
|
| 112 |
+ |
|
| 113 |
+ localLog.WithField("key2", "value2").Info("test")
|
|
| 114 |
+ err := json.Unmarshal(buffer.Bytes(), &fields) |
|
| 115 |
+ assert.Nil(t, err) |
|
| 116 |
+ |
|
| 117 |
+ assert.Equal(t, "value2", fields["key2"]) |
|
| 118 |
+ assert.Equal(t, "value1", fields["key1"]) |
|
| 119 |
+ |
|
| 120 |
+ buffer = bytes.Buffer{}
|
|
| 121 |
+ fields = Fields{}
|
|
| 122 |
+ localLog.Info("test")
|
|
| 123 |
+ err = json.Unmarshal(buffer.Bytes(), &fields) |
|
| 124 |
+ assert.Nil(t, err) |
|
| 125 |
+ |
|
| 126 |
+ _, ok := fields["key2"] |
|
| 127 |
+ assert.Equal(t, false, ok) |
|
| 128 |
+ assert.Equal(t, "value1", fields["key1"]) |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
|
| 132 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 133 |
+ log.WithField("msg", "hello").Info("test")
|
|
| 134 |
+ }, func(fields Fields) {
|
|
| 135 |
+ assert.Equal(t, fields["msg"], "test") |
|
| 136 |
+ }) |
|
| 137 |
+} |
|
| 138 |
+ |
|
| 139 |
+func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
|
| 140 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 141 |
+ log.WithField("msg", "hello").Info("test")
|
|
| 142 |
+ }, func(fields Fields) {
|
|
| 143 |
+ assert.Equal(t, fields["msg"], "test") |
|
| 144 |
+ assert.Equal(t, fields["fields.msg"], "hello") |
|
| 145 |
+ }) |
|
| 146 |
+} |
|
| 147 |
+ |
|
| 148 |
+func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
|
| 149 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 150 |
+ log.WithField("time", "hello").Info("test")
|
|
| 151 |
+ }, func(fields Fields) {
|
|
| 152 |
+ assert.Equal(t, fields["fields.time"], "hello") |
|
| 153 |
+ }) |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
|
| 157 |
+ LogAndAssertJSON(t, func(log *Logger) {
|
|
| 158 |
+ log.WithField("level", 1).Info("test")
|
|
| 159 |
+ }, func(fields Fields) {
|
|
| 160 |
+ assert.Equal(t, fields["level"], "info") |
|
| 161 |
+ assert.Equal(t, fields["fields.level"], 1) |
|
| 162 |
+ }) |
|
| 163 |
+} |
|
| 164 |
+ |
|
| 165 |
+func TestConvertLevelToString(t *testing.T) {
|
|
| 166 |
+ assert.Equal(t, "debug", DebugLevel.String()) |
|
| 167 |
+ assert.Equal(t, "info", InfoLevel.String()) |
|
| 168 |
+ assert.Equal(t, "warning", WarnLevel.String()) |
|
| 169 |
+ assert.Equal(t, "error", ErrorLevel.String()) |
|
| 170 |
+ assert.Equal(t, "fatal", FatalLevel.String()) |
|
| 171 |
+ assert.Equal(t, "panic", PanicLevel.String()) |
|
| 172 |
+} |
| 0 | 173 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+// Based on ssh/terminal: |
|
| 1 |
+// Copyright 2013 The Go Authors. All rights reserved. |
|
| 2 |
+// Use of this source code is governed by a BSD-style |
|
| 3 |
+// license that can be found in the LICENSE file. |
|
| 4 |
+ |
|
| 5 |
+package logrus |
|
| 6 |
+ |
|
| 7 |
+import "syscall" |
|
| 8 |
+ |
|
| 9 |
+const ioctlReadTermios = syscall.TIOCGETA |
|
| 10 |
+ |
|
| 11 |
+type Termios syscall.Termios |
| 0 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,20 @@ |
| 0 |
+/* |
|
| 1 |
+ Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. |
|
| 2 |
+*/ |
|
| 3 |
+package logrus |
|
| 4 |
+ |
|
| 5 |
+import ( |
|
| 6 |
+ "syscall" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+const ioctlReadTermios = syscall.TIOCGETA |
|
| 10 |
+ |
|
| 11 |
+type Termios struct {
|
|
| 12 |
+ Iflag uint32 |
|
| 13 |
+ Oflag uint32 |
|
| 14 |
+ Cflag uint32 |
|
| 15 |
+ Lflag uint32 |
|
| 16 |
+ Cc [20]uint8 |
|
| 17 |
+ Ispeed uint32 |
|
| 18 |
+ Ospeed uint32 |
|
| 19 |
+} |
| 0 | 20 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+// Based on ssh/terminal: |
|
| 1 |
+// Copyright 2013 The Go Authors. All rights reserved. |
|
| 2 |
+// Use of this source code is governed by a BSD-style |
|
| 3 |
+// license that can be found in the LICENSE file. |
|
| 4 |
+ |
|
| 5 |
+package logrus |
|
| 6 |
+ |
|
| 7 |
+import "syscall" |
|
| 8 |
+ |
|
| 9 |
+const ioctlReadTermios = syscall.TCGETS |
|
| 10 |
+ |
|
| 11 |
+type Termios syscall.Termios |
| 0 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,21 @@ |
| 0 |
+// Based on ssh/terminal: |
|
| 1 |
+// Copyright 2011 The Go Authors. All rights reserved. |
|
| 2 |
+// Use of this source code is governed by a BSD-style |
|
| 3 |
+// license that can be found in the LICENSE file. |
|
| 4 |
+ |
|
| 5 |
+// +build linux,!appengine darwin freebsd |
|
| 6 |
+ |
|
| 7 |
+package logrus |
|
| 8 |
+ |
|
| 9 |
+import ( |
|
| 10 |
+ "syscall" |
|
| 11 |
+ "unsafe" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+// IsTerminal returns true if the given file descriptor is a terminal. |
|
| 15 |
+func IsTerminal() bool {
|
|
| 16 |
+ fd := syscall.Stdout |
|
| 17 |
+ var termios Termios |
|
| 18 |
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) |
|
| 19 |
+ return err == 0 |
|
| 20 |
+} |
| 0 | 21 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+// Based on ssh/terminal: |
|
| 1 |
+// Copyright 2011 The Go Authors. All rights reserved. |
|
| 2 |
+// Use of this source code is governed by a BSD-style |
|
| 3 |
+// license that can be found in the LICENSE file. |
|
| 4 |
+ |
|
| 5 |
+// +build windows |
|
| 6 |
+ |
|
| 7 |
+package logrus |
|
| 8 |
+ |
|
| 9 |
+import ( |
|
| 10 |
+ "syscall" |
|
| 11 |
+ "unsafe" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
| 15 |
+ |
|
| 16 |
+var ( |
|
| 17 |
+ procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+// IsTerminal returns true if the given file descriptor is a terminal. |
|
| 21 |
+func IsTerminal() bool {
|
|
| 22 |
+ fd := syscall.Stdout |
|
| 23 |
+ var st uint32 |
|
| 24 |
+ r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) |
|
| 25 |
+ return r != 0 && e == 0 |
|
| 26 |
+} |
| 0 | 27 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,86 @@ |
| 0 |
+package logrus |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "sort" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "time" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+const ( |
|
| 11 |
+ nocolor = 0 |
|
| 12 |
+ red = 31 |
|
| 13 |
+ green = 32 |
|
| 14 |
+ yellow = 33 |
|
| 15 |
+ blue = 34 |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+func init() {
|
|
| 19 |
+ baseTimestamp = time.Now() |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func miniTS() int {
|
|
| 23 |
+ return int(time.Since(baseTimestamp) / time.Second) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+type TextFormatter struct {
|
|
| 27 |
+ // Set to true to bypass checking for a TTY before outputting colors. |
|
| 28 |
+ ForceColors bool |
|
| 29 |
+ DisableColors bool |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|
| 33 |
+ b := &bytes.Buffer{}
|
|
| 34 |
+ |
|
| 35 |
+ prefixFieldClashes(entry) |
|
| 36 |
+ |
|
| 37 |
+ if (f.ForceColors || IsTerminal()) && !f.DisableColors {
|
|
| 38 |
+ levelText := strings.ToUpper(entry.Data["level"].(string))[0:4] |
|
| 39 |
+ |
|
| 40 |
+ levelColor := blue |
|
| 41 |
+ |
|
| 42 |
+ if entry.Data["level"] == "warning" {
|
|
| 43 |
+ levelColor = yellow |
|
| 44 |
+ } else if entry.Data["level"] == "error" || |
|
| 45 |
+ entry.Data["level"] == "fatal" || |
|
| 46 |
+ entry.Data["level"] == "panic" {
|
|
| 47 |
+ levelColor = red |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Data["msg"]) |
|
| 51 |
+ |
|
| 52 |
+ keys := make([]string, 0) |
|
| 53 |
+ for k, _ := range entry.Data {
|
|
| 54 |
+ if k != "level" && k != "time" && k != "msg" {
|
|
| 55 |
+ keys = append(keys, k) |
|
| 56 |
+ } |
|
| 57 |
+ } |
|
| 58 |
+ sort.Strings(keys) |
|
| 59 |
+ for _, k := range keys {
|
|
| 60 |
+ v := entry.Data[k] |
|
| 61 |
+ fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) |
|
| 62 |
+ } |
|
| 63 |
+ } else {
|
|
| 64 |
+ f.AppendKeyValue(b, "time", entry.Data["time"].(string)) |
|
| 65 |
+ f.AppendKeyValue(b, "level", entry.Data["level"].(string)) |
|
| 66 |
+ f.AppendKeyValue(b, "msg", entry.Data["msg"].(string)) |
|
| 67 |
+ |
|
| 68 |
+ for key, value := range entry.Data {
|
|
| 69 |
+ if key != "time" && key != "level" && key != "msg" {
|
|
| 70 |
+ f.AppendKeyValue(b, key, value) |
|
| 71 |
+ } |
|
| 72 |
+ } |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ b.WriteByte('\n')
|
|
| 76 |
+ return b.Bytes(), nil |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func (f *TextFormatter) AppendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
|
| 80 |
+ if _, ok := value.(string); ok {
|
|
| 81 |
+ fmt.Fprintf(b, "%v=%q ", key, value) |
|
| 82 |
+ } else {
|
|
| 83 |
+ fmt.Fprintf(b, "%v=%v ", key, value) |
|
| 84 |
+ } |
|
| 85 |
+} |