Compare commits

...

43 Commits

Author SHA1 Message Date
a
3a569c99c0
merge 2023-07-17 11:43:17 -05:00
finkandreas
61485f3857
Fix #564 (#565)
This could be a potential fix for #564
2023-07-12 14:29:38 +02:00
stergiotis
9070d49a1a
Add event method RawCBOR analogous to RawJSON (#556)
CBOR is encoded as data-url in JSON encoding and tagged in CBOR encoding.
2023-06-19 01:30:44 +02:00
Harish Kukreja
b662f088b9
docs: context.Timestamp uses zerolog.TimeFieldFormat (#554) 2023-06-11 17:20:21 +02:00
Basten Gao
4612e098d2
Fix error chain from pkgerrors (#552) 2023-05-15 14:07:32 +02:00
Angus
8981d80ed3
Correct logger variable name in stdout example (#549) 2023-05-11 14:00:42 +02:00
Koung
927516bcf1
doc(readme): fix a typo (#548)
Co-authored-by: jirasak <jirasak@lmwn.com>
2023-05-01 14:04:49 +02:00
Kirill
a712f61936
doc(readme): explain zerolog with context (#544)
Add section about `context.Context` usage with zerolog instance
2023-04-21 11:55:19 +02:00
dependabot[bot]
64a5863c5e
Bump github.com/rs/xid from 1.4.0 to 1.5.0 (#542)
Bumps [github.com/rs/xid](https://github.com/rs/xid) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/rs/xid/releases)
- [Commits](https://github.com/rs/xid/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/rs/xid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 11:56:52 +02:00
dependabot[bot]
1f50797d7d
Bump actions/setup-go from 3 to 4 (#529)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 12:29:36 -07:00
a
97e8f3922b merge 2023-03-19 06:53:15 -05:00
Menno van Rahden
902d72012d
fix level parser (#523) 2023-03-10 07:15:23 -08:00
dependabot[bot]
4fff5db29c
Bump github.com/coreos/go-systemd/v22 (#526)
Bumps [github.com/coreos/go-systemd/v22](https://github.com/coreos/go-systemd) from 22.3.3-0.20220203105225-a9a7ef127534 to 22.5.0.
- [Release notes](https://github.com/coreos/go-systemd/releases)
- [Commits](https://github.com/coreos/go-systemd/commits/v22.5.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-systemd/v22
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 13:02:23 +01:00
Adam Chalkley
762546b5c6
Replace duplicate 'append' text in doc comments (#520)
Replace 'append append' with 'appends'.

This resolves what appears to be an unintentional
search/replace action that occurred as part of other
cleanup work.

Co-authored-by: Adam Chalkley <atc0005@users.noreply.github.com>
2023-01-28 00:04:14 +01:00
Olivier Poitrey
fa9bf3742a Fix missing cbor encoder AppendType method 2023-01-25 17:34:40 +01:00
Sylvain Rabot
db22191211
Use recent go versions in test workflow (#519)
Signed-off-by: Sylvain Rabot <sylvain@abstraction.fr>
2023-01-25 17:05:09 +01:00
dils2k
164f7aa1a6
Add Any field function (#515) 2023-01-19 12:10:08 +01:00
a
a61f91287f nwe default logger 2022-12-28 04:18:00 -06:00
a
660e96877b fix 2022-12-28 03:41:00 -06:00
a
f380226321 update? 2022-12-28 03:33:41 -06:00
a
1a96beea25 merge? 2022-12-28 03:30:30 -06:00
Pavel Gryaznov
3543e9d94b
parsing CAPS log levels (#506) 2022-11-11 14:15:40 +01:00
Ted Lilley
5bdc93f7eb
Fix console formatter for timestampunixnano (#502)
Co-authored-by: Ted Lilley <ted.lilley@digi.com>
2022-11-03 01:35:19 +01:00
Tim Peoples
e3027a5732
Fix docs and behavior of WithContext (#499) 2022-10-22 19:29:38 +02:00
Patrick Scheid
a9a8199d2d
ConsoleWriter fallbacks to local timezone for missing TZ indicator (#497)
Closes issue #483

Before:
We use time.Parse which defaults to TZ UTC if there is no time zone
indicator specified in the time layout string. During the reparsing in
ConsoleWriter we therefore added the TZ difference to UTC twice.

After:
We use time.ParseInLocal where we need to provide a dedicated fallback
TZ as a fallback. Since we now fallback to the local TZ, we don't add
the TZ difference to UTC twice.
2022-10-21 23:32:46 +02:00
Mario Cormier
89617ff99b
Adding Event.Type(...) method to log the type of an object (#437)
Co-authored-by: Mario Cormier <mcormier@rossvideo.com>
2022-10-18 02:51:54 +02:00
Yuki Furuyama
55aaf043cf
Show original level message if it's not predefined value. (#476) 2022-09-18 16:53:56 +02:00
Donald Nguyen
315967f32d
Avoid race in diode.Close with waiter (#481) 2022-09-15 10:15:42 +02:00
Michael Nikitochkin
e218d18951
Update example of usage CallerMarshalFunc (#475)
There was changes in https://github.com/rs/zerolog/pull/457  pass program counter to CallerMarshalFunc.
Update example in README.md
2022-09-09 23:18:08 +02:00
Uros Marolt
c2b9d0e2de
CLI utility to pipe JSON logs through to pretty print and colorize them (#449) 2022-09-03 04:06:22 -07:00
Martin Rauscher
d894f123bc
pass program counter to CallerMarshalFunc (#457) 2022-07-29 15:29:02 +01:00
Mitar
4099072c03
Support extra arbitrary data at the end of console log (#416) 2022-07-18 23:00:50 +01:00
lazarenkovegor
4c85986254
Unixnano time format support (#454)
Co-authored-by: Лазаренков Егор Алексеевич <ealazarenkov@sberbank.ru>
2022-07-18 17:25:16 +01:00
dependabot[bot]
43be301386
Bump actions/cache from 3.0.1 to 3.0.5 (#453) 2022-07-18 10:41:56 +01:00
Olivier Poitrey
afdf9978ec Revert "remove fields written into "PartsOrder" (#383)"
This reverts commit 2a13872817.
2022-07-16 21:17:58 +01:00
xsteadfastx
14d6629e41
hlog: adds ProtoHandler (#396) 2022-07-16 21:04:30 +01:00
Mitar
dbdec88d16
Use everywhere InterfaceMarshalFunc (#414) 2022-07-16 21:03:29 +01:00
Mitar
b30730fab2
Show local time in console (#415) 2022-07-16 21:02:45 +01:00
dependabot[bot]
68a6bd49b5
Bump github.com/rs/xid from 1.3.0 to 1.4.0 (#430)
Bumps [github.com/rs/xid](https://github.com/rs/xid) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/rs/xid/releases)
- [Commits](https://github.com/rs/xid/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/rs/xid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-16 20:59:27 +01:00
dependabot[bot]
5c08a2724f
Bump actions/cache from 2 to 3.0.1 (#432)
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3.0.1)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-16 20:58:58 +01:00
Aniruddha Maru
60f57432ed
Implement encoding.TextUnmarshaler and encoding.TextMarshaler for Level (#440) 2022-07-16 20:51:01 +01:00
Adam Horacek
2a13872817
remove fields written into "PartsOrder" (#383) 2022-06-29 01:36:09 +02:00
Robson Roberto Souza Peixoto
a4ec5e4cdd
typo: using https to access cbor.io (#439) 2022-05-05 19:17:37 +02:00
32 changed files with 569 additions and 112 deletions

View File

@ -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') }}

View File

@ -4,7 +4,6 @@ opinionated defaults on zerolog
# Zero Allocation JSON Logger # Zero Allocation JSON Logger
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/tuxpa.in/a/zlog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/zlog/master/LICENSE) [![Build Status](https://travis-ci.org/rs/zlog.svg?branch=master)](https://travis-ci.org/rs/zlog) [![Coverage](http://gocover.io/_badge/tuxpa.in/a/zlog)](http://gocover.io/tuxpa.in/a/zlog) [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/tuxpa.in/a/zlog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/zlog/master/LICENSE) [![Build Status](https://travis-ci.org/rs/zlog.svg?branch=master)](https://travis-ci.org/rs/zlog) [![Coverage](http://gocover.io/_badge/tuxpa.in/a/zlog)](http://gocover.io/tuxpa.in/a/zlog)
@ -31,7 +30,7 @@ Find out [who uses zlog](https://tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add you
* [Sampling](#log-sampling) * [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)
@ -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,6 +517,30 @@ 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 `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`.
@ -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 .

View File

@ -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)

40
cmd/prettylog/README.md Normal file
View 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
```

View 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)
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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
View File

@ -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
} }

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -1,3 +1,4 @@
//go:build !binary_log
// +build !binary_log // +build !binary_log
package zlog package zlog
@ -6,6 +7,7 @@ package zlog
// JSON encoded byte stream. // JSON encoded byte stream.
import ( import (
"encoding/base64"
"tuxpa.in/a/zlog/internal/json" "tuxpa.in/a/zlog/internal/json"
) )
@ -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)

View File

@ -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
} }

View File

@ -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

7
go.mod
View File

@ -3,9 +3,10 @@ module tuxpa.in/a/zlog
go 1.15 go 1.15
require ( require (
github.com/coreos/go-systemd/v22 v22.4.0 github.com/coreos/go-systemd/v22 v22.5.0
github.com/mattn/go-colorable v0.1.13 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.4.0 github.com/rs/xid v1.5.0
github.com/rs/zerolog v1.28.0
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
) )

21
go.sum
View File

@ -1,14 +1,19 @@
github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/zWskzfGU= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.4.0/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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -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.

View File

@ -1,3 +1,4 @@
//go:build go1.7
// +build go1.7 // +build go1.7
package hlog package hlog
@ -200,6 +201,22 @@ func TestCustomHeaderHandler(t *testing.T) {
} }
} }
func TestProtoHandler(t *testing.T) {
out := &bytes.Buffer{}
r := &http.Request{
Proto: "test",
}
h := ProtoHandler("proto")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := FromRequest(r)
l.Log().Msg("")
}))
h = NewHandler(zlog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"proto":"test"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
func TestCombinedHandlers(t *testing.T) { func TestCombinedHandlers(t *testing.T) {
out := &bytes.Buffer{} out := &bytes.Buffer{}
r := &http.Request{ r := &http.Request{

View File

@ -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

View File

@ -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:

View File

@ -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...)
}

View File

@ -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)

View File

@ -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, ']')

View File

@ -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 {

View File

@ -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

View File

@ -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 {

25
log.go
View File

@ -104,6 +104,7 @@ import (
"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)
@ -307,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
} }

View File

@ -8,15 +8,17 @@ import (
"os" "os"
"time" "time"
"github.com/rs/zerolog"
"tuxpa.in/a/zlog" "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.StampMicro 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 {

View File

@ -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)
}
})
}
}

View File

@ -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))

View File

@ -4,6 +4,7 @@ package pkgerrors
import ( import (
"bytes" "bytes"
"fmt"
"regexp" "regexp"
"testing" "testing"
@ -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)
} }