zap-loggerの作成およびloggerの使用手順


文書ディレクトリ

  • Conifgに基づいてLogger
  • を作成する
  • buildEncoder
  • openSinks
  • CombineWriteSyncers
  • logの使用
  • check level
  • Wirte
  • EncodeEntry
  • c.out.Write
  • Sync
  • まとめ
  • Conifgによるロガーの作成

    // Build constructs a logger from the Config and Options.
    func (cfg Config) Build(opts ...Option) (*Logger, error) {
        // 
        enc, err := cfg.buildEncoder()
        if err != nil {
            return nil, err
        }
        // OutputPaths ErrOutputPaths sink
        sink, errSink, err := cfg.openSinks()
        if err != nil {
            return nil, err
        }
        // Logger
        log := New(
            zapcore.NewCore(enc, sink, cfg.Level),
            cfg.buildOptions(errSink)...,
        )
        if len(opts) > 0 {
            log = log.WithOptions(opts...)
        }
        return log, nil
    }
    
    func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
        return &ioCore{
            LevelEnabler: enab,
            enc:          enc,
            out:          ws,
        }
    }
    // New constructs a new Logger from the provided zapcore.Core and Options. If
    // the passed zapcore.Core is nil, it falls back to using a no-op
    // implementation.
    //
    // This is the most flexible way to construct a Logger, but also the most
    // verbose. For typical use cases, the highly-opinionated presets
    // (NewProduction, NewDevelopment, and NewExample) or the Config struct are
    // more convenient.
    //
    // For sample code, see the package-level AdvancedConfiguration example.
    func New(core zapcore.Core, options ...Option) *Logger {
        if core == nil {
            return NewNop()
        }
        // Logger
        log := &Logger{
            core:        core,
            errorOutput: zapcore.Lock(os.Stderr),
            addStack:    zapcore.FatalLevel + 1,
        }
        return log.WithOptions(options...)
    }
    // WithOptions clones the current Logger, applies the supplied Options, and
    // returns the resulting Logger. It's safe to use concurrently.
    func (log *Logger) WithOptions(opts ...Option) *Logger {
        c := log.clone()
        for _, opt := range opts {
            opt.apply(c)// hook
        }
        return c
    }
    

    buildEncoder


    buildEncoderはEncodingに基づいて対応するエンコーダを検索し、エンコーダはmapに事前に登録されており、直接取得すればよい.
    func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
        return newEncoder(cfg.Encoding, cfg.EncoderConfig)
    }
    
    func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
        _encoderMutex.RLock()
        defer _encoderMutex.RUnlock()
        if name == "" {
            return nil, errNoEncoderNameSpecified
        }
        constructor, ok := _encoderNameToConstructor[name]
        if !ok {
            return nil, fmt.Errorf("no encoder registered for name %q", name)
        }
        return constructor(encoderConfig)
    }
    
    var (
        errNoEncoderNameSpecified = errors.New("no encoder name specified")
    
        _encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){
            "console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
                return zapcore.NewConsoleEncoder(encoderConfig), nil
            },
            "json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
                return zapcore.NewJSONEncoder(encoderConfig), nil
            },
        }
        _encoderMutex sync.RWMutex
    )
    

    openSinks


    OpenSinksは主にOutputPathsおよびErrorOutputPathsを開き、後でファイル内のWrite情報に直接アクセスしやすい.
    func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
        sink, closeOut, err := Open(cfg.OutputPaths...)
        if err != nil {
            return nil, nil, err
        }
        errSink, _, err := Open(cfg.ErrorOutputPaths...)
        if err != nil {
            closeOut()
            return nil, nil, err
        }
        return sink, errSink, nil
    }
    
    func Open(paths ...string) (zapcore.WriteSyncer, func(), error) {
        writers, close, err := open(paths)
        if err != nil {
            return nil, nil, err
        }
        // writers
        writer := CombineWriteSyncers(writers...)
        return writer, close, nil
    }
    
    func open(paths []string) ([]zapcore.WriteSyncer, func(), error) {
        writers := make([]zapcore.WriteSyncer, 0, len(paths))
        closers := make([]io.Closer, 0, len(paths))
        close := func() {// 
            for _, c := range closers {
                c.Close()
            }
        }
    
        var openErr error
        for _, path := range paths {
            sink, err := newSink(path)
            if err != nil {
                openErr = multierr.Append(openErr, fmt.Errorf("couldn't open sink %q: %v", path, err))
                continue
            }
            writers = append(writers, sink)
            closers = append(closers, sink)
        }
        if openErr != nil {
            close()
            return writers, nil, openErr
        }
    
        return writers, close, nil
    }
    
    func newSink(rawURL string) (Sink, error) {
        ...
        _sinkMutex.RLock()
        // map 
        factory, ok := _sinkFactories[u.Scheme]
        _sinkMutex.RUnlock()
        if !ok {
            return nil, &errSinkNotFound{u.Scheme}
        }
        return factory(u)
    }
    
    var (
        _sinkMutex     sync.RWMutex
        _sinkFactories map[string]func(*url.URL) (Sink, error) // keyed by scheme
    )
    
    func init() {
        resetSinkRegistry()
    }
    
    func resetSinkRegistry() {
        _sinkMutex.Lock()
        defer _sinkMutex.Unlock()
    
        _sinkFactories = map[string]func(*url.URL) (Sink, error){
            schemeFile: newFileSink,
        }
    }
    
    // ( )
    func newFileSink(u *url.URL) (Sink, error) {
        ...
        switch u.Path {
        case "stdout":
            return nopCloserSink{os.Stdout}, nil
        case "stderr":
            return nopCloserSink{os.Stderr}, nil
        }
        // ,*OS.File Write,Close,Sync , Sink, Sink , File, Sync 。
        return os.OpenFile(u.Path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
    }
    

    CombineWriteSyncers


    writersはまずWriteSyncerにカプセル化され、lockedWriteSyncerにカプセル化され、最終的にioCoreのoutに格納され、後続のwrite時に呼び出されます.
    func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer {
        if len(writers) == 0 {
            return zapcore.AddSync(ioutil.Discard)
        }
        return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...))
    }
    
    // NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes
    // and sync calls, much like io.MultiWriter.
    func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer {
        if len(ws) == 1 {
            return ws[0]
        }
        // Copy to protect against https://github.com/golang/go/issues/7809
        return multiWriteSyncer(append([]WriteSyncer(nil), ws...))
    }
    
    type lockedWriteSyncer struct {
        sync.Mutex
        ws WriteSyncer
    }
    
    // Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In
    // particular, *os.Files must be locked before use.
    func Lock(ws WriteSyncer) WriteSyncer {
        if _, ok := ws.(*lockedWriteSyncer); ok {
            // no need to layer on another lock
            return ws
        }
        return &lockedWriteSyncer{ws: ws}
    }
    

    私たちは書類の処理過程を整理して、後の論理の理解とします.
    File->Sink->[]WriteSyncer->WriteSyncer->multiWriteSyncer->lockedWriteSyncer>ioCore.out->Logger.core
    

    ロゴの使用


    Infoを例にとると、他のクラスは同じです.
    // Info logs a message at InfoLevel. The message includes any fields passed
    // at the log site, as well as any fields accumulated on the logger.
    func (log *Logger) Info(msg string, fields ...Field) {
        if ce := log.check(InfoLevel, msg); ce != nil {
            ce.Write(fields...)
        }
    }
    

    check level

    func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
        // check must always be called directly by a method in the Logger interface
        // (e.g., Check, Info, Fatal).
        const callerSkipOffset = 2
    
        // 
        ent := zapcore.Entry{
            LoggerName: log.name,
            Time:       time.Now(),
            Level:      lvl,
            Message:    msg,
        }
        ce := log.core.Check(ent, nil)
        willWrite := ce != nil
    
        // , panic exit
        switch ent.Level {
        case zapcore.PanicLevel:
            ce = ce.Should(ent, zapcore.WriteThenPanic)
        case zapcore.FatalLevel:
            ce = ce.Should(ent, zapcore.WriteThenFatal)
        case zapcore.DPanicLevel:
            if log.development {
                ce = ce.Should(ent, zapcore.WriteThenPanic)
            }
        }
    
        if !willWrite {
            return ce
        }
    
        // Thread the error output through to the CheckedEntry.
        ce.ErrorOutput = log.errorOutput
        if log.addCaller {
            ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset))
            if !ce.Entry.Caller.Defined {
                fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller
    "
    , time.Now().UTC()) log.errorOutput.Sync() } } if log.addStack.Enabled(ce.Entry.Level) { ce.Entry.Stack = Stack("").String } return ce } func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { if c.Enabled(ent.Level) {// , return ce.AddCore(ent, c) } return ce } // Enabled returns true if the given level is at or above this level. // func (l Level) Enabled(lvl Level) bool { return lvl >= l } type LevelEnabler interface { Enabled(Level) bool } // ent core ce func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { if ce == nil { ce = getCheckedEntry() ce.Entry = ent } ce.cores = append(ce.cores, core) return ce } // pool CheckedEntry, , func getCheckedEntry() *CheckedEntry { ce := _cePool.Get().(*CheckedEntry) ce.reset() return ce }

    Wirte

    func (ce *CheckedEntry) Write(fields ...Field) {
        ...
        var err error
        for i := range ce.cores {
            err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields))
        }
       ...
    }
    
    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
    }
    

    ioCore WriteはEncodeEntryでコンテンツをフォーマットしてからフォーマット後のログ情報を書き込む

    EncodeEntry


    jsonEncoderを例にとると,jsonにおけるkey順序の処理は,固定順序であることに注意する.
    func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) {
        final := enc.clone()// pool , enc 
        final.buf.AppendByte('{')
    
        if final.LevelKey != "" {
            final.addKey(final.LevelKey)
            cur := final.buf.Len()
            final.EncodeLevel(ent.Level, final)
            if cur == final.buf.Len() {
                // User-supplied EncodeLevel was a no-op. Fall back to strings to keep
                // output JSON valid.
                final.AppendString(ent.Level.String())
            }
        }
        if final.TimeKey != "" {
            final.AddTime(final.TimeKey, ent.Time)
        }
        if ent.LoggerName != "" && final.NameKey != "" {
            final.addKey(final.NameKey)
            cur := final.buf.Len()
            nameEncoder := final.EncodeName
    
            // if no name encoder provided, fall back to FullNameEncoder for backwards
            // compatibility
            if nameEncoder == nil {
                nameEncoder = FullNameEncoder
            }
    
            nameEncoder(ent.LoggerName, final)
            if cur == final.buf.Len() {
                // User-supplied EncodeName was a no-op. Fall back to strings to
                // keep output JSON valid.
                final.AppendString(ent.LoggerName)
            }
        }
        if ent.Caller.Defined && final.CallerKey != "" {
            final.addKey(final.CallerKey)
            cur := final.buf.Len()
            final.EncodeCaller(ent.Caller, final)
            if cur == final.buf.Len() {
                // User-supplied EncodeCaller was a no-op. Fall back to strings to
                // keep output JSON valid.
                final.AppendString(ent.Caller.String())
            }
        }
        if final.MessageKey != "" {
            final.addKey(enc.MessageKey)
            final.AppendString(ent.Message)
        }
        if enc.buf.Len() > 0 {
            final.addElementSeparator()
            final.buf.Write(enc.buf.Bytes())
        }
        addFields(final, fields)
        final.closeOpenNamespaces()
        if ent.Stack != "" && final.StacktraceKey != "" {
            final.AddString(final.StacktraceKey, ent.Stack)
        }
        final.buf.AppendByte('}')
        if final.LineEnding != "" {
            final.buf.AppendString(final.LineEnding)
        } else {
            final.buf.AppendString(DefaultLineEnding)
        }
    
        ret := final.buf
        putJSONEncoder(final)// 
        return ret, nil
    }
    

    以上のコードから分かるように,対応するキーが存在する場合,jsonシーケンス化の順序は,LevelKey,TimeKey,NameKey,CallerKey,MessageKey,fields(注意:InitialFieldsはASCIIコード順にソートされる)であるため,これらのキーが出現する順序を指定することはできない.

    c.out.Write


    前にwritersがCombineWriteSyncersでmultiWriteSyncerにカプセル化され、lockedWriteSyncerにカプセル化されると、呼び出し順序は先lockedWriteSyncerとなる.Write後multiWriteSyncer.Write、最終的に呼び出されたのはファイル*osです.FileのWriteが実現し、これで情報の書き込みが完了します.
    func (s *lockedWriteSyncer) Write(bs []byte) (int, error) {
        s.Lock()
        n, err := s.ws.Write(bs)
        s.Unlock()
        return n, err
    }
    
    // See https://golang.org/src/io/multi.go
    // When not all underlying syncers write the same number of bytes,
    // the smallest number is returned even though Write() is called on
    // all of them.
    func (ws multiWriteSyncer) Write(p []byte) (int, error) {
        var writeErr error
        nWritten := 0
        for _, w := range ws {
            n, err := w.Write(p)
            writeErr = multierr.Append(writeErr, err)
            if nWritten == 0 && n != 0 {
                nWritten = n
            } else if n < nWritten {
                nWritten = n
            }
        }
        return nWritten, writeErr
    }
    
    // write writes len(b) bytes to the File.
    // It returns the number of bytes written and an error, if any.
    func (f *File) write(b []byte) (n int, err error) {
        n, err = f.pfd.Write(b)
        runtime.KeepAlive(f)
        return n, err
    }
    
    // write writes len(b) bytes to the File.
    // It returns the number of bytes written and an error, if any.
    func (f *File) write(b []byte) (n int, err error) {
        n, err = f.pfd.Write(b)
        runtime.KeepAlive(f)
        return n, err
    }
    

    Sync


    さらに、Syncの呼び出しプロセスを詳しく見ると、最終的にシステム層のFsyncを呼び出し、キャッシュをディスクにブラシすることを実現します.
    func (log *Logger) Sync() error {
        return log.core.Sync()
    }
    
    func (s *lockedWriteSyncer) Sync() error {
        s.Lock()
        err := s.ws.Sync()
        s.Unlock()
        return err
    }
    
    
    func (ws multiWriteSyncer) Sync() error {
        var err error
        for _, w := range ws {
            err = multierr.Append(err, w.Sync())
        }
        return err
    }
    
    // Sync commits the current contents of the file to stable storage.
    // Typically, this means flushing the file system's in-memory copy
    // of recently written data to disk.
    func (f *File) Sync() error {
        if err := f.checkValid("sync"); err != nil {
            return err
        }
        if e := f.pfd.Fsync(); e != nil {
            return f.wrapErr("sync", e)
        }
        return nil
    }
    

    まとめ


    loggerは作成時に書き込むファイル、フォーマットのタイプなどの情報をカプセル化し、正式に呼び出すときは情報レベルをチェックし、情報のフォーマット、ファイルの書き込みなどの操作を完了します.全体の流れから見ると、zapは実際にファイルの読み書き、ログのフォーマットをカプセル化したツールであり、これは実際にはlog機能ツールのようなすべての実現原理でもあり、違いはカプセル化の論理、側面の重点が異なるだけである.