Go 学习笔记(40)— Go 标准库之 log

1. 代码示例

这个示例程序展示如何使用最基本的 log 包。

// 这个示例程序展示如何使用最基本的log包
package main

import (
	"log"
)

func init() {
	log.SetPrefix("TRACE: ")
	log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}

func main() {
	// Println写到标准日志记录器
	log.Println("message")

	// Fatalln在调用Println()之后会接着调用os.Exit(1)
	log.Fatalln("fatal message")

	// Panicln在调用Println()之后会接着调用panic()
	log.Panicln("panic message")
}

输出:

TRACE: 2019/10/31 19:38:54.732475 /home/wohu/GoCode/src/hello.go:15: message
TRACE: 2019/10/31 19:38:54.732590 /home/wohu/GoCode/src/hello.go:18: fatal message
exit status 1

init 这个函数会在运行 main() 之前作为程序初始化的一部分执行。通常程序会在这个 init() 函数里配置日志参数,这样程序一开始就能使用 log 包进行正确的输出。

2. 源码说明

const (
 // 将下面的位使用或运算符连接在一起,可以控制要输出的信息。没有
 // 办法控制这些信息出现的顺序(下面会给出顺序)或者打印的格式
 // (格式在注释里描述)。这些项后面会有一个冒号:
 //  2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message

 // 日期: 2009/01/23
 Ldate = 1 << iota

 // 时间: 01:23:23
 Ltime

 // 毫秒级时间: 01:23:23.123123。该设置会覆盖Ltime标志
 Lmicroseconds

 // 完整路径的文件名和行号: /a/b/c/d.go:23
 Llongfile

 // 最终的文件名元素和行号: d.go:23
 // 覆盖 Llongfile
 Lshortfile

 // 标准日志记录器的初始值
 LstdFlags = Ldate | Ltime
)

这些标志用来控制可以写到每个日志项的其他信息。这些标志被声明为常量。

// 日期: 2009/01/23
Ldate = 1 << iota

关键字 iota 在常量声明区里有特殊的作用。这个关键字让编译器为每个常量复制相同的表达式,直到声明区结束,或者遇到一个新的赋值语句。

关键字 iota 的另一个功能是, iota 的初始值为 0,之后 iota 的值在每次处理为常量后,都会自增 1。

const (
 Ldate = 1 << iota // 1 << 0 = 000000001 = 1
 Ltime        // 1 << 1 = 000000010 = 2
 Lmicroseconds   // 1 << 2 = 000000100 = 4
 Llongfile     // 1 << 3 = 000001000 = 8
 Lshortfile     // 1 << 4 = 000010000 = 16
 ...
)

操作符 << 对左边的操作数执行按位左移操作。在每个常量声明时,都将 1 按位左移 iota 个位置。最终的效果使为每个常量赋予一个独立位置的位,这正好是标志希望的工作方式。

常量 LstdFlags 展示了如何使用这些标志,

const (
 ...
 LstdFlags = Ldate(1) | Ltime(2) = 00000011 = 3
)

因为使用了复制操作符, LstdFlags 打破了 iota 常数链。由于有 | 运算符用于执行或操作,常量 LstdFlags 被赋值为 3。

对位进行或操作等同于将每个位置的位组合在一起,作为最终的值。如果对位 1 和 2 进行或操作,最终的结果就是3。

3. 代码分析

func init() {
	...
	log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}

这里我们将 LdateLmicrosecondsLlongfile 标志组合在一起,将该操作的值传入 SetFlags 函数。这些标志值组合在一起后,最终的值是 13,代表第 1、3 和 4 位为 1(00001101)。

由于每个常量表示单独一个位,这些标志经过或操作组合后的值,可以表示每个需要的日志参数。之后 log 包会按位检查这个传入的整数值,按照需求设置日志项记录的信息。

初始完 log 包后,可以看一下 main() 函数.

func main() {
	// Println写到标准日志记录器
	log.Println("message")

	// Fatalln在调用Println()之后会接着调用os.Exit(1)
	log.Fatalln("fatal message")

	// Panicln在调用Println()之后会接着调用panic()
	log.Panicln("panic message")
}

