mirror of
https://github.com/rs/zerolog
synced 2026-06-08 17:13:30 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f1dd50b8c6 | |||
| 686705b4f0 | |||
| 5d9d7660cc | |||
| 54e95fe699 | |||
| e709c5d91e | |||
| 4502cc1942 | |||
| 43d05e8ddf | |||
| 5861452d64 | |||
| 7592fcbe60 | |||
| 2e41c37ac4 | |||
| 19e454b4c7 | |||
| 33a4561a07 | |||
| 06599535fa | |||
| 61d1749124 | |||
| b806a5ecbe | |||
| a7f9fc2a17 | |||
| 42d101e9f7 | |||
| 77a1695358 | |||
| 60d4b07b61 | |||
| 9938a23cba | |||
| 1a2c7daec4 | |||
| ffd0e9625d | |||
| ad0401954a | |||
| acf3980132 |
@@ -82,9 +82,14 @@ func main() {
|
||||
Str("Scale", "833 cents").
|
||||
Float64("Interval", 833.09).
|
||||
Msg("Fibonacci is everywhere")
|
||||
|
||||
log.Debug().
|
||||
Str("Name", "Tom").
|
||||
Send()
|
||||
}
|
||||
|
||||
// Output: {"time":1524104936,"level":"debug","Scale":"833 cents","Interval":833.09,"message":"Fibonacci is everywhere"}
|
||||
// Output: {"level":"debug","Scale":"833 cents","Interval":833.09,"time":1562212768,"message":"Fibonacci is everywhere"}
|
||||
// Output: {"level":"debug","Name":"Tom","time":1562212768}
|
||||
```
|
||||
|
||||
> You'll note in the above example that when adding contextual fields, the fields are strongly typed. You can find the full list of supported fields [here](#standard-types)
|
||||
@@ -120,6 +125,7 @@ func main() {
|
||||
* warn (`zerolog.WarnLevel`, 2)
|
||||
* info (`zerolog.InfoLevel`, 1)
|
||||
* debug (`zerolog.DebugLevel`, 0)
|
||||
* trace (`zerolog.TraceLevel`, -1)
|
||||
|
||||
You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant.
|
||||
|
||||
@@ -474,11 +480,10 @@ Some settings can be changed and will by applied to all loggers:
|
||||
* `zerolog.LevelFieldName`: Can be set to customize level field name.
|
||||
* `zerolog.MessageFieldName`: Can be set to customize message field name.
|
||||
* `zerolog.ErrorFieldName`: Can be set to customize `Err` field name.
|
||||
* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix` or `zerolog.TimeFormatUnixMs`, times are formated as UNIX timestamp.
|
||||
* DurationFieldUnit defines the unit for time.Duration type fields added using the Dur method.
|
||||
* `DurationFieldUnit`: Sets the unit of the fields added by `Dur` (default: `time.Millisecond`).
|
||||
* `DurationFieldInteger`: If set to true, `Dur` fields are formatted as integers instead of floats.
|
||||
* `ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
|
||||
* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix`, `zerolog.TimeFormatUnixMs` or `zerolog.TimeFormatUnixMicro`, times are formated as UNIX timestamp.
|
||||
* `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`).
|
||||
* `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`).
|
||||
* `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
|
||||
|
||||
## Field Types
|
||||
|
||||
@@ -577,7 +582,7 @@ Log a static string, without any context or `printf`-style templating:
|
||||
|
||||
## Caveats
|
||||
|
||||
Note that zerolog does de-duplication fields. Using the same key multiple times creates multiple keys in final JSON:
|
||||
Note that zerolog does no de-duplication of fields. Using the same key multiple times creates multiple keys in final JSON:
|
||||
|
||||
```go
|
||||
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
@@ -587,4 +592,4 @@ logger.Info().
|
||||
// Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
|
||||
```
|
||||
|
||||
However, it’s not a big deal as JSON accepts dup keys; the last one prevails.
|
||||
In this case, many consumers will take the last value, but this is not guaranteed; check yours if in doubt.
|
||||
|
||||
@@ -85,6 +85,12 @@ func (a *Array) Hex(val []byte) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// RawJSON adds already encoded JSON to the array.
|
||||
func (a *Array) RawJSON(val []byte) *Array {
|
||||
a.buf = appendJSON(enc.AppendArrayDelim(a.buf), val)
|
||||
return a
|
||||
}
|
||||
|
||||
// Err serializes and appends the err to the array.
|
||||
func (a *Array) Err(err error) *Array {
|
||||
marshaled := ErrorMarshalFunc(err)
|
||||
|
||||
+2
-1
@@ -24,10 +24,11 @@ func TestArray(t *testing.T) {
|
||||
Str("a").
|
||||
Bytes([]byte("b")).
|
||||
Hex([]byte{0x1f}).
|
||||
RawJSON([]byte(`{"some":"json"}`)).
|
||||
Time(time.Time{}).
|
||||
IPAddr(net.IP{192, 168, 0, 10}).
|
||||
Dur(0)
|
||||
want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f","0001-01-01T00:00:00Z","192.168.0.10",0]`
|
||||
want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10",0]`
|
||||
if got := decodeObjectToStr(a.write([]byte{})); got != want {
|
||||
t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
|
||||
@@ -108,6 +108,19 @@ func ExampleLogger_Printf() {
|
||||
// Output: {"level":"debug","message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Trace() {
|
||||
dst := bytes.Buffer{}
|
||||
log := New(&dst)
|
||||
|
||||
log.Trace().
|
||||
Str("foo", "bar").
|
||||
Int("n", 123).
|
||||
Msg("hello world")
|
||||
|
||||
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
|
||||
// Output: {"level":"trace","foo":"bar","n":123,"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Debug() {
|
||||
dst := bytes.Buffer{}
|
||||
log := New(&dst)
|
||||
|
||||
+8
-1
@@ -299,7 +299,12 @@ func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
|
||||
if err != nil {
|
||||
t = tt.String()
|
||||
} else {
|
||||
ts := time.Unix(i, 0)
|
||||
var sec, nsec int64 = i, 0
|
||||
if TimeFieldFormat == TimeFormatUnixMs {
|
||||
nsec = int64(time.Duration(i) * time.Millisecond)
|
||||
sec = 0
|
||||
}
|
||||
ts := time.Unix(sec, nsec).UTC()
|
||||
t = ts.Format(timeFormat)
|
||||
}
|
||||
}
|
||||
@@ -312,6 +317,8 @@ func consoleDefaultFormatLevel(noColor bool) Formatter {
|
||||
var l string
|
||||
if ll, ok := i.(string); ok {
|
||||
switch ll {
|
||||
case "trace":
|
||||
l = colorize("TRC", colorMagenta, noColor)
|
||||
case "debug":
|
||||
l = colorize("DBG", colorYellow, noColor)
|
||||
case "info":
|
||||
|
||||
+25
-3
@@ -129,14 +129,36 @@ func TestConsoleWriter(t *testing.T) {
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
|
||||
w := zerolog.ConsoleWriter{Out: buf, TimeFormat: time.StampMilli, NoColor: true}
|
||||
|
||||
_, err := w.Write([]byte(`{"time": 0, "level": "debug", "message": "Foobar", "foo": "bar"}`))
|
||||
_, err := w.Write([]byte(`{"time": 1234, "level": "debug", "message": "Foobar", "foo": "bar"}`))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "4:00PM DBG Foobar foo=bar\n"
|
||||
expectedOutput := "Jan 1 00:20:34.000 DBG Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Unix timestamp ms input format", func(t *testing.T) {
|
||||
of := zerolog.TimeFieldFormat
|
||||
defer func() {
|
||||
zerolog.TimeFieldFormat = of
|
||||
}()
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w := zerolog.ConsoleWriter{Out: buf, TimeFormat: time.StampMilli, NoColor: true}
|
||||
|
||||
_, err := w.Write([]byte(`{"time": 1234567, "level": "debug", "message": "Foobar", "foo": "bar"}`))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "Jan 1 00:20:34.567 DBG Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
var disabledLogger *Logger
|
||||
|
||||
func init() {
|
||||
SetGlobalLevel(TraceLevel)
|
||||
l := Nop()
|
||||
disabledLogger = &l
|
||||
}
|
||||
|
||||
@@ -105,10 +105,20 @@ func (e *Event) Msg(msg string) {
|
||||
e.msg(msg)
|
||||
}
|
||||
|
||||
// Msgf sends the event with formated msg added as the message field if not empty.
|
||||
// Send is equivalent to calling Msg("").
|
||||
//
|
||||
// NOTICE: once this methid is called, the *Event should be disposed.
|
||||
// Calling Msg twice can have unexpected result.
|
||||
// NOTICE: once this method is called, the *Event should be disposed.
|
||||
func (e *Event) Send() {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.msg("")
|
||||
}
|
||||
|
||||
// Msgf sends the event with formatted msg added as the message field if not empty.
|
||||
//
|
||||
// NOTICE: once this method is called, the *Event should be disposed.
|
||||
// Calling Msgf twice can have unexpected result.
|
||||
func (e *Event) Msgf(format string, v ...interface{}) {
|
||||
if e == nil {
|
||||
return
|
||||
@@ -117,13 +127,8 @@ func (e *Event) Msgf(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
func (e *Event) msg(msg string) {
|
||||
if len(e.ch) > 0 {
|
||||
e.ch[0].Run(e, e.level, msg)
|
||||
if len(e.ch) > 1 {
|
||||
for _, hook := range e.ch[1:] {
|
||||
hook.Run(e, e.level, msg)
|
||||
}
|
||||
}
|
||||
for _, hook := range e.ch {
|
||||
hook.Run(e, e.level, msg)
|
||||
}
|
||||
if msg != "" {
|
||||
e.buf = enc.AppendString(enc.AppendKey(e.buf, MessageFieldName), msg)
|
||||
@@ -203,7 +208,7 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
|
||||
return e
|
||||
}
|
||||
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
||||
// EmbedObject marshals an object that implement the LogObjectMarshaler interface.
|
||||
func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
@@ -660,8 +665,14 @@ func (e *Event) Interface(key string, i interface{}) *Event {
|
||||
}
|
||||
|
||||
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
|
||||
func (e *Event) Caller() *Event {
|
||||
return e.caller(CallerSkipFrameCount)
|
||||
// The argument skip is the number of stack frames to ascend
|
||||
// Skip If not passed, use the global variable CallerSkipFrameCount
|
||||
func (e *Event) Caller(skip ...int) *Event {
|
||||
sk := CallerSkipFrameCount
|
||||
if len(skip) > 0 {
|
||||
sk = skip[0] + CallerSkipFrameCount
|
||||
}
|
||||
return e.caller(sk)
|
||||
}
|
||||
|
||||
func (e *Event) caller(skip int) *Event {
|
||||
|
||||
+14
-10
@@ -2,18 +2,22 @@ package zerolog
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
import "sync/atomic"
|
||||
|
||||
const (
|
||||
// TimeFormatUnix defines a time format that makes time fields to be
|
||||
// serialized as Unix timestamp integers.
|
||||
TimeFormatUnix = ""
|
||||
|
||||
// TimeFormatUnix defines a time format that makes time fields to be
|
||||
// TimeFormatUnixMs defines a time format that makes time fields to be
|
||||
// serialized as Unix timestamp integers in milliseconds.
|
||||
TimeFormatUnixMs = "UNIXMS"
|
||||
|
||||
// TimeFormatUnixMicro defines a time format that makes time fields to be
|
||||
// serialized as Unix timestamp integers in microseconds.
|
||||
TimeFormatUnixMicro = "UNIXMICRO"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -57,7 +61,7 @@ var (
|
||||
}
|
||||
|
||||
// TimeFieldFormat defines the time format of the Time field type. If set to
|
||||
// TimeFormatUnix or TimeFormatUnixMs, the time is formatted as an UNIX
|
||||
// TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as an UNIX
|
||||
// timestamp as integer.
|
||||
TimeFieldFormat = time.RFC3339
|
||||
|
||||
@@ -79,8 +83,8 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
gLevel = new(uint32)
|
||||
disableSampling = new(uint32)
|
||||
gLevel = new(int32)
|
||||
disableSampling = new(int32)
|
||||
)
|
||||
|
||||
// SetGlobalLevel sets the global override for log level. If this
|
||||
@@ -88,23 +92,23 @@ var (
|
||||
//
|
||||
// To globally disable logs, set GlobalLevel to Disabled.
|
||||
func SetGlobalLevel(l Level) {
|
||||
atomic.StoreUint32(gLevel, uint32(l))
|
||||
atomic.StoreInt32(gLevel, int32(l))
|
||||
}
|
||||
|
||||
// GlobalLevel returns the current global log level
|
||||
func GlobalLevel() Level {
|
||||
return Level(atomic.LoadUint32(gLevel))
|
||||
return Level(atomic.LoadInt32(gLevel))
|
||||
}
|
||||
|
||||
// DisableSampling will disable sampling in all Loggers if true.
|
||||
func DisableSampling(v bool) {
|
||||
var i uint32
|
||||
var i int32
|
||||
if v {
|
||||
i = 1
|
||||
}
|
||||
atomic.StoreUint32(disableSampling, i)
|
||||
atomic.StoreInt32(disableSampling, i)
|
||||
}
|
||||
|
||||
func samplingDisabled() bool {
|
||||
return atomic.LoadUint32(disableSampling) == 1
|
||||
return atomic.LoadInt32(disableSampling) == 1
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
module github.com/rs/zerolog
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/zenazn/goji v0.9.0
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -171,6 +171,22 @@ func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.
|
||||
}
|
||||
}
|
||||
|
||||
// CustomHeaderHandler adds given header from request's header as a field to
|
||||
// the context's logger using fieldKey as field key.
|
||||
func CustomHeaderHandler(fieldKey, header string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if val := r.Header.Get(header); val != "" {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, val)
|
||||
})
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AccessHandler returns a handler that call f after each request.
|
||||
func AccessHandler(f func(r *http.Request, status, size int, duration time.Duration)) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
|
||||
@@ -182,6 +182,24 @@ func TestRequestIDHandler(t *testing.T) {
|
||||
h.ServeHTTP(httptest.NewRecorder(), r)
|
||||
}
|
||||
|
||||
func TestCustomHeaderHandler(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
r := &http.Request{
|
||||
Header: http.Header{
|
||||
"X-Request-Id": []string{"514bbe5bb5251c92bd07a9846f4a1ab6"},
|
||||
},
|
||||
}
|
||||
h := CustomHeaderHandler("reqID", "X-Request-Id")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"reqID":"514bbe5bb5251c92bd07a9846f4a1ab6"}`+"\n", decodeIfBinary(out); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedHandlers(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
r := &http.Request{
|
||||
|
||||
@@ -17,12 +17,16 @@ func (h HookFunc) Run(e *Event, level Level, message string) {
|
||||
|
||||
// LevelHook applies a different hook for each level.
|
||||
type LevelHook struct {
|
||||
NoLevelHook, DebugHook, InfoHook, WarnHook, ErrorHook, FatalHook, PanicHook Hook
|
||||
NoLevelHook, TraceHook, DebugHook, InfoHook, WarnHook, ErrorHook, FatalHook, PanicHook Hook
|
||||
}
|
||||
|
||||
// Run implements the Hook interface.
|
||||
func (h LevelHook) Run(e *Event, level Level, message string) {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
if h.TraceHook != nil {
|
||||
h.TraceHook.Run(e, level, message)
|
||||
}
|
||||
case DebugLevel:
|
||||
if h.DebugHook != nil {
|
||||
h.DebugHook.Run(e, level, message)
|
||||
|
||||
@@ -24,9 +24,9 @@ func (Encoder) AppendEndMarker(dst []byte) []byte {
|
||||
|
||||
// AppendObjectData takes an object in form of a byte array and appends to dst.
|
||||
func (Encoder) AppendObjectData(dst []byte, o []byte) []byte {
|
||||
// BeginMarker is present in the dst, which
|
||||
// should not be copied when appending to existing data.
|
||||
return append(dst, o[1:]...)
|
||||
// BeginMarker is present in the dst, which
|
||||
// should not be copied when appending to existing data.
|
||||
return append(dst, o[1:]...)
|
||||
}
|
||||
|
||||
// AppendArrayStart adds markers to indicate the start of an array.
|
||||
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
// Import from zerolog/global.go
|
||||
timeFormatUnix = ""
|
||||
timeFormatUnixMs = "UNIXMS"
|
||||
timeFormatUnixMicro = "UNIXMICRO"
|
||||
)
|
||||
|
||||
// AppendTime formats the input time with the given format
|
||||
@@ -19,6 +20,8 @@ func (e Encoder) AppendTime(dst []byte, t time.Time, format string) []byte {
|
||||
return e.AppendInt64(dst, t.Unix())
|
||||
case timeFormatUnixMs:
|
||||
return e.AppendInt64(dst, t.UnixNano()/1000000)
|
||||
case timeFormatUnixMicro:
|
||||
return e.AppendInt64(dst, t.UnixNano()/1000)
|
||||
}
|
||||
return append(t.AppendFormat(append(dst, '"'), format), '"')
|
||||
}
|
||||
|
||||
+10
-5
@@ -373,12 +373,17 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
|
||||
// AppendObjectData takes in an object that is already in a byte array
|
||||
// and adds it to the dst.
|
||||
func (Encoder) AppendObjectData(dst []byte, o []byte) []byte {
|
||||
// Two conditions we want to put a ',' between existing content and
|
||||
// new content:
|
||||
// 1. new content starts with '{' - which shd be dropped OR
|
||||
// 2. existing content has already other fields
|
||||
// Three conditions apply here:
|
||||
// 1. new content starts with '{' - which should be dropped OR
|
||||
// 2. new content starts with '{' - which should be replaced with ','
|
||||
// to separate with existing content OR
|
||||
// 3. existing content has already other fields
|
||||
if o[0] == '{' {
|
||||
o[0] = ','
|
||||
if len(dst) == 0 {
|
||||
o = o[1:]
|
||||
} else {
|
||||
o[0] = ','
|
||||
}
|
||||
} else if len(dst) > 1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
|
||||
@@ -165,3 +165,23 @@ func Test_appendMac(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_appendObjectData(t *testing.T) {
|
||||
tests := []struct {
|
||||
dst []byte
|
||||
obj []byte
|
||||
want []byte
|
||||
}{
|
||||
{[]byte{}, []byte(`{"foo":"bar"}`), []byte(`"foo":"bar"}`)},
|
||||
{[]byte(`{"qux":"quz"`), []byte(`{"foo":"bar"}`), []byte(`{"qux":"quz","foo":"bar"}`)},
|
||||
{[]byte{}, []byte(`"foo":"bar"`), []byte(`"foo":"bar"`)},
|
||||
{[]byte(`{"qux":"quz"`), []byte(`"foo":"bar"`), []byte(`{"qux":"quz","foo":"bar"`)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("ObjectData", func(t *testing.T) {
|
||||
if got := enc.AppendObjectData(tt.dst, tt.obj); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("appendObjectData() = %s, want %s", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-systemd/journal"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/internal/cbor"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultJournalDPrio = journal.PriNotice
|
||||
@@ -47,6 +48,8 @@ func levelToJPrio(zLevel string) journal.Priority {
|
||||
lvl, _ := zerolog.ParseLevel(zLevel)
|
||||
|
||||
switch lvl {
|
||||
case zerolog.TraceLevel:
|
||||
return journal.PriDebug
|
||||
case zerolog.DebugLevel:
|
||||
return journal.PriDebug
|
||||
case zerolog.InfoLevel:
|
||||
@@ -67,7 +70,7 @@ func levelToJPrio(zLevel string) journal.Priority {
|
||||
|
||||
func (w journalWriter) Write(p []byte) (n int, err error) {
|
||||
if !journal.Enabled() {
|
||||
err = fmt.Errorf("Cannot connect to journalD!!")
|
||||
err = fmt.Errorf("cannot connect to journalD")
|
||||
return
|
||||
}
|
||||
var event map[string]interface{}
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
// Msg("dup")
|
||||
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
|
||||
//
|
||||
// However, it’s not a big deal though as JSON accepts dup keys,
|
||||
// the last one prevails.
|
||||
// In this case, many consumers will take the last value,
|
||||
// but this is not guaranteed; check yours if in doubt.
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
@@ -107,7 +107,7 @@ import (
|
||||
)
|
||||
|
||||
// Level defines log levels.
|
||||
type Level uint8
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
// DebugLevel defines debug log level.
|
||||
@@ -126,10 +126,15 @@ const (
|
||||
NoLevel
|
||||
// Disabled disables the logger.
|
||||
Disabled
|
||||
|
||||
// TraceLevel defines trace log level.
|
||||
TraceLevel Level = -1
|
||||
)
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case TraceLevel:
|
||||
return "trace"
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
@@ -152,6 +157,8 @@ func (l Level) String() string {
|
||||
// returns an error if the input string does not match known values.
|
||||
func ParseLevel(levelStr string) (Level, error) {
|
||||
switch levelStr {
|
||||
case LevelFieldMarshalFunc(TraceLevel):
|
||||
return TraceLevel, nil
|
||||
case LevelFieldMarshalFunc(DebugLevel):
|
||||
return DebugLevel, nil
|
||||
case LevelFieldMarshalFunc(InfoLevel):
|
||||
@@ -198,7 +205,7 @@ func New(w io.Writer) Logger {
|
||||
if !ok {
|
||||
lw = levelWriterAdapter{w}
|
||||
}
|
||||
return Logger{w: lw}
|
||||
return Logger{w: lw, level: TraceLevel}
|
||||
}
|
||||
|
||||
// Nop returns a disabled logger for which all operation are no-op.
|
||||
@@ -251,6 +258,11 @@ func (l Logger) Level(lvl Level) Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
// GetLevel returns the current Level of l.
|
||||
func (l Logger) GetLevel() Level {
|
||||
return l.level
|
||||
}
|
||||
|
||||
// Sample returns a logger with the s sampler.
|
||||
func (l Logger) Sample(s Sampler) Logger {
|
||||
l.sampler = s
|
||||
@@ -263,6 +275,13 @@ func (l Logger) Hook(h Hook) Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
// Trace starts a new message with trace level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l *Logger) Trace() *Event {
|
||||
return l.newEvent(TraceLevel, nil)
|
||||
}
|
||||
|
||||
// Debug starts a new message with debug level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
@@ -298,9 +317,9 @@ func (l *Logger) Error() *Event {
|
||||
func (l *Logger) Err(err error) *Event {
|
||||
if err != nil {
|
||||
return l.Error().Err(err)
|
||||
} else {
|
||||
return l.Info()
|
||||
}
|
||||
|
||||
return l.Info()
|
||||
}
|
||||
|
||||
// Fatal starts a new message with fatal level. The os.Exit(1) function
|
||||
@@ -326,6 +345,8 @@ func (l *Logger) Panic() *Event {
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l *Logger) WithLevel(level Level) *Event {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
return l.Trace()
|
||||
case DebugLevel:
|
||||
return l.Debug()
|
||||
case InfoLevel:
|
||||
|
||||
+15
@@ -37,6 +37,21 @@ func Hook(h zerolog.Hook) zerolog.Logger {
|
||||
return Logger.Hook(h)
|
||||
}
|
||||
|
||||
// Err starts a new message with error level with err as a field if not nil or
|
||||
// with info level if err is nil.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func Err(err error) *zerolog.Event {
|
||||
return Logger.Err(err)
|
||||
}
|
||||
|
||||
// Trace starts a new message with trace level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func Trace() *zerolog.Event {
|
||||
return Logger.Trace()
|
||||
}
|
||||
|
||||
// Debug starts a new message with debug level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
|
||||
@@ -54,6 +54,25 @@ func ExampleLog() {
|
||||
// Output: {"time":1199811905,"message":"hello world"}
|
||||
}
|
||||
|
||||
// Example of a conditional level based on the presence of an error.
|
||||
func ExampleErr() {
|
||||
setup()
|
||||
err := errors.New("some error")
|
||||
log.Err(err).Msg("hello world")
|
||||
log.Err(nil).Msg("hello world")
|
||||
|
||||
// Output: {"level":"error","error":"some error","time":1199811905,"message":"hello world"}
|
||||
// {"level":"info","time":1199811905,"message":"hello world"}
|
||||
}
|
||||
|
||||
// Example of a log at a particular "level" (in this case, "trace")
|
||||
func ExampleTrace() {
|
||||
setup()
|
||||
log.Trace().Msg("hello world")
|
||||
|
||||
// Output: {"level":"trace","time":1199811905,"message":"hello world"}
|
||||
}
|
||||
|
||||
// Example of a log at a particular "level" (in this case, "debug")
|
||||
func ExampleDebug() {
|
||||
setup()
|
||||
|
||||
@@ -95,6 +95,17 @@ func ExampleLogger_Printf() {
|
||||
// Output: {"level":"debug","message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Trace() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
log.Trace().
|
||||
Str("foo", "bar").
|
||||
Int("n", 123).
|
||||
Msg("hello world")
|
||||
|
||||
// Output: {"level":"trace","foo":"bar","n":123,"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Debug() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
|
||||
+32
-10
@@ -453,6 +453,24 @@ func TestLevel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetLevel(t *testing.T) {
|
||||
levels := []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
NoLevel,
|
||||
Disabled,
|
||||
}
|
||||
for _, level := range levels {
|
||||
if got, want := New(nil).Level(level).GetLevel(), level; got != want {
|
||||
t.Errorf("GetLevel() = %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSampling(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Sample(&BasicSampler{N: 2})
|
||||
@@ -508,30 +526,34 @@ func TestLevelWriter(t *testing.T) {
|
||||
}{},
|
||||
}
|
||||
log := New(lw)
|
||||
log.Trace().Msg("0")
|
||||
log.Debug().Msg("1")
|
||||
log.Info().Msg("2")
|
||||
log.Warn().Msg("3")
|
||||
log.Error().Msg("4")
|
||||
log.Log().Msg("nolevel-1")
|
||||
log.WithLevel(DebugLevel).Msg("5")
|
||||
log.WithLevel(InfoLevel).Msg("6")
|
||||
log.WithLevel(WarnLevel).Msg("7")
|
||||
log.WithLevel(ErrorLevel).Msg("8")
|
||||
log.WithLevel(TraceLevel).Msg("5")
|
||||
log.WithLevel(DebugLevel).Msg("6")
|
||||
log.WithLevel(InfoLevel).Msg("7")
|
||||
log.WithLevel(WarnLevel).Msg("8")
|
||||
log.WithLevel(ErrorLevel).Msg("9")
|
||||
log.WithLevel(NoLevel).Msg("nolevel-2")
|
||||
|
||||
want := []struct {
|
||||
l Level
|
||||
p string
|
||||
}{
|
||||
{TraceLevel, `{"level":"trace","message":"0"}` + "\n"},
|
||||
{DebugLevel, `{"level":"debug","message":"1"}` + "\n"},
|
||||
{InfoLevel, `{"level":"info","message":"2"}` + "\n"},
|
||||
{WarnLevel, `{"level":"warn","message":"3"}` + "\n"},
|
||||
{ErrorLevel, `{"level":"error","message":"4"}` + "\n"},
|
||||
{NoLevel, `{"message":"nolevel-1"}` + "\n"},
|
||||
{DebugLevel, `{"level":"debug","message":"5"}` + "\n"},
|
||||
{InfoLevel, `{"level":"info","message":"6"}` + "\n"},
|
||||
{WarnLevel, `{"level":"warn","message":"7"}` + "\n"},
|
||||
{ErrorLevel, `{"level":"error","message":"8"}` + "\n"},
|
||||
{TraceLevel, `{"level":"trace","message":"5"}` + "\n"},
|
||||
{DebugLevel, `{"level":"debug","message":"6"}` + "\n"},
|
||||
{InfoLevel, `{"level":"info","message":"7"}` + "\n"},
|
||||
{WarnLevel, `{"level":"warn","message":"8"}` + "\n"},
|
||||
{ErrorLevel, `{"level":"error","message":"9"}` + "\n"},
|
||||
{NoLevel, `{"message":"nolevel-2"}` + "\n"},
|
||||
}
|
||||
if got := lw.ops; !reflect.DeepEqual(got, want) {
|
||||
@@ -678,9 +700,9 @@ func TestCallerMarshalFunc(t *testing.T) {
|
||||
parts := strings.Split(file, "/")
|
||||
if len(parts) > 1 {
|
||||
return strings.Join(parts[len(parts)-2:], "/") + ":" + strconv.Itoa(line)
|
||||
} else {
|
||||
return file + ":" + strconv.Itoa(line)
|
||||
}
|
||||
|
||||
return file + ":" + strconv.Itoa(line)
|
||||
}
|
||||
_, file, line, _ = runtime.Caller(0)
|
||||
caller = CallerMarshalFunc(file, line+2)
|
||||
|
||||
+10
-2
@@ -46,8 +46,12 @@ type BasicSampler struct {
|
||||
|
||||
// Sample implements the Sampler interface.
|
||||
func (s *BasicSampler) Sample(lvl Level) bool {
|
||||
n := s.N
|
||||
if n == 1 {
|
||||
return true
|
||||
}
|
||||
c := atomic.AddUint32(&s.counter, 1)
|
||||
return c%s.N == s.N-1
|
||||
return c%n == 1
|
||||
}
|
||||
|
||||
// BurstSampler lets Burst events pass per Period then pass the decision to
|
||||
@@ -100,11 +104,15 @@ func (s *BurstSampler) inc() uint32 {
|
||||
|
||||
// LevelSampler applies a different sampler for each level.
|
||||
type LevelSampler struct {
|
||||
DebugSampler, InfoSampler, WarnSampler, ErrorSampler Sampler
|
||||
TraceSampler, DebugSampler, InfoSampler, WarnSampler, ErrorSampler Sampler
|
||||
}
|
||||
|
||||
func (s LevelSampler) Sample(lvl Level) bool {
|
||||
switch lvl {
|
||||
case TraceLevel:
|
||||
if s.TraceSampler != nil {
|
||||
return s.TraceSampler.Sample(lvl)
|
||||
}
|
||||
case DebugLevel:
|
||||
if s.DebugSampler != nil {
|
||||
return s.DebugSampler.Sample(lvl)
|
||||
|
||||
@@ -35,6 +35,7 @@ func (sw syslogWriter) Write(p []byte) (n int, err error) {
|
||||
// WriteLevel implements LevelWriter interface.
|
||||
func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
case DebugLevel:
|
||||
err = sw.w.Debug(string(p))
|
||||
case InfoLevel:
|
||||
|
||||
+9
-2
@@ -3,8 +3,10 @@
|
||||
|
||||
package zerolog
|
||||
|
||||
import "testing"
|
||||
import "reflect"
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type syslogEvent struct {
|
||||
level string
|
||||
@@ -17,6 +19,10 @@ type syslogTestWriter struct {
|
||||
func (w *syslogTestWriter) Write(p []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (w *syslogTestWriter) Trace(m string) error {
|
||||
w.events = append(w.events, syslogEvent{"Trace", m})
|
||||
return nil
|
||||
}
|
||||
func (w *syslogTestWriter) Debug(m string) error {
|
||||
w.events = append(w.events, syslogEvent{"Debug", m})
|
||||
return nil
|
||||
@@ -45,6 +51,7 @@ func (w *syslogTestWriter) Crit(m string) error {
|
||||
func TestSyslogWriter(t *testing.T) {
|
||||
sw := &syslogTestWriter{}
|
||||
log := New(SyslogLevelWriter(sw))
|
||||
log.Trace().Msg("trace")
|
||||
log.Debug().Msg("debug")
|
||||
log.Info().Msg("info")
|
||||
log.Warn().Msg("warn")
|
||||
|
||||
Reference in New Issue
Block a user