Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a ZapMapLogger for logging without duplicate entries #94

Merged
merged 32 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
636d04a
Add ZapMap logger which stores params in a map to avoid duplicate fields
smoorpal Jun 25, 2020
55943ea
add an assertion to json tests that the trimmed logEntry is equal to …
smoorpal Jun 25, 2020
74a9ff8
add benchmarks to service logs
smoorpal Jun 25, 2020
7945d96
add benchmark changes
smoorpal Jun 26, 2020
8df1900
restructure to avoid inconsistencies in testing
smoorpal Jun 26, 2020
59dbf36
use truncate to avoidn regularly allocating the buffer
smoorpal Jun 26, 2020
a37c0a7
use pointers for the map implementation
smoorpal Jun 30, 2020
25bd69c
use Reset() instead of Truncate(0)
smoorpal Jul 7, 2020
eb47c08
revert go.mod to avoid pulling in additional vendor files and ready PR.
smoorpal Jul 7, 2020
cb23af3
deprecated comments for zerolog
smoorpal Jul 7, 2020
98d4be3
remove previous implementation of parameters and make Zap Map impleme…
smoorpal Jul 7, 2020
31ce52d
fix check in test
smoorpal Jul 7, 2020
cb2129f
manually track output log levels and always use zerolog.Log in order …
smoorpal Jul 8, 2020
d5e91ac
fix regex to work with forks
smoorpal Jul 8, 2020
d182bf4
do not test for duplicate safe params since this is effectively progr…
smoorpal Jul 8, 2020
3ea8f20
remove comments that have been resolved
smoorpal Jul 8, 2020
3359016
format
smoorpal Jul 8, 2020
0e52c46
fix check
smoorpal Jul 8, 2020
566a68e
add changelog
smoorpal Jul 8, 2020
23cf081
remove deprecated
smoorpal Jul 8, 2020
1676f0e
remove unnecessary additions to tests
smoorpal Jul 8, 2020
7c9eaa6
remove erroneous
smoorpal Jul 8, 2020
6ec6cb5
use a more specific regex
smoorpal Jul 8, 2020
c0b2cb9
check that later safe params overwrite original safe params with context
smoorpal Jul 8, 2020
765c123
expand the contect_test to test for overwritten safe params from the …
smoorpal Jul 8, 2020
d9ede8e
implement string map and anymap values in zerolog since parameters ar…
smoorpal Jul 8, 2020
96badca
remove specialized benchmark
smoorpal Jul 8, 2020
7e50fa6
better context in error
smoorpal Jul 8, 2020
fb50648
use map[string]struct{} instead of int
smoorpal Jul 8, 2020
705d29f
golint
smoorpal Jul 8, 2020
ab8ea66
add comments explaining specific implementation of AnyMapValues and S…
smoorpal Jul 9, 2020
ccf8a1a
update comment
smoorpal Jul 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-94.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: improvement
improvement:
description: modify wlog-zap and wlog-zerolog to avoid printing duplicate fields in the base object. Duplicate fields results in ambiguous behavior when unmarshalling and can break certain json unmarshaler implementations
links:
- https://github.com/palantir/witchcraft-go-logging/pull/94
38 changes: 27 additions & 11 deletions wlog-zap/internal/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ import (
)

type zapLogEntry struct {
fields []zapcore.Field
fields map[string]*zapcore.Field
wlog.MapValueEntries
}

func newZapLogEntry() *zapLogEntry {
return &zapLogEntry{
fields: make(map[string]*zapcore.Field),
}
}

func (e *zapLogEntry) StringValue(key, value string) {
e.fields = append(e.fields, zap.String(key, value))
s := zap.String(key, value)
e.fields[key] = &s
}

