zap是Uber
提供的GoLang
高性能日志库,zap
本身并不提供日志滚动功能,官方 FAQ
提到,可以使用Linux
系统自带的 logrotate
或lumberjack
实现日志滚动功能
lumberjack
只能向文件输出日志,如果我们希望同时向stderr
和文件输出日志,只能使用 logrotate
配合自定义 WriteSyncer
实现了
Go
代码
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main
import (
"bufio"
"context"
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"os/signal"
"sync"
"syscall"
)
type LogWriter struct {
FileName string
fp *os.File
mu sync.Mutex
fileCnt int
sig chan os.Signal
ctx context.Context
cancel context.CancelFunc
}
func (m *LogWriter) Write(p []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.fp == nil {
if err := m.open(); err != nil {
return 0, nil
}
}
if m.fileCnt == 0 {
m.fileCnt++
m.ctx, m.cancel = context.WithCancel(context.Background())
m.sig = make(chan os.Signal, 1)
signal.Notify(m.sig, syscall.SIGHUP)
go func() {
for {
select {
case <-m.ctx.Done():
return
case _, ok := <-m.sig:
if ok == false {
return
}
func() {
m.mu.Lock()
defer m.mu.Unlock()
if m.fp != nil {
_ = m.fp.Close()
m.fp = nil
}
}()
}
}
}()
}
_, _ = os.Stderr.Write(p)
return m.fp.Write(p)
}
func (m *LogWriter) Sync() error {
m.mu.Lock()
defer m.mu.Unlock()
_ = os.Stderr.Sync()
return m.fp.Sync()
}
func (m *LogWriter) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
m.cancel()
if m.fp != nil {
if err := m.fp.Close(); err != nil {
return err
}
}
if m.sig != nil {
signal.Reset(syscall.SIGHUP)
close(m.sig)
}
return nil
}
func (m *LogWriter) open() error {
if m.fp != nil {
if err := m.fp.Sync(); err != nil {
return err
}
if err := m.fp.Close(); err != nil {
return err
}
}
fp, err := os.OpenFile(m.FileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
m.fp = fp
return nil
}
func main() {
zapid, _ := os.OpenFile("/tmp/zap.pid", os.O_CREATE|os.O_WRONLY, 0644)
_, _ = zapid.WriteString(fmt.Sprintf("%d", os.Getpid()))
_ = zapid.Sync()
_ = syscall.Flock(int(zapid.Fd()), syscall.LOCK_EX)
mylog := &LogWriter{FileName: "/tmp/zap.log"}
cfg := zap.NewProductionEncoderConfig()
cfg.EncodeTime = zapcore.ISO8601TimeEncoder
core := zapcore.NewCore(zapcore.NewJSONEncoder(cfg), mylog, zap.InfoLevel)
logger := zap.New(core)
reader := bufio.NewReader(os.Stdin)
for i := 0; i < 10; i++ {
logger.Debug("debug msg", zap.Int("idx", i))
logger.Info("info msg", zap.Int("idx", i))
logger.Error("error msg", zap.Int("idx", i))
logger.Warn("warn msg", zap.Int("idx", i))
fmt.Printf("====================")
_, _, _ = reader.ReadRune()
}
_ = mylog.Close()
_ = syscall.Flock(int(zapid.Fd()), syscall.LOCK_UN)
_ = zapid.Close()
}
编译生成测试程序 zap_exp
1
go build zap_exp.go
logrotate
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
/tmp/zap.log {
su robin robin
missingok
daily
minsize 1M
create 0644 robin robin
rotate 12
postrotate
PIDFILE=/tmp/zap.pid
if [ -f "$PIDFILE" ] ; then kill -HUP $(cat "$PIDFILE") ; fi
endscript
}
zap.conf
的 owner
和 group
必须为 root
,并且 file mode
为 0644
,只有 root
可以读写,其它只读
su robin robin
: 运行zap_exp
程序的用户名及坐在的Group
,本测试中user
和group
均为robin
create 0644 robin robin
: 日志滚动后,重新生成日志文件的file mode
及ower
和group
postrotate
: 需要配合zap_exp.go
源码分析zap_exp.go
在main
函数启动后首先生成/tmp/zap.pid
文件,然后将当前程序的pid
写入该文件,并锁定该文件LogWriter
设置signal.Notify(m.sig, syscall.SIGHUP)
表明zap_exp
程序接受HUP
信号LogWriter
收到HUP
信号后关闭当前日志文件LogWriter
写日志文件时,如果日志文件被关闭,程序自动重新创建logrotate
程序运行时,首先从/tmp/zap.pid
文件读取zap_exp
程序的pid
,然后向zap_exp
程序发送HUP
信号
测试
运行 zap_exp
程序,分别在 idx:3
和 idx:6
时手动强制执行 logrotate
程序
1
logrotate -s ./log.status -vf ./zap.conf
zap_exp
运行结果
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
35
36
37
38
39
40
41
./zap_exp
{"level":"info","ts":"2020-09-10T16:53:54.896+0800","msg":"info msg","idx":0}
{"level":"error","ts":"2020-09-10T16:53:54.896+0800","msg":"error msg","idx":0}
{"level":"warn","ts":"2020-09-10T16:53:54.896+0800","msg":"warn msg","idx":0}
====================
{"level":"info","ts":"2020-09-10T16:53:56.628+0800","msg":"info msg","idx":1}
{"level":"error","ts":"2020-09-10T16:53:56.628+0800","msg":"error msg","idx":1}
{"level":"warn","ts":"2020-09-10T16:53:56.628+0800","msg":"warn msg","idx":1}
====================
{"level":"info","ts":"2020-09-10T16:53:57.060+0800","msg":"info msg","idx":2}
{"level":"error","ts":"2020-09-10T16:53:57.060+0800","msg":"error msg","idx":2}
{"level":"warn","ts":"2020-09-10T16:53:57.060+0800","msg":"warn msg","idx":2}
====================
{"level":"info","ts":"2020-09-10T16:53:57.393+0800","msg":"info msg","idx":3}
{"level":"error","ts":"2020-09-10T16:53:57.393+0800","msg":"error msg","idx":3}
{"level":"warn","ts":"2020-09-10T16:53:57.393+0800","msg":"warn msg","idx":3}
====================
{"level":"info","ts":"2020-09-10T16:54:31.706+0800","msg":"info msg","idx":4}
{"level":"error","ts":"2020-09-10T16:54:31.706+0800","msg":"error msg","idx":4}
{"level":"warn","ts":"2020-09-10T16:54:31.706+0800","msg":"warn msg","idx":4}
====================
{"level":"info","ts":"2020-09-10T16:54:32.050+0800","msg":"info msg","idx":5}
{"level":"error","ts":"2020-09-10T16:54:32.050+0800","msg":"error msg","idx":5}
{"level":"warn","ts":"2020-09-10T16:54:32.050+0800","msg":"warn msg","idx":5}
====================
{"level":"info","ts":"2020-09-10T16:54:32.359+0800","msg":"info msg","idx":6}
{"level":"error","ts":"2020-09-10T16:54:32.359+0800","msg":"error msg","idx":6}
{"level":"warn","ts":"2020-09-10T16:54:32.359+0800","msg":"warn msg","idx":6}
====================
{"level":"info","ts":"2020-09-10T16:54:36.955+0800","msg":"info msg","idx":7}
{"level":"error","ts":"2020-09-10T16:54:36.956+0800","msg":"error msg","idx":7}
{"level":"warn","ts":"2020-09-10T16:54:36.956+0800","msg":"warn msg","idx":7}
====================
{"level":"info","ts":"2020-09-10T16:54:37.255+0800","msg":"info msg","idx":8}
{"level":"error","ts":"2020-09-10T16:54:37.255+0800","msg":"error msg","idx":8}
{"level":"warn","ts":"2020-09-10T16:54:37.255+0800","msg":"warn msg","idx":8}
====================
{"level":"info","ts":"2020-09-10T16:54:37.712+0800","msg":"info msg","idx":9}
{"level":"error","ts":"2020-09-10T16:54:37.712+0800","msg":"error msg","idx":9}
{"level":"warn","ts":"2020-09-10T16:54:37.712+0800","msg":"warn msg","idx":9}
====================
在 /tmp
可以找到如下3
个文件 zap.log
zap.log.1
zap.log.2
zap.log.2
内容如下,刚好对应 idx:3
时执行 logrotate
时产生的日志滚动
{"level":"info","ts":"2020-09-10T16:53:54.896+0800","msg":"info msg","idx":0}
{"level":"error","ts":"2020-09-10T16:53:54.896+0800","msg":"error msg","idx":0}
{"level":"warn","ts":"2020-09-10T16:53:54.896+0800","msg":"warn msg","idx":0}
{"level":"info","ts":"2020-09-10T16:53:56.628+0800","msg":"info msg","idx":1}
{"level":"error","ts":"2020-09-10T16:53:56.628+0800","msg":"error msg","idx":1}
{"level":"warn","ts":"2020-09-10T16:53:56.628+0800","msg":"warn msg","idx":1}
{"level":"info","ts":"2020-09-10T16:53:57.060+0800","msg":"info msg","idx":2}
{"level":"error","ts":"2020-09-10T16:53:57.060+0800","msg":"error msg","idx":2}
{"level":"warn","ts":"2020-09-10T16:53:57.060+0800","msg":"warn msg","idx":2}
{"level":"info","ts":"2020-09-10T16:53:57.393+0800","msg":"info msg","idx":3}
{"level":"error","ts":"2020-09-10T16:53:57.393+0800","msg":"error msg","idx":3}
{"level":"warn","ts":"2020-09-10T16:53:57.393+0800","msg":"warn msg","idx":3}