Compare commits
48 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 | ||
|
99eea905a5 | ||
|
5bdc93f7eb | ||
|
64792032f4 | ||
|
e3027a5732 | ||
|
a9a8199d2d | ||
|
89617ff99b | ||
|
1600f7342f | ||
|
55aaf043cf | ||
|
3ec1a83686 | ||
|
88926fbb91 | ||
|
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:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.15.x, 1.16.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
os: [ubuntu-latest, macos-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
79
README.md
79
README.md
@ -4,10 +4,9 @@ opinionated defaults on zerolog
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Zero Allocation JSON Logger
|
# Zero Allocation JSON Logger
|
||||||
|
|
||||||
[](https://godoc.org/git.tuxpa.in/a/zlog) [](https://raw.githubusercontent.com/rs/zlog/master/LICENSE) [](https://travis-ci.org/rs/zlog) [](http://gocover.io/git.tuxpa.in/a/zlog)
|
[](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)
|
||||||
|
|
||||||
The zlog package provides a fast and simple logger dedicated to JSON output.
|
The zlog package provides a fast and simple logger dedicated to JSON output.
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ To keep the code base and the API simple, zlog focuses on efficient structured l
|
|||||||
|
|
||||||
## Who uses zlog
|
## Who uses zlog
|
||||||
|
|
||||||
Find out [who uses zlog](https://git.tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add your company / project to the list.
|
Find out [who uses zlog](https://tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add your company / project to the list.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ Find out [who uses zlog](https://git.tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add
|
|||||||
* [Sampling](#log-sampling)
|
* [Sampling](#log-sampling)
|
||||||
* [Hooks](#hooks)
|
* [Hooks](#hooks)
|
||||||
* [Contextual fields](#contextual-logging)
|
* [Contextual fields](#contextual-logging)
|
||||||
* `context.Context` integration
|
* [`context.Context` integration](#contextcontext-integration)
|
||||||
* [Integration with `net/http`](#integration-with-nethttp)
|
* [Integration with `net/http`](#integration-with-nethttp)
|
||||||
* [JSON and CBOR encoding formats](#binary-encoding)
|
* [JSON and CBOR encoding formats](#binary-encoding)
|
||||||
* [Pretty logging for development](#pretty-logging)
|
* [Pretty logging for development](#pretty-logging)
|
||||||
@ -40,21 +39,21 @@ Find out [who uses zlog](https://git.tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u git.tuxpa.in/a/zlog/log
|
go get -u tuxpa.in/a/zlog/log
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Simple Logging Example
|
### Simple Logging Example
|
||||||
|
|
||||||
For simple logging, import the global logger package **git.tuxpa.in/a/zlog/log**
|
For simple logging, import the global logger package **tuxpa.in/a/zlog/log**
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -77,8 +76,8 @@ func main() {
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -108,8 +107,8 @@ func main() {
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -145,8 +144,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -196,8 +195,8 @@ You may choose to log without a specific level by using the `Log` method. You ma
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -221,8 +220,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -246,10 +245,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"git.tuxpa.in/a/zlog/pkgerrors"
|
"tuxpa.in/a/zlog/pkgerrors"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -293,8 +292,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -418,7 +417,7 @@ log.Info().Msg("hello world")
|
|||||||
Equivalent of `Lshortfile`:
|
Equivalent of `Lshortfile`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
zlog.CallerMarshalFunc = func(file string, line int) string {
|
zlog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
|
||||||
short := file
|
short := file
|
||||||
for i := len(file) - 1; i > 0; i-- {
|
for i := len(file) - 1; i > 0; i-- {
|
||||||
if file[i] == '/' {
|
if file[i] == '/' {
|
||||||
@ -506,7 +505,7 @@ log.Ctx(ctx).Info().Msg("hello world")
|
|||||||
### Set as standard logger output
|
### Set as standard logger output
|
||||||
|
|
||||||
```go
|
```go
|
||||||
log := zlog.New(os.Stdout).With().
|
stdlog := zlog.New(os.Stdout).With().
|
||||||
Str("foo", "bar").
|
Str("foo", "bar").
|
||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
@ -518,9 +517,33 @@ stdlog.Print("hello world")
|
|||||||
// Output: {"foo":"bar","message":"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`
|
### Integration with `net/http`
|
||||||
|
|
||||||
The `git.tuxpa.in/a/zlog/hlog` package provides some helpers to integrate zlog with `http.Handler`.
|
The `tuxpa.in/a/zlog/hlog` package provides some helpers to integrate zlog with `http.Handler`.
|
||||||
|
|
||||||
In this example we use [alice](https://github.com/justinas/alice) to install logger for better readability.
|
In this example we use [alice](https://github.com/justinas/alice) to install logger for better readability.
|
||||||
|
|
||||||
@ -632,7 +655,11 @@ Most fields are also available in the slice format (`Strs` for `[]string`, `Errs
|
|||||||
|
|
||||||
## Binary Encoding
|
## 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, `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
|
```bash
|
||||||
go build -tags binary_log .
|
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
|
// 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 {
|
func (a *Array) Object(obj LogObjectMarshaler) *Array {
|
||||||
e := Dict()
|
e := Dict()
|
||||||
obj.MarshalZerologObject(e)
|
obj.MarshalZerologObject(e)
|
||||||
@ -67,19 +67,19 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array {
|
|||||||
return a
|
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 {
|
func (a *Array) Str(val string) *Array {
|
||||||
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val)
|
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val)
|
||||||
return a
|
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 {
|
func (a *Array) Bytes(val []byte) *Array {
|
||||||
a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val)
|
a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val)
|
||||||
return a
|
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 {
|
func (a *Array) Hex(val []byte) *Array {
|
||||||
a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val)
|
a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val)
|
||||||
return a
|
return a
|
||||||
@ -115,97 +115,97 @@ func (a *Array) Err(err error) *Array {
|
|||||||
return a
|
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 {
|
func (a *Array) Bool(b bool) *Array {
|
||||||
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b)
|
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b)
|
||||||
return a
|
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 {
|
func (a *Array) Int(i int) *Array {
|
||||||
a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Int8(i int8) *Array {
|
||||||
a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Int16(i int16) *Array {
|
||||||
a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Int32(i int32) *Array {
|
||||||
a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Int64(i int64) *Array {
|
||||||
a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Uint(i uint) *Array {
|
||||||
a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Uint8(i uint8) *Array {
|
||||||
a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Uint16(i uint16) *Array {
|
||||||
a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Uint32(i uint32) *Array {
|
||||||
a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Uint64(i uint64) *Array {
|
||||||
a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i)
|
a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i)
|
||||||
return a
|
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 {
|
func (a *Array) Float32(f float32) *Array {
|
||||||
a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f)
|
a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f)
|
||||||
return a
|
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 {
|
func (a *Array) Float64(f float64) *Array {
|
||||||
a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f)
|
a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f)
|
||||||
return a
|
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 {
|
func (a *Array) Time(t time.Time) *Array {
|
||||||
a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat)
|
a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dur append append d to the array.
|
// Dur appends d to the array.
|
||||||
func (a *Array) Dur(d time.Duration) *Array {
|
func (a *Array) Dur(d time.Duration) *Array {
|
||||||
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger)
|
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface append append i marshaled using reflection.
|
// Interface appends i marshaled using reflection.
|
||||||
func (a *Array) Interface(i interface{}) *Array {
|
func (a *Array) Interface(i interface{}) *Array {
|
||||||
if obj, ok := i.(LogObjectMarshaler); ok {
|
if obj, ok := i.(LogObjectMarshaler); ok {
|
||||||
return a.Object(obj)
|
return a.Object(obj)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module git.tuxpa.in/a/zlog/cmd/lint
|
module tuxpa.in/a/zlog/cmd/lint
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ func init() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// add zlog to recursively ignored packages
|
// add zlog to recursively ignored packages
|
||||||
recursivelyIgnoredPkgs = append(recursivelyIgnoredPkgs, "git.tuxpa.in/a/zlog")
|
recursivelyIgnoredPkgs = append(recursivelyIgnoredPkgs, "tuxpa.in/a/zlog")
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
fmt.Fprintln(os.Stderr, "you must provide exactly one package path")
|
fmt.Fprintln(os.Stderr, "you must provide exactly one package path")
|
||||||
@ -49,14 +49,14 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the git.tuxpa.in/a/zlog.Event type
|
// get the tuxpa.in/a/zlog.Event type
|
||||||
event := getEvent(p)
|
event := getEvent(p)
|
||||||
if event == nil {
|
if event == nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error: git.tuxpa.in/a/zlog.Event declaration not found, maybe zlog is not imported in the scanned package?")
|
fmt.Fprintln(os.Stderr, "Error: tuxpa.in/a/zlog.Event declaration not found, maybe zlog is not imported in the scanned package?")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all selections (function calls) with the git.tuxpa.in/a/zlog.Event (or pointer) receiver
|
// get all selections (function calls) with the tuxpa.in/a/zlog.Event (or pointer) receiver
|
||||||
selections := getSelectionsWithReceiverType(p, event)
|
selections := getSelectionsWithReceiverType(p, event)
|
||||||
|
|
||||||
// print the violations (if any)
|
// print the violations (if any)
|
||||||
@ -80,7 +80,7 @@ func main() {
|
|||||||
|
|
||||||
func getEvent(p *loader.Program) types.Type {
|
func getEvent(p *loader.Program) types.Type {
|
||||||
for _, pkg := range p.AllPackages {
|
for _, pkg := range p.AllPackages {
|
||||||
if strings.HasSuffix(pkg.Pkg.Path(), "git.tuxpa.in/a/zlog") {
|
if strings.HasSuffix(pkg.Pkg.Path(), "tuxpa.in/a/zlog") {
|
||||||
for _, d := range pkg.Defs {
|
for _, d := range pkg.Defs {
|
||||||
if d != nil && d.Name() == "Event" {
|
if d != nil && d.Name() == "Event" {
|
||||||
return d.Type()
|
return d.Type()
|
||||||
|
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
|
FormatFieldValue Formatter
|
||||||
FormatErrFieldName Formatter
|
FormatErrFieldName Formatter
|
||||||
FormatErrFieldValue Formatter
|
FormatErrFieldValue Formatter
|
||||||
|
|
||||||
|
FormatExtra func(map[string]interface{}, *bytes.Buffer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConsoleWriter creates and initializes a new ConsoleWriter.
|
// 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)
|
w.writeFields(evt, buf)
|
||||||
|
|
||||||
|
if w.FormatExtra != nil {
|
||||||
|
err = w.FormatExtra(evt, buf)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = buf.WriteByte('\n')
|
err = buf.WriteByte('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = buf.WriteTo(w.Out)
|
_, err = buf.WriteTo(w.Out)
|
||||||
return len(p), err
|
return len(p), err
|
||||||
}
|
}
|
||||||
@ -221,7 +231,7 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
|
|||||||
case json.Number:
|
case json.Number:
|
||||||
buf.WriteString(fv(fValue))
|
buf.WriteString(fv(fValue))
|
||||||
default:
|
default:
|
||||||
b, err := json.Marshal(fValue)
|
b, err := InterfaceMarshalFunc(fValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
|
fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
|
||||||
} else {
|
} else {
|
||||||
@ -327,27 +337,31 @@ func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
|
|||||||
t := "<nil>"
|
t := "<nil>"
|
||||||
switch tt := i.(type) {
|
switch tt := i.(type) {
|
||||||
case string:
|
case string:
|
||||||
ts, err := time.Parse(TimeFieldFormat, tt)
|
ts, err := time.ParseInLocation(TimeFieldFormat, tt, time.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t = tt
|
t = tt
|
||||||
} else {
|
} else {
|
||||||
t = ts.Format(timeFormat)
|
t = ts.Local().Format(timeFormat)
|
||||||
}
|
}
|
||||||
case json.Number:
|
case json.Number:
|
||||||
i, err := tt.Int64()
|
i, err := tt.Int64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t = tt.String()
|
t = tt.String()
|
||||||
} else {
|
} else {
|
||||||
var sec, nsec int64 = i, 0
|
var sec, nsec int64
|
||||||
|
|
||||||
switch TimeFieldFormat {
|
switch TimeFieldFormat {
|
||||||
case TimeFormatUnixMs:
|
case TimeFormatUnixNano:
|
||||||
nsec = int64(time.Duration(i) * time.Millisecond)
|
sec, nsec = 0, i
|
||||||
sec = 0
|
|
||||||
case TimeFormatUnixMicro:
|
case TimeFormatUnixMicro:
|
||||||
nsec = int64(time.Duration(i) * time.Microsecond)
|
sec, nsec = 0, int64(time.Duration(i)*time.Microsecond)
|
||||||
sec = 0
|
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)
|
t = ts.Format(timeFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +389,7 @@ func consoleDefaultFormatLevel(noColor bool) Formatter {
|
|||||||
case LevelPanicValue:
|
case LevelPanicValue:
|
||||||
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
|
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
|
||||||
default:
|
default:
|
||||||
l = colorize("???", colorBold, noColor)
|
l = colorize(ll, colorBold, noColor)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if i == nil {
|
if i == nil {
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleConsoleWriter() {
|
func ExampleConsoleWriter() {
|
||||||
@ -108,13 +108,14 @@ func TestConsoleWriter(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true}
|
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"}`))
|
_, err := w.Write([]byte(`{"time": "` + d + `", "level": "debug", "message": "Foobar", "foo": "bar"}`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error when writing output: %s", err)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", 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)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", 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)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", 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)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||||
@ -239,7 +240,8 @@ func TestConsoleWriter(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true}
|
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"}`
|
evt := `{"time": "` + d + `", "level": "error", "message": "Foobar", "aaa": "bbb", "error": "Error"}`
|
||||||
// t.Log(evt)
|
// t.Log(evt)
|
||||||
|
|
||||||
@ -248,7 +250,7 @@ func TestConsoleWriter(t *testing.T) {
|
|||||||
t.Errorf("Unexpected error when writing output: %s", err)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", 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)
|
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"}`
|
evt := `{"time": "` + d + `", "level": "debug", "message": "Foobar", "foo": "bar", "caller": "` + cwd + `/foo/bar.go"}`
|
||||||
// t.Log(evt)
|
// t.Log(evt)
|
||||||
|
|
||||||
@ -273,7 +276,7 @@ func TestConsoleWriter(t *testing.T) {
|
|||||||
t.Errorf("Unexpected error when writing output: %s", err)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
|
||||||
@ -305,7 +308,8 @@ func TestConsoleWriterConfiguration(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
w := zlog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: time.RFC3339}
|
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"}`
|
evt := `{"time": "` + d + `", "level": "info", "message": "Foobar"}`
|
||||||
|
|
||||||
_, err := w.Write([]byte(evt))
|
_, err := w.Write([]byte(evt))
|
||||||
@ -313,7 +317,7 @@ func TestConsoleWriterConfiguration(t *testing.T) {
|
|||||||
t.Errorf("Unexpected error when writing output: %s", err)
|
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()
|
actualOutput := buf.String()
|
||||||
if actualOutput != expectedOutput {
|
if actualOutput != expectedOutput {
|
||||||
t.Errorf("Unexpected output %q, want: %q", 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.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) {
|
func BenchmarkConsoleWriter(b *testing.B) {
|
||||||
|
@ -329,8 +329,9 @@ func (ts timestampHook) Run(e *Event, level Level, msg string) {
|
|||||||
|
|
||||||
var th = timestampHook{}
|
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 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.
|
// NOTE: It won't dedupe the "time" key if the *Context has one already.
|
||||||
func (c Context) Timestamp() Context {
|
func (c Context) Timestamp() Context {
|
||||||
|
19
ctx.go
19
ctx.go
@ -14,10 +14,15 @@ func init() {
|
|||||||
|
|
||||||
type ctxKey struct{}
|
type ctxKey struct{}
|
||||||
|
|
||||||
// WithContext returns a copy of ctx with l associated. If an instance of Logger
|
// WithContext returns a copy of ctx with the receiver attached. The Logger
|
||||||
// is already in the context, the context is not updated.
|
// 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:
|
// notation:
|
||||||
//
|
//
|
||||||
// ctx := r.Context()
|
// ctx := r.Context()
|
||||||
@ -25,13 +30,9 @@ type ctxKey struct{}
|
|||||||
// l.UpdateContext(func(c Context) Context {
|
// l.UpdateContext(func(c Context) Context {
|
||||||
// return c.Str("bar", "baz")
|
// return c.Str("bar", "baz")
|
||||||
// })
|
// })
|
||||||
|
//
|
||||||
func (l Logger) WithContext(ctx context.Context) context.Context {
|
func (l Logger) WithContext(ctx context.Context) context.Context {
|
||||||
if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok {
|
if _, ok := ctx.Value(ctxKey{}).(*Logger); !ok && l.level == Disabled {
|
||||||
if lp == &l {
|
|
||||||
// Do not store same logger.
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
} else if l.level == Disabled {
|
|
||||||
// Do not store disabled logger.
|
// Do not store disabled logger.
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog/diode/internal/diodes"
|
"tuxpa.in/a/zlog/diode/internal/diodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bufPool = &sync.Pool{
|
var bufPool = &sync.Pool{
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/diode"
|
"tuxpa.in/a/zlog/diode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNewWriter() {
|
func ExampleNewWriter() {
|
||||||
|
@ -9,9 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/diode"
|
"tuxpa.in/a/zlog/diode"
|
||||||
"git.tuxpa.in/a/zlog/internal/cbor"
|
"tuxpa.in/a/zlog/internal/cbor"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewWriter(t *testing.T) {
|
func TestNewWriter(t *testing.T) {
|
||||||
@ -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) {
|
func Benchmark(b *testing.B) {
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
defer log.SetOutput(os.Stderr)
|
defer log.SetOutput(os.Stderr)
|
||||||
|
@ -39,7 +39,12 @@ func NewWaiter(d Diode, opts ...WaiterConfigOption) *Waiter {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-w.ctx.Done()
|
<-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.c.Broadcast()
|
||||||
|
w.mu.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return w
|
return w
|
||||||
|
@ -5,7 +5,7 @@ package zlog
|
|||||||
// This file contains bindings to do binary encoding.
|
// This file contains bindings to do binary encoding.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.tuxpa.in/a/zlog/internal/cbor"
|
"tuxpa.in/a/zlog/internal/cbor"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,6 +24,9 @@ func init() {
|
|||||||
func appendJSON(dst []byte, j []byte) []byte {
|
func appendJSON(dst []byte, j []byte) []byte {
|
||||||
return cbor.AppendEmbeddedJSON(dst, j)
|
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
|
// decodeIfBinaryToString - converts a binary formatted log msg to a
|
||||||
// JSON formatted String Log message.
|
// JSON formatted String Log message.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !binary_log
|
||||||
// +build !binary_log
|
// +build !binary_log
|
||||||
|
|
||||||
package zlog
|
package zlog
|
||||||
@ -6,7 +7,8 @@ package zlog
|
|||||||
// JSON encoded byte stream.
|
// JSON encoded byte stream.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.tuxpa.in/a/zlog/internal/json"
|
"encoding/base64"
|
||||||
|
"tuxpa.in/a/zlog/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -25,6 +27,17 @@ func init() {
|
|||||||
func appendJSON(dst []byte, j []byte) []byte {
|
func appendJSON(dst []byte, j []byte) []byte {
|
||||||
return append(dst, j...)
|
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 {
|
func decodeIfBinaryToString(in []byte) string {
|
||||||
return string(in)
|
return string(in)
|
||||||
|
30
event.go
30
event.go
@ -318,6 +318,18 @@ func (e *Event) RawJSON(key string, b []byte) *Event {
|
|||||||
return e
|
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.
|
// AnErr adds the field key with serialized err to the *Event context.
|
||||||
// If err is nil, no field is added.
|
// If err is nil, no field is added.
|
||||||
func (e *Event) AnErr(key string, err error) *Event {
|
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
|
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.
|
// Interface adds the field key with i marshaled using reflection.
|
||||||
func (e *Event) Interface(key string, i interface{}) *Event {
|
func (e *Event) Interface(key string, i interface{}) *Event {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
@ -719,6 +736,15 @@ func (e *Event) Interface(key string, i interface{}) *Event {
|
|||||||
return e
|
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.
|
// CallerSkipFrame instructs any future Caller calls to skip the specified number of frames.
|
||||||
// This includes those added via hooks from the context.
|
// This includes those added via hooks from the context.
|
||||||
func (e *Event) CallerSkipFrame(skip int) *Event {
|
func (e *Event) CallerSkipFrame(skip int) *Event {
|
||||||
@ -744,11 +770,11 @@ func (e *Event) caller(skip int) *Event {
|
|||||||
if e == nil {
|
if e == nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
_, file, line, ok := runtime.Caller(skip + e.skipFrame)
|
pc, file, line, ok := runtime.Caller(skip + e.skipFrame)
|
||||||
if !ok {
|
if !ok {
|
||||||
return e
|
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
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,10 @@ const (
|
|||||||
// TimeFormatUnixMicro defines a time format that makes time fields to be
|
// TimeFormatUnixMicro defines a time format that makes time fields to be
|
||||||
// serialized as Unix timestamp integers in microseconds.
|
// serialized as Unix timestamp integers in microseconds.
|
||||||
TimeFormatUnixMicro = "UNIXMICRO"
|
TimeFormatUnixMicro = "UNIXMICRO"
|
||||||
|
|
||||||
|
// TimeFormatUnixNano defines a time format that makes time fields to be
|
||||||
|
// serialized as Unix timestamp integers in nanoseconds.
|
||||||
|
TimeFormatUnixNano = "UNIXNANO"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -61,7 +65,7 @@ var (
|
|||||||
CallerSkipFrameCount = 2
|
CallerSkipFrameCount = 2
|
||||||
|
|
||||||
// CallerMarshalFunc allows customization of global caller marshaling
|
// 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)
|
return file + ":" + strconv.Itoa(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +85,7 @@ var (
|
|||||||
InterfaceMarshalFunc = json.Marshal
|
InterfaceMarshalFunc = json.Marshal
|
||||||
|
|
||||||
// TimeFieldFormat defines the time format of the Time field type. If set to
|
// 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.
|
// timestamp as integer.
|
||||||
TimeFieldFormat = time.RFC3339
|
TimeFieldFormat = time.RFC3339
|
||||||
|
|
||||||
|
8
go.mod
8
go.mod
@ -1,10 +1,12 @@
|
|||||||
module git.tuxpa.in/a/zlog
|
module tuxpa.in/a/zlog
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/mattn/go-colorable v0.1.12
|
github.com/mattn/go-colorable v0.1.12
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/xid v1.3.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
|
||||||
)
|
)
|
||||||
|
15
go.sum
15
go.sum
@ -1,7 +1,6 @@
|
|||||||
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U=
|
|
||||||
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.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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
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-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
@ -9,8 +8,12 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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=
|
||||||
|
20
hlog/hlog.go
20
hlog/hlog.go
@ -7,9 +7,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/hlog/internal/mutil"
|
"tuxpa.in/a/zlog/hlog/internal/mutil"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FromRequest gets the logger in the request's context.
|
// FromRequest gets the logger in the request's context.
|
||||||
@ -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{}
|
type idKey struct{}
|
||||||
|
|
||||||
// IDFromRequest returns the unique id associated to the request if any.
|
// IDFromRequest returns the unique id associated to the request if any.
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/hlog"
|
"tuxpa.in/a/zlog/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fake alice to avoid dep
|
// fake alice to avoid dep
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build go1.7
|
||||||
// +build go1.7
|
// +build go1.7
|
||||||
|
|
||||||
package hlog
|
package hlog
|
||||||
@ -14,8 +15,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/internal/cbor"
|
"tuxpa.in/a/zlog/internal/cbor"
|
||||||
)
|
)
|
||||||
|
|
||||||
func decodeIfBinary(out *bytes.Buffer) string {
|
func decodeIfBinary(out *bytes.Buffer) string {
|
||||||
@ -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) {
|
func TestCombinedHandlers(t *testing.T) {
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
r := &http.Request{
|
r := &http.Request{
|
||||||
|
@ -26,7 +26,8 @@ const (
|
|||||||
additionalTypeBreak byte = 31
|
additionalTypeBreak byte = 31
|
||||||
|
|
||||||
// Tag Sub-types.
|
// Tag Sub-types.
|
||||||
additionalTypeTimestamp byte = 01
|
additionalTypeTimestamp byte = 01
|
||||||
|
additionalTypeEmbeddedCBOR byte = 63
|
||||||
|
|
||||||
// Extended Tags - from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
// Extended Tags - from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
||||||
additionalTypeTagNetworkAddr uint16 = 260
|
additionalTypeTagNetworkAddr uint16 = 260
|
||||||
|
@ -5,6 +5,7 @@ package cbor
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@ -213,6 +214,31 @@ func decodeString(src *bufio.Reader, noQuotes bool) []byte {
|
|||||||
}
|
}
|
||||||
return append(result, '"')
|
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 {
|
func decodeUTF8String(src *bufio.Reader) []byte {
|
||||||
pb := readByte(src)
|
pb := readByte(src)
|
||||||
@ -349,6 +375,20 @@ func decodeTagData(src *bufio.Reader) []byte {
|
|||||||
switch minor {
|
switch minor {
|
||||||
case additionalTypeTimestamp:
|
case additionalTypeTimestamp:
|
||||||
return decodeTimeStamp(src)
|
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).
|
// Tag value is larger than 256 (so uint16).
|
||||||
case additionalTypeIntUint16:
|
case additionalTypeIntUint16:
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeLog(fname string, count int, useCompress bool) {
|
func writeLog(fname string, count int, useCompress bool) {
|
||||||
|
@ -93,3 +93,25 @@ func AppendEmbeddedJSON(dst, s []byte) []byte {
|
|||||||
}
|
}
|
||||||
return append(dst, s...)
|
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"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppendNil inserts a 'Nil' object into the dst byte array.
|
// 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)
|
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).
|
// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6).
|
||||||
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
|
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
|
||||||
dst = append(dst, majorTypeTags|additionalTypeIntUint16)
|
dst = append(dst, majorTypeTags|additionalTypeIntUint16)
|
||||||
|
@ -7,9 +7,10 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Import from zlog/global.go
|
// Import from zlog/global.go
|
||||||
timeFormatUnix = ""
|
timeFormatUnix = ""
|
||||||
timeFormatUnixMs = "UNIXMS"
|
timeFormatUnixMs = "UNIXMS"
|
||||||
timeFormatUnixMicro = "UNIXMICRO"
|
timeFormatUnixMicro = "UNIXMICRO"
|
||||||
|
timeFormatUnixNano = "UNIXNANO"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppendTime formats the input time with the given format
|
// 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)
|
return e.AppendInt64(dst, t.UnixNano()/1000000)
|
||||||
case timeFormatUnixMicro:
|
case timeFormatUnixMicro:
|
||||||
return e.AppendInt64(dst, t.UnixNano()/1000)
|
return e.AppendInt64(dst, t.UnixNano()/1000)
|
||||||
|
case timeFormatUnixNano:
|
||||||
|
return e.AppendInt64(dst, t.UnixNano())
|
||||||
}
|
}
|
||||||
return append(t.AppendFormat(append(dst, '"'), format), '"')
|
return append(t.AppendFormat(append(dst, '"'), format), '"')
|
||||||
}
|
}
|
||||||
@ -33,7 +36,11 @@ func (Encoder) AppendTimes(dst []byte, vals []time.Time, format string) []byte {
|
|||||||
case timeFormatUnix:
|
case timeFormatUnix:
|
||||||
return appendUnixTimes(dst, vals)
|
return appendUnixTimes(dst, vals)
|
||||||
case timeFormatUnixMs:
|
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 {
|
if len(vals) == 0 {
|
||||||
return append(dst, '[', ']')
|
return append(dst, '[', ']')
|
||||||
@ -64,15 +71,15 @@ func appendUnixTimes(dst []byte, vals []time.Time) []byte {
|
|||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendUnixMsTimes(dst []byte, vals []time.Time) []byte {
|
func appendUnixNanoTimes(dst []byte, vals []time.Time, div int64) []byte {
|
||||||
if len(vals) == 0 {
|
if len(vals) == 0 {
|
||||||
return append(dst, '[', ']')
|
return append(dst, '[', ']')
|
||||||
}
|
}
|
||||||
dst = 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 {
|
if len(vals) > 1 {
|
||||||
for _, t := range 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, ']')
|
dst = append(dst, ']')
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -369,6 +370,14 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
|
|||||||
return append(dst, marshaled...)
|
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
|
// AppendObjectData takes in an object that is already in a byte array
|
||||||
// and adds it to the dst.
|
// and adds it to the dst.
|
||||||
func (Encoder) AppendObjectData(dst []byte, o []byte) []byte {
|
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) {
|
func Test_appendObjectData(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
dst []byte
|
dst []byte
|
||||||
|
@ -25,8 +25,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/go-systemd/v22/journal"
|
"github.com/coreos/go-systemd/v22/journal"
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/internal/cbor"
|
"tuxpa.in/a/zlog/internal/cbor"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultJournalDPrio = journal.PriNotice
|
const defaultJournalDPrio = journal.PriNotice
|
||||||
@ -102,7 +102,7 @@ func (w journalWriter) Write(p []byte) (n int, err error) {
|
|||||||
case json.Number:
|
case json.Number:
|
||||||
args[jKey] = fmt.Sprint(value)
|
args[jKey] = fmt.Sprint(value)
|
||||||
default:
|
default:
|
||||||
b, err := json.Marshal(value)
|
b, err := zlog.InterfaceMarshalFunc(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args[jKey] = fmt.Sprintf("[error: %v]", err)
|
args[jKey] = fmt.Sprintf("[error: %v]", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/journald"
|
"tuxpa.in/a/zlog/journald"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNewJournalDWriter() {
|
func ExampleNewJournalDWriter() {
|
||||||
|
144
log.go
144
log.go
@ -2,108 +2,109 @@
|
|||||||
//
|
//
|
||||||
// A global Logger can be use for simple logging:
|
// A global Logger can be use for simple logging:
|
||||||
//
|
//
|
||||||
// import "git.tuxpa.in/a/zlog/log"
|
// import "tuxpa.in/a/zlog/log"
|
||||||
//
|
//
|
||||||
// log.Info().Msg("hello world")
|
// log.Info().Msg("hello world")
|
||||||
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
|
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
|
||||||
//
|
//
|
||||||
// NOTE: To import the global logger, import the "log" subpackage "git.tuxpa.in/a/zlog/log".
|
// NOTE: To import the global logger, import the "log" subpackage "tuxpa.in/a/zlog/log".
|
||||||
//
|
//
|
||||||
// Fields can be added to log messages:
|
// Fields can be added to log messages:
|
||||||
//
|
//
|
||||||
// log.Info().Str("foo", "bar").Msg("hello world")
|
// log.Info().Str("foo", "bar").Msg("hello world")
|
||||||
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
||||||
//
|
//
|
||||||
// Create logger instance to manage different outputs:
|
// Create logger instance to manage different outputs:
|
||||||
//
|
//
|
||||||
// logger := zlog.New(os.Stderr).With().Timestamp().Logger()
|
// logger := zlog.New(os.Stderr).With().Timestamp().Logger()
|
||||||
// logger.Info().
|
// logger.Info().
|
||||||
// Str("foo", "bar").
|
// Str("foo", "bar").
|
||||||
// Msg("hello world")
|
// Msg("hello world")
|
||||||
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
||||||
//
|
//
|
||||||
// Sub-loggers let you chain loggers with additional context:
|
// Sub-loggers let you chain loggers with additional context:
|
||||||
//
|
//
|
||||||
// sublogger := log.With().Str("component": "foo").Logger()
|
// sublogger := log.With().Str("component": "foo").Logger()
|
||||||
// sublogger.Info().Msg("hello world")
|
// sublogger.Info().Msg("hello world")
|
||||||
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
|
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
|
||||||
//
|
//
|
||||||
// Level logging
|
// Level logging
|
||||||
//
|
//
|
||||||
// zlog.SetGlobalLevel(zlog.InfoLevel)
|
// zlog.SetGlobalLevel(zlog.InfoLevel)
|
||||||
//
|
//
|
||||||
// log.Debug().Msg("filtered out message")
|
// log.Debug().Msg("filtered out message")
|
||||||
// log.Info().Msg("routed message")
|
// log.Info().Msg("routed message")
|
||||||
//
|
//
|
||||||
// if e := log.Debug(); e.Enabled() {
|
// if e := log.Debug(); e.Enabled() {
|
||||||
// // Compute log output only if enabled.
|
// // Compute log output only if enabled.
|
||||||
// value := compute()
|
// value := compute()
|
||||||
// e.Str("foo": value).Msg("some debug message")
|
// e.Str("foo": value).Msg("some debug message")
|
||||||
// }
|
// }
|
||||||
// // Output: {"level":"info","time":1494567715,"routed message"}
|
// // Output: {"level":"info","time":1494567715,"routed message"}
|
||||||
//
|
//
|
||||||
// Customize automatic field names:
|
// Customize automatic field names:
|
||||||
//
|
//
|
||||||
// log.TimestampFieldName = "t"
|
// log.TimestampFieldName = "t"
|
||||||
// log.LevelFieldName = "p"
|
// log.LevelFieldName = "p"
|
||||||
// log.MessageFieldName = "m"
|
// log.MessageFieldName = "m"
|
||||||
//
|
//
|
||||||
// log.Info().Msg("hello world")
|
// log.Info().Msg("hello world")
|
||||||
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
|
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
|
||||||
//
|
//
|
||||||
// Log with no level and message:
|
// Log with no level and message:
|
||||||
//
|
//
|
||||||
// log.Log().Str("foo","bar").Msg("")
|
// log.Log().Str("foo","bar").Msg("")
|
||||||
// // Output: {"time":1494567715,"foo":"bar"}
|
// // Output: {"time":1494567715,"foo":"bar"}
|
||||||
//
|
//
|
||||||
// Add contextual fields to global Logger:
|
// Add contextual fields to global Logger:
|
||||||
//
|
//
|
||||||
// log.Logger = log.With().Str("foo", "bar").Logger()
|
// log.Logger = log.With().Str("foo", "bar").Logger()
|
||||||
//
|
//
|
||||||
// Sample logs:
|
// Sample logs:
|
||||||
//
|
//
|
||||||
// sampled := log.Sample(&zlog.BasicSampler{N: 10})
|
// sampled := log.Sample(&zlog.BasicSampler{N: 10})
|
||||||
// sampled.Info().Msg("will be logged every 10 messages")
|
// sampled.Info().Msg("will be logged every 10 messages")
|
||||||
//
|
//
|
||||||
// Log with contextual hooks:
|
// Log with contextual hooks:
|
||||||
//
|
//
|
||||||
// // Create the hook:
|
// // Create the hook:
|
||||||
// type SeverityHook struct{}
|
// type SeverityHook struct{}
|
||||||
//
|
//
|
||||||
// func (h SeverityHook) Run(e *zlog.Event, level zlog.Level, msg string) {
|
// func (h SeverityHook) Run(e *zlog.Event, level zlog.Level, msg string) {
|
||||||
// if level != zlog.NoLevel {
|
// if level != zlog.NoLevel {
|
||||||
// e.Str("severity", level.String())
|
// e.Str("severity", level.String())
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // And use it:
|
// // And use it:
|
||||||
// var h SeverityHook
|
// var h SeverityHook
|
||||||
// log := zlog.New(os.Stdout).Hook(h)
|
// log := zlog.New(os.Stdout).Hook(h)
|
||||||
// log.Warn().Msg("")
|
// log.Warn().Msg("")
|
||||||
// // Output: {"level":"warn","severity":"warn"}
|
// // Output: {"level":"warn","severity":"warn"}
|
||||||
//
|
//
|
||||||
//
|
// # Caveats
|
||||||
// Caveats
|
|
||||||
//
|
//
|
||||||
// There is no fields deduplication out-of-the-box.
|
// There is no fields deduplication out-of-the-box.
|
||||||
// Using the same key multiple times creates new key in final JSON each time.
|
// Using the same key multiple times creates new key in final JSON each time.
|
||||||
//
|
//
|
||||||
// logger := zlog.New(os.Stderr).With().Timestamp().Logger()
|
// logger := zlog.New(os.Stderr).With().Timestamp().Logger()
|
||||||
// logger.Info().
|
// logger.Info().
|
||||||
// Timestamp().
|
// Timestamp().
|
||||||
// Msg("dup")
|
// Msg("dup")
|
||||||
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
|
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
|
||||||
//
|
//
|
||||||
// In this case, many consumers will take the last value,
|
// In this case, many consumers will take the last value,
|
||||||
// but this is not guaranteed; check yours if in doubt.
|
// but this is not guaranteed; check yours if in doubt.
|
||||||
package zlog
|
package zlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Level defines log levels.
|
// Level defines log levels.
|
||||||
@ -159,24 +160,24 @@ func (l Level) String() string {
|
|||||||
// ParseLevel converts a level string into a zlog Level value.
|
// ParseLevel converts a level string into a zlog Level value.
|
||||||
// returns an error if the input string does not match known values.
|
// returns an error if the input string does not match known values.
|
||||||
func ParseLevel(levelStr string) (Level, error) {
|
func ParseLevel(levelStr string) (Level, error) {
|
||||||
switch levelStr {
|
switch {
|
||||||
case LevelFieldMarshalFunc(TraceLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(TraceLevel)):
|
||||||
return TraceLevel, nil
|
return TraceLevel, nil
|
||||||
case LevelFieldMarshalFunc(DebugLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(DebugLevel)):
|
||||||
return DebugLevel, nil
|
return DebugLevel, nil
|
||||||
case LevelFieldMarshalFunc(InfoLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(InfoLevel)):
|
||||||
return InfoLevel, nil
|
return InfoLevel, nil
|
||||||
case LevelFieldMarshalFunc(WarnLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(WarnLevel)):
|
||||||
return WarnLevel, nil
|
return WarnLevel, nil
|
||||||
case LevelFieldMarshalFunc(ErrorLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(ErrorLevel)):
|
||||||
return ErrorLevel, nil
|
return ErrorLevel, nil
|
||||||
case LevelFieldMarshalFunc(FatalLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(FatalLevel)):
|
||||||
return FatalLevel, nil
|
return FatalLevel, nil
|
||||||
case LevelFieldMarshalFunc(PanicLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(PanicLevel)):
|
||||||
return PanicLevel, nil
|
return PanicLevel, nil
|
||||||
case LevelFieldMarshalFunc(Disabled):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(Disabled)):
|
||||||
return Disabled, nil
|
return Disabled, nil
|
||||||
case LevelFieldMarshalFunc(NoLevel):
|
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(NoLevel)):
|
||||||
return NoLevel, nil
|
return NoLevel, nil
|
||||||
}
|
}
|
||||||
i, err := strconv.Atoi(levelStr)
|
i, err := strconv.Atoi(levelStr)
|
||||||
@ -189,6 +190,21 @@ func ParseLevel(levelStr string) (Level, error) {
|
|||||||
return Level(i), nil
|
return Level(i), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler to allow for easy reading from toml/yaml/json formats
|
||||||
|
func (l *Level) UnmarshalText(text []byte) error {
|
||||||
|
if l == nil {
|
||||||
|
return errors.New("can't unmarshal a nil *Level")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
*l, err = ParseLevel(string(text))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler to allow for easy writing into toml/yaml/json formats
|
||||||
|
func (l Level) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(LevelFieldMarshalFunc(l)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// A Logger represents an active logging object that generates lines
|
// A Logger represents an active logging object that generates lines
|
||||||
// of JSON output to an io.Writer. Each logging operation makes a single
|
// of JSON output to an io.Writer. Each logging operation makes a single
|
||||||
// call to the Writer's Write method. There is no guarantee on access
|
// call to the Writer's Write method. There is no guarantee on access
|
||||||
@ -292,7 +308,9 @@ func (l Logger) Sample(s Sampler) Logger {
|
|||||||
|
|
||||||
// Hook returns a logger with the h Hook.
|
// Hook returns a logger with the h Hook.
|
||||||
func (l Logger) Hook(h Hook) Logger {
|
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
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
log/log.go
14
log/log.go
@ -8,15 +8,17 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"github.com/rs/zerolog"
|
||||||
|
"tuxpa.in/a/zlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger is the global logger.
|
// Logger is the global logger.
|
||||||
var Logger = zlog.New(os.Stderr).With().Timestamp().Logger()
|
var Logger = zlog.New(nil).Output(
|
||||||
|
zerolog.ConsoleWriter{
|
||||||
func init() {
|
Out: os.Stderr,
|
||||||
zlog.TimeFieldFormat = time.RFC3339Nano
|
TimeFormat: time.RFC3339,
|
||||||
}
|
},
|
||||||
|
).With().Timestamp().Logger()
|
||||||
|
|
||||||
// Output duplicates the global logger and sets w as its output.
|
// Output duplicates the global logger and sets w as its output.
|
||||||
func Output(w io.Writer) zlog.Logger {
|
func Output(w io.Writer) zlog.Logger {
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
"git.tuxpa.in/a/zlog/log"
|
"tuxpa.in/a/zlog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setup would normally be an init() function, however, there seems
|
// setup would normally be an init() function, however, there seems
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNew() {
|
func ExampleNew() {
|
||||||
|
78
log_test.go
78
log_test.go
@ -320,6 +320,7 @@ func TestFields(t *testing.T) {
|
|||||||
Bytes("bytes", []byte("bar")).
|
Bytes("bytes", []byte("bar")).
|
||||||
Hex("hex", []byte{0x12, 0xef}).
|
Hex("hex", []byte{0x12, 0xef}).
|
||||||
RawJSON("json", []byte(`{"some":"json"}`)).
|
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") }).
|
Func(func(e *Event) { e.Str("func", "func_output") }).
|
||||||
AnErr("some_err", nil).
|
AnErr("some_err", nil).
|
||||||
Err(errors.New("some error")).
|
Err(errors.New("some error")).
|
||||||
@ -344,7 +345,7 @@ func TestFields(t *testing.T) {
|
|||||||
Time("time", time.Time{}).
|
Time("time", time.Time{}).
|
||||||
TimeDiff("diff", now, now.Add(-10*time.Second)).
|
TimeDiff("diff", now, now.Add(-10*time.Second)).
|
||||||
Msg("")
|
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)
|
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
|
// test default behaviour this is really brittle due to the line numbers
|
||||||
// actually mattering for validation
|
// actually mattering for validation
|
||||||
_, file, line, _ := runtime.Caller(0)
|
pc, file, line, _ := runtime.Caller(0)
|
||||||
caller := fmt.Sprintf("%s:%d", file, line+2)
|
caller := fmt.Sprintf("%s:%d", file, line+2)
|
||||||
log.Log().Caller().Msg("msg")
|
log.Log().Caller().Msg("msg")
|
||||||
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
|
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
|
// test custom behavior. In this case we'll take just the last directory
|
||||||
origCallerMarshalFunc := CallerMarshalFunc
|
origCallerMarshalFunc := CallerMarshalFunc
|
||||||
defer func() { CallerMarshalFunc = origCallerMarshalFunc }()
|
defer func() { CallerMarshalFunc = origCallerMarshalFunc }()
|
||||||
CallerMarshalFunc = func(file string, line int) string {
|
CallerMarshalFunc = func(pc uintptr, file string, line int) string {
|
||||||
parts := strings.Split(file, "/")
|
parts := strings.Split(file, "/")
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
return strings.Join(parts[len(parts)-2:], "/") + ":" + strconv.Itoa(line)
|
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)
|
pc, file, line, _ = runtime.Caller(0)
|
||||||
caller = CallerMarshalFunc(file, line+2)
|
caller = CallerMarshalFunc(pc, file, line+2)
|
||||||
log.Log().Caller().Msg("msg")
|
log.Log().Caller().Msg("msg")
|
||||||
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
|
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
|
||||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", 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) {
|
func TestParseLevel(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
levelStr string
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -42,15 +42,32 @@ func frameField(f errors.Frame, s *state, c rune) string {
|
|||||||
|
|
||||||
// MarshalStack implements pkg/errors stack trace marshaling.
|
// MarshalStack implements pkg/errors stack trace marshaling.
|
||||||
//
|
//
|
||||||
// zlog.ErrorStackMarshaler = MarshalStack
|
// zlog.ErrorStackMarshaler = MarshalStack
|
||||||
func MarshalStack(err error) interface{} {
|
func MarshalStack(err error) interface{} {
|
||||||
type stackTracer interface {
|
type stackTracer interface {
|
||||||
StackTrace() errors.StackTrace
|
StackTrace() errors.StackTrace
|
||||||
}
|
}
|
||||||
sterr, ok := err.(stackTracer)
|
var sterr stackTracer
|
||||||
if !ok {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
st := sterr.StackTrace()
|
st := sterr.StackTrace()
|
||||||
s := &state{}
|
s := &state{}
|
||||||
out := make([]map[string]string, 0, len(st))
|
out := make([]map[string]string, 0, len(st))
|
||||||
|
@ -4,11 +4,12 @@ package pkgerrors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"git.tuxpa.in/a/zlog"
|
"tuxpa.in/a/zlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogStack(t *testing.T) {
|
func TestLogStack(t *testing.T) {
|
||||||
@ -17,11 +18,11 @@ func TestLogStack(t *testing.T) {
|
|||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
log := zlog.New(out)
|
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("")
|
log.Log().Stack().Err(err).Msg("")
|
||||||
|
|
||||||
got := out.String()
|
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 {
|
if ok, _ := regexp.MatchString(want, got); !ok {
|
||||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||||
}
|
}
|
||||||
@ -33,11 +34,11 @@ func TestLogStackFromContext(t *testing.T) {
|
|||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
log := zlog.New(out).With().Stack().Logger() // calling Stack() on log context instead of event
|
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()
|
log.Log().Err(err).Msg("") // not explicitly calling Stack()
|
||||||
|
|
||||||
got := out.String()
|
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 {
|
if ok, _ := regexp.MatchString(want, got); !ok {
|
||||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user