上述代码展示了如何使用 3 个函数 PrintlnFatallnPanicln 来写日志消息。这些函数也有可以格式化消息的版本,只需要用 f 替换结尾的 ln。

Fatal 系列函数用来写日志消息,然后使用 os.Exit(1) 终止程序。Panic 系列函数用来写日志消息,然后触发一个 panic

除非程序执行 recover 函数,否则会导致程序打印调用栈后终止。 Print 系列函数是写日志消息的标准方法。

log 包有一个很方便的地方就是,这些日志记录器是多 goroutine 安全的。这意味着在多个 goroutine 可以同时调用来自同一个日志记录器的这些函数,而不会有彼此间的写冲突。

4. 定制日志

要想创建一个定制的日志记录器,需要创建一个 Logger 类型值。可以给每个日志记录器配置一个单独的目的地,并独立设置其前缀和标志。

让我们来看一个示例程序,这个示例程序展示了如何创建不同的 Logger 类型的指针变量来支持不同的日志等级。

// 这个示例程序展示如何创建定制的日志记录器
package main

import (
	"io"
	"io/ioutil"
	"log"
	"os"
)

var ( // 为4个日志等级声明了4个Logger类型的指针变量
	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, // 当某个等级的日志不重要时,使用Discard变量可以禁用这个等级的日志。
		"TRACE: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Info = log.New(os.Stdout,
		"INFO: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Warning = log.New(io.MultiWriter(file, os.Stdout),
		"WARN: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	// io.MultiWriter(file, os.Stderr)
	/*
		这个函数调用会返回一个io.Writer接口类型值,这个值包含之前打开的文件file,以及stderr。
		MultiWriter函数是一个变参函数,可以接受任意个实现了io.Writer接口的值。
		这个函数会返回一个io.Writer值,这个值会把所有传入的io.Writer的值绑在一起。
		当对这个返回值进行写入时,会向所有绑在一起的io.Writer值做写入。
		这让类似log.New这样的函数可以同时向多个Writer做输出。
		现在,当我们使用Error记录器记录日志时,输出会同时写到文件和stderr。
	*/
	Error = log.New(io.MultiWriter(file, os.Stderr),
		"ERROR: ",
		log.Ldate|log.Ltime|log.Lshortfile)
}

func main() {
	Trace.Println("I have something standard to say")
	Info.Println("Special Information")
	Warning.Println("There is something you need to know about")
	Error.Println("Something has failed")
}

使用了 log 包的 New 函数,它创建并正确初始化一个 Logger 类型的值。函数 New 会返回新创建的值的地址。在 New 函数创建对应值的时候,我们需要给它传入一些参数,如下代码所示:

// New创建一个新的Logger。out参数设置日志数据将被写入的目的地
// 参数prefix会在生成的每行日志的最开始出现
// 参数flag定义日志记录包含哪些属性
func New(out io.Writer, prefix string, flag int) *Logger {
  return &Logger{out: out, prefix: prefix, flag: flag}
}

上述代码来自 log 包的源代码里的 New 函数的声明。第一个参数 out 指定了日志要写到的目的地。这个参数传入的值必须实现了 io.Writer 接口。第二个参数 prefix 是之前看到的前缀,而日志的标志则是最后一个参数。

以上是我们通过 go 语言自带的 log 来实现的自己的日志工具。Go
社区很强大,社区的大佬们为我们实现了更加强大好用的工具类,比如支持按照日期、大小滚动切割文件输出;有着更细致的日志级别有更高的更好的性能;支持各种插件可以直接对接
elk、prometheus 等。下面为大家介绍两款日志框架:

logrus : https://github.com/sirupsen/logrus
seelog:https://github.com/cihub/seelog

将上面代码中的 main 函数注释掉,修改文件名为 mylog ,包名修改为 package mylog


├── task.go
└── mylog
    └── mylog.go

task.go

package main

import "mylog"

func main() {
	mylog.Trace.Println("trace")
	mylog.Info.Println("info")
	mylog.Error.Println("error")
}

可以看到输出为:

INFO: 2020/04/20 18:24:25 task.go:7: info
ERROR: 2020/04/20 18:24:25 task.go:8: error
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值