Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
3a569c99c0 | |||
|
61485f3857 | ||
|
9070d49a1a | ||
|
b662f088b9 | ||
|
4612e098d2 | ||
|
8981d80ed3 | ||
|
927516bcf1 | ||
|
a712f61936 | ||
|
64a5863c5e | ||
|
1f50797d7d | ||
97e8f3922b | |||
|
902d72012d | ||
|
4fff5db29c | ||
|
762546b5c6 | ||
|
fa9bf3742a | ||
|
db22191211 | ||
|
164f7aa1a6 | ||
a61f91287f | |||
660e96877b | |||
f380226321 | |||
1a96beea25 | |||
|
3543e9d94b | ||
|
5bdc93f7eb | ||
|
e3027a5732 | ||
|
a9a8199d2d | ||
|
89617ff99b | ||
|
55aaf043cf | ||
|
315967f32d | ||
|
e218d18951 | ||
|
c2b9d0e2de | ||
|
d894f123bc | ||
|
4099072c03 | ||
|
4c85986254 | ||
|
43be301386 | ||
|
afdf9978ec | ||
|
14d6629e41 | ||
|
dbdec88d16 | ||
|
b30730fab2 | ||
|
68a6bd49b5 | ||
|
5c08a2724f | ||
|
60f57432ed | ||
|
2a13872817 | ||
|
a4ec5e4cdd |
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -4,17 +4,17 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.15.x, 1.16.x]
|
||||
go-version: [1.18.x, 1.19.x]
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
|
35
README.md
35
README.md
@ -4,7 +4,6 @@ opinionated defaults on zerolog
|
||||
|
||||
|
||||
|
||||
|
||||
# Zero Allocation JSON Logger
|
||||
|
||||
[](https://godoc.org/tuxpa.in/a/zlog) [](https://raw.githubusercontent.com/rs/zlog/master/LICENSE) [](https://travis-ci.org/rs/zlog) [](http://gocover.io/tuxpa.in/a/zlog)
|
||||
@ -31,7 +30,7 @@ Find out [who uses zlog](https://tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add you
|
||||
* [Sampling](#log-sampling)
|
||||
* [Hooks](#hooks)
|
||||
* [Contextual fields](#contextual-logging)
|
||||
* `context.Context` integration
|
||||
* [`context.Context` integration](#contextcontext-integration)
|
||||
* [Integration with `net/http`](#integration-with-nethttp)
|
||||
* [JSON and CBOR encoding formats](#binary-encoding)
|
||||
* [Pretty logging for development](#pretty-logging)
|
||||
@ -418,7 +417,7 @@ log.Info().Msg("hello world")
|
||||
Equivalent of `Lshortfile`:
|
||||
|
||||
```go
|
||||
zlog.CallerMarshalFunc = func(file string, line int) string {
|
||||
zlog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
|
||||
short := file
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' {
|
||||
@ -506,7 +505,7 @@ log.Ctx(ctx).Info().Msg("hello world")
|
||||
### Set as standard logger output
|
||||
|
||||
```go
|
||||
log := zlog.New(os.Stdout).With().
|
||||
stdlog := zlog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
Logger()
|
||||
|
||||
@ -518,6 +517,30 @@ stdlog.Print("hello world")
|
||||
// Output: {"foo":"bar","message":"hello world"}
|
||||
```
|
||||
|
||||
### context.Context integration
|
||||
|
||||
The `Logger` instance could be attached to `context.Context` values with `logger.WithContext(ctx)`
|
||||
and extracted from it using `zerolog.Ctx(ctx)`.
|
||||
|
||||
Example to add logger to context:
|
||||
```go
|
||||
// this code attach logger instance to context fields
|
||||
ctx := context.Background()
|
||||
logger := zerolog.New(os.Stdout)
|
||||
ctx = logger.WithContext(ctx)
|
||||
someFunc(ctx)
|
||||
```
|
||||
|
||||
Extracting logger from context:
|
||||
```go
|
||||
func someFunc(ctx context.Context) {
|
||||
// get logger from context. if it's nill, then `zerolog.DefaultContextLogger` is returned,
|
||||
// if `DefaultContextLogger` is nil, then disabled logger returned.
|
||||
logger := zerolog.Ctx(ctx)
|
||||
logger.Info().Msg("Hello")
|
||||
}
|
||||
```
|
||||
|
||||
### Integration with `net/http`
|
||||
|
||||
The `tuxpa.in/a/zlog/hlog` package provides some helpers to integrate zlog with `http.Handler`.
|
||||
@ -632,7 +655,11 @@ Most fields are also available in the slice format (`Strs` for `[]string`, `Errs
|
||||
|
||||
## Binary Encoding
|
||||
|
||||
<<<<<<< HEAD
|
||||
In addition to the default JSON encoding, `zlog` can produce binary logs using [CBOR](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows:
|
||||
=======
|
||||
In addition to the default JSON encoding, `zerolog` can produce binary logs using [CBOR](https://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows:
|
||||
>>>>>>> github
|
||||
|
||||
```bash
|
||||
go build -tags binary_log .
|
||||
|
40
array.go
40
array.go
@ -57,7 +57,7 @@ func (a *Array) write(dst []byte) []byte {
|
||||
}
|
||||
|
||||
// Object marshals an object that implement the LogObjectMarshaler
|
||||
// interface and append append it to the array.
|
||||
// interface and appends it to the array.
|
||||
func (a *Array) Object(obj LogObjectMarshaler) *Array {
|
||||
e := Dict()
|
||||
obj.MarshalZerologObject(e)
|
||||
@ -67,19 +67,19 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Str append append the val as a string to the array.
|
||||
// Str appends the val as a string to the array.
|
||||
func (a *Array) Str(val string) *Array {
|
||||
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val)
|
||||
return a
|
||||
}
|
||||
|
||||
// Bytes append append the val as a string to the array.
|
||||
// Bytes appends the val as a string to the array.
|
||||
func (a *Array) Bytes(val []byte) *Array {
|
||||
a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val)
|
||||
return a
|
||||
}
|
||||
|
||||
// Hex append append the val as a hex string to the array.
|
||||
// Hex appends the val as a hex string to the array.
|
||||
func (a *Array) Hex(val []byte) *Array {
|
||||
a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val)
|
||||
return a
|
||||
@ -115,97 +115,97 @@ func (a *Array) Err(err error) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Bool append append the val as a bool to the array.
|
||||
// Bool appends the val as a bool to the array.
|
||||
func (a *Array) Bool(b bool) *Array {
|
||||
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int append append i as a int to the array.
|
||||
// Int appends i as a int to the array.
|
||||
func (a *Array) Int(i int) *Array {
|
||||
a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int8 append append i as a int8 to the array.
|
||||
// Int8 appends i as a int8 to the array.
|
||||
func (a *Array) Int8(i int8) *Array {
|
||||
a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int16 append append i as a int16 to the array.
|
||||
// Int16 appends i as a int16 to the array.
|
||||
func (a *Array) Int16(i int16) *Array {
|
||||
a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int32 append append i as a int32 to the array.
|
||||
// Int32 appends i as a int32 to the array.
|
||||
func (a *Array) Int32(i int32) *Array {
|
||||
a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int64 append append i as a int64 to the array.
|
||||
// Int64 appends i as a int64 to the array.
|
||||
func (a *Array) Int64(i int64) *Array {
|
||||
a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint append append i as a uint to the array.
|
||||
// Uint appends i as a uint to the array.
|
||||
func (a *Array) Uint(i uint) *Array {
|
||||
a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint8 append append i as a uint8 to the array.
|
||||
// Uint8 appends i as a uint8 to the array.
|
||||
func (a *Array) Uint8(i uint8) *Array {
|
||||
a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint16 append append i as a uint16 to the array.
|
||||
// Uint16 appends i as a uint16 to the array.
|
||||
func (a *Array) Uint16(i uint16) *Array {
|
||||
a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint32 append append i as a uint32 to the array.
|
||||
// Uint32 appends i as a uint32 to the array.
|
||||
func (a *Array) Uint32(i uint32) *Array {
|
||||
a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint64 append append i as a uint64 to the array.
|
||||
// Uint64 appends i as a uint64 to the array.
|
||||
func (a *Array) Uint64(i uint64) *Array {
|
||||
a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Float32 append append f as a float32 to the array.
|
||||
// Float32 appends f as a float32 to the array.
|
||||
func (a *Array) Float32(f float32) *Array {
|
||||
a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f)
|
||||
return a
|
||||
}
|
||||
|
||||
// Float64 append append f as a float64 to the array.
|
||||
// Float64 appends f as a float64 to the array.
|
||||
func (a *Array) Float64(f float64) *Array {
|
||||
a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f)
|
||||
return a
|
||||
}
|
||||
|
||||
// Time append append t formatted as string using zlog.TimeFieldFormat.
|
||||
// Time appends t formatted as string using zlog.TimeFieldFormat.
|
||||
func (a *Array) Time(t time.Time) *Array {
|
||||
a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat)
|
||||
return a
|
||||
}
|
||||
|
||||
// Dur append append d to the array.
|
||||
// Dur appends d to the array.
|
||||
func (a *Array) Dur(d time.Duration) *Array {
|
||||
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return a
|
||||
}
|
||||
|
||||
// Interface append append i marshaled using reflection.
|
||||
// Interface appends i marshaled using reflection.
|
||||
func (a *Array) Interface(i interface{}) *Array {
|
||||
if obj, ok := i.(LogObjectMarshaler); ok {
|
||||
return a.Object(obj)
|
||||
|
40
cmd/prettylog/README.md
Normal file
40
cmd/prettylog/README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Zerolog PrettyLog
|
||||
|
||||
This is a basic CLI utility that will colorize and pretty print your structured JSON logs.
|
||||
|
||||
## Usage
|
||||
|
||||
You can compile it or run it directly. The only issue is that by default Zerolog does not output to `stdout`
|
||||
but rather to `stderr` so we must pipe `stderr` stream to this CLI tool.
|
||||
|
||||
### Linux
|
||||
|
||||
These commands will redirect `stderr` to our `prettylog` tool and `stdout` will remain unaffected.
|
||||
|
||||
1. Compiled version
|
||||
|
||||
```shell
|
||||
some_program_with_zerolog 2> >(prettylog)
|
||||
```
|
||||
|
||||
2. Run it directly with `go run`
|
||||
|
||||
```shell
|
||||
some_program_with_zerolog 2> >(go run cmd/prettylog/prettylog.go)
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
These commands will redirect `stderr` to `stdout` and then pipe it to our `prettylog` tool.
|
||||
|
||||
1. Compiled version
|
||||
|
||||
```shell
|
||||
some_program_with_zerolog 2>&1 | prettylog
|
||||
```
|
||||
|
||||
2. Run it directly with `go run`
|
||||
|
||||
```shell
|
||||
some_program_with_zerolog 2>&1 | go run cmd/prettylog/prettylog.go
|
||||
```
|
26
cmd/prettylog/prettylog.go
Normal file
26
cmd/prettylog/prettylog.go
Normal file
@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"tuxpa.in/a/zlog"
|
||||
)
|
||||
|
||||
func isInputFromPipe() bool {
|
||||
fileInfo, _ := os.Stdin.Stat()
|
||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
if !isInputFromPipe() {
|
||||
fmt.Println("The command is intended to work with pipes.")
|
||||
fmt.Println("Usage: app_with_zerolog | 2> >(prettylog)")
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
writer := zlog.NewConsoleWriter()
|
||||
_, _ = io.Copy(writer, os.Stdin)
|
||||
}
|
36
console.go
36
console.go
@ -74,6 +74,8 @@ type ConsoleWriter struct {
|
||||
FormatFieldValue Formatter
|
||||
FormatErrFieldName Formatter
|
||||
FormatErrFieldValue Formatter
|
||||
|
||||
FormatExtra func(map[string]interface{}, *bytes.Buffer) error
|
||||
}
|
||||
|
||||
// NewConsoleWriter creates and initializes a new ConsoleWriter.
|
||||
@ -128,10 +130,18 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
|
||||
|
||||
w.writeFields(evt, buf)
|
||||
|
||||
if w.FormatExtra != nil {
|
||||
err = w.FormatExtra(evt, buf)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
err = buf.WriteByte('\n')
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
_, err = buf.WriteTo(w.Out)
|
||||
return len(p), err
|
||||
}
|
||||
@ -221,7 +231,7 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
|
||||
case json.Number:
|
||||
buf.WriteString(fv(fValue))
|
||||
default:
|
||||
b, err := json.Marshal(fValue)
|
||||
b, err := InterfaceMarshalFunc(fValue)
|
||||
if err != nil {
|
||||
fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
|
||||
} else {
|
||||
@ -327,27 +337,31 @@ func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
|
||||
t := "<nil>"
|
||||
switch tt := i.(type) {
|
||||
case string:
|
||||
ts, err := time.Parse(TimeFieldFormat, tt)
|
||||
ts, err := time.ParseInLocation(TimeFieldFormat, tt, time.Local)
|
||||
if err != nil {
|
||||
t = tt
|
||||
} else {
|
||||
t = ts.Format(timeFormat)
|
||||
t = ts.Local().Format(timeFormat)
|
||||
}
|
||||
case json.Number:
|
||||
i, err := tt.Int64()
|
||||
if err != nil {
|
||||
t = tt.String()
|
||||
} else {
|
||||
var sec, nsec int64 = i, 0
|
||||
var sec, nsec int64
|
||||
|
||||
switch TimeFieldFormat {
|
||||
case TimeFormatUnixMs:
|
||||
nsec = int64(time.Duration(i) * time.Millisecond)
|
||||
sec = 0
|
||||
case TimeFormatUnixNano:
|
||||
sec, nsec = 0, i
|
||||
case TimeFormatUnixMicro:
|
||||
nsec = int64(time.Duration(i) * time.Microsecond)
|
||||
sec = 0
|
||||
sec, nsec = 0, int64(time.Duration(i)*time.Microsecond)
|
||||
case TimeFormatUnixMs:
|
||||
sec, nsec = 0, int64(time.Duration(i)*time.Millisecond)
|
||||
default:
|
||||
sec, nsec = i, 0
|
||||
}
|
||||
ts := time.Unix(sec, nsec).UTC()
|
||||
|
||||
ts := time.Unix(sec, nsec)
|
||||
t = ts.Format(timeFormat)
|
||||
}
|
||||
}
|
||||
@ -375,7 +389,7 @@ func consoleDefaultFormatLevel(noColor bool) Formatter {
|
||||
case LevelPanicValue:
|
||||
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
|
||||
default:
|
||||
l = colorize("???", colorBold, noColor)
|
||||
l = colorize(ll, colorBold, noColor)
|
||||
}
|
||||
} else {
|
||||
if i == nil {
|
||||
|
@ -108,13 +108,14 @@ func TestConsoleWriter(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true}
|
||||
|
||||
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
|
||||
ts := time.Unix(0, 0)
|
||||
d := ts.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"
|
||||
expectedOutput := ts.Format(time.Kitchen) + " DBG Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -136,7 +137,7 @@ func TestConsoleWriter(t *testing.T) {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "Jan 1 00:20:34.000 DBG Foobar foo=bar\n"
|
||||
expectedOutput := time.Unix(1234, 0).Format(time.StampMilli) + " DBG Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -158,7 +159,7 @@ func TestConsoleWriter(t *testing.T) {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "Jan 1 00:20:34.567 DBG Foobar foo=bar\n"
|
||||
expectedOutput := time.Unix(1234, 567000000).Format(time.StampMilli) + " DBG Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -180,7 +181,7 @@ func TestConsoleWriter(t *testing.T) {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "Jan 1 00:20:34.567891 DBG Foobar foo=bar\n"
|
||||
expectedOutput := time.Unix(1234, 567891000).Format(time.StampMicro) + " DBG Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -239,7 +240,8 @@ func TestConsoleWriter(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true}
|
||||
|
||||
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
|
||||
ts := time.Unix(0, 0)
|
||||
d := ts.UTC().Format(time.RFC3339)
|
||||
evt := `{"time": "` + d + `", "level": "error", "message": "Foobar", "aaa": "bbb", "error": "Error"}`
|
||||
// t.Log(evt)
|
||||
|
||||
@ -248,7 +250,7 @@ func TestConsoleWriter(t *testing.T) {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "12:00AM ERR Foobar error=Error aaa=bbb\n"
|
||||
expectedOutput := ts.Format(time.Kitchen) + " ERR Foobar error=Error aaa=bbb\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -264,7 +266,8 @@ func TestConsoleWriter(t *testing.T) {
|
||||
t.Fatalf("Cannot get working directory: %s", err)
|
||||
}
|
||||
|
||||
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
|
||||
ts := time.Unix(0, 0)
|
||||
d := ts.UTC().Format(time.RFC3339)
|
||||
evt := `{"time": "` + d + `", "level": "debug", "message": "Foobar", "foo": "bar", "caller": "` + cwd + `/foo/bar.go"}`
|
||||
// t.Log(evt)
|
||||
|
||||
@ -273,7 +276,7 @@ func TestConsoleWriter(t *testing.T) {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "12:00AM DBG foo/bar.go > Foobar foo=bar\n"
|
||||
expectedOutput := ts.Format(time.Kitchen) + " DBG foo/bar.go > Foobar foo=bar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -305,7 +308,8 @@ func TestConsoleWriterConfiguration(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: time.RFC3339}
|
||||
|
||||
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
|
||||
ts := time.Unix(0, 0)
|
||||
d := ts.UTC().Format(time.RFC3339)
|
||||
evt := `{"time": "` + d + `", "level": "info", "message": "Foobar"}`
|
||||
|
||||
_, err := w.Write([]byte(evt))
|
||||
@ -313,7 +317,7 @@ func TestConsoleWriterConfiguration(t *testing.T) {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "1970-01-01T00:00:00Z INF Foobar\n"
|
||||
expectedOutput := ts.Format(time.RFC3339) + " INF Foobar\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
@ -371,6 +375,55 @@ func TestConsoleWriterConfiguration(t *testing.T) {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Sets FormatExtra", func(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
w := zlog.ConsoleWriter{
|
||||
Out: buf, NoColor: true, PartsOrder: []string{"level", "message"},
|
||||
FormatExtra: func(evt map[string]interface{}, buf *bytes.Buffer) error {
|
||||
buf.WriteString("\nAdditional stacktrace")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
evt := `{"level": "info", "message": "Foobar"}`
|
||||
_, err := w.Write([]byte(evt))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
expectedOutput := "INF Foobar\nAdditional stacktrace\n"
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Uses local time for console writer without time zone", func(t *testing.T) {
|
||||
// Regression test for issue #483 (check there for more details)
|
||||
|
||||
timeFormat := "2006-01-02 15:04:05"
|
||||
expectedOutput := "2022-10-20 20:24:50 INF Foobar\n"
|
||||
evt := `{"time": "2022-10-20 20:24:50", "level": "info", "message": "Foobar"}`
|
||||
|
||||
of := zlog.TimeFieldFormat
|
||||
defer func() {
|
||||
zlog.TimeFieldFormat = of
|
||||
}()
|
||||
zlog.TimeFieldFormat = timeFormat
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: timeFormat}
|
||||
_, err := w.Write([]byte(evt))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when writing output: %s", err)
|
||||
}
|
||||
|
||||
actualOutput := buf.String()
|
||||
if actualOutput != expectedOutput {
|
||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkConsoleWriter(b *testing.B) {
|
||||
|
@ -329,8 +329,9 @@ func (ts timestampHook) Run(e *Event, level Level, msg string) {
|
||||
|
||||
var th = timestampHook{}
|
||||
|
||||
// Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key.
|
||||
// Timestamp adds the current local time to the logger context with the "time" key, formatted using zlog.TimeFieldFormat.
|
||||
// To customize the key name, change zlog.TimestampFieldName.
|
||||
// To customize the time format, change zlog.TimeFieldFormat.
|
||||
//
|
||||
// NOTE: It won't dedupe the "time" key if the *Context has one already.
|
||||
func (c Context) Timestamp() Context {
|
||||
|
19
ctx.go
19
ctx.go
@ -14,10 +14,15 @@ func init() {
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
// WithContext returns a copy of ctx with l associated. If an instance of Logger
|
||||
// is already in the context, the context is not updated.
|
||||
// WithContext returns a copy of ctx with the receiver attached. The Logger
|
||||
// attached to the provided Context (if any) will not be effected. If the
|
||||
// receiver's log level is Disabled it will only be attached to the returned
|
||||
// Context if the provided Context has a previously attached Logger. If the
|
||||
// provided Context has no attached Logger, a Disabled Logger will not be
|
||||
// attached.
|
||||
//
|
||||
// For instance, to add a field to an existing logger in the context, use this
|
||||
// Note: to modify the existing Logger attached to a Context (instead of
|
||||
// replacing it in a new Context), use UpdateContext with the following
|
||||
// notation:
|
||||
//
|
||||
// ctx := r.Context()
|
||||
@ -25,13 +30,9 @@ type ctxKey struct{}
|
||||
// l.UpdateContext(func(c Context) Context {
|
||||
// return c.Str("bar", "baz")
|
||||
// })
|
||||
//
|
||||
func (l Logger) WithContext(ctx context.Context) context.Context {
|
||||
if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok {
|
||||
if lp == &l {
|
||||
// Do not store same logger.
|
||||
return ctx
|
||||
}
|
||||
} else if l.level == Disabled {
|
||||
if _, ok := ctx.Value(ctxKey{}).(*Logger); !ok && l.level == Disabled {
|
||||
// Do not store disabled logger.
|
||||
return ctx
|
||||
}
|
||||
|
@ -30,6 +30,14 @@ func TestNewWriter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
buf := bytes.Buffer{}
|
||||
w := diode.NewWriter(&buf, 1000, 0, func(missed int) {})
|
||||
log := zlog.New(w)
|
||||
log.Print("test")
|
||||
w.Close()
|
||||
}
|
||||
|
||||
func Benchmark(b *testing.B) {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
defer log.SetOutput(os.Stderr)
|
||||
|
@ -39,7 +39,12 @@ func NewWaiter(d Diode, opts ...WaiterConfigOption) *Waiter {
|
||||
|
||||
go func() {
|
||||
<-w.ctx.Done()
|
||||
|
||||
// Mutex is strictly necessary here to avoid a race in Next() (between
|
||||
// w.isDone() and w.c.Wait()) and w.c.Broadcast() here.
|
||||
w.mu.Lock()
|
||||
w.c.Broadcast()
|
||||
w.mu.Unlock()
|
||||
}()
|
||||
|
||||
return w
|
||||
|
@ -24,6 +24,9 @@ func init() {
|
||||
func appendJSON(dst []byte, j []byte) []byte {
|
||||
return cbor.AppendEmbeddedJSON(dst, j)
|
||||
}
|
||||
func appendCBOR(dst []byte, c []byte) []byte {
|
||||
return cbor.AppendEmbeddedCBOR(dst, c)
|
||||
}
|
||||
|
||||
// decodeIfBinaryToString - converts a binary formatted log msg to a
|
||||
// JSON formatted String Log message.
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build !binary_log
|
||||
// +build !binary_log
|
||||
|
||||
package zlog
|
||||
@ -6,6 +7,7 @@ package zlog
|
||||
// JSON encoded byte stream.
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"tuxpa.in/a/zlog/internal/json"
|
||||
)
|
||||
|
||||
@ -25,6 +27,17 @@ func init() {
|
||||
func appendJSON(dst []byte, j []byte) []byte {
|
||||
return append(dst, j...)
|
||||
}
|
||||
func appendCBOR(dst []byte, cbor []byte) []byte {
|
||||
dst = append(dst, []byte("\"data:application/cbor;base64,")...)
|
||||
l := len(dst)
|
||||
enc := base64.StdEncoding
|
||||
n := enc.EncodedLen(len(cbor))
|
||||
for i := 0; i < n; i++ {
|
||||
dst = append(dst, '.')
|
||||
}
|
||||
enc.Encode(dst[l:], cbor)
|
||||
return append(dst, '"')
|
||||
}
|
||||
|
||||
func decodeIfBinaryToString(in []byte) string {
|
||||
return string(in)
|
||||
|
30
event.go
30
event.go
@ -318,6 +318,18 @@ func (e *Event) RawJSON(key string, b []byte) *Event {
|
||||
return e
|
||||
}
|
||||
|
||||
// RawCBOR adds already encoded CBOR to the log line under key.
|
||||
//
|
||||
// No sanity check is performed on b
|
||||
// Note: The full featureset of CBOR is supported as data will not be mapped to json but stored as data-url
|
||||
func (e *Event) RawCBOR(key string, b []byte) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendCBOR(enc.AppendKey(e.buf, key), b)
|
||||
return e
|
||||
}
|
||||
|
||||
// AnErr adds the field key with serialized err to the *Event context.
|
||||
// If err is nil, no field is added.
|
||||
func (e *Event) AnErr(key string, err error) *Event {
|
||||
@ -707,6 +719,11 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
|
||||
return e
|
||||
}
|
||||
|
||||
// Any is a wrapper around Event.Interface.
|
||||
func (e *Event) Any(key string, i interface{}) *Event {
|
||||
return e.Interface(key, i)
|
||||
}
|
||||
|
||||
// Interface adds the field key with i marshaled using reflection.
|
||||
func (e *Event) Interface(key string, i interface{}) *Event {
|
||||
if e == nil {
|
||||
@ -719,6 +736,15 @@ func (e *Event) Interface(key string, i interface{}) *Event {
|
||||
return e
|
||||
}
|
||||
|
||||
// Type adds the field key with val's type using reflection.
|
||||
func (e *Event) Type(key string, val interface{}) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = enc.AppendType(enc.AppendKey(e.buf, key), val)
|
||||
return e
|
||||
}
|
||||
|
||||
// CallerSkipFrame instructs any future Caller calls to skip the specified number of frames.
|
||||
// This includes those added via hooks from the context.
|
||||
func (e *Event) CallerSkipFrame(skip int) *Event {
|
||||
@ -744,11 +770,11 @@ func (e *Event) caller(skip int) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
_, file, line, ok := runtime.Caller(skip + e.skipFrame)
|
||||
pc, file, line, ok := runtime.Caller(skip + e.skipFrame)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(file, line))
|
||||
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(pc, file, line))
|
||||
return e
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,10 @@ const (
|
||||
// TimeFormatUnixMicro defines a time format that makes time fields to be
|
||||
// serialized as Unix timestamp integers in microseconds.
|
||||
TimeFormatUnixMicro = "UNIXMICRO"
|
||||
|
||||
// TimeFormatUnixNano defines a time format that makes time fields to be
|
||||
// serialized as Unix timestamp integers in nanoseconds.
|
||||
TimeFormatUnixNano = "UNIXNANO"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -61,7 +65,7 @@ var (
|
||||
CallerSkipFrameCount = 2
|
||||
|
||||
// CallerMarshalFunc allows customization of global caller marshaling
|
||||
CallerMarshalFunc = func(file string, line int) string {
|
||||
CallerMarshalFunc = func(pc uintptr, file string, line int) string {
|
||||
return file + ":" + strconv.Itoa(line)
|
||||
}
|
||||
|
||||
@ -81,7 +85,7 @@ var (
|
||||
InterfaceMarshalFunc = json.Marshal
|
||||
|
||||
// TimeFieldFormat defines the time format of the Time field type. If set to
|
||||
// TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as a UNIX
|
||||
// TimeFormatUnix, TimeFormatUnixMs, TimeFormatUnixMicro or TimeFormatUnixNano, the time is formatted as a UNIX
|
||||
// timestamp as integer.
|
||||
TimeFieldFormat = time.RFC3339
|
||||
|
||||
|
7
go.mod
7
go.mod
@ -3,9 +3,10 @@ module tuxpa.in/a/zlog
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/coreos/go-systemd/v22 v22.4.0
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/xid v1.4.0
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
|
||||
)
|
||||
|
21
go.sum
21
go.sum
@ -1,14 +1,19 @@
|
||||
github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/zWskzfGU=
|
||||
github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
14
hlog/hlog.go
14
hlog/hlog.go
@ -121,6 +121,20 @@ func RefererHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// ProtoHandler adds the requests protocol version as a field to the context logger
|
||||
// using fieldKey as field Key.
|
||||
func ProtoHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log := zlog.Ctx(r.Context())
|
||||
log.UpdateContext(func(c zlog.Context) zlog.Context {
|
||||
return c.Str(fieldKey, r.Proto)
|
||||
})
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type idKey struct{}
|
||||
|
||||
// IDFromRequest returns the unique id associated to the request if any.
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build go1.7
|
||||
// +build go1.7
|
||||
|
||||
package hlog
|
||||
@ -200,6 +201,22 @@ func TestCustomHeaderHandler(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtoHandler(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
r := &http.Request{
|
||||
Proto: "test",
|
||||
}
|
||||
h := ProtoHandler("proto")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
}))
|
||||
h = NewHandler(zlog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"proto":"test"}`+"\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{
|
||||
|
@ -27,6 +27,7 @@ const (
|
||||
|
||||
// Tag Sub-types.
|
||||
additionalTypeTimestamp byte = 01
|
||||
additionalTypeEmbeddedCBOR byte = 63
|
||||
|
||||
// Extended Tags - from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
||||
additionalTypeTagNetworkAddr uint16 = 260
|
||||
|
@ -5,6 +5,7 @@ package cbor
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
@ -213,6 +214,31 @@ func decodeString(src *bufio.Reader, noQuotes bool) []byte {
|
||||
}
|
||||
return append(result, '"')
|
||||
}
|
||||
func decodeStringToDataUrl(src *bufio.Reader, mimeType string) []byte {
|
||||
pb := readByte(src)
|
||||
major := pb & maskOutAdditionalType
|
||||
minor := pb & maskOutMajorType
|
||||
if major != majorTypeByteString {
|
||||
panic(fmt.Errorf("Major type is: %d in decodeString", major))
|
||||
}
|
||||
length := decodeIntAdditionalType(src, minor)
|
||||
l := int(length)
|
||||
enc := base64.StdEncoding
|
||||
lEnc := enc.EncodedLen(l)
|
||||
result := make([]byte, len("\"data:;base64,\"")+len(mimeType)+lEnc)
|
||||
dest := result
|
||||
u := copy(dest, "\"data:")
|
||||
dest = dest[u:]
|
||||
u = copy(dest, mimeType)
|
||||
dest = dest[u:]
|
||||
u = copy(dest, ";base64,")
|
||||
dest = dest[u:]
|
||||
pbs := readNBytes(src, l)
|
||||
enc.Encode(dest, pbs)
|
||||
dest = dest[lEnc:]
|
||||
dest[0] = '"'
|
||||
return result
|
||||
}
|
||||
|
||||
func decodeUTF8String(src *bufio.Reader) []byte {
|
||||
pb := readByte(src)
|
||||
@ -349,6 +375,20 @@ func decodeTagData(src *bufio.Reader) []byte {
|
||||
switch minor {
|
||||
case additionalTypeTimestamp:
|
||||
return decodeTimeStamp(src)
|
||||
case additionalTypeIntUint8:
|
||||
val := decodeIntAdditionalType(src, minor)
|
||||
switch byte(val) {
|
||||
case additionalTypeEmbeddedCBOR:
|
||||
pb := readByte(src)
|
||||
dataMajor := pb & maskOutAdditionalType
|
||||
if dataMajor != majorTypeByteString {
|
||||
panic(fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedCBOR", dataMajor))
|
||||
}
|
||||
src.UnreadByte()
|
||||
return decodeStringToDataUrl(src, "application/cbor")
|
||||
default:
|
||||
panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val))
|
||||
}
|
||||
|
||||
// Tag value is larger than 256 (so uint16).
|
||||
case additionalTypeIntUint16:
|
||||
|
@ -93,3 +93,25 @@ func AppendEmbeddedJSON(dst, s []byte) []byte {
|
||||
}
|
||||
return append(dst, s...)
|
||||
}
|
||||
|
||||
// AppendEmbeddedCBOR adds a tag and embeds input CBOR as such.
|
||||
func AppendEmbeddedCBOR(dst, s []byte) []byte {
|
||||
major := majorTypeTags
|
||||
minor := additionalTypeEmbeddedCBOR
|
||||
|
||||
// Append the TAG to indicate this is Embedded JSON.
|
||||
dst = append(dst, major|additionalTypeIntUint8)
|
||||
dst = append(dst, minor)
|
||||
|
||||
// Append the CBOR Object as Byte String.
|
||||
major = majorTypeByteString
|
||||
|
||||
l := len(s)
|
||||
if l <= additionalMax {
|
||||
lb := byte(l)
|
||||
dst = append(dst, major|lb)
|
||||
} else {
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l))
|
||||
}
|
||||
return append(dst, s...)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// AppendNil inserts a 'Nil' object into the dst byte array.
|
||||
@ -438,6 +439,14 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
|
||||
return AppendEmbeddedJSON(dst, marshaled)
|
||||
}
|
||||
|
||||
// AppendType appends the parameter type (as a string) to the input byte slice.
|
||||
func (e Encoder) AppendType(dst []byte, i interface{}) []byte {
|
||||
if i == nil {
|
||||
return e.AppendString(dst, "<nil>")
|
||||
}
|
||||
return e.AppendString(dst, reflect.TypeOf(i).String())
|
||||
}
|
||||
|
||||
// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6).
|
||||
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
|
||||
dst = append(dst, majorTypeTags|additionalTypeIntUint16)
|
||||
|
@ -10,6 +10,7 @@ const (
|
||||
timeFormatUnix = ""
|
||||
timeFormatUnixMs = "UNIXMS"
|
||||
timeFormatUnixMicro = "UNIXMICRO"
|
||||
timeFormatUnixNano = "UNIXNANO"
|
||||
)
|
||||
|
||||
// AppendTime formats the input time with the given format
|
||||
@ -22,6 +23,8 @@ func (e Encoder) AppendTime(dst []byte, t time.Time, format string) []byte {
|
||||
return e.AppendInt64(dst, t.UnixNano()/1000000)
|
||||
case timeFormatUnixMicro:
|
||||
return e.AppendInt64(dst, t.UnixNano()/1000)
|
||||
case timeFormatUnixNano:
|
||||
return e.AppendInt64(dst, t.UnixNano())
|
||||
}
|
||||
return append(t.AppendFormat(append(dst, '"'), format), '"')
|
||||
}
|
||||
@ -33,7 +36,11 @@ func (Encoder) AppendTimes(dst []byte, vals []time.Time, format string) []byte {
|
||||
case timeFormatUnix:
|
||||
return appendUnixTimes(dst, vals)
|
||||
case timeFormatUnixMs:
|
||||
return appendUnixMsTimes(dst, vals)
|
||||
return appendUnixNanoTimes(dst, vals, 1000000)
|
||||
case timeFormatUnixMicro:
|
||||
return appendUnixNanoTimes(dst, vals, 1000)
|
||||
case timeFormatUnixNano:
|
||||
return appendUnixNanoTimes(dst, vals, 1)
|
||||
}
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
@ -64,15 +71,15 @@ func appendUnixTimes(dst []byte, vals []time.Time) []byte {
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendUnixMsTimes(dst []byte, vals []time.Time) []byte {
|
||||
func appendUnixNanoTimes(dst []byte, vals []time.Time, div int64) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, vals[0].UnixNano()/1000000, 10)
|
||||
dst = strconv.AppendInt(dst, vals[0].UnixNano()/div, 10)
|
||||
if len(vals) > 1 {
|
||||
for _, t := range vals[1:] {
|
||||
dst = strconv.AppendInt(append(dst, ','), t.UnixNano()/1000000, 10)
|
||||
dst = strconv.AppendInt(append(dst, ','), t.UnixNano()/div, 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -369,6 +370,14 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
|
||||
return append(dst, marshaled...)
|
||||
}
|
||||
|
||||
// AppendType appends the parameter type (as a string) to the input byte slice.
|
||||
func (e Encoder) AppendType(dst []byte, i interface{}) []byte {
|
||||
if i == nil {
|
||||
return e.AppendString(dst, "<nil>")
|
||||
}
|
||||
return e.AppendString(dst, reflect.TypeOf(i).String())
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -166,6 +166,28 @@ func Test_appendMac(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_appendType(t *testing.T) {
|
||||
typeTests := []struct {
|
||||
label string
|
||||
input interface{}
|
||||
want []byte
|
||||
}{
|
||||
{"int", 42, []byte(`"int"`)},
|
||||
{"MAC", net.HardwareAddr{0x12, 0x34, 0x00, 0x00, 0x90, 0xab}, []byte(`"net.HardwareAddr"`)},
|
||||
{"float64", float64(2.50), []byte(`"float64"`)},
|
||||
{"nil", nil, []byte(`"<nil>"`)},
|
||||
{"bool", true, []byte(`"bool"`)},
|
||||
}
|
||||
|
||||
for _, tt := range typeTests {
|
||||
t.Run(tt.label, func(t *testing.T) {
|
||||
if got := enc.AppendType([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("appendType() = %s, want %s", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_appendObjectData(t *testing.T) {
|
||||
tests := []struct {
|
||||
dst []byte
|
||||
|
@ -102,7 +102,7 @@ func (w journalWriter) Write(p []byte) (n int, err error) {
|
||||
case json.Number:
|
||||
args[jKey] = fmt.Sprint(value)
|
||||
default:
|
||||
b, err := json.Marshal(value)
|
||||
b, err := zlog.InterfaceMarshalFunc(value)
|
||||
if err != nil {
|
||||
args[jKey] = fmt.Sprintf("[error: %v]", err)
|
||||
} else {
|
||||
|
25
log.go
25
log.go
@ -104,6 +104,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Level defines log levels.
|
||||
@ -159,24 +160,24 @@ func (l Level) String() string {
|
||||
// ParseLevel converts a level string into a zlog Level value.
|
||||
// returns an error if the input string does not match known values.
|
||||
func ParseLevel(levelStr string) (Level, error) {
|
||||
switch levelStr {
|
||||
case LevelFieldMarshalFunc(TraceLevel):
|
||||
switch {
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(TraceLevel)):
|
||||
return TraceLevel, nil
|
||||
case LevelFieldMarshalFunc(DebugLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(DebugLevel)):
|
||||
return DebugLevel, nil
|
||||
case LevelFieldMarshalFunc(InfoLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(InfoLevel)):
|
||||
return InfoLevel, nil
|
||||
case LevelFieldMarshalFunc(WarnLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(WarnLevel)):
|
||||
return WarnLevel, nil
|
||||
case LevelFieldMarshalFunc(ErrorLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(ErrorLevel)):
|
||||
return ErrorLevel, nil
|
||||
case LevelFieldMarshalFunc(FatalLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(FatalLevel)):
|
||||
return FatalLevel, nil
|
||||
case LevelFieldMarshalFunc(PanicLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(PanicLevel)):
|
||||
return PanicLevel, nil
|
||||
case LevelFieldMarshalFunc(Disabled):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(Disabled)):
|
||||
return Disabled, nil
|
||||
case LevelFieldMarshalFunc(NoLevel):
|
||||
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(NoLevel)):
|
||||
return NoLevel, nil
|
||||
}
|
||||
i, err := strconv.Atoi(levelStr)
|
||||
@ -307,7 +308,9 @@ func (l Logger) Sample(s Sampler) Logger {
|
||||
|
||||
// Hook returns a logger with the h Hook.
|
||||
func (l Logger) Hook(h Hook) Logger {
|
||||
l.hooks = append(l.hooks, h)
|
||||
newHooks := make([]Hook, len(l.hooks), len(l.hooks)+1)
|
||||
copy(newHooks, l.hooks)
|
||||
l.hooks = append(newHooks, h)
|
||||
return l
|
||||
}
|
||||
|
||||
|
12
log/log.go
12
log/log.go
@ -8,15 +8,17 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"tuxpa.in/a/zlog"
|
||||
)
|
||||
|
||||
// Logger is the global logger.
|
||||
var Logger = zlog.New(os.Stderr).With().Timestamp().Logger()
|
||||
|
||||
func init() {
|
||||
zlog.TimeFieldFormat = time.StampMicro
|
||||
}
|
||||
var Logger = zlog.New(nil).Output(
|
||||
zerolog.ConsoleWriter{
|
||||
Out: os.Stderr,
|
||||
TimeFormat: time.RFC3339,
|
||||
},
|
||||
).With().Timestamp().Logger()
|
||||
|
||||
// Output duplicates the global logger and sets w as its output.
|
||||
func Output(w io.Writer) zlog.Logger {
|
||||
|
78
log_test.go
78
log_test.go
@ -320,6 +320,7 @@ func TestFields(t *testing.T) {
|
||||
Bytes("bytes", []byte("bar")).
|
||||
Hex("hex", []byte{0x12, 0xef}).
|
||||
RawJSON("json", []byte(`{"some":"json"}`)).
|
||||
RawCBOR("cbor", []byte{0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}).
|
||||
Func(func(e *Event) { e.Str("func", "func_output") }).
|
||||
AnErr("some_err", nil).
|
||||
Err(errors.New("some error")).
|
||||
@ -344,7 +345,7 @@ func TestFields(t *testing.T) {
|
||||
Time("time", time.Time{}).
|
||||
TimeDiff("diff", now, now.Add(-10*time.Second)).
|
||||
Msg("")
|
||||
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
|
||||
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"cbor":"data:application/cbor;base64,gwGCAgOCBAU=","func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
@ -782,7 +783,7 @@ func TestCallerMarshalFunc(t *testing.T) {
|
||||
|
||||
// test default behaviour this is really brittle due to the line numbers
|
||||
// actually mattering for validation
|
||||
_, file, line, _ := runtime.Caller(0)
|
||||
pc, file, line, _ := runtime.Caller(0)
|
||||
caller := fmt.Sprintf("%s:%d", file, line+2)
|
||||
log.Log().Caller().Msg("msg")
|
||||
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
|
||||
@ -793,16 +794,16 @@ func TestCallerMarshalFunc(t *testing.T) {
|
||||
// test custom behavior. In this case we'll take just the last directory
|
||||
origCallerMarshalFunc := CallerMarshalFunc
|
||||
defer func() { CallerMarshalFunc = origCallerMarshalFunc }()
|
||||
CallerMarshalFunc = func(file string, line int) string {
|
||||
CallerMarshalFunc = func(pc uintptr, file string, line int) string {
|
||||
parts := strings.Split(file, "/")
|
||||
if len(parts) > 1 {
|
||||
return strings.Join(parts[len(parts)-2:], "/") + ":" + strconv.Itoa(line)
|
||||
}
|
||||
|
||||
return file + ":" + strconv.Itoa(line)
|
||||
return runtime.FuncForPC(pc).Name() + ":" + file + ":" + strconv.Itoa(line)
|
||||
}
|
||||
_, file, line, _ = runtime.Caller(0)
|
||||
caller = CallerMarshalFunc(file, line+2)
|
||||
pc, file, line, _ = runtime.Caller(0)
|
||||
caller = CallerMarshalFunc(pc, file, line+2)
|
||||
log.Log().Caller().Msg("msg")
|
||||
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
@ -907,6 +908,33 @@ func TestLevel_String(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevel_MarshalText(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
l Level
|
||||
want string
|
||||
}{
|
||||
{"trace", TraceLevel, "trace"},
|
||||
{"debug", DebugLevel, "debug"},
|
||||
{"info", InfoLevel, "info"},
|
||||
{"warn", WarnLevel, "warn"},
|
||||
{"error", ErrorLevel, "error"},
|
||||
{"fatal", FatalLevel, "fatal"},
|
||||
{"panic", PanicLevel, "panic"},
|
||||
{"disabled", Disabled, "disabled"},
|
||||
{"nolevel", NoLevel, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, err := tt.l.MarshalText(); err != nil {
|
||||
t.Errorf("MarshalText couldn't marshal: %v", tt.l)
|
||||
} else if string(got) != tt.want {
|
||||
t.Errorf("String() = %v, want %v", string(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLevel(t *testing.T) {
|
||||
type args struct {
|
||||
levelStr string
|
||||
@ -943,3 +971,41 @@ func TestParseLevel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalTextLevel(t *testing.T) {
|
||||
type args struct {
|
||||
levelStr string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Level
|
||||
wantErr bool
|
||||
}{
|
||||
{"trace", args{"trace"}, TraceLevel, false},
|
||||
{"debug", args{"debug"}, DebugLevel, false},
|
||||
{"info", args{"info"}, InfoLevel, false},
|
||||
{"warn", args{"warn"}, WarnLevel, false},
|
||||
{"error", args{"error"}, ErrorLevel, false},
|
||||
{"fatal", args{"fatal"}, FatalLevel, false},
|
||||
{"panic", args{"panic"}, PanicLevel, false},
|
||||
{"disabled", args{"disabled"}, Disabled, false},
|
||||
{"nolevel", args{""}, NoLevel, false},
|
||||
{"-1", args{"-1"}, TraceLevel, false},
|
||||
{"-2", args{"-2"}, Level(-2), false},
|
||||
{"-3", args{"-3"}, Level(-3), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var l Level
|
||||
err := l.UnmarshalText([]byte(tt.args.levelStr))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if l != tt.want {
|
||||
t.Errorf("UnmarshalText() got = %v, want %v", l, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -47,10 +47,27 @@ func MarshalStack(err error) interface{} {
|
||||
type stackTracer interface {
|
||||
StackTrace() errors.StackTrace
|
||||
}
|
||||
sterr, ok := err.(stackTracer)
|
||||
var sterr stackTracer
|
||||
var ok bool
|
||||
for err != nil {
|
||||
sterr, ok = err.(stackTracer)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
|
||||
u, ok := err.(interface {
|
||||
Unwrap() error
|
||||
})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = u.Unwrap()
|
||||
}
|
||||
if sterr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
st := sterr.StackTrace()
|
||||
s := &state{}
|
||||
out := make([]map[string]string, 0, len(st))
|
||||
|
@ -4,6 +4,7 @@ package pkgerrors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
@ -17,11 +18,11 @@ func TestLogStack(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := zlog.New(out)
|
||||
|
||||
err := errors.Wrap(errors.New("error message"), "from error")
|
||||
err := fmt.Errorf("from error: %w", errors.New("error message"))
|
||||
log.Log().Stack().Err(err).Msg("")
|
||||
|
||||
got := out.String()
|
||||
want := `\{"stack":\[\{"func":"TestLogStack","line":"20","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
|
||||
want := `\{"stack":\[\{"func":"TestLogStack","line":"21","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
|
||||
if ok, _ := regexp.MatchString(want, got); !ok {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
@ -33,11 +34,11 @@ func TestLogStackFromContext(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := zlog.New(out).With().Stack().Logger() // calling Stack() on log context instead of event
|
||||
|
||||
err := errors.Wrap(errors.New("error message"), "from error")
|
||||
err := fmt.Errorf("from error: %w", errors.New("error message"))
|
||||
log.Log().Err(err).Msg("") // not explicitly calling Stack()
|
||||
|
||||
got := out.String()
|
||||
want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"36","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
|
||||
want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"37","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
|
||||
if ok, _ := regexp.MatchString(want, got); !ok {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user