Skip to content

Commit

Permalink
Added Hooks
Browse files Browse the repository at this point in the history
hooks allow intercepting and modifying the fields in each log entry.  Can be added globally at the factory level, or at the logger level.
  • Loading branch information
ansel1 committed Jul 30, 2021
1 parent 4ff7923 commit 18f5418
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 110 deletions.
52 changes: 42 additions & 10 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ linters-settings:
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
golint:
# minimal confidence for issues, default is 0.8
# min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
# simplify: true
Expand All @@ -119,11 +116,19 @@ linters-settings:
include-go-root: false
packages:
- github.com/magiconair/properties/assert
- gopkg.in/go-playground/assert.v1
- github.com/pborman/uuid #replace with github.com/google/uuid
inTests:
- github.com/davecgh/go-spew/spew
- github.com/stretchr/testify
gomodguard:
blocked:
modules:
- gopkg.in/go-playground/assert.v1:
recommendations:
- github.com/stretchr/testify
reason: "testify is the test assertion framework we use"
- github.com/pborman/uuid:
recommendations:
- github.com/google/uuid
misspell:
# Correct spellings using locale preferences for US or UK.
# Default is to use a neutral variety of English.
Expand Down Expand Up @@ -201,25 +206,44 @@ linters:
- depguard
## - dogsled # checks for too many blank identifiers. don't care
- dupl
- errorlint
# - exhaustive
# - exhaustivestruct
- exportloopref
## - funlen # checks function length. don't care
# - gci
## - gochecknoglobals # too common
- gochecknoinits
- gocognit
- goconst
- gocritic
## - gocyclo # checks cyclomatic complexity. don't care
# - godot
## - godox # checks for TODO comments. not standardized
- goerr113
## - gofmt # checks code is formatted, handled by make prep
# - gofumpt
# - goheader
## - goimports # checks import order. We're not using goimports
- golint
- revive
# - gomnd
- gomodguard
- goprintffuncname
- gosec
- interfacer
## - lll # checks line length. not enforced
## - maligned # optimizies struct field order, but structs are usually ordered for legibility
- misspell
- nakedret
- nestif
# - nlreturn # don't really like how this looks in all cases. wsl covers similar ground anyway.
- noctx
- nolintlint
# - prealloc # slice optimizations, but promotes too much premature optimization
- scopelint
- rowserrcheck
- exportloopref
- stylecheck
# - testpackage
- tparallel
- unconvert
## - unparam # too many false positives
## - whitespace # not enforced
Expand All @@ -235,8 +259,10 @@ issues:
# But independently from this option we use default exclude patterns,
# it can be disabled by `exclude-use-default: false`. To list all
# excluded by default patterns execute `golangci-lint run --help`
# exclude:
# - abcdef
exclude:
- Error return value of .(.*\.Write). is not checked
# we use merry errors a lot, and goerr113 doesn't recognize it as a valid sentinel error
- use wrapped static errors instead

# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
Expand All @@ -250,11 +276,17 @@ issues:
- scopelint
- gochecknoinits
- gochecknoglobals
- wsl
- goconst
- path: cmd
linters:
# init() functions are pretty common in main packages
- gochecknoinits
- gochecknoglobals
# exclude requiring comments on all exported stuff
- linters:
- revive
text: "exported:"

# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via "nolint" comments.
Expand Down
20 changes: 10 additions & 10 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,16 @@ var errExample = errors.New("fail")

