mirror of
https://github.com/rs/zerolog
synced 2026-06-08 17:13:30 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ed2f2c974 | |||
| 90fdb63d84 | |||
| a83efb6080 | |||
| 89ff8dbc5f |
@@ -8,16 +8,20 @@ Zerolog's API is designed to provide both a great developer experience and stunn
|
||||
|
||||
The uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with simpler to use API and even better performance.
|
||||
|
||||
To keep the code base and the API simple, zerolog focuses on JSON logging only. As [suggested on reddit](https://www.reddit.com/r/golang/comments/6c9k7n/zerolog_is_now_faster_than_zap/), you may use tools like [humanlog](https://github.com/aybabtme/humanlog) to pretty print JSON on the console during development.
|
||||
To keep the code base and the API simple, zerolog focuses on JSON logging only. Pretty logging on the console is made possible using the provided `zerolog.ConsoleWriter`.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Blazing fast
|
||||
* Low to zero allocation
|
||||
* Level logging
|
||||
* Sampling
|
||||
* Contextual fields
|
||||
* `context.Context` integration
|
||||
* `net/http` helpers
|
||||
* Pretty logging for development
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -96,6 +100,18 @@ if e := log.Debug(); e.Enabled() {
|
||||
// Output: {"level":"info","time":1494567715,"message":"routed message"}
|
||||
```
|
||||
|
||||
### Pretty logging
|
||||
|
||||
```go
|
||||
if isConsole {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
}
|
||||
|
||||
log.Info().Str("foo", "bar").Msg("Hello world")
|
||||
|
||||
// Output: 1494567715 |INFO| Hello world foo=bar
|
||||
```
|
||||
|
||||
### Sub dictionary
|
||||
|
||||
```go
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
cReset = 0
|
||||
cBold = 1
|
||||
cRed = 31
|
||||
cGreen = 32
|
||||
cYellow = 33
|
||||
cBlue = 34
|
||||
cMagenta = 35
|
||||
cCyan = 36
|
||||
cGray = 37
|
||||
cDarkGray = 90
|
||||
)
|
||||
|
||||
var consoleBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bytes.NewBuffer(make([]byte, 0, 100))
|
||||
},
|
||||
}
|
||||
|
||||
// ConsoleWriter reads a JSON object per write operation and output an
|
||||
// optionally colored human readable version on the Out writer.
|
||||
type ConsoleWriter struct {
|
||||
Out io.Writer
|
||||
NoColor bool
|
||||
}
|
||||
|
||||
func (w ConsoleWriter) Write(p []byte) (n int, err error) {
|
||||
var event map[string]interface{}
|
||||
err = json.Unmarshal(p, &event)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf := consoleBufPool.Get().(*bytes.Buffer)
|
||||
defer consoleBufPool.Put(buf)
|
||||
lvlColor := cReset
|
||||
level := "????"
|
||||
if l, ok := event[LevelFieldName].(string); ok {
|
||||
if !w.NoColor {
|
||||
lvlColor = levelColor(l)
|
||||
}
|
||||
level = strings.ToUpper(l)[0:4]
|
||||
}
|
||||
fmt.Fprintf(buf, "%s |%s| %s",
|
||||
colorize(event[TimestampFieldName], cDarkGray, !w.NoColor),
|
||||
colorize(level, lvlColor, !w.NoColor),
|
||||
colorize(event[MessageFieldName], cReset, !w.NoColor))
|
||||
fields := make([]string, 0, len(event))
|
||||
for field := range event {
|
||||
switch field {
|
||||
case LevelFieldName, TimestampFieldName, MessageFieldName:
|
||||
continue
|
||||
}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
sort.Strings(fields)
|
||||
for _, field := range fields {
|
||||
fmt.Fprintf(buf, " %s=", colorize(field, cCyan, !w.NoColor))
|
||||
switch value := event[field].(type) {
|
||||
case string:
|
||||
if needsQuote(value) {
|
||||
buf.WriteString(strconv.Quote(value))
|
||||
} else {
|
||||
buf.WriteString(value)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(buf, value)
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteTo(w.Out)
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
|
||||
func colorize(s interface{}, color int, enabled bool) string {
|
||||
if !enabled {
|
||||
return fmt.Sprintf("%v", s)
|
||||
}
|
||||
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s)
|
||||
}
|
||||
|
||||
func levelColor(level string) int {
|
||||
switch level {
|
||||
case "debug":
|
||||
return cMagenta
|
||||
case "info":
|
||||
return cGreen
|
||||
case "warn":
|
||||
return cYellow
|
||||
case "error", "fatal", "panic":
|
||||
return cRed
|
||||
default:
|
||||
return cReset
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuote(s string) bool {
|
||||
for i := range s {
|
||||
if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func (e *Event) appendObject(obj LogObjectMarshaler) {
|
||||
obj.MarshalZerologObject(e)
|
||||
if pos < len(e.buf) {
|
||||
// As MarshalZerologObject will use event API, the first field will be
|
||||
// preceded by a coma. If at least one field has been added (buf grew),
|
||||
// preceded by a comma. If at least one field has been added (buf grew),
|
||||
// we replace this coma by the opening bracket.
|
||||
e.buf[pos] = '{'
|
||||
} else {
|
||||
@@ -197,6 +197,9 @@ func (e *Event) Strs(key string, vals []string) *Event {
|
||||
}
|
||||
|
||||
// Bytes adds the field key with val as a string to the *Event context.
|
||||
//
|
||||
// Runes outside of normal ASCII ranges will be hex-encoded in the resulting
|
||||
// JSON.
|
||||
func (e *Event) Bytes(key string, val []byte) *Event {
|
||||
if !e.enabled {
|
||||
return e
|
||||
|
||||
@@ -162,6 +162,16 @@ func Nop() Logger {
|
||||
return New(nil).Level(Disabled)
|
||||
}
|
||||
|
||||
// Output duplicates the current logger and sets w as its output.
|
||||
func (l Logger) Output(w io.Writer) Logger {
|
||||
l2 := New(w)
|
||||
l2.level = l.level
|
||||
l2.sample = l.sample
|
||||
l2.context = make([]byte, len(l.context))
|
||||
copy(l2.context, l.context)
|
||||
return l2
|
||||
}
|
||||
|
||||
// With creates a child logger with the field added to its context.
|
||||
func (l Logger) With() Context {
|
||||
context := l.context
|
||||
|
||||
@@ -3,6 +3,7 @@ package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -11,6 +12,11 @@ import (
|
||||
// Logger is the global logger.
|
||||
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
|
||||
// Output duplicates the global logger and sets w as its output.
|
||||
func Output(w io.Writer) zerolog.Logger {
|
||||
return Logger.Output(w)
|
||||
}
|
||||
|
||||
// With creates a child logger with the field added to its context.
|
||||
func With() zerolog.Context {
|
||||
return Logger.With()
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 377 KiB |
Reference in New Issue
Block a user