阅读 618

GO的日志怎么玩 ?

这是我参与更文挑战的第 28 天,活动详情查看: 更文挑战

GO的日志怎么玩

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

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

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

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

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

如何简单使用 log 包

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

一看,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 一个日志,供我们默认使用

此处 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 默认是标准输出,也可以自己设置输出到文件

配置一个 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("小魔童的重要日志...")
}
复制代码

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

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 包原理和具体实现
  • 自定义日志

欢迎点赞,关注,收藏

朋友们,写作不易

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

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

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

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

文章分类
后端
文章标签