上次咱们分享了 GO的定时器timer和定时任务 cron
,咱们来回顾一下:
要是想了解如上问题的答案,欢迎查看文章 GO的定时器Timer 和定时任务cron
今天咱们来看看 GO 的标准库里面的 日志包 log
具体源码路径:src/log/log.go
咱们在编辑器中看看使用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
默认可以打印出日期、时间、以及打印的内容
使用 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
有上面这几个成员,看上去每一个参数都比较好理解,根据成员名字就能够基本知道其含义
锁,确保原子操作
每一行日志的前缀
输出位置,可以是文件,可以是标准输出
缓冲区的buffer
具体属性,通过源码我们可以看出,具体属性有如下几种选择
这些参数,都是用于控制日志输出的细节,例如时间,代码行数,前缀等等
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
默认是标准输出,也可以自己设置输出到文件咱们用一下 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
实际上是调用了 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
实际上是调用了 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的单元测试和性能测试分享
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~