Skip to content

Commit 090d6b7

Browse files
committed
Merge pull request #1 from Sirupsen/initial
Initial commit
2 parents bc7134c + 53371e3 commit 090d6b7

File tree

10 files changed

+659
-44
lines changed

10 files changed

+659
-44
lines changed

README.md

Lines changed: 98 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,121 @@
11
# Logrus
22

3-
Logrus is a simple, opinionated logging package for Go. It has three debugging
4-
levels:
3+
Logrus is a simple, opinionated structured logging package for Go which is
4+
completely API compatible with the standard library logger.
55

6-
* `LevelDebug`: Debugging, usually turned off for deploys.
7-
* `LevelInfo`: Info, useful for monitoring in production.
8-
* `LevelWarning`: Warnings that should definitely be noted. These are sent to
9-
`airbrake`.
10-
* `LevelFatal`: Fatal messages that causes the application to crash. These are
11-
sent to `airbrake`.
6+
#### Fields
127

13-
## Usage
8+
Logrus encourages careful, informative logging. It encourages the use of logging
9+
fields instead of long, unparseable error messages. For example, instead of:
10+
`log.Fatalf("Failed to send event %s to topic %s with key %d")`, you should log
11+
the much more discoverable:
1412

15-
The global logging level is set by: `logrus.Level = logrus.{LevelDebug,LevelWarning,LevelFatal}`.
13+
```go
14+
log = logrus.New()
15+
log.WithFields(&logrus.Fields{
16+
"event": event,
17+
"topic": topic,
18+
"key": key
19+
}).Fatal("Failed to send event")
20+
```
21+
22+
We've found this API forces you to think about logging in a way that produces
23+
much more useful logging messages. The `WithFields` call is optional.
1624

17-
Note that for `airbrake` to work, `airbrake.Endpoint` and `airbrake.ApiKey`
18-
should be set.
25+
In general, with Logrus using any of the `printf`-family functions should be
26+
seen as a hint you want to add a field, however, you can still use the
27+
`printf`-family functions with Logrus.
1928

20-
There is a global logger, which new loggers inherit their settings from when
21-
created (see example below), such as the place to redirect output. Logging can
22-
be done with the global logging module:
29+
#### Hooks
30+
31+
You can add hooks for logging levels. For example to send errors to an exception
32+
tracking service:
2333

2434
```go
25-
logrus.Debug("Something debugworthy happened: %s", importantStuff)
26-
logrus.Info("Something infoworthy happened: %s", importantStuff)
35+
log.AddHook("error", func(entry logrus.Entry) {
36+
err := airbrake.Notify(errors.New(entry.String()))
37+
if err != nil {
38+
log.WithFields(logrus.Fields{
39+
"source": "airbrake",
40+
"endpoint": airbrake.Endpoint,
41+
}).Info("Failed to send error to Airbrake")
42+
}
43+
})
44+
```
2745

28-
logrus.Warning("Something bad happened: %s", importantStuff)
29-
// Reports to Airbrake
46+
#### Level logging
3047

31-
logrus.Fatal("Something fatal happened: %s", importantStuff)
32-
// Reports to Airbrake
33-
// Then exits
48+
Logrus has six levels: Debug, Info, Warning, Error, Fatal and Panic.
49+
50+
```go
51+
log.Debug("Useful debugging information.")
52+
log.Info("Something noteworthy happened!")
53+
log.Warn("You should probably take a look at this.")
54+
log.Error("Something failed but I'm not quitting.")
55+
log.Fatal("Bye.")
56+
log.Panic("I'm bailing.")
3457
```
3558

36-
Types are encouraged to include their own logging object. This allows to set a
37-
context dependent prefix to know where a certain message is coming from, without
38-
cluttering every single message with this.
59+
You can set the logging level:
3960

4061
```go
41-
type Walrus struct {
42-
TuskSize uint64
43-
Sex bool
44-
logger logrus.Logger
45-
}
62+
// Will log anything that is info or above, default.
63+
logrus.Level = LevelInfo
64+
```
65+
66+
#### Entries
4667