func fakeFields() []interface{} {
return []interface{}{
//"int", 1,
//"int64",int64(2),
//"float", 3.0,
//"string", "four!",
//"bool", true,
//"time", time.Unix(0, 0),
//"error", errExample,
//"duration", time.Second,
//"user-defined type", _jane,
//"another string", "done!",
// "int", 1,
// "int64",int64(2),
// "float", 3.0,
// "string", "four!",
// "bool", true,
// "time", time.Unix(0, 0),
// "error", errExample,
// "duration", time.Second,
// "user-defined type", _jane,
// "another string", "done!",
zap.Int("int", 1),
zap.Int64("int64", 2),
zap.Float64("float", 3.0),
Expand Down
2 changes: 1 addition & 1 deletion console_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (c *consoleEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field)
// Add the message itself.
if c.MessageKey != "" {
c.colorReset(final.buf)
//c.colorBright(&final)
// c.colorBright(&final)
final.safeAddString(ent.Message, false)
// ensure a minimum of 2 spaces between the message and the fields,
// to improve readability
Expand Down
22 changes: 21 additions & 1 deletion core.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type innerCore struct {
zapcore.Core
addCaller bool
errorOutput zapcore.WriteSyncer
hooks []HookFunc
}

// Core is the concrete implementation of Logger. It has some additional
Expand All @@ -41,6 +42,8 @@ type Core struct {
*atomicInnerCore
context []zap.Field
callerSkip int
// these are logger-scoped hooks, which only hook into this particular logger
hooks []HookFunc
}

// Log is the core logging method, used by the convenience methods Debug(), Info(), and Error().
Expand Down Expand Up @@ -94,10 +97,27 @@ func (l *Core) log(lvl Level, template string, fmtArgs, context []interface{}) b
ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(l.callerSkip + callerSkipOffset))
if !ce.Entry.Caller.Defined {
_, _ = fmt.Fprintf(c.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC())
_ = ce.ErrorOutput.Sync()
}
}

ce.Write(append(l.context, l.sweetenFields(context)...)...)
fields := append(l.context, l.sweetenFields(context)...) //nolint:gocritic

// execute global hooks, which might modify the fields
for i := range c.hooks {
if f := c.hooks[i](ce, fields); f != nil {
fields = f
}
}

// execute logger hooks
for i := range l.hooks {
if f := l.hooks[i](ce, fields); f != nil {
fields = f
}
}

ce.Write(fields...)
return true
}

Expand Down
37 changes: 37 additions & 0 deletions factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Factory struct {
sync.Mutex

addCaller bool

hooks []HookFunc
}

// Encoder serializes log entries. Re-exported from zap for now to avoid exporting zap.
Expand Down Expand Up @@ -125,6 +127,7 @@ func (r *Factory) newInnerCore(name string, info *loggerInfo) *innerCore {
Core: zc,
addCaller: r.addCaller,
errorOutput: zapcore.AddSync(os.Stderr),
hooks: r.hooks,
}
}

Expand Down Expand Up @@ -167,6 +170,40 @@ func (r *Factory) SetDefaultLevel(l Level) {
r.defaultLevel.SetLevel(zapcore.Level(l))
}

type Entry = zapcore.Entry
type CheckedEntry = zapcore.CheckedEntry
type Field = zapcore.Field

// HookFunc adapts a single function to the Hook interface.
type HookFunc func(*CheckedEntry, []Field) []Field

// Hooks adds functions which are called before a log entry is encoded. The hook function
// is given the entry and the total set of fields to be logged. The set of fields which are
// returned are then logged. Hook functions can return a modified set of fields, or just return
// the unaltered fields.
//
// The Entry is not modified. It is purely informational.
//
// If a hook returns an error, that error is logged, but the in-flight log entry
// will proceed with the original set of fields.
//
// These global hooks will be injected into all loggers owned by this factory. They will
// execute before any hooks installed in individual loggers.
func (r *Factory) Hooks(hooks ...HookFunc) {
r.Lock()
defer r.Unlock()
r.hooks = append(r.hooks, hooks...)
r.refreshLoggers()
}

// ClearHooks removes all hooks.
func (r *Factory) ClearHooks() {
r.Lock()
defer r.Unlock()
r.hooks = nil
r.refreshLoggers()
}

func parseConfigString(s string) map[string]interface{} {
if s == "" {
return nil
Expand Down
15 changes: 8 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module github.com/gemalto/flume

require (
github.com/ansel1/merry v1.5.1
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/ansel1/merry v1.6.1
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/stretchr/testify v1.4.0
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.15.0
golang.org/x/sys v0.0.0-20200817085935-3ff754bf58a9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0
go.uber.org/zap v1.18.1
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)

go 1.14
Loading

0 comments on commit 18f5418

Please sign in to comment.