Golang官方log包源码分析

栏目: Go · 发布时间: 7年前

内容简介:以下全是代码, 详解在注释中, 请从头到尾看

以下全是代码, 详解在注释中, 请从头到尾看

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package log implements a simple logging package. It defines a type, Logger,
// with methods for formatting output. It also has a predefined 'standard'
// Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and
// Panic[f|ln], which are easier to use than creating a Logger manually.
// That logger writes to standard error and prints the date and time
// of each logged message.
// Every log message is output on a separate line: if the message being
// printed does not end in a newline, the logger will add one.
// The Fatal functions call os.Exit(1) after writing the log message.
// The Panic functions call panic after writing the log message.
// 官方包注释, 这个包让你自己不需要手动实现日志包, 但是还是我建议还是使用第三方包!
package golog

import (
    "fmt"
    "io"
    "os"
    "runtime"
    "sync"
    "time"
)

// 稍加注释!
// These flags define which text to prefix to each log entry generated by the Logger.
const (
    // Bits or'ed together to control what's printed.
    // There is no control over the order they appear (the order listed
    // here) or the format they present (as described in the comments).
    // The prefix is followed by a colon only when Llongfile or Lshortfile
    // is specified.
    // For example, flags Ldate | Ltime (or LstdFlags) produce,
    //  2009/01/23 01:23:23 message
    // while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,
    //  2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
    // 二进制或标志!
    Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
    Ltime                         // the time in the local time zone: 01:23:23
    Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
    Llongfile                     // full file name and line number: /a/b/c/d.go:23
    Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
    LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
    // 默认不带文件名
    LstdFlags     = Ldate | Ltime // initial values for the standard logger
)

// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
// 这个可同时被多个协程使用
type Logger struct {
    // 加锁
    mu     sync.Mutex // ensures atomic writes; protects the following fields
    // 打日志的前缀
    prefix string     // prefix to write at beginning of each line
    // 日志格式的标志
    flag   int        // properties
    // 写入的目标
    out    io.Writer  // destination for output
    // 日志缓存, 待写入
    buf    []byte     // for accumulating text to write
}

// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
// 新一个日志记录, 这个对象(结构对象)承接了日志输出的责任.
func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

// SetOutput sets the output destination for the logger.
// 设置日志输出的地方
func (l *Logger) SetOutput(w io.Writer) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.out = w
}

// 默认记录对象, 输出为标准输出, 日志没前缀, 格式是标准: 2009/01/23 01:23:23 message
var std = New(os.Stderr, "", LstdFlags)

// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
// 数字填充, 方便时间对齐
func itoa(buf *[]byte, i int, wid int) {
    // Assemble decimal in reverse order.
    // 填充后最长长度20!
    var b [20]byte
    bp := len(b) - 1

    // 类似辗转相除法取最大公约数, 此处是每次取数字的后一位, 等到数字取不到位了就按照wid开始填充
    // 比如 i=106 wid=5:
    //      wid q byte i
    //      4 10 6 106
    //      3 1 0 10
    //      2 0 1 1
    //      1 0 0 0  ===> 不符合i>=0 || wid>1, 跳出
    //      00106
    for i >= 10 || wid > 1 {
        wid--
        // 商, 如果第一次i是106, 那么就变成10
        // 填到q为0时
        q := i / 10
        // 余数, 第一次i-q*10就是6 ==> '0' + 6 ==> '6' 原封不动
        // 填到q为0时, i也为0了, 这时就按wid开始不断填'0'
        b[bp] = byte('0' + i - q*10)
        bp--
        //fmt.Println(wid,q,string(byte('0' + i - q*10)),i)
        i = q
    }
    // i < 10
    b[bp] = byte('0' + i)
    *buf = append(*buf, b[bp:]...)
}

// formatHeader writes log header to buf in following order:
//   * l.prefix (if it's not blank),
//   * date and/or time (if corresponding flags are provided),
//   * file and line number (if corresponding flags are provided).
// 日志输出时日志头部的格式化
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 {
        // 以上表示有使用了时间格式
        // 如果有, 转换为UTC时间
        if l.flag&LUTC != 0 {
            t = t.UTC()
        }

        // 日期转换
        if l.flag&Ldate != 0 {
            year, month, day := t.Date()
            // 年份填充, 长度4位
            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)  // 纳秒转微秒, 长度6位
            }
            // 空一点后面接文件名或者日志消
            *buf = append(*buf, ' ')
        }
    }

    // 长文件名, 短文件名输出
    if l.flag&(Lshortfile|Llongfile) != 0 {
        // 短文件名那么截取最后一个, 如a/b/c ==> c
        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, ':')

        // 行数, 代码打日志所在的地方, wid为-1表示不填冲
        itoa(buf, line, -1)

        // 后面开始接消息, :再空一点!
        *buf = append(*buf, ": "...)
    }
}

// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
// 日志输出最重要的地方来了!
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()

    // 使用文件标志格式, 可能会产生bug!
    // 此篇文章: https://www.cnblogs.com/zhangym/p/6709282.html
    // 重复加锁时协程会阻塞, 直到锁被解开, 只要不让主协程阻塞, 就不会死锁! 要及时解开锁!
    if l.flag&(Lshortfile|Llongfile) != 0 {
        // Release lock while getting caller info - it's expensive.
        // 先将锁释放, 方便其他协程能更快获取到调用的文件路径. 文件调用获取路径是一个昂贵的过程
        // 在这个解锁期间, 其他协程中的一个又会进来Output进行加锁, 加锁的这一个瞬间, 如果获取路径过程结束了, 之前的协程会再次加锁, 两次加锁会报错!
        // may BE BUG
        l.mu.Unlock()
        var ok bool
        // 0 表示获取自己的路径
        // 1 表示获取上一层调用的路径
        // 越往上那么逐次加一

        // 在此, 直接调用Output, 那么calldepth为1时可以知道是谁调用了Output, 但是Output被Print等又封装了一层, 所以是2!
        _, file, line, ok = runtime.Caller(calldepth)  // 昂贵!
        if !ok {
            file = "???"
            line = 0
        }

        // 路径获取完后, 在加锁之后, 其他协程可能也进入开头加锁! Maybe bug
        l.mu.Lock()
        // 下面这个测试, 可以Lock两次? 只要保证主进程不阻塞即可?
        //go func(){
        //  time.Sleep(time.Duration(2)*time.Second)
        //  l.mu.Unlock()   // 最终还是会解开锁, Golang的智能检测会发现死锁? 如果主协程在一定时间内还没有响应!
        //}()
        //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
}



// 下面的都是采用Output包装而来
// Printf calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, v ...interface{}) {
    l.Output(2, fmt.Sprintf(format, v...))
}

// Print calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) }

// Println calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Println.
func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) }

// Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *Logger) Fatal(v ...interface{}) {
    l.Output(2, fmt.Sprint(v...))
    os.Exit(1)
}

// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, v ...interface{}) {
    l.Output(2, fmt.Sprintf(format, v...))
    os.Exit(1)
}

// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).
func (l *Logger) Fatalln(v ...interface{}) {
    l.Output(2, fmt.Sprintln(v...))
    os.Exit(1)
}

// Panic is equivalent to l.Print() followed by a call to panic().
func (l *Logger) Panic(v ...interface{}) {
    s := fmt.Sprint(v...)
    l.Output(2, s)
    panic(s)
}

// Panicf is equivalent to l.Printf() followed by a call to panic().
func (l *Logger) Panicf(format string, v ...interface{}) {
    s := fmt.Sprintf(format, v...)
    l.Output(2, s)
    panic(s)
}

// Panicln is equivalent to l.Println() followed by a call to panic().
func (l *Logger) Panicln(v ...interface{}) {
    s := fmt.Sprintln(v...)
    l.Output(2, s)
    panic(s)
}

// 以下都是原子更新
// Flags returns the output flags for the logger.
func (l *Logger) Flags() int {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.flag
}

// SetFlags sets the output flags for the logger.
func (l *Logger) SetFlags(flag int) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.flag = flag
}

// Prefix returns the output prefix for the logger.
func (l *Logger) Prefix() string {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.prefix
}

// SetPrefix sets the output prefix for the logger.
func (l *Logger) SetPrefix(prefix string) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.prefix = prefix
}

// SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {
    std.mu.Lock()
    defer std.mu.Unlock()
    std.out = w
}

// Flags returns the output flags for the standard logger.
func Flags() int {
    return std.Flags()
}

// SetFlags sets the output flags for the standard logger.
func SetFlags(flag int) {
    std.SetFlags(flag)
}

// Prefix returns the output prefix for the standard logger.
func Prefix() string {
    return std.Prefix()
}

// SetPrefix sets the output prefix for the standard logger.
func SetPrefix(prefix string) {
    std.SetPrefix(prefix)
}

// These functions write to the standard logger.

// Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
    std.Output(2, fmt.Sprint(v...))
}

// Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
    std.Output(2, fmt.Sprintf(format, v...))
}

// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
}

// Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...interface{}) {
    std.Output(2, fmt.Sprint(v...))
    os.Exit(1)
}

// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
func Fatalf(format string, v ...interface{}) {
    std.Output(2, fmt.Sprintf(format, v...))
    os.Exit(1)
}

// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
func Fatalln(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
    os.Exit(1)
}

// Panic is equivalent to Print() followed by a call to panic().
func Panic(v ...interface{}) {
    s := fmt.Sprint(v...)
    std.Output(2, s)
    panic(s)
}

// Panicf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interface{}) {
    s := fmt.Sprintf(format, v...)
    std.Output(2, s)
    panic(s)
}

// Panicln is equivalent to Println() followed by a call to panic().
func Panicln(v ...interface{}) {
    s := fmt.Sprintln(v...)
    std.Output(2, s)
    panic(s)
}

// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is the count of the number of
// frames to skip when computing the file name and line number
// if Llongfile or Lshortfile is set; a value of 1 will print the details
// for the caller of Output.

// 默认的让你再包装一层的函数...
func Output(calldepth int, s string) error {
    return std.Output(calldepth+1, s) // +1 for this frame.
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Master Switch

The Master Switch

Tim Wu / Knopf / 2010-11-2 / USD 27.95

In this age of an open Internet, it is easy to forget that every American information industry, beginning with the telephone, has eventually been taken captive by some ruthless monopoly or cartel. Wit......一起来看看 《The Master Switch》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码