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的单元测试和性能测试分享
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~