本文目录一览:
- golang const and itoa 2022-03-23
- golang logger输出格式怎么修改
- golang标准库的strconv函数的解释说Itoa是FormatInt(i, 10) 的简写,FormatInt(i, 10)又是什么意思呢
golang const and itoa 2022-03-23
golang const定义常量时,如果出现省略的行,则自动沿用上一行的assignment。
golang logger输出格式怎么修改
- Logger结构
首先来看下类型Logger的定义:
主要有5个成员,其中3个我们比较熟悉,分别是表示Log前缀的 "prefix",表示Log头标签的 "flag",以及Log的输出目的地type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix to write at beginning of each line flag int // properties out io.Writer // destination for output buf []byte // for accumulating text to write }
out
。buf
是一个字节数组,主要用来存放即将刷入out
的内容,相当于一个临时缓存,在对输出内容进行序列化时作为存储目的地。mu
是一个 mutex,主要用来作线程安全的实现,当有多个 goroutine 同时往一个目的刷内容的时候,通过 mutex 保证每次写入是一条完整的信息。 - std及整体结构
在前一篇文章中我们提到了 log 模块提供了一套包级别的简单接口,使用该接口可以直接将日志内容打印到标准错误。那么该过程是怎么实现的呢?其实就是通过一个内置的
Logger
类型的变量 "std" 来实现的。该变量使用:
进行初始化,默认输出到系统的标准输出var std = New(os.Stderr, "", LstdFlags)
os.Stderr
,前缀为空,使用日期加时间作为 Log 抬头。 当我们调用log.Print
的时候是怎么执行的呢?我们看其代码:
这里实际就是调用了 Logger 对象的 Output 方法,将日志内容按照 fmt 包中约定的格式转义后传给 Output。Output 定义如下:func Print(v ...interface{}) { std.Output(2, fmt.Sprint(v...)) }
其中func (l *Logger) Output(calldepth int, s string) error
s
为日志没有加前缀和 Log 抬头的具体内容。该函数执行具体的将日志刷入到对应的位置。 - 核心函数的实现
Logger.Output
是执行具体的将日志刷入到对应位置的方法。 该方法首先根据需要获得当前时间和调用该方法的文件及行号信息。然后调用formatHeader
方法将 Log 的前缀和 Log 抬头先格式化好放入Logger.buf
中,然后再将 Log 的内容存入到Logger.buf
中,最后调用Logger.out.Write
方法将完整的日志写入到输出目的地中。 由于写入文件以及拼接buf
的过程是线程非安全的,因此使用 mutex 保证每次写入的原子性。
将l.mu.Lock() defer l.mu.Unlock()
buf
的拼接和文件的写入放入这个后面,使得在多个 goroutine 使用同一个 Logger 对象时,不会弄乱buf
,也不会杂糅地写入。 该方法的第一个参数最终会传递给runtime.Caller
的skip
,指的是跳过的栈的深度。这里记住给 2 就可以了。这样就会得到我们调用 log 时所处的位置。 在 golang 的注释中说锁住runtime.Caller
的过程比较重,这点我还是不是很了解,只是从代码中看到其在这里把锁打开了。
在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() }
formatHeader
里面首先将前缀直接复制到Logger.buf
中,然后根据 flag 选择 Log 抬头的内容,这里用到了一个 log 模块实现的itoa
的方法,作用类似 C 的itoa
,将一个整数转换成一个字符串。只是其转换后将结果直接追加到了buf
的尾部。 纵观整个实现,最值得学习的就是线程安全的部分。在什么位置合适做怎样的同步操作。 - 对外接口的实现
在了解了核心格式化和输出结构后,再看其封装就非常简单了,几乎都是首先用
Output
进行日志的记录,然后在必要的时候做os.Exit
或者panic
的操作,这里看下Fatal
的实现。func (l *Logger) Fatal(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) os.Exit(1) }
// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1). func (l *Logger) Fatalf(format string, v ...interface{}) { l.Output(2, fmt.Sprintf(format, v...)) os.Exit(1) }
这里也验证了我们之前做的 Panic 的结果,先做输出日志操作,再进行 panic。// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1). func (l *Logger) Fatalln(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) os.Exit(1) }
golang标准库的strconv函数的解释说Itoa是FormatInt(i, 10) 的简写,FormatInt(i, 10)又是什么意思呢
两个参数是:要输入的数,返回几进制。 10 表示 10 进制;如果输入一个大于 10 的值,就会用 a-z 的字母表示更高的数字; 比如 16 进制时,a=10, b=11, c=12...