GO的日志怎么玩

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

  • Timer 是什么
  • Timer 如何使用
  • Ticker 是什么
  • Ticker 如何使用
  • cron 是什么
  • cron 如何使用
要是想了解如上问题的答案,欢迎查看文章 GO的定时器Timer 和定时任务cron
GO的日志怎么玩
文章图片

今天咱们来看看 GO 的标准库里面的 日志包 log
具体源码路径:src/log/log.go
如何简单使用 log 包 咱们在编辑器中看看使用log包,会有什么提示
GO的日志怎么玩
文章图片

一看,log包里面就涉及这些方法和数据结构,一点都不复杂,方法如上图
咱们来用一用小案例,再来看数据结构
package mainimport "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, "", log.Lshortfile|log.Ldate|log.Ltime) myLog.Println("小魔童打印了带有前缀的日志 ... ") }

执行效果如下:
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 { musync.Mutex // ensures atomic writes; protects the following fields prefix string// prefix on each line to identify the logger (but see Lmsgprefix) flagint// properties outio.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 一个日志,供我们默认使用
GO的日志怎么玩
文章图片

此处 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 默认是标准输出,也可以自己设置输出到文件
GO的日志怎么玩
文章图片

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

运行上述代码,效果如下:
GO的日志怎么玩
文章图片

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的日志怎么玩
文章图片

好了,本次就到这里,GO的单元测试和性能测试分享
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
【GO的日志怎么玩】我是小魔童哪吒,欢迎点赞关注收藏,下次见~

    推荐阅读