Golang日志包的使用


Posted in Golang onApril 20, 2022

引言:

在我们的日常编程中,日志很重要。只要是我们写代码,就有可能出现 Bug。日志文件就是一种快速找到这些 bug,更好地了解程序工作状态的方法。

让我们来看一下日志文件的定义:

日志文件是记录操作系统或其他软件运行中发生的事件或通信软件的不同用户之间的消息的文件。记录是保存日志的行为。

日志是开发人员的眼睛和耳朵,可以用来跟踪、定位错误、调试和分析代码,并监控应用程序的性能。在最简单的情况下,消息被写入单个日志文件。

Go 语言标准库之log 包

正因为日志很重要,所以 Go 语言标准库提供了 ​​log​​ 包,可以对日志做一些简单的配置,我们可以定制一套自己的日志记录器。

Golang 中的 ​​log​​ 包实现了简单的 logging 包。它定义了一个类型、Logger 以及格式化输出的方法。 它还具有预定义的“标准”记录器,可通过辅助函数 ​​Print[f|ln]​​、​​Fatal[f|ln]​​ 和 ​​Panic[f|ln]​​ 访问,它们比手动创建记录器更易于使用。

基本的日志项包括:前缀、日期时间戳、该日志由哪个源文件记录的、源文件记录日志所在行,最后是日志消息。让我们来看一下简单的使用 ​​log​​ 包:在除法运算时除 0 时的消息返回程序,而不是直接退出程序。

package main
import (
"errors"
"fmt"
"log"
)
func myDiv(x float64, y float64) (float64, error) {
if y == 0 {
return 0, errors.New("被除数不能为0")
}
return x / y, nil
}
func main() {
var x float64 = 128.2
var y float64
res, err := myDiv(x, y)
if err != nil {
log.Print(err) // Print 写到标准日志记录器
}
fmt.Println(res)
}

运行该程序:

$ go run learninglog.go
2022/04/19 23:18:06 被除数不能为0
0

在上面的程序中,我们导入了三个包:​​errors​​ 、​​fmt​​、​​log​​ 。

然后使用了 ​​log​​ 包中的 ​​Print​​ 函数:此时日志记录器将写入标准错误并打印每条记录消息的日期和时间,这在一般情况下非常有用。每条日志消息在单独的行上都有一个输出:如果正在打印的消息没有在新行结束,记录器将添加一行。

另外,​​log​​ 包中的 ​​Fatal​​ 函数在写入日志消息后调用 ​​os.Exit(1)​​。

func main() {
var x float64 = 128.2
var y float64
res, err := myDiv(x, y)
if err != nil {
log.Fatal(err) // 在调用 Print() 之后会接着调用 os.Exit(1)
}
fmt.Println(res)
}
// 2022/04/19 23:22:44 被除数不能为0
// exit status 1

而 ​​Panic​​ 函数在写入日志消息后调用 ​​panic​​,除非程序执行 ​​recover()​​ 函数,否则会导致程序打印调用栈后终止。

func main() {
var x float64 = 128.2
var y float64
res, err := myDiv(x, y)
if err != nil {
log.Panic(err) // Panic 会在调用 Print() 之后接着用 panic()
}
fmt.Println(res)
}

运行结果:

2022/04/19 23:24:18 被除数不能为0
panic: 被除数不能为0

goroutine 1 [running]:
log.Panic({0xc000086f60?, 0xc000086f70?, 0x404f99?})
/usr/local/go/src/log/log.go:385 +0x65
main.main()
/home/wade/go/src/logLearning/learninglog.go:26 +0x65
exit status 2

由此可知,​​Print​​ 系列函数才是写日志消息的标准方法。

如何将日志消息存储在 Go 中的文件中

上述代码中,只是简单把日志消息打印到控制台,这是远远不够的,因为控制台是实时的,如果关闭控制台日志消息也就关闭了。

鉴于我们已经学习了 Go 语言如何读取文件,所以就可以把日志消息存储到一个文件中,把所有的日志存储到该文件中。

