forked from TrueCloudLab/zapjournald
Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
67c21299a6 | |||
cb2e66427d | |||
045b966ecb | |||
2b6d84de9a | |||
f358e67c81 | |||
8d45f23fcd | |||
d6ea4d0bbf | |||
8c4cf38d6a | |||
49c42ba807 | |||
b9d389933b | |||
a6eb2f6261 |
12 changed files with 616 additions and 68 deletions
50
.gitignore
vendored
Normal file
50
.gitignore
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Vendoring
|
||||
vendor
|
||||
|
||||
# tempfiles
|
||||
.DS_Store
|
||||
*~
|
||||
.cache
|
||||
|
||||
temp
|
||||
tmp
|
||||
|
||||
# binary
|
||||
bin/
|
||||
release/
|
||||
|
||||
# coverage
|
||||
coverage.txt
|
||||
coverage.html
|
||||
|
||||
# testing
|
||||
cmd/test
|
||||
/plugins/
|
||||
testfile
|
||||
|
||||
# misc
|
||||
|
||||
# debhelpers
|
||||
debian/*debhelper*
|
||||
|
||||
# logfiles
|
||||
debian/*.log
|
||||
|
||||
# .substvars
|
||||
debian/*.substvars
|
||||
|
||||
# .bash-completion
|
||||
debian/*.bash-completion
|
||||
|
||||
# Install folders and files
|
||||
debian/frostfs-cli/
|
||||
debian/frostfs-ir/
|
||||
debian/files
|
||||
debian/frostfs-storage/
|
||||
debian/changelog
|
||||
man/
|
||||
debs/
|
11
.gitlint
Normal file
11
.gitlint
Normal file
|
@ -0,0 +1,11 @@
|
|||
[general]
|
||||
fail-without-commits=True
|
||||
regex-style-search=True
|
||||
contrib=CC1
|
||||
|
||||
[title-match-regex]
|
||||
regex=^\[\#[0-9Xx]+\]\s
|
||||
|
||||
[ignore-by-title]
|
||||
regex=^Release(.*)
|
||||
ignore=title-match-regex
|
|
@ -4,10 +4,10 @@
|
|||
# options for analysis running
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 5m
|
||||
timeout: 10m
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
tests: false
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
|
@ -24,6 +24,13 @@ linters-settings:
|
|||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
staticcheck:
|
||||
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||
funlen:
|
||||
lines: 80 # default 60
|
||||
statements: 60 # default 40
|
||||
gocognit:
|
||||
min-complexity: 40 # default 30
|
||||
|
||||
linters:
|
||||
enable:
|
||||
|
@ -34,23 +41,26 @@ linters:
|
|||
# some default golangci-lint linters
|
||||
- errcheck
|
||||
- gosimple
|
||||
- godot
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
# extra linters
|
||||
- bidichk
|
||||
- durationcheck
|
||||
- exhaustive
|
||||
- godot
|
||||
- exportloopref
|
||||
- gofmt
|
||||
- whitespace
|
||||
- goimports
|
||||
- misspell
|
||||
- predeclared
|
||||
- reassign
|
||||
- whitespace
|
||||
- containedctx
|
||||
- funlen
|
||||
- gocognit
|
||||
- contextcheck
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0002 # should have a comment
|
||||
- EXC0003 # test/Test ... consider calling this
|
||||
- EXC0004 # govet
|
||||
- EXC0005 # C-style breaks
|
45
.pre-commit-config.yaml
Normal file
45
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
ci:
|
||||
autofix_prs: false
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: v0.19.1
|
||||
hooks:
|
||||
- id: gitlint
|
||||
stages: [commit-msg]
|
||||
- id: gitlint-ci
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- id: end-of-file-fixer
|
||||
exclude: ".key$"
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.9.0.2
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.51.2
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: go-unit-tests
|
||||
name: go unit tests
|
||||
entry: make test
|
||||
pass_filenames: false
|
||||
types: [go]
|
||||
language: system
|
65
benchmark_test.go
Normal file
65
benchmark_test.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package zapjournald
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ssgreg/journald"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type nopSync struct{}
|
||||
|
||||
func (nopSync) Write([]byte) (int, error) { return 0, nil }
|
||||
func (nopSync) Sync() error { return nil }
|
||||
|
||||
func BenchmarkLogger(b *testing.B) {
|
||||
zc := zap.NewProductionConfig()
|
||||
zc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
zc.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||
|
||||
b.Run("standard", func(b *testing.B) {
|
||||
encoder := zapcore.NewJSONEncoder(zc.EncoderConfig)
|
||||
core := zapcore.NewCore(encoder, nopSync{}, zc.Level)
|
||||
coreWithContext := core.With([]zapcore.Field{
|
||||
SyslogFacility(LogDaemon),
|
||||
SyslogIdentifier(),
|
||||
SyslogPid()})
|
||||
l := zap.New(coreWithContext)
|
||||
benchmarkLog(b, l)
|
||||
})
|
||||
b.Run("journald", func(b *testing.B) {
|
||||
encoder := zapcore.NewJSONEncoder(zc.EncoderConfig)
|
||||
core := NewCore(zc.Level, encoder, &journald.Journal{}, SyslogFields)
|
||||
core.j.TestModeEnabled = true // Disable actual writing to the journal.
|
||||
|
||||
coreWithContext := core.With([]zapcore.Field{
|
||||
SyslogFacility(LogDaemon),
|
||||
SyslogIdentifier(),
|
||||
SyslogPid(),
|
||||
})
|
||||
l := zap.New(coreWithContext)
|
||||
benchmarkLog(b, l)
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkLog(b *testing.B, l *zap.Logger) {
|
||||
b.Run("no fields", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Info("Simple log message")
|
||||
}
|
||||
})
|
||||
b.Run("application fields", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Info("Simple log message", zap.Uint32("count", 123), zap.String("details", "nothing"))
|
||||
}
|
||||
})
|
||||
b.Run("journald fields", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Info("Simple log message", SyslogIdentifier())
|
||||
}
|
||||
})
|
||||
}
|
70
encoder.go
Normal file
70
encoder.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package zapjournald
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap/buffer"
|
||||
)
|
||||
|
||||
var pool = buffer.NewPool()
|
||||
|
||||
func encodeJournaldField(buf *buffer.Buffer, key string, value any) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
writeField(buf, key, v)
|
||||
case []byte:
|
||||
writeFieldBytes(buf, key, v)
|
||||
default:
|
||||
writeField(buf, key, fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
|
||||
func writeFieldBytes(buf *buffer.Buffer, name string, value []byte) {
|
||||
buf.Write([]byte(name))
|
||||
if bytes.ContainsRune(value, '\n') {
|
||||
// According to the format, if the value includes a newline
|
||||
// need to write the field name, plus a newline, then the
|
||||
// size (64bit LE), the field data and a final newline.
|
||||
|
||||
buf.Write([]byte{'\n'})
|
||||
appendUint64Binary(buf, uint64(len(value)))
|
||||
} else {
|
||||
buf.Write([]byte{'='})
|
||||
}
|
||||
buf.Write(value)
|
||||
buf.Write([]byte{'\n'})
|
||||
}
|
||||
|
||||
func writeField(buf *buffer.Buffer, name string, value string) {
|
||||
buf.Write([]byte(name))
|
||||
if strings.ContainsRune(value, '\n') {
|
||||
// According to the format, if the value includes a newline
|
||||
// need to write the field name, plus a newline, then the
|
||||
// size (64bit LE), the field data and a final newline.
|
||||
|
||||
buf.Write([]byte{'\n'})
|
||||
// 1 allocation here.
|
||||
// binary.Write(w, binary.LittleEndian, uint64(len(value)))
|
||||
appendUint64Binary(buf, uint64(len(value)))
|
||||
} else {
|
||||
buf.Write([]byte{'='})
|
||||
}
|
||||
buf.WriteString(value)
|
||||
buf.Write([]byte{'\n'})
|
||||
}
|
||||
|
||||
func appendUint64Binary(buf *buffer.Buffer, v uint64) {
|
||||
// Copied from https://github.com/golang/go/blob/go1.21.3/src/encoding/binary/binary.go#L119
|
||||
buf.Write([]byte{
|
||||
byte(v),
|
||||
byte(v >> 8),
|
||||
byte(v >> 16),
|
||||
byte(v >> 24),
|
||||
byte(v >> 32),
|
||||
byte(v >> 40),
|
||||
byte(v >> 48),
|
||||
byte(v >> 56),
|
||||
})
|
||||
}
|
7
go.mod
7
go.mod
|
@ -4,16 +4,15 @@ go 1.19
|
|||
|
||||
require (
|
||||
github.com/ssgreg/journald v1.0.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
go.uber.org/zap v1.24.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
19
go.sum
19
go.sum
|
@ -1,25 +1,22 @@
|
|||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/ssgreg/journald v1.0.0 h1:0YmTDPJXxcWDPba12qNMdO6TxvfkFSYpFIJ31CwmLcU=
|
||||
github.com/ssgreg/journald v1.0.0/go.mod h1:RUckwmTM8ghGWPslq2+ZBZzbb9/2KgjzYZ4JEP+oRt0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
|
|
214
partial_encoder.go
Normal file
214
partial_encoder.go
Normal file
|
@ -0,0 +1,214 @@
|
|||
package zapjournald
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type partialEncoder struct {
|
||||
wrap zapcore.Encoder
|
||||
ignore map[string]struct{}
|
||||
}
|
||||
|
||||
// NewPartialEncoder wraps existing encoder to avoid output of some provided
|
||||
// fields. The main use case is to ignore SyslogFields that leak into
|
||||
// ConsoleEncoder and provide no additional info for the human.
|
||||
func NewPartialEncoder(enc zapcore.Encoder, ignore []string) zapcore.Encoder {
|
||||
m := make(map[string]struct{}, len(ignore))
|
||||
for _, i := range ignore {
|
||||
m[i] = struct{}{}
|
||||
}
|
||||
return partialEncoder{
|
||||
wrap: enc,
|
||||
ignore: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return nil
|
||||
}
|
||||
return enc.wrap.AddArray(key, marshaler)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return nil
|
||||
}
|
||||
return enc.wrap.AddObject(key, marshaler)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddBinary(key string, value []byte) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddBinary(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddByteString(key string, value []byte) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddByteString(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddBool(key string, value bool) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddBool(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddComplex128(key string, value complex128) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddComplex128(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddComplex64(key string, value complex64) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddComplex64(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddDuration(key string, value time.Duration) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddDuration(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddFloat64(key string, value float64) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddFloat64(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddFloat32(key string, value float32) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddFloat32(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddInt(key string, value int) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddInt(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddInt64(key string, value int64) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddInt64(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddInt32(key string, value int32) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddInt32(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddInt16(key string, value int16) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddInt16(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddInt8(key string, value int8) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddInt8(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddString(key, value string) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddString(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddTime(key string, value time.Time) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddTime(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddUint(key string, value uint) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddUint(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddUint64(key string, value uint64) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddUint64(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddUint32(key string, value uint32) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddUint32(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddUint16(key string, value uint16) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddUint16(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddUint8(key string, value uint8) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddUint8(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddUintptr(key string, value uintptr) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.AddUintptr(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) AddReflected(key string, value interface{}) error {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return nil
|
||||
}
|
||||
return enc.wrap.AddReflected(key, value)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) OpenNamespace(key string) {
|
||||
if _, ok := enc.ignore[key]; ok {
|
||||
return
|
||||
}
|
||||
enc.wrap.OpenNamespace(key)
|
||||
}
|
||||
|
||||
func (enc partialEncoder) Clone() zapcore.Encoder {
|
||||
return partialEncoder{
|
||||
wrap: enc.wrap.Clone(),
|
||||
ignore: maps.Clone(enc.ignore),
|
||||
}
|
||||
}
|
||||
|
||||
func (enc partialEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||
return enc.wrap.EncodeEntry(entry, fields)
|
||||
}
|
63
partial_encoder_bench_test.go
Normal file
63
partial_encoder_bench_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package zapjournald
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ssgreg/journald"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func BenchmarkEncoder(b *testing.B) {
|
||||
zc := zap.NewProductionConfig()
|
||||
zc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
zc.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||
|
||||
b.Run("console", func(b *testing.B) {
|
||||
encoder := zapcore.NewConsoleEncoder(zc.EncoderConfig)
|
||||
core := NewCore(zc.Level, encoder, &journald.Journal{}, SyslogFields)
|
||||
core.j.TestModeEnabled = true // Disable actual writing to the journal.
|
||||
|
||||
coreWithContext := core.With([]zapcore.Field{
|
||||
SyslogFacility(LogDaemon),
|
||||
SyslogIdentifier(),
|
||||
SyslogPid(),
|
||||
})
|
||||
l := zap.New(coreWithContext)
|
||||
benchmarkEncoder(b, l)
|
||||
})
|
||||
b.Run("partial", func(b *testing.B) {
|
||||
encoder := NewPartialEncoder(zapcore.NewConsoleEncoder(zc.EncoderConfig), SyslogFields)
|
||||
core := NewCore(zc.Level, encoder, &journald.Journal{}, SyslogFields)
|
||||
core.j.TestModeEnabled = true // Disable actual writing to the journal.
|
||||
|
||||
coreWithContext := core.With([]zapcore.Field{
|
||||
SyslogFacility(LogDaemon),
|
||||
SyslogIdentifier(),
|
||||
SyslogPid(),
|
||||
})
|
||||
l := zap.New(coreWithContext)
|
||||
benchmarkEncoder(b, l)
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkEncoder(b *testing.B, l *zap.Logger) {
|
||||
b.Run("no fields", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Info("Simple log message")
|
||||
}
|
||||
})
|
||||
b.Run("application fields", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Info("Simple log message", zap.Uint32("count", 123), zap.String("details", "nothing"))
|
||||
}
|
||||
})
|
||||
b.Run("journald fields", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Info("Simple log message", SyslogIdentifier())
|
||||
}
|
||||
})
|
||||
}
|
108
zapjournald.go
108
zapjournald.go
|
@ -2,6 +2,9 @@ package zapjournald
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ssgreg/journald"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
@ -65,47 +68,38 @@ func (core *Core) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zap
|
|||
// If called, Write should always log the Entry and Fields; it should not
|
||||
// replicate the logic of Check.
|
||||
func (core *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
prio, err := zapLevelToJournald(entry.Level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := pool.Get()
|
||||
defer b.Free()
|
||||
|
||||
writeField(b, "PRIORITY", strconv.Itoa(int(prio)))
|
||||
|
||||
if len(core.contextStructuredFields) != 0 {
|
||||
for k, v := range core.contextStructuredFields {
|
||||
encodeJournaldField(b, k, v)
|
||||
}
|
||||
for _, field := range fields {
|
||||
if _, isJournalField := core.storedFieldNames[field.Key]; isJournalField {
|
||||
encodeJournaldField(b, field.Key, getFieldValue(field))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the message.
|
||||
buffer, err := core.encoder.EncodeEntry(entry, fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode log entry: %w", err)
|
||||
}
|
||||
defer buffer.Free()
|
||||
|
||||
message := buffer.String()
|
||||
|
||||
structuredFields := maps.Clone(core.contextStructuredFields)
|
||||
for _, field := range fields {
|
||||
if _, isJournalField := core.storedFieldNames[field.Key]; isJournalField {
|
||||
structuredFields[field.Key] = getFieldValue(field)
|
||||
}
|
||||
}
|
||||
writeFieldBytes(b, "MESSAGE", buffer.Bytes())
|
||||
|
||||
// Write the message.
|
||||
switch entry.Level {
|
||||
case zapcore.DebugLevel:
|
||||
return core.j.Send(message, journald.PriorityDebug, structuredFields)
|
||||
|
||||
case zapcore.InfoLevel:
|
||||
return core.j.Send(message, journald.PriorityInfo, structuredFields)
|
||||
|
||||
case zapcore.WarnLevel:
|
||||
return core.j.Send(message, journald.PriorityWarning, structuredFields)
|
||||
|
||||
case zapcore.ErrorLevel:
|
||||
return core.j.Send(message, journald.PriorityErr, structuredFields)
|
||||
|
||||
case zapcore.DPanicLevel:
|
||||
return core.j.Send(message, journald.PriorityCrit, structuredFields)
|
||||
|
||||
case zapcore.PanicLevel:
|
||||
return core.j.Send(message, journald.PriorityCrit, structuredFields)
|
||||
|
||||
case zapcore.FatalLevel:
|
||||
return core.j.Send(message, journald.PriorityCrit, structuredFields)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown log level: %v", entry.Level)
|
||||
}
|
||||
return core.j.WriteMsg(b.Bytes())
|
||||
}
|
||||
|
||||
// Sync flushes buffered logs (not used).
|
||||
|
@ -141,20 +135,29 @@ func getFieldValue(f zapcore.Field) interface{} {
|
|||
zapcore.ErrorType,
|
||||
zapcore.SkipType:
|
||||
return f.Interface
|
||||
case zapcore.DurationType,
|
||||
zapcore.Float64Type,
|
||||
zapcore.Float32Type,
|
||||
zapcore.Int64Type,
|
||||
case zapcore.DurationType:
|
||||
return time.Duration(f.Integer).String()
|
||||
case zapcore.Float64Type:
|
||||
// See https://github.com/uber-go/zap/blob/v1.26.0/buffer/buffer.go#L79
|
||||
f := math.Float64frombits(uint64(f.Integer))
|
||||
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||
case zapcore.Float32Type:
|
||||
f := math.Float32frombits(uint32(f.Integer))
|
||||
return strconv.FormatFloat(float64(f), 'f', -1, 32)
|
||||
case zapcore.Int64Type,
|
||||
zapcore.Int32Type,
|
||||
zapcore.Int16Type,
|
||||
zapcore.Int8Type,
|
||||
zapcore.Int8Type:
|
||||
return strconv.FormatInt(f.Integer, 10)
|
||||
case
|
||||
zapcore.Uint64Type,
|
||||
zapcore.Uint32Type,
|
||||
zapcore.Uint16Type,
|
||||
zapcore.Uint8Type,
|
||||
zapcore.UintptrType,
|
||||
zapcore.BoolType:
|
||||
return f.Integer
|
||||
zapcore.UintptrType:
|
||||
return strconv.FormatUint(uint64(f.Integer), 10)
|
||||
case zapcore.BoolType:
|
||||
return strconv.FormatBool(f.Integer == 1)
|
||||
case zapcore.StringType:
|
||||
return f.String
|
||||
case zapcore.TimeType:
|
||||
|
@ -162,8 +165,29 @@ func getFieldValue(f zapcore.Field) interface{} {
|
|||
// for example: zap.Time("k", time.Unix(100900, 0).In(time.UTC)) - will produce: "100900000000000 UTC" (result in nanoseconds)
|
||||
return fmt.Sprintf("%d %v", f.Integer, f.Interface)
|
||||
}
|
||||
return f.Integer
|
||||
return strconv.FormatUint(uint64(f.Integer), 10)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown field type: %v", f))
|
||||
}
|
||||
}
|
||||
|
||||
func zapLevelToJournald(l zapcore.Level) (journald.Priority, error) {
|
||||
switch l {
|
||||
case zapcore.DebugLevel:
|
||||
return journald.PriorityDebug, nil
|
||||
case zapcore.InfoLevel:
|
||||
return journald.PriorityInfo, nil
|
||||
case zapcore.WarnLevel:
|
||||
return journald.PriorityWarning, nil
|
||||
case zapcore.ErrorLevel:
|
||||
return journald.PriorityErr, nil
|
||||
case zapcore.DPanicLevel:
|
||||
return journald.PriorityCrit, nil
|
||||
case zapcore.PanicLevel:
|
||||
return journald.PriorityCrit, nil
|
||||
case zapcore.FatalLevel:
|
||||
return journald.PriorityCrit, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown log level: %v", l)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue