日志库 log库 + zap库介绍

日志库

概念

日志库是用于在开发中记录日志所要用到的库。由于fmt库输出的信息灵活性不够,所以需要日志库提供更好的功能。

log库

参考:Go 每日一库之 log - 大俊的博客 (darjun.github.io)

快速认识

Go 标准库提供了一个日志库log,可直接使用。log默认输出到标准错误(stderr),每条日志前会自动加上日期和时间;每条日志会在新行中输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"log"
)

type User struct {
Name string
Age int
}

func main() {
u := User{
Name: "dj",
Age: 18,
}

log.Printf("%s login, age:%d", u.Name, u.Age)
log.Panicf("Oh, system error when %s login", u.Name)
log.Fatalf("Danger! hacker %s login", u.Name)
}

比如上述demo的打印结果如下所示,程序执行到log.Panicf就会触发程序panic,结束运行,后续log.Fatalf不会被执行。

1
2
3
4
5
6
7
8
9
10
2024/09/24 11:46:46 dj login, age:18
2024/09/24 11:46:46 Oh, system error when dj login
panic: Oh, system error when dj login

goroutine 1 [running]:
log.Panicf({0x8d68ca?, 0x2?}, {0xc00011bf00?, 0xc00011bf10?, 0xc000062000?})
D:/Go/src/log/log.go:439 +0x65
main.main()
d:/许伟强的大学生活/自我学习汇总文件夹/Item-BlogBackend/CodeDemo/log.go:19 +0x114
exit status 2

log提供了三组函数:

  • Print/Printf/Println:正常输出日志;
  • Panic/Panicf/Panicln:输出日志后,以拼装好的字符串为参数调用panic;先输出日志,再调用panic;
  • Fatal/Fatalf/Fatalln:输出日志后,调用os.Exit(1)退出程序。

其中,带f后缀的有格式化功能,带ln后缀的会在日志后增加一个换行符。

定制

前缀

可以调用log.SetPrefix为每条日志文本前增加一个前缀。可以调用log.Prefix()来查看获取当前设置的日志的前缀。例如增加前缀Login:只需要在日志输出前简单添加log.SetPrefix("Login: ")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"log"
)

type User struct {
Name string
Age int
}

func main() {
u := User{
Name: "dj",
Age: 18,
}

log.SetPrefix("Login: ")
log.Printf("%s login, age:%d", u.Name, u.Age)
}

选项

选项是为日志打印增加日期时间、文件名等信息,调用log.SetFlag设置选项,可以一次设置多个,例如:

1
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)

其中选项有以下几种,调用log.Flags()可以获取当前设置的选项

1
2
3
4
5
6
7
8
9
// src/log/log.go
const (
Ldate = 1 << iota
Ltime
Lmicroseconds
Llongfile
Lshortfile
LUTC
)
  • Ldate:输出当地时区的日期,如2020/02/07
  • Ltime:输出当地时区的时间,如11:45:45
  • Lmicroseconds:输出的时间精确到微秒,设置了该选项就不用设置Ltime了。如11:45:45.123123
  • Llongfile:输出长文件名+行号,含包名,如github.com/darjun/go-daily-lib/log/flag/main.go:50
  • Lshortfile:输出短文件名+行号,不含包名,如main.go:50
  • LUTC:如果设置了LdateLtime,将输出 UTC 时间,而非当地时区。

log库还定义了一个Lstdflag,为Ldate | Ltime,这是默认的选项。

1
2
3
4
// src/log/log.go
const (
LstdFlags = Ldate | Ltime
)

自定义

log库使用Logger结构体作为设计的关键部分,默认情况下创建的是名为stdLogger实例。但我们可以对Logger做一些自定义。调用log.New(...)来创建Logger实例,New函数如下所示:

1
2
3
4
5
6
7
func New(out io.Writer, prefix string, flag int) *Logger {
l := new(Logger)
l.SetOutput(out)
l.SetPrefix(prefix)
l.SetFlags(flag)
return l
}

log.New接受三个参数:

  • io.Writer:日志都会写到这个Writer中;
  • prefix:前缀,也可以后面调用logger.SetPrefix设置;
  • flag:选项,也可以后面调用logger.SetFlag设置。

可以使用io.MultiWriter实现多目的地输出,也是一个writer,下面我们将日志同时输出到标准输出、bytes.Buffer和文件,写一个小小的例子来展示一下自定义Logger的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"bytes"
"fmt"
"log"
)

type User struct {
Name string
Age int
}

func main() {
u := User{
Name: "dj",
Age: 18,
}

// buf := &bytes.Buffer{}
// logger := log.New(buf, "", log.Lshortfile|log.LstdFlags)
writer1 := &bytes.Buffer{}
writer2 := os.Stdout
writer3, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0755)
if err != nil {
log.Fatalf("create file log.txt failed: %v", err)
}

logger := log.New(io.MultiWriter(writer1, writer2, writer3), "", log.Lshortfile|log.LstdFlags)

logger.Printf("%s login, age:%d", u.Name, u.Age)

fmt.Print(buf.String())
}

注意到,第一个参数为io.Writer,我们。下面我们将日志同时输出到标准输出、bytes.Buffer和文件中:

实现

log库的核心是Output方法。

logrus库

Go 每日一库之 logrus - 大俊的博客 (darjun.github.io)

这是结构化的日志库,kv形式

zap库

golang常用库包:log日志记录-uber的Go日志库zap使用详解 - 九卷 - 博客园 (cnblogs.com)

在 Go 语言中,函数选项模式是一种强大的编码技巧,它可以使函数的调用更加灵活和可配置。

一、函数选项模式的概念 函数选项模式允许你通过一系列的选项来配置一个函数的行为,而不是使用一个庞大的参数列表。这种模式通常使用函数类型的参数来实现,每个函数代表一个选项,可以在调用函数时选择是否应用这些选项。

二、实现函数选项模式的步骤

  1. 定义一个结构体,代表函数的可配置选项:

    1
    2
    3
    4
    type Option struct {// 可配置的选项字段    
    optionField1 int
    optionField2 string
    }
  2. 定义一个函数类型,代表选项函数: type OptionFunc func(*Option)

  3. 定义一个接受选项函数的目标函数:

1
2
3
4
5
6
func TargetFunction(opts...OptionFunc) {    
option := Option{}
for _, opt := range opts {
opt(&option)
} // 使用配置后的选项执行目标函数的逻辑
}
  1. 实现选项函数,用于设置选项值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func WithOptionField1(value int) OptionFunc {    
    return func(o *Option) {
    o.optionField1 = value
    }
    }

    func WithOptionField2(value string) OptionFunc {
    return func(o *Option) {
    o.optionField2 = value
    }
    }

三、使用函数选项模式 可以通过以下方式调用目标函数并应用选项:

1
2
3
func main() {    
TargetFunction(WithOptionField1(42), WithOptionField2("Hello"))
}

在这个例子中,TargetFunction 接受一系列的选项函数作为参数,可以根据需要选择应用哪些选项来配置函数的行为。

四、函数选项模式的优点

  1. 灵活性:可以根据不同的需求轻松地配置函数的行为,而不需要修改函数的签名。
  2. 可扩展性:可以轻松地添加新的选项,而不会影响现有的代码。
  3. 可读性:使函数的调用更加清晰,易于理解每个选项的作用。 函数选项模式在 Go 语言中被广泛应用于各种库和框架中,例如 HTTP 服务器、数据库驱动等,以提供更加灵活和可配置的 API。

日志库 log库 + zap库介绍
http://willxu0313.github.io/2025/01/22/gin-backend/日志库/
作者
Will
发布于
2025年1月22日
许可协议