package main
import (
"errors"
"fmt"
"log"
"os"
)
func myDiv(x float64, y float64) (float64, error) {
if y == 0 {
return 0, errors.New("被除数不能为0")
}
return x / y, nil
}
func main() {
var x float64 = 128.2
var y float64
res, exception := myDiv(x, y)
file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
defer file.Close() // 关闭文件
log.SetOutput(file)
log.Print(exception)
fmt.Println(res)
}

运行代码:

$ go run learninglog.go
0

此外,我们会发现目录中多了一个创建的 ​​info.log​​ 文件。打开文件,你会看到打印出类似下面的内容。

定制你的日志记录器

要想创建一个定制的日志记录器,我们需要创建一个 ​​Logger​​ 类型的结构体,然后给每个日志记录器配置一个输出目的地、前缀和标志。而且每一个日志记录器是多 goroutine 安全的,每一个 ​​Logger​​ 结构体都有一个互斥锁,意味着多个 goroutine 可以同时调用来自同一个日志记录器的这些函数,而不会有彼此间的写冲突。来看一下 ​​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
}

然后我们来看一个示例程序:

package main
import (
"io"
"io/ioutil"
"log"
"os"
)
var (
Trace *log.Logger // 记录所有日志
Info *log.Logger // 重要的信息
Warning *log.Logger // 警告信息
Error *log.Logger // 错误信息
)
func init() {
file, err := os.OpenFile("errors.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open error log file: ", err)
}
Trace = log.New(ioutil.Discard,
"Trace: ", log.Ldate|log.Ltime|log.Lshortfile)
Info = log.New(os.Stdout, "Info: ", log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(os.Stdout, "Warning: ", log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(io.MultiWriter(file, os.Stderr), "Error: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
Trace.Println("hello")
Info.Println("Information")
Warning.Println("Warning")
Error.Println("Error")
}

运行结果:

Info: 2022/04/20 00:37:34 learninglog.go:36: Information
Warning: 2022/04/20 00:37:34 learninglog.go:37: Warning
Error: 2022/04/20 00:37:34 learninglog.go:38: Error

使用 ​​log​​ 包的 ​​New​​ 函数,创建并初始化一个 Logger 类型的值,然后 ​​New​​ 函数返回新创建的值的地址。

New 函数

func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag
}
  • New 函数的第一个参数 out 指定了日志要写入的目的地,这个参数传入的值必须实现了 io.Writer 接口
  • 第二个参数 prefix 会在生成的每行日志的最开始出现
  • 第三个参数 flag 定义日志包含哪些属性

在本程序中:

  • Trace 日志记录器使用了 ioutil 包里的 Discard 变量作为写到的目的地,所有的 Writer 调用都不会有动作,但是会成功返回。当某个等级的日志不重要时,使用 Discard 变量可以禁用这个等级的日志。
  • 日志记录器 Info 和 Warning 都使用 stdout 作为日志输出。
  • 日志记录去 Error 中的 New 函数第一个参数使用了 MultiWriter 函数,这个函数调用会返回一个 io.Writer 接口类型的值,这个值包含之前打开的文件 file,以及 stderr。这个函数是一个变参函数,可以接受任意个实现了 io.Writer 接口的值,所以会把所有传入的 io.Writer 绑定在一起,当对这个返回值进行写入时,会向所有绑在一起的 io.Writer 值做写入。从而实现向多个 Writer 做输出。这样做的好处就是使用 Error 记录器记录日志时,输出会同时写到文件和 stderr 中

总结

Golang 中 ​​log​​ 包的实现是基于对记录日志这个需求长时间的实践和积累而形成的。比如将输出写入 ​​stdout​​,将日志记录到 ​​stderr​​,这同样也是很多基于命令行界面的程序使用的方法。

写日志的第一件事就是找到一个完美的库。然后,在您选择了日志库之后,您还需要计划在代码中的何处调用记录器,如何存储日志,如何在任何给定时间使它们可用,以及如何分析它们。

Go 日志的最佳实践是从主应用程序进程中调用自定义记录器,而不是在 goroutines 中。同时应该将应用程序中的日志消息写入本地文件,做到永久保存,同时针对错误的信息应该给到提醒,方便及时定位和处理。

到此这篇关于如何在 Go语言中使用日志包的文章就介绍到这了!

Golang 相关文章推荐
Golang二维切片初始化的实现
Apr 08 Golang
Go语言中的UTF-8实现
Apr 26 Golang
Go 实现英尺和米的简单单位换算方式
Apr 29 Golang
golang协程池模拟实现群发邮件功能
May 02 Golang
golang日志包logger的用法详解
May 05 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
go 实现简易端口扫描的示例
May 22 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
Golang中channel的原理解读(推荐)
Oct 16 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
深入理解 Golang 的字符串
May 04 Golang
Golang获取List列表元素的四种方式
Apr 20 #Golang
Golang 对es的操作实例
Apr 20 #Golang
Golang 遍历二叉树
Apr 19 #Golang
Golang MatrixOne使用介绍和汇编语法
Apr 19 #Golang
Golang 字符串的常见操作
Golang 链表的学习和使用
Golang Elasticsearches 批量修改查询及发送MQ
Apr 19 #Golang
You might like
php 生成随机验证码图片代码
2010/02/08 PHP
php+mysqli批量查询多张表数据的方法
2015/01/29 PHP
PHP中的魔术方法总结和使用实例
2015/05/11 PHP
Yii中srbac权限扩展模块工作原理与用法分析
2016/07/14 PHP
PHP接入支付宝接口失效流程详解
2020/11/10 PHP
破除网页鼠标右键被禁用的绝招大全
2006/12/27 Javascript
JavaScript的public、private和privileged模式
2009/12/28 Javascript
IE8下关于querySelectorAll()的问题
2010/05/13 Javascript
JavaScript编程开发中的五个实用小技巧
2010/07/22 Javascript
JavaScript 垃圾回收机制分析
2013/10/10 Javascript
JavaScript触发onScroll事件的函数节流详解
2016/12/14 Javascript
JavaScript数据结构之二叉树的遍历算法示例
2017/04/13 Javascript
jQuery滚动插件scrollable.js用法分析
2017/05/25 jQuery
angular 用拦截器统一处理http请求和响应的方法
2017/06/08 Javascript
基于JavaScript实现无缝滚动效果
2017/07/21 Javascript
echarts学习笔记之箱线图的分析与绘制详解
2017/11/22 Javascript
Javascript 之封装(Package)
2018/09/14 Javascript
js删除对象中的某一个字段的方法实现
2021/01/11 Javascript
浅谈Python中数据解析
2015/05/05 Python
python tensorflow基于cnn实现手写数字识别
2018/01/01 Python
教你用 Python 实现微信跳一跳(Mac+iOS版)
2018/01/04 Python
python的socket编程入门
2018/01/29 Python
python 读写excel文件操作示例【附源码下载】
2019/06/19 Python
关于pymysql模块的使用以及代码详解
2019/09/01 Python
python中使用paramiko模块并实现远程连接服务器执行上传下载功能
2020/02/29 Python
python自动提取文本中的时间(包含中文日期)
2020/08/31 Python
css3教程之倾斜页面
2014/01/27 HTML / CSS
美国大型的健身社区和补充商店:Bodybuilding.com
2016/09/06 全球购物
美国知名的旅游网站:OneTravel
2018/10/09 全球购物
美体小铺法国官方网站:The Body Shop法国
2020/06/04 全球购物
简短证婚人证婚词
2014/01/09 职场文书
先进工作者推荐材料
2014/12/23 职场文书
人才市场接收函
2015/01/30 职场文书
中学生综合素质自我评价
2015/03/06 职场文书
MySQL 如何设计统计数据表
2021/06/15 MySQL
css中:last-child不生效的解决方法
2022/08/05 HTML / CSS