1
0
mirror of https://github.com/rs/zerolog synced 2026-06-08 17:13:30 +00:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Olivier Poitrey 8e30c71369 Revert the wrapping of write errors
All errors generated by Go libraries on stderr won't be json encoded
anyway, so it does not make sense to have such a treatment for this one.
2018-11-08 14:49:44 -08:00
Olivier Poitrey 3f112dae87 Fix "could not write" error missing carriage return 2018-11-07 15:21:38 -08:00
Karel Minarik a4c54e5d8b Fix the ConsoleWriter default parts order (#113)
In order to prevent incorrect output when somebody uses a different name eg. for the "MessageFieldName",
the `consoleDefaultPartsOrder` variable has been switched to a function, which is called in `Write()`,
in order to pick up the custom name.

Related: rs/zerolog#92
2018-11-07 09:39:38 -08:00
Karel Minarik 96f91bb4f5 Refactored zerolog.ConsoleWriter to allow customization (#92)
* Added a simple benchmarking test for the ConsoleWriter
* Refactored `zerolog.ConsoleWriter` to allow customization

Closes #84
2018-11-05 02:15:13 -08:00
Olivier Poitrey 51c79ca476 Fix com typo 2018-11-02 13:06:29 -07:00
Olivier Poitrey e7627a4f73 diode: let use a waiter instead of a poller by using 0 as a poolInterval 2018-10-31 16:57:15 -07:00
anthony baa31cfa85 Fix nil pointer dereference when call Fields with a typed nil value (#112) 2018-10-31 10:40:46 -07:00
Vojtech Vitek 8e36cbf881 Don't call panic() and os.Exit(1) on .WithLevel() (#110)
* Don't call panic() and os.Exit(1) on .WithLevel()
* Explain behavior of WithLevel(), compared to Panic() & Fatal()
2018-09-27 18:11:43 -07:00
Olivier Poitrey 20ad1708e7 Fix nil pointer exception on Discard when called with nil logger
Fixes #108
2018-09-26 09:52:52 -07:00
Olivier Poitrey 338f9bc140 Fix typo 2018-09-19 07:40:00 -07:00
12 changed files with 757 additions and 159 deletions
+1
View File
@@ -6,6 +6,7 @@
# Folders
_obj
_test
tmp
# Architecture specific extensions/prefixes
*.[568vq]
+30 -6
View File
@@ -8,7 +8,7 @@ Zerolog's API is designed to provide both a great developer experience and stunn
Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance.
To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`.
To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) [`zerolog.ConsoleWriter`](#pretty-logging).
![Pretty Logging Image](pretty.png)
@@ -60,7 +60,7 @@ func main() {
// Output: {"time":1516134303,"level":"debug","message":"hello world"}
```
> Note: By default log writes to `os.Stderr`
> Note: By default log writes to `os.Stderr`
> Note: The default log level for `log.Print` is *debug*
### Contextual Logging
@@ -252,14 +252,38 @@ sublogger.Info().Msg("hello world")
### Pretty logging
To log a human-friendly, colorized output, use `zerolog.ConsoleWriter`:
```go
if isConsole {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
log.Info().Str("foo", "bar").Msg("Hello world")
// Output: 1494567715 |INFO| Hello world foo=bar
// Output: 3:04PM INF Hello World foo=bar
```
To customize the configuration and formatting:
```go
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
output.FormatLevel = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
}
output.FormatMessage = func(i interface{}) string {
return fmt.Sprintf("***%s****", i)
}
output.FormatFieldName = func(i interface{}) string {
return fmt.Sprintf("%s:", i)
}
output.FormatFieldValue = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("%s", i))
}
log := zerolog.New(output).With().Timestamp().Logger()
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: 2006-01-02T15:04:05Z07:00 | INFO | ***Hello World**** foo:BAR
```
### Sub dictionary
+310 -94
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
@@ -13,133 +14,262 @@ import (
)
const (
cReset = 0
cBold = 1
cRed = 31
cGreen = 32
cYellow = 33
cBlue = 34
cMagenta = 35
cCyan = 36
cGray = 37
cDarkGray = 90
colorBold = iota + 1
colorFaint
)
var consoleBufPool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 100))
},
}
const (
colorBlack = iota + 30
colorRed
colorGreen
colorYellow
colorBlue
colorMagenta
colorCyan
colorWhite
)
// LevelWidth defines the desired character width of the log level column.
// Default 0 does not trim or pad (variable width based level text, e.g. "INFO" or "ERROR")
var LevelWidth = 0
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.
consoleDefaultTimeFormat = time.Kitchen
consoleDefaultFormatter = func(i interface{}) string { return fmt.Sprintf("%s", i) }
consoleDefaultPartsOrder = func() []string {
return []string{
TimestampFieldName,
LevelFieldName,
CallerFieldName,
MessageFieldName,
}
}
consoleNoColor = false
consoleTimeFormat = consoleDefaultTimeFormat
)
// Formatter transforms the input into a formatted string.
type Formatter func(interface{}) string
// ConsoleWriter parses the JSON input and writes it in an
// (optionally) colorized, human-friendly format to Out.
type ConsoleWriter struct {
Out io.Writer
// Out is the output destination.
Out io.Writer
// NoColor disables the colorized output.
NoColor bool
// TimeFormat specifies the format for timestamp in output.
TimeFormat string
// PartsOrder defines the order of parts in output.
PartsOrder []string
FormatTimestamp Formatter
FormatLevel Formatter
FormatCaller Formatter
FormatMessage Formatter
FormatFieldName Formatter
FormatFieldValue Formatter
FormatErrFieldName Formatter
FormatErrFieldValue Formatter
}
// NewConsoleWriter creates and initializes a new ConsoleWriter.
func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
w := ConsoleWriter{
Out: os.Stdout,
TimeFormat: consoleDefaultTimeFormat,
PartsOrder: consoleDefaultPartsOrder(),
}
for _, opt := range options {
opt(&w)
}
return w
}
// Write transforms the JSON input with formatters and appends to w.Out.
func (w ConsoleWriter) Write(p []byte) (n int, err error) {
var event map[string]interface{}
if w.PartsOrder == nil {
w.PartsOrder = consoleDefaultPartsOrder()
}
if w.TimeFormat == "" && consoleTimeFormat != consoleDefaultTimeFormat {
consoleTimeFormat = consoleDefaultTimeFormat
}
if w.TimeFormat != "" && consoleTimeFormat != w.TimeFormat {
consoleTimeFormat = w.TimeFormat
}
if w.NoColor == false && consoleNoColor != false {
consoleNoColor = false
}
if w.NoColor == true && consoleNoColor != w.NoColor {
consoleNoColor = w.NoColor
}
var buf = consoleBufPool.Get().(*bytes.Buffer)
defer consoleBufPool.Put(buf)
var evt map[string]interface{}
p = decodeIfBinaryToBytes(p)
d := json.NewDecoder(bytes.NewReader(p))
d.UseNumber()
err = d.Decode(&event)
err = d.Decode(&evt)
if err != nil {
return
return n, fmt.Errorf("cannot decode event: %s", err)
}
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)
if LevelWidth > 0 {
if padding := LevelWidth - len(level); padding > 0 {
level += strings.Repeat(" ", padding)
} else {
level = level[0:LevelWidth]
}
}
for _, p := range w.PartsOrder {
w.writePart(buf, evt, p)
}
fmt.Fprintf(buf, "%s |%s| %s",
colorize(formatTime(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 {
w.writeFields(evt, buf)
buf.WriteByte('\n')
buf.WriteTo(w.Out)
return len(p), nil
}
// writeFields appends formatted key-value pairs to buf.
func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) {
var fields = make([]string, 0, len(evt))
for field := range evt {
switch field {
case LevelFieldName, TimestampFieldName, MessageFieldName:
case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName:
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)
if len(fields) > 0 {
buf.WriteByte(' ')
}
// Move the "error" field to the front
ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= ErrorFieldName })
if ei < len(fields) && fields[ei] == ErrorFieldName {
fields[ei] = ""
fields = append([]string{ErrorFieldName}, fields...)
var xfields = make([]string, 0, len(fields))
for _, field := range fields {
if field == "" { // Skip empty fields
continue
}
case json.Number:
fmt.Fprint(buf, value)
default:
b, err := json.Marshal(value)
if err != nil {
fmt.Fprintf(buf, "[error: %v]", err)
xfields = append(xfields, field)
}
fields = xfields
}
for i, field := range fields {
var fn Formatter
var fv Formatter
if field == ErrorFieldName {
if w.FormatErrFieldName == nil {
fn = consoleDefaultFormatErrFieldName
} else {
fmt.Fprint(buf, string(b))
fn = w.FormatErrFieldName
}
if w.FormatErrFieldValue == nil {
fv = consoleDefaultFormatErrFieldValue
} else {
fv = w.FormatErrFieldValue
}
} else {
if w.FormatFieldName == nil {
fn = consoleDefaultFormatFieldName
} else {
fn = w.FormatFieldName
}
if w.FormatFieldValue == nil {
fv = consoleDefaultFormatFieldValue
} else {
fv = w.FormatFieldValue
}
}
buf.WriteString(fn(field))
switch fValue := evt[field].(type) {
case string:
if needsQuote(fValue) {
buf.WriteString(fv(strconv.Quote(fValue)))
} else {
buf.WriteString(fv(fValue))
}
case json.Number:
buf.WriteString(fv(fValue))
default:
b, err := json.Marshal(fValue)
if err != nil {
fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
} else {
fmt.Fprint(buf, fv(b))
}
}
if i < len(fields)-1 { // Skip space for last field
buf.WriteByte(' ')
}
}
buf.WriteByte('\n')
buf.WriteTo(w.Out)
n = len(p)
return
}
func formatTime(t interface{}) string {
switch t := t.(type) {
case string:
return t
case json.Number:
u, _ := t.Int64()
return time.Unix(u, 0).Format(time.RFC3339)
}
return "<nil>"
}
// writePart appends a formatted part to buf.
func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) {
var f Formatter
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
switch p {
case LevelFieldName:
if w.FormatLevel == nil {
f = consoleDefaultFormatLevel
} else {
f = w.FormatLevel
}
case TimestampFieldName:
if w.FormatTimestamp == nil {
f = consoleDefaultFormatTimestamp
} else {
f = w.FormatTimestamp
}
case MessageFieldName:
if w.FormatMessage == nil {
f = consoleDefaultFormatMessage
} else {
f = w.FormatMessage
}
case CallerFieldName:
if w.FormatCaller == nil {
f = consoleDefaultFormatCaller
} else {
f = w.FormatCaller
}
default:
return cReset
if w.FormatFieldValue == nil {
f = consoleDefaultFormatFieldValue
} else {
f = w.FormatFieldValue
}
}
var s = f(evt[p])
if len(s) > 0 {
buf.WriteString(s)
if p != w.PartsOrder[len(w.PartsOrder)-1] { // Skip space for last part
buf.WriteByte(' ')
}
}
}
// needsQuote returns true when the string s should be quoted in output.
func needsQuote(s string) bool {
for i := range s {
if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
@@ -148,3 +278,89 @@ func needsQuote(s string) bool {
}
return false
}
// colorize returns the string s wrapped in ANSI code c, unless disabled is true.
func colorize(s interface{}, c int, disabled bool) string {
if disabled {
return fmt.Sprintf("%s", s)
}
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s)
}
// ----- DEFAULT FORMATTERS ---------------------------------------------------
var (
consoleDefaultFormatTimestamp = func(i interface{}) string {
t := "<nil>"
if tt, ok := i.(string); ok {
ts, err := time.Parse(time.RFC3339, tt)
if err != nil {
t = tt
} else {
t = ts.Format(consoleTimeFormat)
}
}
return colorize(t, colorFaint, consoleNoColor)
}
consoleDefaultFormatLevel = func(i interface{}) string {
var l string
if ll, ok := i.(string); ok {
switch ll {
case "debug":
l = colorize("DBG", colorYellow, consoleNoColor)
case "info":
l = colorize("INF", colorGreen, consoleNoColor)
case "warn":
l = colorize("WRN", colorRed, consoleNoColor)
case "error":
l = colorize(colorize("ERR", colorRed, consoleNoColor), colorBold, consoleNoColor)
case "fatal":
l = colorize(colorize("FTL", colorRed, consoleNoColor), colorBold, consoleNoColor)
case "panic":
l = colorize(colorize("PNC", colorRed, consoleNoColor), colorBold, consoleNoColor)
default:
l = colorize("???", colorBold, consoleNoColor)
}
} else {
l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3]
}
return l
}
consoleDefaultFormatCaller = func(i interface{}) string {
var c string
if cc, ok := i.(string); ok {
c = cc
}
if len(c) > 0 {
cwd, err := os.Getwd()
if err == nil {
c = strings.TrimPrefix(c, cwd)
c = strings.TrimPrefix(c, "/")
}
c = colorize(c, colorBold, consoleNoColor) + colorize(" >", colorFaint, consoleNoColor)
}
return c
}
consoleDefaultFormatMessage = func(i interface{}) string {
return fmt.Sprintf("%s", i)
}
consoleDefaultFormatFieldName = func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorFaint, consoleNoColor)
}
consoleDefaultFormatFieldValue = func(i interface{}) string {
return fmt.Sprintf("%s", i)
}
consoleDefaultFormatErrFieldName = func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorRed, consoleNoColor)
}
consoleDefaultFormatErrFieldValue = func(i interface{}) string {
return colorize(fmt.Sprintf("%s", i), colorRed, consoleNoColor)
}
)
+221 -13
View File
@@ -2,29 +2,237 @@ package zerolog_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"time"
"github.com/rs/zerolog"
)
func ExampleConsoleWriter_Write() {
func ExampleConsoleWriter() {
log := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true})
log.Info().Msg("hello world")
// Output: <nil> |INFO| hello world
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: <nil> INF Hello World foo=bar
}
func TestConsoleWriterNumbers(t *testing.T) {
buf := &bytes.Buffer{}
log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true})
log.Info().
Float64("float", 1.23).
Uint64("small", 123).
Uint64("big", 1152921504606846976).
Msg("msg")
if got, want := strings.TrimSpace(buf.String()), "<nil> |INFO| msg big=1152921504606846976 float=1.23 small=123"; got != want {
t.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
func ExampleConsoleWriter_customFormatters() {
out := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true}
out.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%-6s|", i)) }
out.FormatFieldName = func(i interface{}) string { return fmt.Sprintf("%s:", i) }
out.FormatFieldValue = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%s", i)) }
log := zerolog.New(out)
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: <nil> INFO | Hello World foo:BAR
}
func ExampleNewConsoleWriter() {
out := zerolog.NewConsoleWriter()
out.NoColor = true // For testing purposes only
log := zerolog.New(out)
log.Debug().Str("foo", "bar").Msg("Hello World")
// Output: <nil> DBG Hello World foo=bar
}
func ExampleNewConsoleWriter_customFormatters() {
out := zerolog.NewConsoleWriter(
func(w *zerolog.ConsoleWriter) {
// Customize time format
w.TimeFormat = time.RFC822
// Customize level formatting
w.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("[%-5s]", i)) }
},
)
out.NoColor = true // For testing purposes only
log := zerolog.New(out)
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: <nil> [INFO ] Hello World foo=bar
}
func TestConsoleLogger(t *testing.T) {
t.Run("Numbers", func(t *testing.T) {
buf := &bytes.Buffer{}
log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true})
log.Info().
Float64("float", 1.23).
Uint64("small", 123).
Uint64("big", 1152921504606846976).
Msg("msg")
if got, want := strings.TrimSpace(buf.String()), "<nil> INF msg big=1152921504606846976 float=1.23 small=123"; got != want {
t.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
func TestConsoleWriter(t *testing.T) {
t.Run("Default field formatter", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsOrder: []string{"foo"}}
_, err := w.Write([]byte(`{"foo" : "DEFAULT"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "DEFAULT foo=DEFAULT\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write colorized", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: false}
_, err := w.Write([]byte(`{"level" : "warn", "message" : "Foobar"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "\x1b[2m<nil>\x1b[0m \x1b[31mWRN\x1b[0m Foobar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write fields", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
_, err := w.Write([]byte(`{"time" : "` + d + `", "level" : "debug", "message" : "Foobar", "foo" : "bar"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM DBG Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write error field", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
evt := `{"time" : "` + d + `", "level" : "error", "message" : "Foobar", "aaa" : "bbb", "error" : "Error"}`
// t.Log(evt)
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM ERR Foobar error=Error aaa=bbb\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write caller field", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("Cannot get working directory: %s", err)
}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
evt := `{"time" : "` + d + `", "level" : "debug", "message" : "Foobar", "foo" : "bar", "caller" : "` + cwd + `/foo/bar.go"}`
// t.Log(evt)
_, err = w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM DBG foo/bar.go > Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write JSON field", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
evt := `{"level" : "debug", "message" : "Foobar", "foo" : [1, 2, 3], "bar" : true}`
// t.Log(evt)
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "<nil> DBG Foobar bar=true foo=[1,2,3]\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
}
func TestConsoleWriterConfiguration(t *testing.T) {
t.Run("Sets TimeFormat", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: time.RFC3339}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
evt := `{"time" : "` + d + `", "level" : "info", "message" : "Foobar"}`
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "1970-01-01T00:00:00Z INF Foobar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Sets PartsOrder", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsOrder: []string{"message", "level"}}
evt := `{"level" : "info", "message" : "Foobar"}`
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "Foobar INF\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
}
func BenchmarkConsoleWriter(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
var msg = []byte(`{"level" : "info", "foo" : "bar", "message" : "HELLO", "time" : "1990-01-01"}`)
w := zerolog.ConsoleWriter{Out: ioutil.Discard, NoColor: false}
for i := 0; i < b.N; i++ {
w.Write(msg)
}
}
+23 -10
View File
@@ -19,12 +19,16 @@ var bufPool = &sync.Pool{
type Alerter func(missed int)
type diodeFetcher interface {
diodes.Diode
Next() diodes.GenericDataType
}
// Writer is a io.Writer wrapper that uses a diode to make Write lock-free,
// non-blocking and thread safe.
type Writer struct {
w io.Writer
d *diodes.ManyToOne
p *diodes.Poller
d diodeFetcher
c context.CancelFunc
done chan struct{}
}
@@ -35,25 +39,34 @@ type Writer struct {
//
// Use a diode.Writer when
//
// wr := diode.NewWriter(w, 1000, 10 * time.Millisecond, func(missed int) {
// wr := diode.NewWriter(w, 1000, 0, func(missed int) {
// log.Printf("Dropped %d messages", missed)
// })
// log := zerolog.New(wr)
//
// If pollInterval is greater than 0, a poller is used otherwise a waiter is
// used.
//
// See code.cloudfoundry.org/go-diodes for more info on diode.
func NewWriter(w io.Writer, size int, poolInterval time.Duration, f Alerter) Writer {
ctx, cancel := context.WithCancel(context.Background())
d := diodes.NewManyToOne(size, diodes.AlertFunc(f))
dw := Writer{
w: w,
d: d,
p: diodes.NewPoller(d,
diodes.WithPollingInterval(poolInterval),
diodes.WithPollingContext(ctx)),
w: w,
c: cancel,
done: make(chan struct{}),
}
if f == nil {
f = func(int) {}
}
d := diodes.NewManyToOne(size, diodes.AlertFunc(f))
if poolInterval > 0 {
dw.d = diodes.NewPoller(d,
diodes.WithPollingInterval(poolInterval),
diodes.WithPollingContext(ctx))
} else {
dw.d = diodes.NewWaiter(d,
diodes.WithWaiterContext(ctx))
}
go dw.poll()
return dw
}
@@ -80,7 +93,7 @@ func (dw Writer) Close() error {
func (dw Writer) poll() {
defer close(dw.done)
for {
d := dw.p.Next()
d := dw.d.Next()
if d == nil {
return
}
+1 -2
View File
@@ -5,14 +5,13 @@ package diode_test
import (
"fmt"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/diode"
)
func ExampleNewWriter() {
w := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) {
w := diode.NewWriter(os.Stdout, 1000, 0, func(missed int) {
fmt.Printf("Dropped %d messages\n", missed)
})
log := zerolog.New(w)
+18 -11
View File
@@ -16,7 +16,7 @@ import (
func TestNewWriter(t *testing.T) {
buf := bytes.Buffer{}
w := diode.NewWriter(&buf, 1000, 10*time.Millisecond, func(missed int) {
w := diode.NewWriter(&buf, 1000, 0, func(missed int) {
fmt.Printf("Dropped %d messages\n", missed)
})
log := zerolog.New(w)
@@ -33,15 +33,22 @@ func TestNewWriter(t *testing.T) {
func Benchmark(b *testing.B) {
log.SetOutput(ioutil.Discard)
defer log.SetOutput(os.Stderr)
w := diode.NewWriter(ioutil.Discard, 100000, 10*time.Millisecond, nil)
log := zerolog.New(w)
defer w.Close()
b.SetParallelism(1000)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
log.Print("test")
}
})
benchs := map[string]time.Duration{
"Waiter": 0,
"Pooler": 10 * time.Millisecond,
}
for name, interval := range benchs {
b.Run(name, func(b *testing.B) {
w := diode.NewWriter(ioutil.Discard, 100000, interval, nil)
log := zerolog.New(w)
defer w.Close()
b.SetParallelism(1000)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
log.Print("test")
}
})
})
}
}
+4 -1
View File
@@ -92,6 +92,9 @@ func (e *Event) Enabled() bool {
// Discard disables the event so Msg(f) won't print it.
func (e *Event) Discard() *Event {
if e == nil {
return e
}
e.level = Disabled
return nil
}
@@ -155,7 +158,7 @@ func (e *Event) Dict(key string, dict *Event) *Event {
}
dict.buf = enc.AppendEndMarker(dict.buf)
e.buf = append(enc.AppendKey(e.buf, key), dict.buf...)
putEvent(e)
putEvent(dict)
return e
}
+80 -16
View File
@@ -99,37 +99,101 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
case time.Duration:
dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger)
case *string:
dst = enc.AppendString(dst, *val)
if val != nil {
dst = enc.AppendString(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *bool:
dst = enc.AppendBool(dst, *val)
if val != nil {
dst = enc.AppendBool(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *int:
dst = enc.AppendInt(dst, *val)
if val != nil {
dst = enc.AppendInt(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *int8:
dst = enc.AppendInt8(dst, *val)
if val != nil {
dst = enc.AppendInt8(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *int16:
dst = enc.AppendInt16(dst, *val)
if val != nil {
dst = enc.AppendInt16(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *int32:
dst = enc.AppendInt32(dst, *val)
if val != nil {
dst = enc.AppendInt32(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *int64:
dst = enc.AppendInt64(dst, *val)
if val != nil {
dst = enc.AppendInt64(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *uint:
dst = enc.AppendUint(dst, *val)
if val != nil {
dst = enc.AppendUint(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *uint8:
dst = enc.AppendUint8(dst, *val)
if val != nil {
dst = enc.AppendUint8(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *uint16:
dst = enc.AppendUint16(dst, *val)
if val != nil {
dst = enc.AppendUint16(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *uint32:
dst = enc.AppendUint32(dst, *val)
if val != nil {
dst = enc.AppendUint32(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *uint64:
dst = enc.AppendUint64(dst, *val)
if val != nil {
dst = enc.AppendUint64(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *float32:
dst = enc.AppendFloat32(dst, *val)
if val != nil {
dst = enc.AppendFloat32(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *float64:
dst = enc.AppendFloat64(dst, *val)
if val != nil {
dst = enc.AppendFloat64(dst, *val)
} else {
dst = enc.AppendNil(dst)
}
case *time.Time:
dst = enc.AppendTime(dst, *val, TimeFieldFormat)
if val != nil {
dst = enc.AppendTime(dst, *val, TimeFieldFormat)
} else {
dst = enc.AppendNil(dst)
}
case *time.Duration:
dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger)
if val != nil {
dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger)
} else {
dst = enc.AppendNil(dst)
}
case []string:
dst = enc.AppendStrings(dst, val)
case []bool:
+8 -6
View File
@@ -292,22 +292,24 @@ func (l *Logger) Error() *Event {
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method.
// is called by the Msg method, which terminates the program immediately.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Fatal() *Event {
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
}
// Panic starts a new message with panic level. The message is also sent
// to the panic function.
// Panic starts a new message with panic level. The panic() function
// is called by the Msg method, which stops the ordinary flow of a goroutine.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Panic() *Event {
return l.newEvent(PanicLevel, func(msg string) { panic(msg) })
}
// WithLevel starts a new message with level.
// WithLevel starts a new message with level. Unlike Fatal and Panic
// methods, WithLevel does not terminate the program or stop the ordinary
// flow of a gourotine when used with their respective levels.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) WithLevel(level Level) *Event {
@@ -321,9 +323,9 @@ func (l *Logger) WithLevel(level Level) *Event {
case ErrorLevel:
return l.Error()
case FatalLevel:
return l.Fatal()
return l.newEvent(FatalLevel, nil)
case PanicLevel:
return l.Panic()
return l.newEvent(PanicLevel, nil)
case NoLevel:
return l.Log()
case Disabled:
+61
View File
@@ -164,6 +164,52 @@ func TestFieldsMapPnt(t *testing.T) {
}
}
func TestFieldsMapNilPnt(t *testing.T) {
var (
stringPnt *string
boolPnt *bool
intPnt *int
int8Pnt *int8
int16Pnt *int16
int32Pnt *int32
int64Pnt *int64
uintPnt *uint
uint8Pnt *uint8
uint16Pnt *uint16
uint32Pnt *uint32
uint64Pnt *uint64
float32Pnt *float32
float64Pnt *float64
durPnt *time.Duration
timePnt *time.Time
)
out := &bytes.Buffer{}
log := New(out)
fields := map[string]interface{}{
"string": stringPnt,
"bool": boolPnt,
"int": intPnt,
"int8": int8Pnt,
"int16": int16Pnt,
"int32": int32Pnt,
"int64": int64Pnt,
"uint": uintPnt,
"uint8": uint8Pnt,
"uint16": uint16Pnt,
"uint32": uint32Pnt,
"uint64": uint64Pnt,
"float32": float32Pnt,
"float64": float64Pnt,
"dur": durPnt,
"time": timePnt,
}
log.Log().Fields(fields).Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":null,"dur":null,"float32":null,"float64":null,"int":null,"int16":null,"int32":null,"int64":null,"int8":null,"string":null,"time":null,"uint":null,"uint16":null,"uint32":null,"uint64":null,"uint8":null}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
func TestFields(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
@@ -403,6 +449,21 @@ func TestSampling(t *testing.T) {
}
}
func TestDiscard(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Discard().Str("a", "b").Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
// Double call
log.Log().Discard().Discard().Str("a", "b").Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
type levelWriter struct {
ops []struct {
l Level
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 KiB

After

Width:  |  Height:  |  Size: 141 KiB