日志配置 原创
1、说明
日志,用于记录整个业务系统的运行情况,比如:
- HTTP 请求日志
- SQL 日志
- 错误日志
一个好的应用,都会有自己的日志记录以及保存方式。
本节课,我们使用 zap 来构建自己的日志库。
2、安装依赖
$ go get go.uber.org/zap
$ go get gopkg.in/natefinch/lumberjack.v2
3、开发组件
3.1、日志分级
我们将日志分为 6 个等级,如下:
- debug:信息量大,可以记录非常详细的运行日志
- info:业务级别的运行日志
- warn:需要引起关注的信息,也称为警告信息
- error:错误信息,需要引起重视
- panic:记录异常日志,并抛出异常
- fatal:致命错误,会导致应用程序退出
在pkg/logger
目录中创建logger.go
文件,写入以下内容:
package logger
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// 定义日志级别
type Level = zapcore.Level
const (
DebugLevel Level = zap.DebugLevel
InfoLevel Level = zap.InfoLevel
WarnLevel Level = zap.WarnLevel
ErrorLevel Level = zap.ErrorLevel
PanicLevel Level = zap.PanicLevel
FatalLevel Level = zap.FatalLevel
)
func WithLevel(level string) Level {
switch level {
case "debug":
return DebugLevel
case "info":
return InfoLevel
case "warn":
return WarnLevel
case "error":
return ErrorLevel
case "panic":
return PanicLevel
case "fatal":
return PanicLevel
default:
return InfoLevel
}
}
在上述代码中,预定义了应用日志的 Level 的具体类型,并且把日志分为 debug、info、warn、error、fatal 和 panic 六个等级,以便在不同的使用场景中记录不同级别的日志。
3.2、日志配置
首先,在 configs/config.yaml
中增加如下配置:
App:
LogLevel: debug
LogType: single
LogFileName: storage/logs/app.log
LogMaxSize: 64
LogMaxBackup: 5
LogMaxAge: 30
LogCompress: false
然后,在 pkg/setting/section.go
中,添加日志对应的结构体,如下:
......
type AppSettingS struct {
LogLevel string
LogType string
LogFileName string
LogMaxSize int
LogMaxBackup int
LogMaxAge int
LogCompress bool
}
......
再到 global/setting.go
中添加全局变量,如下:
package global
import "github.com/joker-bai/hawkeye/pkg/setting"
var (
ServerSetting *setting.ServerSettingS
DatabaseSetting *setting.DatabaseSettingS
AppSetting *setting.AppSettingS
)
再者,到 initialize/setting.go
中把配置和结构体关联,如下:
func SetupSetting() error {
......
// 加载应用配置
if err := s.ReadSection("App", &global.AppSetting); err != nil {
return err
}
......
}
到此,日志配置相关操作完成。
3.2、日志初始化
在完成了日志的分级方法后,开始编写具体的方法,用来对日志的实例初始化和标准化参数进行绑定。
继续在 pkg/logger/logger.go
中写入日志初始化的内容:
type Logger struct {
logger *zap.Logger
level Level
}
// RotateOptions 日志切割结构体
type RotateOptions struct {
FileName string
MaxSize int
MaxAge int
MaxBackups int
Compress bool
}
// 其他选项
type Option = zap.Option
var (
AddCaller = zap.AddCaller
AddStacktrace = zap.AddStacktrace
AddCallerSkip = zap.AddCallerSkip
)
func NewLogger(writer io.Writer, level Level, ropt RotateOptions, options ...Option) *Logger {
// 获取日志写入介质
if writer == nil {
panic("writer is nil")
}
cfg := zap.NewProductionConfig()
// 处理日志输出时间
cfg.EncoderConfig.EncodeTime = func(t time.Time, pae zapcore.PrimitiveArrayEncoder) {
pae.AppendString(t.Format("2006-01-02T15:04:05"))
}
// 指定日志记录方式
// 这里采取的是终端和文件两种输出方式
writeSync := zapcore.NewMultiWriteSyncer(
zapcore.AddSync(os.Stdout),
zapcore.AddSync(&lumberjack.Logger{
Filename: ropt.FileName,
MaxSize: ropt.MaxSize,
MaxBackups: ropt.MaxBackups,
MaxAge: ropt.MaxAge,
Compress: ropt.Compress,
}),
)
core := zapcore.NewCore(
zapcore.NewJSONEncoder(cfg.EncoderConfig),
zapcore.AddSync(writeSync),
zapcore.Level(level),
)
return &Logger{
logger: zap.New(core, options...),
level: level,
}
}
func (l *Logger) Sync() error {
return l.logger.Sync()
}
然后,在 global
包中创建 logger.go
文件,声明全局的日志对象,如下:
package global
import "github.com/joker-bai/hawkeye/pkg/logger"
var (
Log *logger.Logger
)
再到 initialize
包中创建 logger.go
文件,写入以下内容:
package initialize
import (
"os"
"github.com/joker-bai/kubemana/global"
"github.com/joker-bai/kubemana/pkg/logger"
)
func SetupLogger() error {
file, err := os.OpenFile(global.AppSetting.LogFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
level := logger.WithLevel(global.AppSetting.LogLevel)
options := []logger.Option{
logger.AddCaller(),
logger.AddCallerSkip(1),
logger.AddStacktrace(logger.ErrorLevel),
}
global.Logger = logger.NewLogger(file, level, logger.RotateOptions{
MaxSize: global.AppSetting.LogMaxSize,
MaxBackups: global.AppSetting.LogMaxBackup,
MaxAge: global.AppSetting.LogMaxAge,
Compress: global.AppSetting.LogCompress,
FileName: global.AppSetting.LogFileName,
}, options...)
defer global.Logger.Sync()
return nil
}
最后,再将日志初始化的操作加入到 main.go
文件中,使之在服务启动过后能正常使用。
func init() {
// 初始化配置
if err := initialize.SetupSetting(); err != nil {
log.Fatalf("Failed to init setting, error: %s", err)
}
// 初始化日志
if err := initialize.SetupLogger(); err != nil {
log.Fatalf("Failed to init logger, error: %s", err)
}
// 初始化数据库
if err := initialize.SetupDB(); err != nil {
log.Fatalf("Failed to init database, error: %s", err)
}
}
到此,日志初始化操作完成。
3.3、添加日志操作方法
日志操作方法主要是对 zap 的封装,方法我们调用。
在 pkg/logger/logger.go
中写入如下代码:
// Dump 调试专用,不会中断程序,会在终端打印出 warning 消息。
// 第一个参数会使用 json.Marshal 进行渲染,第二个参数消息(可选)
//
// logger.Dump(user.User{Name:"test"})
// logger.Dump(user.User{Name:"test"}, "用户信息")
func (l *Logger) Dump(value interface{}, msg ...string) {
valueString := l.jsonString(value)
// 判断第二个参数是否传参 msg
if len(msg) > 0 {
l.logger.Warn("Dump", zap.String(msg[0], valueString))
} else {
l.logger.Warn("Dump", zap.String("data", valueString))
}
}
// Debug 调试日志,详尽的程序日志
// 调用示例:
//
// logger.Debug("Database", zap.String("sql", sql))
func (l *Logger) Debug(msg string, fields ...zap.Field) {
l.logger.Debug(msg, fields...)
}
// Info 告知类日志
func (l *Logger) Info(msg string, fields ...zap.Field) {
l.logger.Info(msg, fields...)
}
// Warn 警告类
func (l *Logger) Warn(msg string, fields ...zap.Field) {
l.logger.Warn(msg, fields...)
}
// Error 错误时记录,不应该中断程序,查看日志时重点关注
func (l *Logger) Error(msg string, fields ...zap.Field) {
l.logger.Error(msg, fields...)
}
func (l *Logger) Panic(msg string, fields ...zap.Field) {
l.logger.Panic(msg, fields...)
}
func (l *Logger) Fatal(msg string, fields ...zap.Field) {
l.logger.Fatal(msg, fields...)
}
func (l *Logger) jsonString(value interface{}) string {
b, err := json.Marshal(value)
if err != nil {
l.logger.Error("Logger", zap.String("JSON marshal error", err.Error()))
}
return string(b)
}
3.4、测试一下
现在在 main.go 中添加一行日志打印记录,如下:
func main() {
// 初始化
engine := initialize.NewEngine()
server := &http.Server{
Addr: ":" + global.ServerSetting.Port,
WriteTimeout: time.Second * global.ServerSetting.WriteTimeout,
ReadTimeout: time.Second * global.ServerSetting.ReadTimeout,
IdleTimeout: time.Second * global.ServerSetting.IdleTimeout,
Handler: engine,
}
global.Log.Info("server start", zap.String("addr", server.Addr))
// 运行服务
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to start http server, error: %s", err)
}
}
然后使用 go run main.go
启动服务,控制台输出如下:
然后日志文件也正常生成,记录如下:
3.5、添加.gitignore 文件
````*
!.gitignore
3.6、代码版本
本节功能开发完成后,记得给代码标记版本:
$ git add .
$ git commit -m "日志配置"