47-
func NewWalrus(tuskSize uint64, sex bool) *Walrus {
48-
return &Walrus{
49-
TuskSize: tuskSize,
50-
Sex: bool,
51-
logger: logrus.NewLogger("Walrus"),
68+
Besides the fields added with `WithField` or `WithFields` some fields are
69+
automatically added to all logging events:
70+
71+
1. `time`. The timestamp when the entry was created.
72+
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
73+
the `AddFields` call. E.g. `Failed to send event.`
74+
3. `level`. The logging level. E.g. `info`.
75+
4. `file`. The file (and line) where the logging entry was created. E.g.,
76+
`main.go:82`.
77+
78+
#### Environments
79+
80+
Logrus has no notion of environment. If you wish for hooks and formatters to
81+
only be used in specific environments, you should handle that yourself. For
82+
example, if your application has a global variable `Environment`, which is a
83+
string representation of the environment you could do:
84+
85+
```go
86+
init() {
87+
// do something here to set environment depending on an environment variable
88+
// or command-line flag
89+
90+
if Environment == "production" {
91+
log.SetFormatter(logrus.JSONFormatter)
92+
} else {
93+
// The TextFormatter is default, you don't actually have to do this.
94+
log.SetFormatter(logrus.TextFormatter)
5295
}
5396
}
97+
```
5498

55-
func (walrus *Walrus) Mate(partner *Walrus) error {
56-
if walrus.Sex == partner.Sex {
57-
return errors.New("Incompatible mating partner.")
58-
}
99+
#### Formats
59100

60-
walrus.logger.Info("Walrus with tusk sizes %d and %d are mating!", walrus.TuskSize, partner.TuskSize)
61-
// Generates a logging message: <timestamp> [Info] [Walrus] Walrus with tusk sizes <int> and <int> are mating!
101+
The built in logging formatters are:
62102

63-
// Walrus mating happens here
103+
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
104+
without colors.
105+
* `logrus.JSONFormatter`. Logs fields as JSON.
64106

65-
return nil
66-
}
107+
You can define your formatter taking an entry. `entry.Data` is a `Fields` type
108+
which is a `map[string]interface{}` with all your fields as well as the default
109+
ones (see Entries above):
110+
111+
```go
112+
log.SetFormatter(func(entry *logrus.Entry) {
113+
serialized, err = json.Marshal(entry.Data)
114+
if err != nil {
115+
return nil, log.WithFields(&logrus.Fields{
116+
"source": "log formatter",
117+
"entry": entry.Data
118+
}).AsError("Failed to serialize log entry to JSON")
119+
}
120+
})
67121
```

entry.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package logrus
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"os"
8+
"time"
9+
)
10+
11+
type Entry struct {
12+
logger *Logger
13+
Data Fields
14+
}
15+
16+
var baseTimestamp time.Time
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+
func NewEntry(logger *Logger) *Entry {
27+
return &Entry{
28+
logger: logger,
29+
// Default is three fields, give a little extra room
30+
Data: make(Fields, 5),
31+
}
32+
}
33+
34+
func (entry *Entry) Reader() (*bytes.Buffer, error) {
35+
serialized, err := entry.logger.Formatter.Format(entry)
36+
return bytes.NewBuffer(serialized), err
37+
}
38+
39+
func (entry *Entry) String() (string, error) {
40+
reader, err := entry.Reader()
41+
if err != nil {
42+
return "", err
43+
}
44+
45+
return reader.String(), err
46+
}
47+
48+
func (entry *Entry) WithField(key string, value interface{}) *Entry {
49+
entry.Data[key] = value
50+
return entry
51+
}
52+
53+
func (entry *Entry) WithFields(fields Fields) *Entry {
54+
for key, value := range fields {
55+
entry.WithField(key, value)
56+
}
57+
return entry
58+
}
59+
60+
func (entry *Entry) log(level string, msg string) string {
61+
entry.Data["time"] = time.Now().String()
62+
entry.Data["level"] = level
63+
entry.Data["msg"] = msg
64+
65+
reader, err := entry.Reader()
66+
if err != nil {
67+
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v", err)
68+
}
69+
70+
entry.logger.mu.Lock()
71+
defer entry.logger.mu.Unlock()
72+
73+
_, err = io.Copy(entry.logger.Out, reader)
74+
if err != nil {
75+
fmt.Fprintf(os.Stderr, "Failed to write to log, %v", err)
76+
}
77+
78+
return reader.String()
79+
}
80+
81+
func (entry *Entry) Debug(args ...interface{}) {
82+
if Level >= LevelDebug {
83+
entry.log("debug", fmt.Sprint(args...))
84+
entry.logger.Hooks.Fire(LevelDebug, entry)
85+
}
86+
}
87+
88+
func (entry *Entry) Info(args ...interface{}) {
89+
if Level >= LevelInfo {
90+
entry.log("info", fmt.Sprint(args...))
91+
entry.logger.Hooks.Fire(LevelInfo, entry)
92+
}
93+
}
94+
95+
func (entry *Entry) Print(args ...interface{}) {
96+
if Level >= LevelInfo {
97+
entry.log("info", fmt.Sprint(args...))
98+
entry.logger.Hooks.Fire(LevelInfo, entry)
99+
}
100+
}
101+
102+
func (entry *Entry) Warn(args ...interface{}) {
103+
if Level >= LevelWarn {
104+
entry.log("warning", fmt.Sprint(args...))
105+
entry.logger.Hooks.Fire(LevelWarn, entry)
106+
}
107+
}
108+
109+
func (entry *Entry) Error(args ...interface{}) {
110+
if Level >= LevelError {
111+
entry.log("error", fmt.Sprint(args...))
112+
entry.logger.Hooks.Fire(LevelError, entry)
113+
}
114+
}
115+
116+
func (entry *Entry) Fatal(args ...interface{}) {
117+
if Level >= LevelFatal {
118+
entry.log("fatal", fmt.Sprint(args...))
119+
entry.logger.Hooks.Fire(LevelFatal, entry)
120+
}
121+
os.Exit(1)
122+
}
123+
124+
func (entry *Entry) Panic(args ...interface{}) {
125+
if Level >= LevelPanic {
126+
msg := entry.log("panic", fmt.Sprint(args...))
127+
entry.logger.Hooks.Fire(LevelPanic, entry)
128+
panic(msg)
129+
}
130+
panic(fmt.Sprint(args...))
131+
}
132+
133+
// Entry Printf family functions
134+
135+
func (entry *Entry) Debugf(format string, args ...interface{}) {
136+
entry.Debug(fmt.Sprintf(format, args...))
137+
}
138+
139+
func (entry *Entry) Infof(format string, args ...interface{}) {
140+
entry.Info(fmt.Sprintf(format, args...))
141+
}
142+
143+
func (entry *Entry) Printf(format string, args ...interface{}) {
144+
entry.Print(fmt.Sprintf(format, args...))
145+
}
146+
147+
func (entry *Entry) Warnf(format string, args ...interface{}) {
148+
entry.Warn(fmt.Sprintf(format, args...))
149+
}
150+
151+
func (entry *Entry) Warningf(format string, args ...interface{}) {
152+
entry.Warn(fmt.Sprintf(format, args...))
153+
}
154+
155+
func (entry *Entry) Errorf(format string, args ...interface{}) {
156+
entry.Print(fmt.Sprintf(format, args...))
157+
}
158+
159+
func (entry *Entry) Fatalf(format string, args ...interface{}) {
160+
entry.Fatal(fmt.Sprintf(format, args...))
161+
}
162+
163+
func (entry *Entry) Panicf(format string, args ...interface{}) {
164+
entry.Panic(fmt.Sprintf(format, args...))
165+
}
166+
167+
// Entry Println family functions
168+
169+
func (entry *Entry) Debugln(args ...interface{}) {
170+
entry.Debug(fmt.Sprint(args...))
171+
}
172+
173+
func (entry *Entry) Infoln(args ...interface{}) {
174+
entry.Info(fmt.Sprint(args...))
175+
}
176+
177+
func (entry *Entry) Println(args ...interface{}) {
178+
entry.Print(fmt.Sprint(args...))
179+
}
180+
181+
func (entry *Entry) Warnln(args ...interface{}) {
182+
entry.Warn(fmt.Sprint(args...))
183+
}
184+
185+
func (entry *Entry) Warningln(args ...interface{}) {
186+
entry.Warn(fmt.Sprint(args...))
187+
}
188+
189+
func (entry *Entry) Errorln(args ...interface{}) {
190+
entry.Error(fmt.Sprint(args...))
191+
}
192+
193+
func (entry *Entry) Fatalln(args ...interface{}) {
194+
entry.Fatal(fmt.Sprint(args...))
195+
}
196+
197+
func (entry *Entry) Panicln(args ...interface{}) {
198+
entry.Panic(fmt.Sprint(args...))
199+
}

0 commit comments

Comments
 (0)