-
Notifications
You must be signed in to change notification settings - Fork 13
/
logger.go
294 lines (268 loc) · 8.88 KB
/
logger.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// Package logging 简单封装了在日常使用 zap 打日志时的常用方法。
//
// 提供快速使用 zap 打印日志的全部方法,所有日志打印方法开箱即用
//
// 提供多种快速创建 logger 的方法
//
// 支持在使用 Error 及其以上级别打印日志时自动将该事件上报到 Sentry
//
// 支持从 context.Context/gin.Context 中创建、获取带有 Trace ID 的 logger
package logging
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strings"
"sync"
"syscall"
"github.com/getsentry/sentry-go"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
// global zap Logger with pid field
logger *zap.Logger
// 默认 sentry client
sentryClient *sentry.Client
// outPaths zap 日志默认输出位置
outPaths = []string{"stdout"}
// initialFields 默认初始字段为进程 id
initialFields = map[string]interface{}{
"pid": syscall.Getpid(),
"server_ip": ServerIP(),
}
// loggerName 默认 logger name 为 logging
loggerName = "logging"
// atomicLevel 默认 logger atomic level 级别默认为 debug
atomicLevel = zap.NewAtomicLevelAt(zap.DebugLevel)
// EncoderConfig 默认的日志字段名配置
EncoderConfig = zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: CallerEncoder,
}
// 读写锁
rwMutex sync.RWMutex
)
// AtomicLevelServerOption AtomicLevel server 相关配置
type AtomicLevelServerOption struct {
Addr string // http 动态修改日志级别服务运行地址
Path string // 设置 url path ,可选
Username string // 请求时设置 basic auth 认证的用户名,可选
Password string // 请求时设置 basic auth 认证的密码,可选,与 username 同时存在才开启 basic auth
}
// Options new logger options
type Options struct {
Name string // logger 名称
Level string // 日志级别 debug, info, warn, error dpanic, panic, fatal
Format string // 日志格式
OutputPaths []string // 日志输出位置
InitialFields map[string]interface{} // 日志初始字段
DisableCaller bool // 是否关闭打印 caller
DisableStacktrace bool // 是否关闭打印 stackstrace
SentryClient *sentry.Client // sentry 客户端
EncoderConfig *zapcore.EncoderConfig // 配置日志字段 key 的名称
LumberjackSink *LumberjackSink // lumberjack sink 支持日志文件 rotate
AtomicLevelServer AtomicLevelServerOption // AtomicLevel server 相关配置
}
const (
// AtomicLevelAddrEnvKey 初始化时尝试获取该环境变量用于设置动态修改日志级别的 http 服务运行地址
AtomicLevelAddrEnvKey = "ATOMIC_LEVEL_ADDR"
)
// init the global logger
func init() {
var err error
options := Options{
Name: loggerName,
Level: "debug",
Format: "json",
OutputPaths: outPaths,
InitialFields: initialFields,
DisableCaller: false,
DisableStacktrace: true,
SentryClient: sentryClient,
AtomicLevelServer: AtomicLevelServerOption{
Addr: os.Getenv(AtomicLevelAddrEnvKey),
},
EncoderConfig: &EncoderConfig,
LumberjackSink: nil,
}
logger, err = NewLogger(options)
if err != nil {
log.Println(err)
}
}
// NewLogger return a zap Logger instance
func NewLogger(options Options) (*zap.Logger, error) {
cfg := zap.Config{}
// 设置日志级别
lvl := strings.ToLower(options.Level)
if _, exists := AtomicLevelMap[lvl]; !exists {
cfg.Level = atomicLevel
} else {
cfg.Level = AtomicLevelMap[lvl]
atomicLevel = cfg.Level
}
// 设置 encoding 默认为 json
if strings.ToLower(options.Format) == "console" {
cfg.Encoding = "console"
} else {
cfg.Encoding = "json"
}
// 设置 output 没有传参默认全部输出到 stderr
if len(options.OutputPaths) == 0 {
cfg.OutputPaths = outPaths
cfg.ErrorOutputPaths = outPaths
} else {
cfg.OutputPaths = options.OutputPaths
cfg.ErrorOutputPaths = options.OutputPaths
}
// 设置 InitialFields 没有传参使用默认字段
// 传了就添加到现有的初始化字段中
if len(options.InitialFields) > 0 {
for k, v := range options.InitialFields {
initialFields[k] = v
}
}
cfg.InitialFields = initialFields
// 设置 disablecaller
cfg.DisableCaller = options.DisableCaller
// 设置 disablestacktrace
cfg.DisableStacktrace = options.DisableStacktrace
// 设置 encoderConfig
if options.EncoderConfig == nil {
cfg.EncoderConfig = EncoderConfig
} else {
cfg.EncoderConfig = *options.EncoderConfig
}
// Sampling 实现了日志的流控功能,或者叫采样配置,主要有两个配置参数, Initial 和 Thereafter ,实现的效果是在 1s 的时间单位内,如果某个日志级别下同样内容的日志输出数量超过了 Initial 的数量,那么超过之后,每隔 Thereafter 的数量,才会再输出一次。是一个对日志输出的保护功能。
cfg.Sampling = &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
}
// 注册 lumberjack sink ,支持 Outputs 指定为文件时可以使用 lumberjack 对日志文件自动 rotate
if options.LumberjackSink != nil {
if err := RegisterLumberjackSink(options.LumberjackSink); err != nil {
Error(nil, "RegisterSink error", zap.Error(err))
}
}
// 生成 logger
logger, err := cfg.Build()
if err != nil {
return nil, err
}
// 如果传了 sentryclient 则设置 sentrycore
if options.SentryClient != nil {
logger = SentryAttach(logger, options.SentryClient)
}
// 设置 logger 名字,没有传参使用默认名字
if options.Name != "" {
logger = logger.Named(options.Name)
} else {
logger = logger.Named(loggerName)
}
if options.AtomicLevelServer.Addr != "" {
runAtomicLevelServer(cfg.Level, options.AtomicLevelServer)
}
return logger, nil
}
// CloneLogger return the global logger copy which add a new name
func CloneLogger(name string, fields ...zap.Field) *zap.Logger {
rwMutex.RLock()
defer rwMutex.RUnlock()
copy := *logger
clogger := ©
clogger = clogger.Named(name)
if len(fields) > 0 {
clogger = clogger.With(fields...)
}
return clogger
}
// AttachCore add a core to zap logger
func AttachCore(l *zap.Logger, c zapcore.Core) *zap.Logger {
return l.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewTee(core, c)
}))
}
// ReplaceLogger 替换默认的全局 logger 为传入的新 logger
// 返回函数,调用它可以恢复全局 logger 为上一次的 logger
func ReplaceLogger(newLogger *zap.Logger) func() {
rwMutex.Lock()
defer rwMutex.Unlock()
// 备份原始 logger 以便恢复
prevLogger := logger
// 替换为新 logger
logger = newLogger
return func() { ReplaceLogger(prevLogger) }
}
// TextLevel 返回默认 logger 的 字符串 level
func TextLevel() string {
b, _ := atomicLevel.MarshalText()
return string(b)
}
// SetLevel 使用字符串级别设置默认 logger 的 atomic level
func SetLevel(lvl string) {
Warn(nil, "Set logging atomicLevel "+lvl)
atomicLevel.UnmarshalText([]byte(strings.ToLower(lvl)))
}
// SentryClient 返回默认 sentry client
func SentryClient() *sentry.Client {
return sentryClient
}
// ServerIP 获取当前 IP
func ServerIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return ""
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
// 运行 atomic level server
func runAtomicLevelServer(atomicLevel zap.AtomicLevel, options AtomicLevelServerOption) {
go func() {
// curl -X GET http://host:port
// curl -X PUT http://host:port -d '{"level":"info"}'
Debug(nil, "Running AtomicLevel HTTP server on "+options.Addr+options.Path)
urlPath := "/"
if options.Path != "" {
urlPath = options.Path
}
levelServer := http.NewServeMux()
levelServer.HandleFunc(urlPath, func(w http.ResponseWriter, r *http.Request) {
msg := fmt.Sprintf("%s %s the logger atomic level", r.RemoteAddr, r.Method)
_, logger := NewCtxLogger(r.Context(), CloneLogger("atomiclevel"), r.Header.Get(string(TraceIDKeyname)))
if r.Method == http.MethodPut {
b, _ := ioutil.ReadAll(r.Body)
msg += " to " + string(b)
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
logger.Warn(msg)
} else {
logger.Info(msg)
}
if options.Username != "" && options.Password != "" {
if _, _, ok := r.BasicAuth(); !ok {
http.Error(w, "need to basic auth", http.StatusUnauthorized)
return
}
}
atomicLevel.ServeHTTP(w, r)
})
if err := http.ListenAndServe(options.Addr, levelServer); err != nil {
Error(nil, "logging NewLogger levelServer ListenAndServe error:"+err.Error())
}
}()
}