sequence

This paper mainly studies the ZAP Core of Golang

Core

[email protected] / zapcore/core. Go

type Core interface { LevelEnabler // With adds structured context to the Core. With([]Field) Core // 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. Check(Entry, *CheckedEntry) *CheckedEntry // Write serializes the Entry and any Fields supplied at the log site and // writes them to  their destination. // // If called, Write should always log the Entry and Fields; it should not // replicate the logic of Check. Write(Entry, []Field) error // Sync flushes buffered logs (if any). Sync() error }Copy the code

The LevelEnabler is embedded in the Core interface and defines the With, Check, Write, and Sync methods

LevelEnabler

[email protected] / zapcore/level. Go

type LevelEnabler interface {
	Enabled(Level) bool
}
Copy the code

LevelEnabler defines the Enabled method

nopCore

[email protected] / zapcore/core. Go

type nopCore struct{}

// NewNopCore returns a no-op Core.
func NewNopCore() Core                                        { return nopCore{} }
func (nopCore) Enabled(Level) bool                            { return false }
func (n nopCore) With([]Field) Core                           { return n }
func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce }
func (nopCore) Write(Entry, []Field) error                    { return nil }
func (nopCore) Sync() error                                   { return nil }
Copy the code

NopCore implements the Core interface and is null

ioCore

[email protected] / zapcore/core. Go

type ioCore struct { LevelEnabler enc Encoder out WriteSyncer } func (c *ioCore) With(fields []Field) Core { clone := c.clone() addFields(clone.enc, fields) return clone } func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { if c.Enabled(ent.Level) { return ce.AddCore(ent, c) } return ce } func (c *ioCore) Write(ent Entry, fields []Field) error { buf, err := c.enc.EncodeEntry(ent, fields) if err ! = nil { return err } _, err = c.out.Write(buf.Bytes()) buf.Free() if err ! = nil { return err } if ent.Level > ErrorLevel { // Since we may be crashing the program, sync the output. Ignore Sync // errors, pending a clean solution to issue #370. c.Sync() } return nil } func (c *ioCore) Sync() error { return c.out.Sync() } func (c *ioCore) clone() *ioCore { return &ioCore{ LevelEnabler: c.LevelEnabler, enc: c.enc.Clone(), out: c.out, } }Copy the code

The LevelEnabler is embedded in ioCore, which defines Encoder, WriteSyncer(out) properties and implements Core interface. The With method executes clone and addFields; The Check method checks c. ennabled (ent.Level). If true, ce.AddCore(ent, c) is executed. The Write method serializes the entry through EncodeEntry of encoder, and then writes bytes to WriteSyncer. If the entry level is greater than ErrorLevel, the Sync method will be executed. The Sync method executes out.sync; The clone method uses the original core to create a new ioCore

addFields

[email protected] / zapcore/field. Go

func addFields(enc ObjectEncoder, fields []Field) { for i := range fields { fields[i].AddTo(enc) } } func (f Field) AddTo(enc ObjectEncoder) { var err error switch f.Type { case ArrayMarshalerType: err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler)) case ObjectMarshalerType: err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler)) case BinaryType: enc.AddBinary(f.Key, f.Interface.([]byte)) case BoolType: enc.AddBool(f.Key, f.Integer == 1) case ByteStringType: enc.AddByteString(f.Key, f.Interface.([]byte)) case Complex128Type: enc.AddComplex128(f.Key, f.Interface.(complex128)) case Complex64Type: enc.AddComplex64(f.Key, f.Interface.(complex64)) case DurationType: enc.AddDuration(f.Key, time.Duration(f.Integer)) case Float64Type: enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer))) case Float32Type: enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer))) case Int64Type: enc.AddInt64(f.Key, f.Integer) case Int32Type: enc.AddInt32(f.Key, int32(f.Integer)) case Int16Type: enc.AddInt16(f.Key, int16(f.Integer)) case Int8Type: enc.AddInt8(f.Key, int8(f.Integer)) case StringType: enc.AddString(f.Key, f.String) case TimeType: if f.Interface ! = nil { enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location))) } else { // Fall back to UTC if location is nil. enc.AddTime(f.Key, time.Unix(0, f.Integer)) } case TimeFullType: enc.AddTime(f.Key, f.Interface.(time.Time)) case Uint64Type: enc.AddUint64(f.Key, uint64(f.Integer)) case Uint32Type: enc.AddUint32(f.Key, uint32(f.Integer)) case Uint16Type: enc.AddUint16(f.Key, uint16(f.Integer)) case Uint8Type: enc.AddUint8(f.Key, uint8(f.Integer)) case UintptrType: enc.AddUintptr(f.Key, uintptr(f.Integer)) case ReflectType: err = enc.AddReflected(f.Key, f.Interface) case NamespaceType: enc.OpenNamespace(f.Key) case StringerType: err = encodeStringer(f.Key, f.Interface, enc) case ErrorType: encodeError(f.Key, f.Interface.(error), enc) case SkipType: break default: panic(fmt.Sprintf("unknown field type: %v", f)) } if err ! = nil { enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error()) } }Copy the code

The addFields method traverses fields, executing the AddTo method of Field; The AddTo method executes ObjectEncoder’s corresponding method based on f. type

The instance

func newCoreDemo() { temp, err := ioutil.TempFile("", "zapcore-test-iocore") if err ! = nil { panic(err) } fmt.Println(temp.Name()) //defer os.Remove(temp.Name()) cfg := zapcore.EncoderConfig{ MessageKey: "msg", LevelKey: "level", NameKey: "name", TimeKey: "ts", CallerKey: "caller", FunctionKey: "func", StacktraceKey: "stacktrace", LineEnding: "\n", EncodeTime: zapcore.EpochTimeEncoder, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } cfg.TimeKey = "" core := zapcore.NewCore( zapcore.NewJSONEncoder(cfg), temp, zapcore.InfoLevel, ).With([]zapcore.Field{zapcore.Field{Type: zapcore.Int64Type, Integer: int64(1), Key: "k"}}) if ce := core.Check(zapcore.Entry{Level: zapcore.DebugLevel, Message: "debug"}, nil); ce ! = nil { ce.Write(zapcore.Field{Type: zapcore.Int64Type, Integer: int64(2), Key: "k"}) } if ce := core.Check(zapcore.Entry{Level: zapcore.InfoLevel, Message: "info"}, nil); ce ! = nil { ce.Write(zapcore.Field{Type: zapcore.Int64Type, Integer: int64(3), Key: "k"}) } if ce := core.Check(zapcore.Entry{Level: zapcore.WarnLevel, Message: "warn"}, nil); ce ! = nil { ce.Write(zapcore.Field{Type: zapcore.Int64Type, Integer: int64(4), Key: "k"}) } }Copy the code

In this case, NewJSONEncoder uses the specified Zapcore. EncoderConfig, zapcore.NewCore out is set to temp, and level is set to InfoLevel, so only entries with level or higher will be output

The output

{"level":"info","msg":"info","k":1,"k":3}
{"level":"warn","msg":"warn","k":1,"k":4}
Copy the code

Where k = 1 is specified withField globally, and k = 2 is not output because it is debug level

summary

LevelEnabler is embedded in the Core interface, which defines the With, Check, Write, Sync methods. LevelEnabler is embedded in ioCore, which defines Encoder and WriteSyncer(out) attributes. Encoder is used to serialize entry to bytes. WriteSyncer is used to write bytes.

doc

  • zap