/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package flogging import ( "go.uber.org/zap/zapcore" ) type Encoding int8 const ( CONSOLE = iota JSON LOGFMT ) // EncodingSelector is used to determine whether log records are // encoded as JSON or in human readable CONSOLE or LOGFMT formats. type EncodingSelector interface { Encoding() Encoding } // Core is a custom implementation of a zapcore.Core. It's a terrible hack that // only exists to work around the intersection of state associated with // encoders, implementation hiding in zapcore, and implicit, ad-hoc logger // initialization within fabric. // // In addition to encoding log entries and fields to a buffer, zap Encoder // implementations also need to maintain field state. When zapcore.Core.With is // used, the associated encoder is cloned and the fields are added to the // encoder. This means that encoder instances cannot be shared across cores. // // In terms of implementation hiding, it's difficult for our FormatEncoder to // cleanly wrap the JSON and console implementations from zap as all methods // from the zapcore.ObjectEncoder would need to be implemented to delegate to // the correct backend. // // This implementation works by associating multiple encoders with a core. When // fields are added to the core, the fields are added to all of the encoder // implementations. The core also references the logging configuration to // determine the proper encoding to use, the writer to delegate to, and the // enabled levels. type Core struct { zapcore.LevelEnabler Levels *LoggerLevels Encoders map[Encoding]zapcore.Encoder Selector EncodingSelector Output zapcore.WriteSyncer Observer Observer } //go:generate counterfeiter -o mock/observer.go -fake-name Observer . Observer type Observer interface { Check(e zapcore.Entry, ce *zapcore.CheckedEntry) WriteEntry(e zapcore.Entry, fields []zapcore.Field) } func (c *Core) With(fields []zapcore.Field) zapcore.Core { clones := map[Encoding]zapcore.Encoder{} for name, enc := range c.Encoders { clone := enc.Clone() addFields(clone, fields) clones[name] = clone } return &Core{ LevelEnabler: c.LevelEnabler, Levels: c.Levels, Encoders: clones, Selector: c.Selector, Output: c.Output, Observer: c.Observer, } } func (c *Core) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { if c.Observer != nil { c.Observer.Check(e, ce) } if c.Enabled(e.Level) && c.Levels.Level(e.LoggerName).Enabled(e.Level) { return ce.AddCore(e, c) } return ce } func (c *Core) Write(e zapcore.Entry, fields []zapcore.Field) error { encoding := c.Selector.Encoding() enc := c.Encoders[encoding] buf, err := enc.EncodeEntry(e, fields) if err != nil { return err } _, err = c.Output.Write(buf.Bytes()) buf.Free() if err != nil { return err } if e.Level >= zapcore.PanicLevel { c.Sync() } if c.Observer != nil { c.Observer.WriteEntry(e, fields) } return nil } func (c *Core) Sync() error { return c.Output.Sync() } func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) { for i := range fields { fields[i].AddTo(enc) } }