GO的日志怎么玩?

作者: 阿兵云原生 | 来源:发表于2021-06-28 23:29 被阅读0次

    GO的日志怎么玩

    GO的日志怎么玩.jpg

    上次咱们分享了 GO的定时器timer和定时任务 cron,咱们来回顾一下:

    • Timer 是什么
    • Timer 如何使用
    • Ticker 是什么
    • Ticker 如何使用
    • cron 是什么
    • cron 如何使用

    要是想了解如上问题的答案,欢迎查看文章 GO的定时器Timer 和定时任务cron

    image

    今天咱们来看看 GO 的标准库里面的 日志包 log

    具体源码路径:src/log/log.go

    如何简单使用 log 包

    咱们在编辑器中看看使用log包,会有什么提示

    image

    一看,log包里面就涉及这些方法和数据结构,一点都不复杂,方法如上图

    咱们来用一用小案例,再来看数据结构

    package main
    
    import "log"
    
    func main() {
       log.Println("小魔童打日志 ... ")
       test := "Hello wrold "
       // Printf 有格式控制符
       log.Printf("%s 小魔童打日志 ... \n", test)
    
       log.Fatalln("小魔童 打日志,触发了 Fatal")
    
       log.Panicln("小魔童 打日志,触发了 Panic")
    }
    

    运行上述代码,效果如下:

    2021/06/xx xx:25:53 小魔童打日志 ...
    2021/06/xx xx:25:53 Hello wrold  小魔童打日志 ...
    2021/06/xx xx:25:53 小魔童 打日志,触发了 Fatal
    exit status 1
    

    默认可以打印出日期、时间、以及打印的内容

    如何配置 log 以及相应的原理

    使用 GO 里面的 这个log包,咱们使用默认的 log 那肯定是不够用的,例如上述小案例打印的日志,你就不知道具体是代码的哪一行打印出来的,以及设置日志打印到哪个日志文件里面,等等

    咱们一起来看看如何配置 log,从创建logger开始看起

    新建一个 logger

    咱们在基本的日志上,加上一个前缀

    func main() {
      // 打印到标准输出上
       myLog := log.New(os.Stdout, "<XMT>", log.Lshortfile|log.Ldate|log.Ltime)
       myLog.Println("小魔童打印了带有前缀的日志 ... ")
    }
    

    执行效果如下:

    <XMT>2021/06/28 12:35:47 main.go:20: 小魔童打印了带有前缀的日志 ...
    

    咱们看看 log.New 方法的具体实现

    具体源码路径:src/log/log.go

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

    可以看出 func New(out io.Writer, prefix string, flag int) *Logger 方法实际上是调用了 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 on each line to identify the logger (but see Lmsgprefix)
       flag   int        // properties
       out    io.Writer  // destination for output
       buf    []byte     // for accumulating text to write
    }
    

    type Logger struct有上面这几个成员,看上去每一个参数都比较好理解,根据成员名字就能够基本知道其含义

    • mu sync.Mutex

    锁,确保原子操作

    • prefix string

    每一行日志的前缀

    • out io.Writer

    输出位置,可以是文件,可以是标准输出

    • buf []byte

    缓冲区的buffer

    • flag int

    具体属性,通过源码我们可以看出,具体属性有如下几种选择

    这些参数,都是用于控制日志输出的细节,例如时间,代码行数,前缀等等

    const (
       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
       Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message
       LstdFlags     = Ldate | Ltime // initial values for the standard logger
    )
    

    源码写的注释还是很清晰的,具体每一个字段是做什么的,用了之后是什么样的效果,根据这个注释,一目了然

    咱们查看源码就知道,为什么上述的小案例,日志里面默认就输出了 日期、时间、具体内容,因为 log包里面会默认 New 一个日志,供我们默认使用

    image

    此处 var std = New(os.Stderr, "", LstdFlags) New 里面的第三个参数需要填属性,此处默认填了 LstdFlags

    LstdFlags = Ldate | Ltime // initial values for the standard logger

    LstdFlags 属性,默认是打印日期,和时间

    // 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...)) }
    

    (l *Logger) Println进行具体的输出,调用了(l *Logger) Output

    // 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()
       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
    }
    

    func (l *Logger) Output(calldepth int, s string) error {函数做了如下几个事情:

    • 拼接日志字符串数据
    • 输出到 out 中 , 此处的out 默认是标准输出,也可以自己设置输出到文件
    image

    配置一个 logger

    咱们用一下 log 里面设置输出日志到文件中

    func main() {
       logFile, err := os.OpenFile("./XMT.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
       if err != nil {
          fmt.Println("os.OpenFile error :", err)
          return
       }
       // 设置输出位置 ,里面有锁进行控制
       log.SetOutput(logFile)
    
       // 设置日志属性
       log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)
       // 打印日志
       log.Println("小魔童的 新 日志 ... ")
       // 手动设置前缀
       log.SetPrefix("【重点】")
       
       log.Println("小魔童的重要日志...")
    }
    

    运行上述代码,效果如下:

    image
    2021/06/28 12:57:14 D:/mycode/my_new_first/my_log/main.go:36: 小魔童的 新 日志 ... 
    【重点】2021/06/28 12:57:14 D:/mycode/my_new_first/my_log/main.go:40: 小魔童的重要日志...
    
    • log.SetOutput

      log.SetOutput 实际上是调用了 Logger 对应的 func (l *Logger) SetOutput(w io.Writer) 方法

    func (l *Logger) SetOutput(w io.Writer) {
       l.mu.Lock()
       defer l.mu.Unlock()
       l.out = w
    }
    
    • log.SetFlags

    log.SetFlags 实际上是调用了 Logger 对应的 SetFlags 方法

    SetPrefix 也是同样的道理

    // SetFlags sets the output flags for the logger.
    // The flag bits are Ldate, Ltime, and so on.
    func (l *Logger) SetFlags(flag int) {
       l.mu.Lock()
       defer l.mu.Unlock()
       l.flag = flag
    }
    

    总结

    • 如何使用log
    • log 包原理和具体实现
    • 自定义日志

    欢迎点赞,关注,收藏

    朋友们,写作不易

    你的支持和鼓励,是我坚持分享,提高质量的动力

    image

    好了,本次就到这里,GO的单元测试和性能测试分享

    技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

    我是小魔童哪吒,欢迎点赞关注收藏,下次见~

    相关文章

      网友评论

        本文标题:GO的日志怎么玩?

        本文链接:https://www.haomeiwen.com/subject/mpzcultx.html