您当前的位置: 首页 >  golang

Linux小百科

暂无认证

  • 1浏览

    0关注

    1185博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

log包在Golang语言的标准库中是怎么使用的?

Linux小百科 发布时间:2021-02-13 22:22:57 ,浏览量:1

Golang 语言的标准库中提供了一个简单的 log 日志包,它不仅提供了很多函数,还定义了一个包含很多方法的类型 Logger。但是它也有缺点,比如不支持区分日志级别,不支持日志文件切割等。

01、介绍

Golang 语言的标准库中提供了一个简单的 log 日志包,它不仅提供了很多函数,还定义了一个包含很多方法的类型 Logger。但是它也有缺点,比如不支持区分日志级别,不支持日志文件切割等。

log包在Golang语言的标准库中是怎么使用的?log包在Golang语言的标准库中是怎么使用的?

02、函数

Golang 的 log 包主要提供了以下几个具备输出功能的函数:

func Fatal(v ...interface{})  
func Fatalf(format string, v ...interface{})  
func Fatalln(v ...interface{})  
func Panic(v ...interface{})  
func Panicf(format string, v ...interface{})  
func Panicln(v ...interface{})  
func Print(v ...interface{})  
func Printf(format string, v ...interface{})  
func Println(v ...interface{}) 

这些函数的使用方法和 fmt 包完全相同,通过查看源码可以发现,Fatal[ln|f] 和 Panic[ln|f] 实际上是调用的 Print[ln|f],而 Print[ln|f] 实际上是调用的 Output() 函数。

其中 Fatal[ln|f] 是调用 Print[ln|f] 之后,又调用了 os.Exit(1) 退出程序。

其中 Panic[ln|f] 是调用 Panic[ln|f] 之后,又调用了 panic() 函数,抛出一个恐慌。

所以,我们很有必要阅读一下 Output() 函数的源码。

函数 Output() 的源码:

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 
} 

通过阅读 Output() 函数的源码,可以发现使用互斥锁来保证多个 goroutine 写日志的安全,并且在调用 runtime.Caller() 函数之前,先释放互斥锁,获取到信息后再加上互斥锁来保证安全。

使用 formatHeader() 函数来格式化日志的信息,然后保存到 buf 中,然后再把日志信息追加到 buf 的末尾,然后再通过判断,查看日志是否为空或末尾不是 \n,如果是就再把 \n 追加到 buf 的末尾,最后将日志信息输出。

函数 Output() 的源码也比较简单,其中最值得注意的是 runtime.Caller() 函数,源码如下:

func Caller(skip int) (pc uintptr, file string, line int, ok bool) { 
 rpc := make([]uintptr, 1) 
 n := callers(skip+1, rpc[:]) 
 if n < 1 { 
  return 
 } 
 frame, _ := CallersFrames(rpc).Next() 
 return frame.PC, frame.File, frame.Line, frame.PC != 0 
} 

通过阅读 runtime.Caller() 函数的源码,可以发现它接收一个 int 类型的参数 skip,该参数表示跳过栈帧数,log 包中的输出功能的函数,使用的默认值都是 2,原因是什么?

举例说明,比如在 main 函数中调用 log.Print,方法调用栈为 main->log.Print->*Logger.Output->runtime.Caller,所以此时参数 skip 的值为 2,表示 main 函数中调用 log.Print 的源文件和代码行号;

参数值为 1,表示 log.Print 函数中调用 *Logger.Output 的源文件和代码行号;参数值为 0,表示 *Logger.Output 函数中调用 runtime.Caller 的源文件和代码行号。

至此,我们发现 log 包的输出功能的函数,全部都是把信息输出到控制台,那么该怎么将信息输出到文件中呢?

函数 SetOutPut 就是用来设置输出目标的,源码如下:

func SetOutput(w io.Writer) { 
 std.mu.Lock() 
 defer std.mu.Unlock() 
 std.out = w 
} 

我们可以通过函数 os.OpenFile 来打开一个用于 I/O 的文件,返回值作为函数 SetOutput 的参数。

除此之外,读者应该还发现了一个问题,输出信息都是以日期和时间开头,我们该怎么记录更加丰富的信息呢?比如源文件和行号。

这就用到了函数 SetFlags,它可以设置输出的格式,源码如下:

func SetFlags(flag int) { 
 std.SetFlags(flag) 
} 

参数 flag 的值可以是以下任意常量:

const ( 
 Ldate         = 1             
关注
打赏
1665632672
查看更多评论
立即登录/注册

微信扫码登录

0.0435s