Golang source code analysis: log standard library

  golang, Log, php

Golang source code analysis: log standard library

Original address:Golang source code analysis: log standard library

Log

Output

2018/09/28 20:03:08 EDDYCJY Blog...

constitute

[date] < space > [hour and minute] < space > [content ]<n >

Source code analysis

Logger

type Logger struct {
    mu     sync.Mutex 
    prefix string
    flag   int
    out    io.Writer
    buf    []byte
}

(1) mu: Mutually exclusive lock to ensure atomic writing
(2) prefix: log prefix content to be written in each line
(3) flag: set the writing of log auxiliary information (time, file name, line number). The following identification bits can be selected:

const (
    Ldate         = 1 << iota       // value: 1
    Ltime                           // value: 2
    Lmicroseconds                   // value: 4
    Llongfile                       // value: 8
    Lshortfile                      // value: 16
    LUTC                            // value: 32
    LstdFlags     = Ldate | Ltime   // value: 3
)
  • Ldate: Format Date of Local Time Zone: 2009/01/23
  • Ltime: Format Time of Local Time Zone: 01:23:23
  • Lmicroseconds: On the basis of Ltime, add microsecond time value display
  • Llongfile: full file name and line number: /a/b/c/d.go:23
  • Lshortfile: current file name and line number: d.go: 23, overwriting Llongfile id
  • LUTC: If Ldate or Ltime is set and LUTC is set, UTC time zone is preferred over local time zone
  • LstdFlags:Logger’s default initial values (Ldate and Ltime)

(4) out:io.Writer
(5) buf: used to store log content to be written

New

func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

var std = New(os.Stderr, "", LstdFlags)

The New method is used to initialize Logger. It accepts three initial parameters and can be customized. By default, an std will be initialized in the log package, which points to the standard input stream. The default standard output and standard error are the display (output to the screen) and the standard input is the keyboard. The auxiliary time information defaults toLdate | Ltime, that is to say2009/01/23 01:23:23

// os
var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
  • Stdin: standard input
  • Stdout: standard output
  • Stderr: standard error

Getter

  • Flags
  • Prefix

Setter

  • SetFlags
  • SetPrefix
  • SetOutput

Print.., Fatal.., Panic ..

func Print(v ...interface{}) {
    std.Output(2, fmt.Sprint(v...))
}

func Printf(format string, v ...interface{}) {
    std.Output(2, fmt.Sprintf(format, v...))
}

func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
}

func Fatal(v ...interface{}) {
    std.Output(2, fmt.Sprint(v...))
    os.Exit(1)
}

func Panic(v ...interface{}) {
    s := fmt.Sprint(v...)
    std.Output(2, s)
    panic(s)
}

...

This section introduces the most commonly used log writing methods, which can be seen from the source code.XrintlnXrintfFunctionWrapvariable parameterAll throughfmtStandard library method to achieve

FatalAndPanicThroughos.Exit(1)panic(s)Integrated implementation. And the specific assembly logic is throughOutputMethod

Logger.Output

func (l *Logger) Output(calldepth int, s string) error {
    now := time.Now() // get this early.
    var file string
    var line int
    l.mu.Lock()
    defer l.mu.Unlock()
    if l.flag&(Lshortfile|Llongfile) != 0 {
        // Release lock while getting caller info - it's expensive.
        l.mu.Unlock()
        var ok bool
        _, file, line, ok = runtime.Caller(calldepth)
        if !ok {
            file = "???"
            line = 0
        }
        l.mu.Lock()
    }
    l.buf = l.buf[:0]
    l.formatHeader(&l.buf, now, file, line)
    l.buf = append(l.buf, s...)
    if len(s) == 0 || s[len(s)-1] != '\n' {
        l.buf = append(l.buf, '\n')
    }
    _, err := l.out.Write(l.buf)
    return err
}

The Output method is simply to assemble and output the written log event information, which will be used according to the difference of flag identification bits.runtime.CallerTo obtain the call information such as function file and line number executed by current goroutine (default depth in log standard library is 2). In addition, if the end is not a newline character\n, will automatically complete a line break

Logger.formatHeader

func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
    *buf = append(*buf, l.prefix...)
    if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
        if l.flag&LUTC != 0 {
            t = t.UTC()
        }
        if l.flag&Ldate != 0 {
            year, month, day := t.Date()
            itoa(buf, year, 4)
            *buf = append(*buf, '/')
            itoa(buf, int(month), 2)
            *buf = append(*buf, '/')
            itoa(buf, day, 2)
            *buf = append(*buf, ' ')
        }
        if l.flag&(Ltime|Lmicroseconds) != 0 {
            hour, min, sec := t.Clock()
            itoa(buf, hour, 2)
            *buf = append(*buf, ':')
            itoa(buf, min, 2)
            *buf = append(*buf, ':')
            itoa(buf, sec, 2)
            if l.flag&Lmicroseconds != 0 {
                *buf = append(*buf, '.')
                itoa(buf, t.Nanosecond()/1e3, 6)
            }
            *buf = append(*buf, ' ')
        }
    }
    if l.flag&(Lshortfile|Llongfile) != 0 {
        if l.flag&Lshortfile != 0 {
            short := file
            for i := len(file) - 1; i > 0; i-- {
                if file[i] == '/' {
                    short = file[i+1:]
                    break
                }
            }
            file = short
        }
        *buf = append(*buf, file...)
        *buf = append(*buf, ':')
        itoa(buf, line, -1)
        *buf = append(*buf, ": "...)
    }
}

This method is mainly used to format the log header (prefix) and add delimiters and corresponding values to the log information according to the different identification bits of the input parameters. The implementation process is as follows:

(1) If it is not null, write prefix to buf

(2) if setLdateLtimeLmicrosecondsThen the date and time should be written to buf

(3) if setLshortfileLlongfileThen the file and line number information should be written to buf

Logger.itoa

func itoa(buf *[]byte, i int, wid int) {
    // Assemble decimal in reverse order.
    var b [20]byte
    bp := len(b) - 1
    for i >= 10 || wid > 1 {
        wid--
        q := i / 10
        b[bp] = byte('0' + i - q*10)
        bp--
        i = q
    }
    // i < 10
    b[bp] = byte('0' + i)
    *buf = append(*buf, b[bp:]...)
}

This method is mainly used to convert integers into fixed-length decimal ASCII, and at the same time give negative width to avoid the left complement of 0. In addition, the decimal system will be combined in reverse order.

How to customize Logger

Within the standard library, various custom Logger components can be implemented through its open New method, but why can it be directly implementedlog.Print*How about waiting?

func New(out io.Writer, prefix string, flag int) *Logger

In fact, it is in the standard library. If you have just carefully looked at the previous section, it is not difficult to find that it implements a Logger component by default.

var std = New(os.Stderr, "", LstdFlags)

This is also a small subtlety

Summary

By looking up the source code of the log standard library, we can know how to write the simplest log package. In addition, the log package is correct in all places where Logger is involved.sync.MutexTo operate (to solve the atomic problem), the rest of the logic is to assemble log information and convert numerical format. This package is more classic and can be read several times more.

Problem

Why are you callingruntime.CallerDo you want to unlock before locking?