caching/vendor/github.com/blendle/zapdriver/core.go

244 lines
6.1 KiB
Go

package zapdriver
import (
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// driverConfig is used to configure core.
type driverConfig struct {
// Report all logs with level error or above to stackdriver using
// `ErrorReport()` when set to true
ReportAllErrors bool
// ServiceName is added as `ServiceContext()` to all logs when set
ServiceName string
}
// Core is a zapdriver specific core wrapped around the default zap core. It
// allows to merge all defined labels
type core struct {
zapcore.Core
// permLabels is a collection of labels that have been added to the logger
// through the use of `With()`. These labels should never be cleared after
// logging a single entry, unlike `tempLabel`.
permLabels *labels
// tempLabels keeps a record of all the labels that need to be applied to the
// current log entry. Zap serializes log fields at different parts of the
// stack, one such location is when calling `core.With` and the other one is
// when calling `core.Write`. This makes it impossible to (for example) take
// all `labels.xxx` fields, and wrap them in the `labels` namespace in one go.
//
// Instead, we have to filter out these labels at both locations, and then add
// them back in the proper format right before we call `Write` on the original
// Zap core.
tempLabels *labels
// Configuration for the zapdriver core
config driverConfig
}
// zapdriver core option to report all logs with level error or above to stackdriver
// using `ErrorReport()` when set to true
func ReportAllErrors(report bool) func(*core) {
return func(c *core) {
c.config.ReportAllErrors = report
}
}
// zapdriver core option to add `ServiceContext()` to all logs with `name` as
// service name
func ServiceName(name string) func(*core) {
return func(c *core) {
c.config.ServiceName = name
}
}
// WrapCore returns a `zap.Option` that wraps the default core with the
// zapdriver one.
func WrapCore(options ...func(*core)) zap.Option {
return zap.WrapCore(func(c zapcore.Core) zapcore.Core {
newcore := &core{
Core: c,
permLabels: newLabels(),
tempLabels: newLabels(),
}
for _, option := range options {
option(newcore)
}
return newcore
})
}
// With adds structured context to the Core.
func (c *core) With(fields []zap.Field) zapcore.Core {
var lbls *labels
lbls, fields = c.extractLabels(fields)
lbls.mutex.RLock()
c.permLabels.mutex.Lock()
for k, v := range lbls.store {
c.permLabels.store[k] = v
}
c.permLabels.mutex.Unlock()
lbls.mutex.RUnlock()
return &core{
Core: c.Core.With(fields),
permLabels: c.permLabels,
tempLabels: newLabels(),
config: c.config,
}
}
// Check determines whether the supplied Entry should be logged (using the
// embedded LevelEnabler and possibly some extra logic). If the entry
// should be logged, the Core adds itself to the CheckedEntry and returns
// the result.
//
// Callers must use Check before calling Write.
func (c *core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if c.Enabled(ent.Level) {
return ce.AddCore(ent, c)
}
return ce
}
func (c *core) Write(ent zapcore.Entry, fields []zapcore.Field) error {
var lbls *labels
lbls, fields = c.extractLabels(fields)
lbls.mutex.RLock()
c.tempLabels.mutex.Lock()
for k, v := range lbls.store {
c.tempLabels.store[k] = v
}
c.tempLabels.mutex.Unlock()
lbls.mutex.RUnlock()
fields = append(fields, labelsField(c.allLabels()))
fields = c.withSourceLocation(ent, fields)
if c.config.ServiceName != "" {
fields = c.withServiceContext(c.config.ServiceName, fields)
}
if c.config.ReportAllErrors && zapcore.ErrorLevel.Enabled(ent.Level) {
fields = c.withErrorReport(ent, fields)
if c.config.ServiceName == "" {
// A service name was not set but error report needs it
// So attempt to add a generic service name
fields = c.withServiceContext("unknown", fields)
}
}
c.tempLabels.reset()
return c.Core.Write(ent, fields)
}
// Sync flushes buffered logs (if any).
func (c *core) Sync() error {
return c.Core.Sync()
}
func (c *core) allLabels() *labels {
lbls := newLabels()
lbls.mutex.Lock()
c.permLabels.mutex.RLock()
for k, v := range c.permLabels.store {
lbls.store[k] = v
}
c.permLabels.mutex.RUnlock()
c.tempLabels.mutex.RLock()
for k, v := range c.tempLabels.store {
lbls.store[k] = v
}
c.tempLabels.mutex.RUnlock()
lbls.mutex.Unlock()
return lbls
}
func (c *core) extractLabels(fields []zapcore.Field) (*labels, []zapcore.Field) {
lbls := newLabels()
out := []zapcore.Field{}
lbls.mutex.Lock()
for i := range fields {
if !isLabelField(fields[i]) {
out = append(out, fields[i])
continue
}
lbls.store[strings.Replace(fields[i].Key, "labels.", "", 1)] = fields[i].String
}
lbls.mutex.Unlock()
return lbls, out
}
func (c *core) withLabels(fields []zapcore.Field) []zapcore.Field {
lbls := newLabels()
out := []zapcore.Field{}
lbls.mutex.Lock()
for i := range fields {
if isLabelField(fields[i]) {
lbls.store[strings.Replace(fields[i].Key, "labels.", "", 1)] = fields[i].String
continue
}
out = append(out, fields[i])
}
lbls.mutex.Unlock()
return append(out, labelsField(lbls))
}
func (c *core) withSourceLocation(ent zapcore.Entry, fields []zapcore.Field) []zapcore.Field {
// If the source location was manually set, don't overwrite it
for i := range fields {
if fields[i].Key == sourceKey {
return fields
}
}
if !ent.Caller.Defined {
return fields
}
return append(fields, SourceLocation(ent.Caller.PC, ent.Caller.File, ent.Caller.Line, true))
}
func (c *core) withServiceContext(name string, fields []zapcore.Field) []zapcore.Field {
// If the service context was manually set, don't overwrite it
for i := range fields {
if fields[i].Key == serviceContextKey {
return fields
}
}
return append(fields, ServiceContext(name))
}
func (c *core) withErrorReport(ent zapcore.Entry, fields []zapcore.Field) []zapcore.Field {
// If the error report was manually set, don't overwrite it
for i := range fields {
if fields[i].Key == contextKey {
return fields
}
}
if !ent.Caller.Defined {
return fields
}
return append(fields, ErrorReport(ent.Caller.PC, ent.Caller.File, ent.Caller.Line, true))
}