func (e *zapLogEntry) OptionalStringValue(key, value string) {
Expand All @@ -43,29 +50,38 @@ func (e *zapLogEntry) OptionalStringValue(key, value string) {

func (e *zapLogEntry) StringListValue(k string, v []string) {
if len(v) > 0 {
e.fields = append(e.fields, zap.Strings(k, v))
s := zap.Strings(k, v)
e.fields[k] = &s
}
}

func (e *zapLogEntry) SafeLongValue(key string, value int64) {
e.fields = append(e.fields, zap.Int64(key, value))
s := zap.Int64(key, value)
e.fields[key] = &s
}

func (e *zapLogEntry) IntValue(key string, value int32) {
e.fields = append(e.fields, zap.Int32(key, value))
s := zap.Int32(key, value)
e.fields[key] = &s
}

func (e *zapLogEntry) ObjectValue(k string, v interface{}, marshalerType reflect.Type) {
if field, ok := marshalers.FieldForType(marshalerType, k, v); ok {
e.fields = append(e.fields, field)
e.fields[k] = &field
} else {
e.fields = append(e.fields, zap.Reflect(k, v))
s := zap.Reflect(k, v)
e.fields[k] = &s
}
}

func (e *zapLogEntry) Fields() []zapcore.Field {
fields := e.fields
for key, values := range e.StringMapValues() {
stringMapValues := e.StringMapValues()
anyMapValues := e.AnyMapValues()
fields := make([]zapcore.Field, 0, len(e.fields)+len(stringMapValues)+len(anyMapValues))
for _, field := range e.fields {
fields = append(fields, *field)
}
for key, values := range stringMapValues {
key := key
values := values
fields = append(fields, zap.Object(key, zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error {
Expand All @@ -80,7 +96,7 @@ func (e *zapLogEntry) Fields() []zapcore.Field {
return nil
})))
}
for key, values := range e.AnyMapValues() {
for key, values := range anyMapValues {
key := key
values := values
fields = append(fields, zap.Object(key, zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error {
Expand Down Expand Up @@ -130,7 +146,7 @@ func (l *zapLogger) SetLevel(level wlog.LogLevel) {
}

func logOutput(logFn func(string, ...zap.Field), msg string, params []wlog.Param) {
entry := &zapLogEntry{}
entry := newZapLogEntry()
wlog.ApplyParams(entry, params)
logFn(msg, entry.Fields()...)
}
Expand Down
125 changes: 118 additions & 7 deletions wlog-zerolog/internal/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,24 @@ import (
)

type zeroLogEntry struct {
evt *zerolog.Event
wlog.MapValueEntries
evt *zerolog.Event
keys map[string]struct{}
stringMapValues map[string]map[string]string
anyMapValues map[string]map[string]interface{}
}

func (e *zeroLogEntry) keyExists(key string) bool {
if _, exists := e.keys[key]; exists {
return true
}
e.keys[key] = struct{}{}
return false
}

func (e *zeroLogEntry) StringValue(key, value string) {
if e.keyExists(key) {
return
}
e.evt = e.evt.Str(key, value)
}

Expand All @@ -41,25 +54,91 @@ func (e *zeroLogEntry) OptionalStringValue(key, value string) {

func (e *zeroLogEntry) StringListValue(k string, v []string) {
if len(v) > 0 {
if e.keyExists(k) {
return
}
e.evt.Strs(k, v)
}
}

func (e *zeroLogEntry) SafeLongValue(key string, value int64) {
if e.keyExists(key) {
return
}
e.evt = e.evt.Int64(key, value)
}

func (e *zeroLogEntry) IntValue(key string, value int32) {
if e.keyExists(key) {
return
}
e.evt = e.evt.Int32(key, value)
}

func (e *zeroLogEntry) ObjectValue(k string, v interface{}, marshalerType reflect.Type) {
if e.keyExists(k) {
return
}
ok := marshalers.EncodeType(e.evt, marshalerType, k, v)
if !ok {
e.evt.Interface(k, v)
}
}

//StringMapValue adds or merges the strings in values
//Since wlog overrides duplicates with a preference for the last parameter
//The parameters should not replace an existing key because parameters are passed to zerolog in reverse
//This differs from the default wlog StringMapValue since parameters are not reversed
func (e *zeroLogEntry) StringMapValue(key string, values map[string]string) {
if len(values) == 0 {
return
}
if e.stringMapValues == nil {
e.stringMapValues = make(map[string]map[string]string)
}
entryMapVals, ok := e.stringMapValues[key]
if !ok {
entryMapVals = make(map[string]string)
e.stringMapValues[key] = entryMapVals
}
for k, v := range values {
if _, exists := entryMapVals[k]; !exists {
entryMapVals[k] = v
}
}
}

//AnyMapValue adds or merges the values in values
//Since wlog overrides duplicates with a preference for the last parameter
//The parameters should not replace an existing key because parameters are passed to zerolog in reverse
//This differs from the default wlog AnyMapValue since parameters are not reversed
func (e *zeroLogEntry) AnyMapValue(key string, values map[string]interface{}) {
if len(values) == 0 {
return
}
if e.anyMapValues == nil {
e.anyMapValues = make(map[string]map[string]interface{})
}
entryMapVals, ok := e.anyMapValues[key]
if !ok {
entryMapVals = make(map[string]interface{})
e.anyMapValues[key] = entryMapVals
}
for k, v := range values {
if _, exists := entryMapVals[k]; !exists {
entryMapVals[k] = v
}
}
}

func (e *zeroLogEntry) StringMapValues() map[string]map[string]string {
return e.stringMapValues
}

func (e *zeroLogEntry) AnyMapValues() map[string]map[string]interface{} {
return e.anyMapValues
}

func (e *zeroLogEntry) Evt() *zerolog.Event {
evt := e.evt
for key, values := range e.StringMapValues() {
Expand All @@ -85,35 +164,66 @@ func (e *zeroLogEntry) Evt() *zerolog.Event {

type zeroLogger struct {
logger zerolog.Logger
level zerolog.Level
}

func (l *zeroLogger) should(level zerolog.Level) bool {
if level < l.level {
return false
}
return true
}

func (l *zeroLogger) Log(params ...wlog.Param) {
if !l.should(zerolog.NoLevel) {
return
}
logOutput(l.logger.Log, "", "", params)
}

func (l *zeroLogger) Debug(msg string, params ...wlog.Param) {
logOutput(l.logger.Debug, msg, svc1log.LevelDebugValue, params)
if !l.should(zerolog.DebugLevel) {
return
}
logOutput(l.logger.Log, msg, svc1log.LevelDebugValue, params)
}

func (l *zeroLogger) Info(msg string, params ...wlog.Param) {
logOutput(l.logger.Info, msg, svc1log.LevelInfoValue, params)
if !l.should(zerolog.InfoLevel) {
return
}
logOutput(l.logger.Log, msg, svc1log.LevelInfoValue, params)
}

func (l *zeroLogger) Warn(msg string, params ...wlog.Param) {
logOutput(l.logger.Warn, msg, svc1log.LevelWarnValue, params)
if !l.should(zerolog.WarnLevel) {
return
}
logOutput(l.logger.Log, msg, svc1log.LevelWarnValue, params)
}

func (l *zeroLogger) Error(msg string, params ...wlog.Param) {
logOutput(l.logger.Error, msg, svc1log.LevelErrorValue, params)
if !l.should(zerolog.ErrorLevel) {
return
}
logOutput(l.logger.Log, msg, svc1log.LevelErrorValue, params)
}

func (l *zeroLogger) SetLevel(level wlog.LogLevel) {
l.level = toZeroLevel(level)
l.logger = l.logger.Level(toZeroLevel(level))
}

func reverseParams(params []wlog.Param) {
for i, j := 0, len(params)-1; i < j; i, j = i+1, j-1 {
params[i], params[j] = params[j], params[i]
}
}

func logOutput(newEvt func() *zerolog.Event, msg, levelVal string, params []wlog.Param) {
entry := &zeroLogEntry{
evt: newEvt(),
evt: newEvt(),
keys: make(map[string]struct{}),
}
if !entry.evt.Enabled() {
return
Expand All @@ -122,6 +232,7 @@ func logOutput(newEvt func() *zerolog.Event, msg, levelVal string, params []wlog
if levelVal != "" {
entry.evt = entry.evt.Str(svc1log.LevelKey, levelVal)
}
reverseParams(params)
wlog.ApplyParams(entry, params)
entry.Evt().Msg(msg)
}
1 change: 1 addition & 0 deletions wlog-zerolog/internal/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func (lp *loggerProvider) NewLogger(w io.Writer) wlog.Logger {
func (lp *loggerProvider) NewLeveledLogger(w io.Writer, level wlog.LogLevel) wlog.LeveledLogger {
return &zeroLogger{
logger: newZeroLogger(w, level),
level: toZeroLevel(level),
}
}

Expand Down
4 changes: 2 additions & 2 deletions wlog/context_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func testFromContextFromEmptyContextForSingleLogger(t *testing.T, tmpDir string,
logOutput := string(bytes)

firstPortionRegexp := regexp.MustCompile(
regexp.QuoteMeta(`[WARNING] github.com/palantir/witchcraft-go-logging/wlog_test.TestOutputFromContextEmptyContext`) + ".+" + regexp.QuoteMeta(`/github.com/palantir/witchcraft-go-logging/wlog/context_default_test.go:`) + "[0-9]+" + regexp.QuoteMeta(`]: usage of `+loggerTestCaseInfo.loggerPkg+`.Logger from FromContext that did not have that logger set: `))
regexp.QuoteMeta(`[WARNING]`) + ".*" + regexp.QuoteMeta(`github.com/`) + "[^/]+" + regexp.QuoteMeta(`/witchcraft-go-logging/wlog_test.TestOutputFromContextEmptyContext`) + ".+" + regexp.QuoteMeta(`/github.com/`) + "[^/]+" + regexp.QuoteMeta(`/witchcraft-go-logging/wlog/context_default_test.go:`) + "[0-9]+" + regexp.QuoteMeta(`]: usage of `+loggerTestCaseInfo.loggerPkg+`.Logger from FromContext that did not have that logger set: `))
loc := firstPortionRegexp.FindStringIndex(logOutput)
require.NotNil(t, loc, "Unexpected log output: %s", logOutput)

Expand All @@ -196,7 +196,7 @@ func testFromContextFromEmptyContextForSingleLogger(t *testing.T, tmpDir string,
logOutput := string(bytes)

firstPortionRegexp := regexp.MustCompile(
regexp.QuoteMeta(`[WARNING] github.com/palantir/witchcraft-go-logging/wlog_test.TestOutputFromContextEmptyContext`) + ".+" + regexp.QuoteMeta(`/github.com/palantir/witchcraft-go-logging/wlog/context_default_test.go:`) + "[0-9]+" + regexp.QuoteMeta(`]: usage of `+loggerTestCaseInfo.loggerPkg+`.Logger from FromContext that did not have that logger set: `))
regexp.QuoteMeta(`[WARNING]`) + ".*" + regexp.QuoteMeta(`github.com/`) + "[^/]+" + regexp.QuoteMeta(`/witchcraft-go-logging/wlog_test.TestOutputFromContextEmptyContext`) + ".+" + regexp.QuoteMeta(`/github.com/`) + "[^/]+" + regexp.QuoteMeta(`/witchcraft-go-logging/wlog/context_default_test.go:`) + "[0-9]+" + regexp.QuoteMeta(`]: usage of `+loggerTestCaseInfo.loggerPkg+`.Logger from FromContext that did not have that logger set: `))
loc := firstPortionRegexp.FindStringIndex(logOutput)
require.NotNil(t, loc, "Unexpected log output: %s", logOutput)

Expand Down
Loading