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:
strategy:
matrix:
go-version: [1.15.x, 1.16.x]
go-version: [1.18.x, 1.19.x]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/cache@v2
uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

View File

@ -4,7 +4,6 @@ opinionated defaults on zerolog
# 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)
@ -31,7 +30,7 @@ Find out [who uses zlog](https://tuxpa.in/a/zlog/wiki/Who-uses-zlog) and add you
* [Sampling](#log-sampling)
* [Hooks](#hooks)
* [Contextual fields](#contextual-logging)
* `context.Context` integration
* [`context.Context` integration](#contextcontext-integration)
* [Integration with `net/http`](#integration-with-nethttp)
* [JSON and CBOR encoding formats](#binary-encoding)
* [Pretty logging for development](#pretty-logging)
@ -418,7 +417,7 @@ log.Info().Msg("hello world")
Equivalent of `Lshortfile`:
```go
zlog.CallerMarshalFunc = func(file string, line int) string {
zlog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
@ -506,7 +505,7 @@ log.Ctx(ctx).Info().Msg("hello world")
### Set as standard logger output
```go
log := zlog.New(os.Stdout).With().
stdlog := zlog.New(os.Stdout).With().
Str("foo", "bar").
Logger()
@ -518,6 +517,30 @@ stdlog.Print("hello world")
// Output: {"foo":"bar","message":"hello world"}
```
### context.Context integration
The `Logger` instance could be attached to `context.Context` values with `logger.WithContext(ctx)`
and extracted from it using `zerolog.Ctx(ctx)`.
Example to add logger to context:
```go
// this code attach logger instance to context fields
ctx := context.Background()
logger := zerolog.New(os.Stdout)
ctx = logger.WithContext(ctx)
someFunc(ctx)
```
Extracting logger from context:
```go
func someFunc(ctx context.Context) {
// get logger from context. if it's nill, then `zerolog.DefaultContextLogger` is returned,
// if `DefaultContextLogger` is nil, then disabled logger returned.
logger := zerolog.Ctx(ctx)
logger.Info().Msg("Hello")
}
```
### Integration with `net/http`
The `tuxpa.in/a/zlog/hlog` package provides some helpers to integrate zlog with `http.Handler`.
@ -632,7 +655,11 @@ Most fields are also available in the slice format (`Strs` for `[]string`, `Errs
## Binary Encoding
<<<<<<< HEAD
In addition to the default JSON encoding, `zlog` can produce binary logs using [CBOR](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows:
=======
In addition to the default JSON encoding, `zerolog` can produce binary logs using [CBOR](https://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows:
>>>>>>> github
```bash
go build -tags binary_log .

View File

@ -57,7 +57,7 @@ func (a *Array) write(dst []byte) []byte {
}
// Object marshals an object that implement the LogObjectMarshaler
// interface and append append it to the array.
// interface and appends it to the array.
func (a *Array) Object(obj LogObjectMarshaler) *Array {
e := Dict()
obj.MarshalZerologObject(e)
@ -67,19 +67,19 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array {
return a
}
// Str append append the val as a string to the array.
// Str appends the val as a string to the array.
func (a *Array) Str(val string) *Array {
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val)
return a
}
// Bytes append append the val as a string to the array.
// Bytes appends the val as a string to the array.
func (a *Array) Bytes(val []byte) *Array {
a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val)
return a
}
// Hex append append the val as a hex string to the array.
// Hex appends the val as a hex string to the array.
func (a *Array) Hex(val []byte) *Array {
a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val)
return a
@ -115,97 +115,97 @@ func (a *Array) Err(err error) *Array {
return a
}
// Bool append append the val as a bool to the array.
// Bool appends the val as a bool to the array.
func (a *Array) Bool(b bool) *Array {
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b)
return a
}
// Int append append i as a int to the array.
// Int appends i as a int to the array.
func (a *Array) Int(i int) *Array {
a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i)
return a
}
// Int8 append append i as a int8 to the array.
// Int8 appends i as a int8 to the array.
func (a *Array) Int8(i int8) *Array {
a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i)
return a
}
// Int16 append append i as a int16 to the array.
// Int16 appends i as a int16 to the array.
func (a *Array) Int16(i int16) *Array {
a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i)
return a
}
// Int32 append append i as a int32 to the array.
// Int32 appends i as a int32 to the array.
func (a *Array) Int32(i int32) *Array {
a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i)
return a
}
// Int64 append append i as a int64 to the array.
// Int64 appends i as a int64 to the array.
func (a *Array) Int64(i int64) *Array {
a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i)
return a
}
// Uint append append i as a uint to the array.
// Uint appends i as a uint to the array.
func (a *Array) Uint(i uint) *Array {
a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i)
return a
}
// Uint8 append append i as a uint8 to the array.
// Uint8 appends i as a uint8 to the array.
func (a *Array) Uint8(i uint8) *Array {
a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i)
return a
}
// Uint16 append append i as a uint16 to the array.
// Uint16 appends i as a uint16 to the array.
func (a *Array) Uint16(i uint16) *Array {
a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i)
return a
}
// Uint32 append append i as a uint32 to the array.
// Uint32 appends i as a uint32 to the array.
func (a *Array) Uint32(i uint32) *Array {
a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i)
return a
}
// Uint64 append append i as a uint64 to the array.
// Uint64 appends i as a uint64 to the array.
func (a *Array) Uint64(i uint64) *Array {
a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i)
return a
}
// Float32 append append f as a float32 to the array.
// Float32 appends f as a float32 to the array.
func (a *Array) Float32(f float32) *Array {
a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f)
return a
}
// Float64 append append f as a float64 to the array.
// Float64 appends f as a float64 to the array.
func (a *Array) Float64(f float64) *Array {
a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f)
return a
}
// Time append append t formatted as string using zlog.TimeFieldFormat.
// Time appends t formatted as string using zlog.TimeFieldFormat.
func (a *Array) Time(t time.Time) *Array {
a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat)
return a
}
// Dur append append d to the array.
// Dur appends d to the array.
func (a *Array) Dur(d time.Duration) *Array {
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger)
return a
}
// Interface append append i marshaled using reflection.
// Interface appends i marshaled using reflection.
func (a *Array) Interface(i interface{}) *Array {
if obj, ok := i.(LogObjectMarshaler); ok {
return a.Object(obj)

40
cmd/prettylog/README.md Normal file
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
FormatErrFieldName Formatter
FormatErrFieldValue Formatter
FormatExtra func(map[string]interface{}, *bytes.Buffer) error
}
// NewConsoleWriter creates and initializes a new ConsoleWriter.
@ -128,10 +130,18 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
w.writeFields(evt, buf)
if w.FormatExtra != nil {
err = w.FormatExtra(evt, buf)
if err != nil {
return n, err
}
}
err = buf.WriteByte('\n')
if err != nil {
return n, err
}
_, err = buf.WriteTo(w.Out)
return len(p), err
}
@ -221,7 +231,7 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
case json.Number:
buf.WriteString(fv(fValue))
default:
b, err := json.Marshal(fValue)
b, err := InterfaceMarshalFunc(fValue)
if err != nil {
fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
} else {
@ -327,27 +337,31 @@ func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
t := "<nil>"
switch tt := i.(type) {
case string:
ts, err := time.Parse(TimeFieldFormat, tt)
ts, err := time.ParseInLocation(TimeFieldFormat, tt, time.Local)
if err != nil {
t = tt
} else {
t = ts.Format(timeFormat)
t = ts.Local().Format(timeFormat)
}
case json.Number:
i, err := tt.Int64()
if err != nil {
t = tt.String()
} else {
var sec, nsec int64 = i, 0
var sec, nsec int64
switch TimeFieldFormat {
case TimeFormatUnixMs:
nsec = int64(time.Duration(i) * time.Millisecond)
sec = 0
case TimeFormatUnixNano:
sec, nsec = 0, i
case TimeFormatUnixMicro:
nsec = int64(time.Duration(i) * time.Microsecond)
sec = 0
sec, nsec = 0, int64(time.Duration(i)*time.Microsecond)
case TimeFormatUnixMs:
sec, nsec = 0, int64(time.Duration(i)*time.Millisecond)
default:
sec, nsec = i, 0
}
ts := time.Unix(sec, nsec).UTC()
ts := time.Unix(sec, nsec)
t = ts.Format(timeFormat)
}
}
@ -375,7 +389,7 @@ func consoleDefaultFormatLevel(noColor bool) Formatter {
case LevelPanicValue:
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
default:
l = colorize("???", colorBold, noColor)
l = colorize(ll, colorBold, noColor)
}
} else {
if i == nil {

View File

@ -108,13 +108,14 @@ func TestConsoleWriter(t *testing.T) {
buf := &bytes.Buffer{}
w := zlog.ConsoleWriter{Out: buf, NoColor: true}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
ts := time.Unix(0, 0)
d := ts.UTC().Format(time.RFC3339)
_, err := w.Write([]byte(`{"time": "` + d + `", "level": "debug", "message": "Foobar", "foo": "bar"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM DBG Foobar foo=bar\n"
expectedOutput := ts.Format(time.Kitchen) + " DBG Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -136,7 +137,7 @@ func TestConsoleWriter(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "Jan 1 00:20:34.000 DBG Foobar foo=bar\n"
expectedOutput := time.Unix(1234, 0).Format(time.StampMilli) + " DBG Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -158,7 +159,7 @@ func TestConsoleWriter(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "Jan 1 00:20:34.567 DBG Foobar foo=bar\n"
expectedOutput := time.Unix(1234, 567000000).Format(time.StampMilli) + " DBG Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -180,7 +181,7 @@ func TestConsoleWriter(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "Jan 1 00:20:34.567891 DBG Foobar foo=bar\n"
expectedOutput := time.Unix(1234, 567891000).Format(time.StampMicro) + " DBG Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -239,7 +240,8 @@ func TestConsoleWriter(t *testing.T) {
buf := &bytes.Buffer{}
w := zlog.ConsoleWriter{Out: buf, NoColor: true}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
ts := time.Unix(0, 0)
d := ts.UTC().Format(time.RFC3339)
evt := `{"time": "` + d + `", "level": "error", "message": "Foobar", "aaa": "bbb", "error": "Error"}`
// t.Log(evt)
@ -248,7 +250,7 @@ func TestConsoleWriter(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM ERR Foobar error=Error aaa=bbb\n"
expectedOutput := ts.Format(time.Kitchen) + " ERR Foobar error=Error aaa=bbb\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -264,7 +266,8 @@ func TestConsoleWriter(t *testing.T) {
t.Fatalf("Cannot get working directory: %s", err)
}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
ts := time.Unix(0, 0)
d := ts.UTC().Format(time.RFC3339)
evt := `{"time": "` + d + `", "level": "debug", "message": "Foobar", "foo": "bar", "caller": "` + cwd + `/foo/bar.go"}`
// t.Log(evt)
@ -273,7 +276,7 @@ func TestConsoleWriter(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM DBG foo/bar.go > Foobar foo=bar\n"
expectedOutput := ts.Format(time.Kitchen) + " DBG foo/bar.go > Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -305,7 +308,8 @@ func TestConsoleWriterConfiguration(t *testing.T) {
buf := &bytes.Buffer{}
w := zlog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: time.RFC3339}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
ts := time.Unix(0, 0)
d := ts.UTC().Format(time.RFC3339)
evt := `{"time": "` + d + `", "level": "info", "message": "Foobar"}`
_, err := w.Write([]byte(evt))
@ -313,7 +317,7 @@ func TestConsoleWriterConfiguration(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "1970-01-01T00:00:00Z INF Foobar\n"
expectedOutput := ts.Format(time.RFC3339) + " INF Foobar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@ -371,6 +375,55 @@ func TestConsoleWriterConfiguration(t *testing.T) {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Sets FormatExtra", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zlog.ConsoleWriter{
Out: buf, NoColor: true, PartsOrder: []string{"level", "message"},
FormatExtra: func(evt map[string]interface{}, buf *bytes.Buffer) error {
buf.WriteString("\nAdditional stacktrace")
return nil
},
}
evt := `{"level": "info", "message": "Foobar"}`
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "INF Foobar\nAdditional stacktrace\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Uses local time for console writer without time zone", func(t *testing.T) {
// Regression test for issue #483 (check there for more details)
timeFormat := "2006-01-02 15:04:05"
expectedOutput := "2022-10-20 20:24:50 INF Foobar\n"
evt := `{"time": "2022-10-20 20:24:50", "level": "info", "message": "Foobar"}`
of := zlog.TimeFieldFormat
defer func() {
zlog.TimeFieldFormat = of
}()
zlog.TimeFieldFormat = timeFormat
buf := &bytes.Buffer{}
w := zlog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: timeFormat}
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
}
func BenchmarkConsoleWriter(b *testing.B) {

View File

@ -329,8 +329,9 @@ func (ts timestampHook) Run(e *Event, level Level, msg string) {
var th = timestampHook{}
// Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key.
// Timestamp adds the current local time to the logger context with the "time" key, formatted using zlog.TimeFieldFormat.
// To customize the key name, change zlog.TimestampFieldName.
// To customize the time format, change zlog.TimeFieldFormat.
//
// NOTE: It won't dedupe the "time" key if the *Context has one already.
func (c Context) Timestamp() Context {

19
ctx.go
View File

@ -14,10 +14,15 @@ func init() {
type ctxKey struct{}
// WithContext returns a copy of ctx with l associated. If an instance of Logger
// is already in the context, the context is not updated.
// WithContext returns a copy of ctx with the receiver attached. The Logger
// attached to the provided Context (if any) will not be effected. If the
// receiver's log level is Disabled it will only be attached to the returned
// Context if the provided Context has a previously attached Logger. If the
// provided Context has no attached Logger, a Disabled Logger will not be
// attached.
//
// For instance, to add a field to an existing logger in the context, use this
// Note: to modify the existing Logger attached to a Context (instead of
// replacing it in a new Context), use UpdateContext with the following
// notation:
//
// ctx := r.Context()
@ -25,13 +30,9 @@ type ctxKey struct{}
// l.UpdateContext(func(c Context) Context {
// return c.Str("bar", "baz")
// })
//
func (l Logger) WithContext(ctx context.Context) context.Context {
if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok {
if lp == &l {
// Do not store same logger.
return ctx
}
} else if l.level == Disabled {
if _, ok := ctx.Value(ctxKey{}).(*Logger); !ok && l.level == Disabled {
// Do not store disabled logger.
return ctx
}

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) {
log.SetOutput(ioutil.Discard)
defer log.SetOutput(os.Stderr)

View File

@ -39,7 +39,12 @@ func NewWaiter(d Diode, opts ...WaiterConfigOption) *Waiter {
go func() {
<-w.ctx.Done()
// Mutex is strictly necessary here to avoid a race in Next() (between
// w.isDone() and w.c.Wait()) and w.c.Broadcast() here.
w.mu.Lock()
w.c.Broadcast()
w.mu.Unlock()
}()
return w

View File

@ -24,6 +24,9 @@ func init() {
func appendJSON(dst []byte, j []byte) []byte {
return cbor.AppendEmbeddedJSON(dst, j)
}
func appendCBOR(dst []byte, c []byte) []byte {
return cbor.AppendEmbeddedCBOR(dst, c)
}
// decodeIfBinaryToString - converts a binary formatted log msg to a
// JSON formatted String Log message.

View File

@ -1,3 +1,4 @@
//go:build !binary_log
// +build !binary_log
package zlog
@ -6,6 +7,7 @@ package zlog
// JSON encoded byte stream.
import (
"encoding/base64"
"tuxpa.in/a/zlog/internal/json"
)
@ -25,6 +27,17 @@ func init() {
func appendJSON(dst []byte, j []byte) []byte {
return append(dst, j...)
}
func appendCBOR(dst []byte, cbor []byte) []byte {
dst = append(dst, []byte("\"data:application/cbor;base64,")...)
l := len(dst)
enc := base64.StdEncoding
n := enc.EncodedLen(len(cbor))
for i := 0; i < n; i++ {
dst = append(dst, '.')
}
enc.Encode(dst[l:], cbor)
return append(dst, '"')
}
func decodeIfBinaryToString(in []byte) string {
return string(in)

View File

@ -318,6 +318,18 @@ func (e *Event) RawJSON(key string, b []byte) *Event {
return e
}
// RawCBOR adds already encoded CBOR to the log line under key.
//
// No sanity check is performed on b
// Note: The full featureset of CBOR is supported as data will not be mapped to json but stored as data-url
func (e *Event) RawCBOR(key string, b []byte) *Event {
if e == nil {
return e
}
e.buf = appendCBOR(enc.AppendKey(e.buf, key), b)
return e
}
// AnErr adds the field key with serialized err to the *Event context.
// If err is nil, no field is added.
func (e *Event) AnErr(key string, err error) *Event {
@ -707,6 +719,11 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
return e
}
// Any is a wrapper around Event.Interface.
func (e *Event) Any(key string, i interface{}) *Event {
return e.Interface(key, i)
}
// Interface adds the field key with i marshaled using reflection.
func (e *Event) Interface(key string, i interface{}) *Event {
if e == nil {
@ -719,6 +736,15 @@ func (e *Event) Interface(key string, i interface{}) *Event {
return e
}
// Type adds the field key with val's type using reflection.
func (e *Event) Type(key string, val interface{}) *Event {
if e == nil {
return e
}
e.buf = enc.AppendType(enc.AppendKey(e.buf, key), val)
return e
}
// CallerSkipFrame instructs any future Caller calls to skip the specified number of frames.
// This includes those added via hooks from the context.
func (e *Event) CallerSkipFrame(skip int) *Event {
@ -744,11 +770,11 @@ func (e *Event) caller(skip int) *Event {
if e == nil {
return e
}
_, file, line, ok := runtime.Caller(skip + e.skipFrame)
pc, file, line, ok := runtime.Caller(skip + e.skipFrame)
if !ok {
return e
}
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(file, line))
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(pc, file, line))
return e
}

View File

@ -19,6 +19,10 @@ const (
// TimeFormatUnixMicro defines a time format that makes time fields to be
// serialized as Unix timestamp integers in microseconds.
TimeFormatUnixMicro = "UNIXMICRO"
// TimeFormatUnixNano defines a time format that makes time fields to be
// serialized as Unix timestamp integers in nanoseconds.
TimeFormatUnixNano = "UNIXNANO"
)
var (
@ -61,7 +65,7 @@ var (
CallerSkipFrameCount = 2
// CallerMarshalFunc allows customization of global caller marshaling
CallerMarshalFunc = func(file string, line int) string {
CallerMarshalFunc = func(pc uintptr, file string, line int) string {
return file + ":" + strconv.Itoa(line)
}
@ -81,7 +85,7 @@ var (
InterfaceMarshalFunc = json.Marshal
// TimeFieldFormat defines the time format of the Time field type. If set to
// TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as a UNIX
// TimeFormatUnix, TimeFormatUnixMs, TimeFormatUnixMicro or TimeFormatUnixNano, the time is formatted as a UNIX
// timestamp as integer.
TimeFieldFormat = time.RFC3339

7
go.mod
View File

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

21
go.sum
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.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

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{}
// IDFromRequest returns the unique id associated to the request if any.

View File

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

View File

@ -26,7 +26,8 @@ const (
additionalTypeBreak byte = 31
// 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
additionalTypeTagNetworkAddr uint16 = 260

View File

@ -5,6 +5,7 @@ package cbor
import (
"bufio"
"bytes"
"encoding/base64"
"fmt"
"io"
"math"
@ -213,6 +214,31 @@ func decodeString(src *bufio.Reader, noQuotes bool) []byte {
}
return append(result, '"')
}
func decodeStringToDataUrl(src *bufio.Reader, mimeType string) []byte {
pb := readByte(src)
major := pb & maskOutAdditionalType
minor := pb & maskOutMajorType
if major != majorTypeByteString {
panic(fmt.Errorf("Major type is: %d in decodeString", major))
}
length := decodeIntAdditionalType(src, minor)
l := int(length)
enc := base64.StdEncoding
lEnc := enc.EncodedLen(l)
result := make([]byte, len("\"data:;base64,\"")+len(mimeType)+lEnc)
dest := result
u := copy(dest, "\"data:")
dest = dest[u:]
u = copy(dest, mimeType)
dest = dest[u:]
u = copy(dest, ";base64,")
dest = dest[u:]
pbs := readNBytes(src, l)
enc.Encode(dest, pbs)
dest = dest[lEnc:]
dest[0] = '"'
return result
}
func decodeUTF8String(src *bufio.Reader) []byte {
pb := readByte(src)
@ -349,6 +375,20 @@ func decodeTagData(src *bufio.Reader) []byte {
switch minor {
case additionalTypeTimestamp:
return decodeTimeStamp(src)
case additionalTypeIntUint8:
val := decodeIntAdditionalType(src, minor)
switch byte(val) {
case additionalTypeEmbeddedCBOR:
pb := readByte(src)
dataMajor := pb & maskOutAdditionalType
if dataMajor != majorTypeByteString {
panic(fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedCBOR", dataMajor))
}
src.UnreadByte()
return decodeStringToDataUrl(src, "application/cbor")
default:
panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val))
}
// Tag value is larger than 256 (so uint16).
case additionalTypeIntUint16:

View File

@ -93,3 +93,25 @@ func AppendEmbeddedJSON(dst, s []byte) []byte {
}
return append(dst, s...)
}
// AppendEmbeddedCBOR adds a tag and embeds input CBOR as such.
func AppendEmbeddedCBOR(dst, s []byte) []byte {
major := majorTypeTags
minor := additionalTypeEmbeddedCBOR
// Append the TAG to indicate this is Embedded JSON.
dst = append(dst, major|additionalTypeIntUint8)
dst = append(dst, minor)
// Append the CBOR Object as Byte String.
major = majorTypeByteString
l := len(s)
if l <= additionalMax {
lb := byte(l)
dst = append(dst, major|lb)
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
return append(dst, s...)
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"net"
"reflect"
)
// AppendNil inserts a 'Nil' object into the dst byte array.
@ -438,6 +439,14 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
return AppendEmbeddedJSON(dst, marshaled)
}
// AppendType appends the parameter type (as a string) to the input byte slice.
func (e Encoder) AppendType(dst []byte, i interface{}) []byte {
if i == nil {
return e.AppendString(dst, "<nil>")
}
return e.AppendString(dst, reflect.TypeOf(i).String())
}
// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6).
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
dst = append(dst, majorTypeTags|additionalTypeIntUint16)

View File

@ -7,9 +7,10 @@ import (
const (
// Import from zlog/global.go
timeFormatUnix = ""
timeFormatUnixMs = "UNIXMS"
timeFormatUnix = ""
timeFormatUnixMs = "UNIXMS"
timeFormatUnixMicro = "UNIXMICRO"
timeFormatUnixNano = "UNIXNANO"
)
// AppendTime formats the input time with the given format
@ -22,6 +23,8 @@ func (e Encoder) AppendTime(dst []byte, t time.Time, format string) []byte {
return e.AppendInt64(dst, t.UnixNano()/1000000)
case timeFormatUnixMicro:
return e.AppendInt64(dst, t.UnixNano()/1000)
case timeFormatUnixNano:
return e.AppendInt64(dst, t.UnixNano())
}
return append(t.AppendFormat(append(dst, '"'), format), '"')
}
@ -33,7 +36,11 @@ func (Encoder) AppendTimes(dst []byte, vals []time.Time, format string) []byte {
case timeFormatUnix:
return appendUnixTimes(dst, vals)
case timeFormatUnixMs:
return appendUnixMsTimes(dst, vals)
return appendUnixNanoTimes(dst, vals, 1000000)
case timeFormatUnixMicro:
return appendUnixNanoTimes(dst, vals, 1000)
case timeFormatUnixNano:
return appendUnixNanoTimes(dst, vals, 1)
}
if len(vals) == 0 {
return append(dst, '[', ']')
@ -64,15 +71,15 @@ func appendUnixTimes(dst []byte, vals []time.Time) []byte {
return dst
}
func appendUnixMsTimes(dst []byte, vals []time.Time) []byte {
func appendUnixNanoTimes(dst []byte, vals []time.Time, div int64) []byte {
if len(vals) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
dst = strconv.AppendInt(dst, vals[0].UnixNano()/1000000, 10)
dst = strconv.AppendInt(dst, vals[0].UnixNano()/div, 10)
if len(vals) > 1 {
for _, t := range vals[1:] {
dst = strconv.AppendInt(append(dst, ','), t.UnixNano()/1000000, 10)
dst = strconv.AppendInt(append(dst, ','), t.UnixNano()/div, 10)
}
}
dst = append(dst, ']')

View File

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"net"
"reflect"
"strconv"
)
@ -369,6 +370,14 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
return append(dst, marshaled...)
}
// AppendType appends the parameter type (as a string) to the input byte slice.
func (e Encoder) AppendType(dst []byte, i interface{}) []byte {
if i == nil {
return e.AppendString(dst, "<nil>")
}
return e.AppendString(dst, reflect.TypeOf(i).String())
}
// AppendObjectData takes in an object that is already in a byte array
// and adds it to the dst.
func (Encoder) AppendObjectData(dst []byte, o []byte) []byte {

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) {
tests := []struct {
dst []byte

View File

@ -102,7 +102,7 @@ func (w journalWriter) Write(p []byte) (n int, err error) {
case json.Number:
args[jKey] = fmt.Sprint(value)
default:
b, err := json.Marshal(value)
b, err := zlog.InterfaceMarshalFunc(value)
if err != nil {
args[jKey] = fmt.Sprintf("[error: %v]", err)
} else {

25
log.go
View File

@ -104,6 +104,7 @@ import (
"io/ioutil"
"os"
"strconv"
"strings"
)
// Level defines log levels.
@ -159,24 +160,24 @@ func (l Level) String() string {
// ParseLevel converts a level string into a zlog Level value.
// returns an error if the input string does not match known values.
func ParseLevel(levelStr string) (Level, error) {
switch levelStr {
case LevelFieldMarshalFunc(TraceLevel):
switch {
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(TraceLevel)):
return TraceLevel, nil
case LevelFieldMarshalFunc(DebugLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(DebugLevel)):
return DebugLevel, nil
case LevelFieldMarshalFunc(InfoLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(InfoLevel)):
return InfoLevel, nil
case LevelFieldMarshalFunc(WarnLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(WarnLevel)):
return WarnLevel, nil
case LevelFieldMarshalFunc(ErrorLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(ErrorLevel)):
return ErrorLevel, nil
case LevelFieldMarshalFunc(FatalLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(FatalLevel)):
return FatalLevel, nil
case LevelFieldMarshalFunc(PanicLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(PanicLevel)):
return PanicLevel, nil
case LevelFieldMarshalFunc(Disabled):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(Disabled)):
return Disabled, nil
case LevelFieldMarshalFunc(NoLevel):
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(NoLevel)):
return NoLevel, nil
}
i, err := strconv.Atoi(levelStr)
@ -307,7 +308,9 @@ func (l Logger) Sample(s Sampler) Logger {
// Hook returns a logger with the h Hook.
func (l Logger) Hook(h Hook) Logger {
l.hooks = append(l.hooks, h)
newHooks := make([]Hook, len(l.hooks), len(l.hooks)+1)
copy(newHooks, l.hooks)
l.hooks = append(newHooks, h)
return l
}

View File

@ -8,15 +8,17 @@ import (
"os"
"time"
"github.com/rs/zerolog"
"tuxpa.in/a/zlog"
)
// Logger is the global logger.
var Logger = zlog.New(os.Stderr).With().Timestamp().Logger()
func init() {
zlog.TimeFieldFormat = time.StampMicro
}
var Logger = zlog.New(nil).Output(
zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: time.RFC3339,
},
).With().Timestamp().Logger()
// Output duplicates the global logger and sets w as its output.
func Output(w io.Writer) zlog.Logger {

View File

@ -320,6 +320,7 @@ func TestFields(t *testing.T) {
Bytes("bytes", []byte("bar")).
Hex("hex", []byte{0x12, 0xef}).
RawJSON("json", []byte(`{"some":"json"}`)).
RawCBOR("cbor", []byte{0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}).
Func(func(e *Event) { e.Str("func", "func_output") }).
AnErr("some_err", nil).
Err(errors.New("some error")).
@ -344,7 +345,7 @@ func TestFields(t *testing.T) {
Time("time", time.Time{}).
TimeDiff("diff", now, now.Add(-10*time.Second)).
Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"cbor":"data:application/cbor;base64,gwGCAgOCBAU=","func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -782,7 +783,7 @@ func TestCallerMarshalFunc(t *testing.T) {
// test default behaviour this is really brittle due to the line numbers
// actually mattering for validation
_, file, line, _ := runtime.Caller(0)
pc, file, line, _ := runtime.Caller(0)
caller := fmt.Sprintf("%s:%d", file, line+2)
log.Log().Caller().Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
@ -793,16 +794,16 @@ func TestCallerMarshalFunc(t *testing.T) {
// test custom behavior. In this case we'll take just the last directory
origCallerMarshalFunc := CallerMarshalFunc
defer func() { CallerMarshalFunc = origCallerMarshalFunc }()
CallerMarshalFunc = func(file string, line int) string {
CallerMarshalFunc = func(pc uintptr, file string, line int) string {
parts := strings.Split(file, "/")
if len(parts) > 1 {
return strings.Join(parts[len(parts)-2:], "/") + ":" + strconv.Itoa(line)
}
return file + ":" + strconv.Itoa(line)
return runtime.FuncForPC(pc).Name() + ":" + file + ":" + strconv.Itoa(line)
}
_, file, line, _ = runtime.Caller(0)
caller = CallerMarshalFunc(file, line+2)
pc, file, line, _ = runtime.Caller(0)
caller = CallerMarshalFunc(pc, file, line+2)
log.Log().Caller().Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
@ -907,6 +908,33 @@ func TestLevel_String(t *testing.T) {
}
}
func TestLevel_MarshalText(t *testing.T) {
tests := []struct {
name string
l Level
want string
}{
{"trace", TraceLevel, "trace"},
{"debug", DebugLevel, "debug"},
{"info", InfoLevel, "info"},
{"warn", WarnLevel, "warn"},
{"error", ErrorLevel, "error"},
{"fatal", FatalLevel, "fatal"},
{"panic", PanicLevel, "panic"},
{"disabled", Disabled, "disabled"},
{"nolevel", NoLevel, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, err := tt.l.MarshalText(); err != nil {
t.Errorf("MarshalText couldn't marshal: %v", tt.l)
} else if string(got) != tt.want {
t.Errorf("String() = %v, want %v", string(got), tt.want)
}
})
}
}
func TestParseLevel(t *testing.T) {
type args struct {
levelStr string
@ -943,3 +971,41 @@ func TestParseLevel(t *testing.T) {
})
}
}
func TestUnmarshalTextLevel(t *testing.T) {
type args struct {
levelStr string
}
tests := []struct {
name string
args args
want Level
wantErr bool
}{
{"trace", args{"trace"}, TraceLevel, false},
{"debug", args{"debug"}, DebugLevel, false},
{"info", args{"info"}, InfoLevel, false},
{"warn", args{"warn"}, WarnLevel, false},
{"error", args{"error"}, ErrorLevel, false},
{"fatal", args{"fatal"}, FatalLevel, false},
{"panic", args{"panic"}, PanicLevel, false},
{"disabled", args{"disabled"}, Disabled, false},
{"nolevel", args{""}, NoLevel, false},
{"-1", args{"-1"}, TraceLevel, false},
{"-2", args{"-2"}, Level(-2), false},
{"-3", args{"-3"}, Level(-3), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var l Level
err := l.UnmarshalText([]byte(tt.args.levelStr))
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
return
}
if l != tt.want {
t.Errorf("UnmarshalText() got = %v, want %v", l, tt.want)
}
})
}
}

View File

@ -42,15 +42,32 @@ func frameField(f errors.Frame, s *state, c rune) string {
// MarshalStack implements pkg/errors stack trace marshaling.
//
// zlog.ErrorStackMarshaler = MarshalStack
// zlog.ErrorStackMarshaler = MarshalStack
func MarshalStack(err error) interface{} {
type stackTracer interface {
StackTrace() errors.StackTrace
}
sterr, ok := err.(stackTracer)
if !ok {
var sterr stackTracer
var ok bool
for err != nil {
sterr, ok = err.(stackTracer)
if ok {
break
}
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
err = u.Unwrap()
}
if sterr == nil {
return nil
}
st := sterr.StackTrace()
s := &state{}
out := make([]map[string]string, 0, len(st))

View File

@ -4,6 +4,7 @@ package pkgerrors
import (
"bytes"
"fmt"
"regexp"
"testing"
@ -17,11 +18,11 @@ func TestLogStack(t *testing.T) {
out := &bytes.Buffer{}
log := zlog.New(out)
err := errors.Wrap(errors.New("error message"), "from error")
err := fmt.Errorf("from error: %w", errors.New("error message"))
log.Log().Stack().Err(err).Msg("")
got := out.String()
want := `\{"stack":\[\{"func":"TestLogStack","line":"20","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
want := `\{"stack":\[\{"func":"TestLogStack","line":"21","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
if ok, _ := regexp.MatchString(want, got); !ok {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
@ -33,11 +34,11 @@ func TestLogStackFromContext(t *testing.T) {
out := &bytes.Buffer{}
log := zlog.New(out).With().Stack().Logger() // calling Stack() on log context instead of event
err := errors.Wrap(errors.New("error message"), "from error")
err := fmt.Errorf("from error: %w", errors.New("error message"))
log.Log().Err(err).Msg("") // not explicitly calling Stack()
got := out.String()
want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"36","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"37","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
if ok, _ := regexp.MatchString(want, got); !ok {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}