目录
- 一. 基础版
- 二. gin 中的日志处理
- 三. Zap日志库
- SugearedLogger
- Logger
- 使用介绍
- 1. 初始化logger
- 2. 定制logger
- 3. 格式化时间和添加调用者信息
- 日志轮转与归档 lumberjack
- gin 与 zap
- 三. go-zero 中的 logx
- 四. 日志收集 Logstash
- 五. go-zero 之 go-stash
一. 基础版
- 基础版本中通过手动写文件的方式,基于golang标准库的log.Logger实现日志的写入
- 创建保存日志配置的结构体
- 创建用来保存处理日志信息的上下文结构体,绑定日志文件处理,日志打印方法
- 在日志打印方法中使用标准库中的log.Logger.()组合协程实现异步日志打印
package test
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
const DATE_FORMAT = "2006-01-02 15:04:05"
//日志级别
type LEVEL byte
const (
TRACE LEVEL = iota
INFO
WARN
ERROR
OFF
)
//日志打印相关全局锁
var msg_mutex sync.Mutex
var fileLog *FileLogger
//1.创建日志配置结构体
type LoggerConf struct {
FileDir string //当前日志文件存放路径
FileName string //当前日志文件名
Prefix string //当前日志文件名前缀
Level string //当前日志级别
}
//关闭日志
func CloseLogger() {
if fileLog != nil {
close(fileLog.logChan)
fileLog.lg = nil
fileLog.logFile.Close()
fileLog = nil
}
}
//2.创建日志上下文结构体
type FileLogger struct {
fileDir string
fileName string
prefix string
date *time.Time
logFile *os.File //当前日志文件句柄
lg *log.Logger //golang底层提供的日志操作对象
logLevel LEVEL //日志级别
mu *sync.RWMutex //用来保证创建日志文件并发安全锁
logChan chan string //存放日志数据通道
}
//3.启动服务时调用该函对日志相关业务进行初始化
func New() *FileLogger {
//1.读取配置文件,创建日志配置结构体
conf := LoggerConf{
FileDir: "./log",
FileName: "mgr",
Prefix: "pms",
Level: "TRACE",
}
//2.判断当前配置的日志级别
var logLevel LEVEL
if strings.EqualFold(conf.Level, "OFF") {
logLevel = OFF
} else if strings.EqualFold(conf.Level, "TRACE") {
logLevel = TRACE
} else if strings.EqualFold(conf.Level, "WARN") {
logLevel = WARN
} else if strings.EqualFold(conf.Level, "ERROR") {
logLevel = ERROR
} else {
logLevel = INFO
}
//3.封装打印日志上下文
t, _ := time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
f := &FileLogger{
fileDir: conf.FileDir,
fileName: conf.FileName,
prefix: conf.Prefix,
mu: new(sync.RWMutex),
logChan: make(chan string, 5000),
date: &t,
logLevel: logLevel,
}
//4.判断日志文件是否存在,不存在则创建
f.isExistOrCreate()
f.deleteFiles()
logFile := filepath.Join(f.fileDir, f.fileName)
var err error
f.logFile, err = os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return nil
}
fmt.Println("open log file : " + logFile)
f.lg = log.New(f.logFile, f.prefix, log.LstdFlags|log.Lmicroseconds)
go f.logWriter()
return f
}
// 日志文件是否存在,不存在则创建
func (f FileLogger) isExistOrCreate() {
_, err := os.Stat(f.fileDir)
if err != nil && !os.IsExist(err) {
err2 := os.Mkdir(f.fileDir, 0755)
if err2 != nil {
fmt.Println("log create path error : " + f.fileDir)
os.Exit(0)
}
}
}
//如果分割写入日志时,删除过期日志文件
func (f *FileLogger) deleteFiles() {
rd, err := ioutil.ReadDir(f.fileDir)
if err != nil {
return
}
now := time.Now()
//todo 读取配置文件配置的日志过期天数
days := 2
if days < 1 || days > 30 {
days = 7
}
_onemonthbefore := now.Add(time.Duration(-24*days) * time.Hour)
for _, fi := range rd {
if !fi.IsDir() {
if fi.ModTime().Before(_onemonthbefore) {
fmt.Println("remove log file : " + filepath.Join(f.fileDir, fi.Name()))
os.Remove(filepath.Join(f.fileDir, fi.Name()))
}
}
}
}
//一条日志格式化方法
func (f *FileLogger) split() (err error) {
f.mu.Lock()
defer f.mu.Unlock()
logFile := filepath.Join(f.fileDir, f.fileName)
logFileBak := logFile + "." + f.date.Format(DATE_FORMAT)
if f.logFile != nil {
f.logFile.Close()
}
err = os.Rename(logFile, logFileBak)
if err != nil {
return
}
t, _ := time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
f.date = &t
f.logFile, err = os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return
}
f.lg = log.New(f.logFile, f.prefix, log.LstdFlags|log.Lmicroseconds)
return
}
// 日志写入
func (f *FileLogger) logWriter() {
defer func() { recover() }()
for {
str := <-f.logChan
f.mu.RLock()
f.lg.Output(2, str)
f.mu.RUnlock()
}
}
//打印日志
func TraceLog(msgid string, format string, a ...interface{}) {
msg_mutex.Lock()
if fileLog == nil {
fileLog = New()
}
msg_mutex.Unlock()
_, file, line, _ := runtime.Caller(1)
if fileLog.logLevel <= TRACE {
fileLog.logChan <- fmt.Sprintf(" Trace [%v:%v] %s ", filepath.Base(file), line, msgid) + fmt.Sprintf(format, a...)
}
}
//打印info级别日志
func InfoLog(msgid string, format string, a ...interface{}) {
msg_mutex.Lock()
if fileLog == nil {
fileLog = New()
}
msg_mutex.Unlock()
_, file, line, _ := runtime.Caller(1)
if fileLog.logLevel <= INFO {
fileLog.logChan <- fmt.Sprintf(" INFO [%v:%v] %s ", filepath.Base(file), line, msgid) + fmt.Sprintf(format, a...)
}
}
//打印warn级别日志
func WarnLog(msgid string, format string, a ...interface{}) {
msg_mutex.Lock()
if fileLog == nil {
fileLog = New()
}
msg_mutex.Unlock()
_, file, line, _ := runtime.Caller(1)
if fileLog.logLevel <= WARN {
fileLog.logChan <- fmt.Sprintf(" WARN [%v:%v] %s ", filepath.Base(file), line, msgid) + fmt.Sprintf(format, a...)
}
}
//打印error级别日志
func ErrorLog(msgid string, format string, a ...interface{}) {
msg_mutex.Lock()
if fileLog == nil {
fileLog = New()
}
msg_mutex.Unlock()
_, file, line, _ := runtime.Caller(1)
if fileLog.logLevel <= ERROR {
fileLog.logChan <- fmt.Sprintf(" ERROR [%v:%v] %s ", filepath.Base(file), line, msgid) + fmt.Sprintf(format, a...)
}
//fmt.Printf("ERROR:["+getTimeStr()+"]"+format, a...)
//fmt.Println("")
}
二. gin 中的日志处理
- gin提供了专门用来处理long配置:
- LoggerConfig
- LogFormatter: 一个函数类型,用于格式化日志
- LogFormatterParams: 当使用 LogFormatter 格式化日志时,LogFormatterParams 作为参数传入
- defaultLogFormatter: 是一个默认的日志数据的格式化函数
type LoggerConfig struct {
// 默认为gin.defaultLogFormatter
Formatter LogFormatter
// 日志写到哪里去,默认为gin.DefaultWriter
Output io.Writer
// 哪些URL路径的日志不用记录
SkipPaths []string
}
type LogFormatter func(params LogFormatterParams) string
var defaultLogFormatter = func(param LogFormatterParams) string {
// 获取颜色码
var statusColor, methodColor, resetColor string
if param.IsOutputColor() {
statusColor = param.StatusCodeColor()
methodColor = param.MethodColor()
resetColor = param.ResetColor()
}
// 处理请求超过一分钟,将处理请求时间对秒取整
if param.Latency > time.Minute {
// Truncate in a golang < 1.8 safe way
param.Latency = param.Latency - param.Latency%time.Second
}
// 日志数据格式化
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
statusColor, param.StatusCode, resetColor,
param.Latency,
param.ClientIP,
methodColor, param.Method, resetColor,
param.Path,
param.ErrorMessage,
)
}
- 使用示例
import (
"fmt"
"gin_hello/gb"
"github.com/gin-gonic/gin"
"io"
"os"
)
func main() {
r := gin.New()
r.Use(gin.Recovery())
//1.日志输出到文件,文件所在位置
f,_ := os.OpenFile("./app01.log",os.O_CREATE|os.O_APPEND|os.O_RDWR,0644)
//配置中间件
//r.Use(gin.LoggerWithWriter(io.MultiWriter(f,os.Stdout)))
//2.配置日志输出格式与指定输出位置,返回什么格式,日志格式就是什么样子
var conf = gin.LoggerConfig{
Formatter: func(param gin.LogFormatterParams) string{
return fmt.Sprintf("客户端IP:%s,请求时间:[%s],请求方式:%s,请求地址:%s,http协议版本:%s,请求状态码:%d,响应时间:%s,客户端:%s,错误信息:%s\n",
param.ClientIP,
param.TimeStamp.Format("2006年01月02日 15:03:04"),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
},
Output: io.MultiWriter(os.Stdout,f),
}
//3.日志中间件
r.Use(gin.LoggerWithConfig(conf))
r.GET("/", func(c *gin.Context) {
c.String(200,"成功")
})
// 如果需要同时将日志写入文件和控制台,请使用以下代码。
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
//禁用控制台颜色
gin.DisableConsoleColor()
r.Run(":8000")
}
三. Zap日志库
- 由于默认日志功能有缺陷,例如不能轮转,在视图函数中不能直接使用日志记录(go标准库的logger)不能序列化等等,go又有很多开源的日志包如下:
- logrus: 目前Github上star数量最多的日志库,也是最兼容标准库的日志库
- zap: Uber推出的快速,结构化的分级日志库,无反射,零分配的JSON编码器,不是基于反射做的最快的一个日志库
- ZAP 地址
go get -u go.uber.org/zap
- 中提供了两种日志记录器: SugearedLogger 与 Logger
SugearedLogger
- SugearedLogger: 加了糖的 Logger,推荐不关键的环境中使用SugaredLogger,比其他结构化日志包快4-10倍,并且包含结构化和printf风格的api,基本使用示例
func main() {
// 初始化得到 logger 对象
logger, _ := zap.NewProduction()
// 刷新缓冲区,存盘
defer logger.Sync()
// 创建 Suger 的 logger
sugar := logger.Sugar()
sugar.Info("info 级别日志")
// 因为 NewProduction 是生成环境用的,最低级别就是info,所以不显示debug
sugar.Debug("debug 级别日志")
sugar.Error("error 级别日志")
sugar.Infof("info--格式化字符串格式日志: %s", "lqz")
sugar.Infow("info---松散类型的键值对格式日志",
// 结构化上下文为松散类型的键值对,随便写键值对
"name", "lxx",
"attempt", 3,
"backoff", time.Second,
)
}
Logger
- Logger: 当性能和类型安全重要时使用Logger,甚至比SugaredLogger还要快,但是它只支持结构化日志, 简单使用示例
func main() {
// 初始化得到 logger 对象
logger, _ := zap.NewProduction()
// 刷新缓冲区,存盘
defer logger.Sync()
logger.Info("info--松散类型的键值对格式日志",
// 作为强类型字段值的结构化上下文.
zap.String("name", "lxx"),
zap.Int("age", 19),
zap.Duration("backoff", time.Second),
)
logger.Error("error--松散类型的键值对格式日志",
zap.String("name", "lxx"),
zap.Int("age", 19),
zap.Duration("backoff", time.Second),)
}
使用介绍
- 先了解一下zpa中的日志级别划分
//const 文档下面有介绍日志级别的定义,7个日志级别
const (
// 测试 Debug
DebugLevel = zapcore.DebugLevel
// 正常 Info
InfoLevel = zapcore.InfoLevel
// 警告 warn
WarnLevel = zapcore.WarnLevel
// 错误 error
ErrorLevel = zapcore.ErrorLevel
// 严重错误级别,但小于 panic级别
DPanicLevel = zapcore.DPanicLevel
// panic 级别日志, 展示错误位置
PanicLevel = zapcore.PanicLevel
// 报错后写入日志直接退出程序
FatalLevel = zapcore.FatalLevel
)
1. 初始化logger
- zap默认提供了三种初始化logger的方式: NewExample,NewProduction和NewDevelopment
//1. NewExample: 测试阶段使用,构建了一个专门为zap的可测试示例设计的Logger
//它将DebugLevel及以上的日志作为JSON写入标准输出,
//但省略了时间戳和调用函数,以保持示例输出的简短和确定性
func NewExample(options ...Option) *Logger
//2. NewProduction: 上线阶段使用,是NewProductionConfig().build(…Option)的快捷方式,
//构建了一个合理的生产日志记录器,它将infollevel及以上的日志以JSON的形式写入标准错误
func NewProduction(options ...Option) (*Logger, error)
//3. NewDevelopment: 开发阶段使用,是NewDevelopmentConfig().Build(…选项)的快捷方式
//构建一个开发日志记录器,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。
func NewDevelopment(options ...Option) (*Logger, error)
- 通过配置生成对应的 logger,可以自定义配置,生成自己自定义的 logger
2. 定制logger
- 查看NewProduction的源码底层实际就是执行的:NewProductionConfig().Build(options…)
func NewProduction(options ...Option) (*Logger, error) {
//调用了 NewProductionConfig()方法,内部初始化创建,返回了一个 Config 对象
//Build内部通过 Config对象的配置, 利用New方法生成相应的 logger对象,并返回
return NewProductionConfig().Build(options...)
}
func (cfg Config) Build(opts ...Option) (*Logger, error) {
...
log := New(
zapcore.NewCore(enc, sink, cfg.Level),
cfg.buildOptions(errSink)...,
)
...
return log, nil
}
func New(core zapcore.Core, options ...Option) *Logger {
log := &Logger{
//Core是一个最小的、快速的记录器接口。它是为库作者设计的,用来封装更友好的API
core: core,
// 错误输出位置
errorOutput: zapcore.Lock(os.Stderr),
// 设置日志上限
addStack: zapcore.FatalLevel + 1,
// 设置时间方式
clock: zapcore.DefaultClock,
}
// 返回一个 Logger 对象的指针
return log.WithOptions(options...)
}
- 我们可以自己调用内部的相关方法, 模仿 NewProductionConfig().Build(options…) 定制化 logger对象
- 方式一: 过 new 方法得到logger对象
func main() {
//方式1
// encoder 编码, 就两种方式
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
// 日志输出路径
f,_ := os.OpenFile("./test.log",os.O_RDWR|os.O_CREATE|os.O_APPEND,0644)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(f)
core := zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel)
logger := zap.New(core)
defer logger.Sync()
logger.Info("info级别写到文件", zap.String("name", "lxx"))
logger.Debug("debug级别写到文件", zap.String("name", "lxx"))
}
- 方式二: 通过修改config配置生成logger对象
func main() {
// 方式2
conf := zap.NewProductionConfig()
// 修改 config对象的属性
// conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./test1.log"}
// 修改日志级别
conf.Level=zap.NewAtomicLevelAt(zap.DebugLevel)
// 通过config对象得到logger对象指针
logger,_ := conf.Build()
logger.Debug("debug级别日志")
logger.Error("error级别日志")
}
3. 格式化时间和添加调用者信息
- 提供的三种配置,时间显示都是时间戳格式,对人来说,这个时间格式是极其不友好的,因此我们可以通过自定制将时间格式转换为对人友好的时间格式
- 添加调用者信息"caller":“gin_log/main.go:152” 后可以快速定位错误
func main() {
//方式1
// 修改时间格式
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// encoder 编码, 就两种方式
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewJSONEncoder(encoderConfig)
// 日志输出路径
f,_ := os.OpenFile("./test.log",os.O_RDWR|os.O_CREATE|os.O_APPEND,0644)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(f)
core := zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel)
// 增加调用者信息
logger := zap.New(core,zap.AddCaller())
defer logger.Sync()
logger.Info("info级别写到文件", zap.String("name", "lxx"))
logger.Debug("debug级别写到文件", zap.String("name", "lxx"))
}
func main() {
//方式2 自带调用者信息
conf := zap.NewProductionConfig()
// 修改 config对象的属性
// conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./test1.log"}
// 修改日志级别
conf.Level=zap.NewAtomicLevelAt(zap.DebugLevel)
// 修改时间格式
conf.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 通过config对象得到logger对象指针
logger,_ := conf.Build()
logger.Debug("debug级别日志")
logger.Error("error级别日志")
}
日志轮转与归档 lumberjack
- 为了避免日志文件过大,将日志文件分类,但 Zap 本身不支持切割归档日志文件,使用第三方库 Lumberjack 来实现
go get -u github.com/natefinch/lumberjack
- 方案
func getwriteSyncer() zapcore.WriteSyncer{
lumberJackLogger := &lumberjack.Logger{
Filename: "./test3.log", // Filename: 日志文件的位置
MaxSize: 1, // 在进行切割之前,日志文件的最大大小(以 MB 为单位)
MaxBackups: 5, // 保留旧文件的最大个数
MaxAge: 30, // 保留旧文件的最大天数
Compress: false, // 是否压缩 / 归档旧文件
}
return zapcore.AddSync(lumberJackLogger)
}
func main() {
// 修改时间格式
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// encoder 编码, 就两种方式
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewJSONEncoder(encoderConfig)
// 日志输出路径
writeSyncer := getwriteSyncer()
core := zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel)
// 增加调用者信息
logger := zap.New(core,zap.AddCaller())
defer logger.Sync()
logger.Info("info级别写到文件", zap.String("name", "lxx"))
logger.Debug("debug级别写到文件", zap.String("name", "lxx"))
}
gin 与 zap
- 自定制gin中使用zap
package logger
import (
"github.com/gin-gonic/gin"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"time"
)
// 1 定义一下logger使用的常量
const (
mode = "dev" //开发模式
filename = "web_app.log" // 日志存放路径
//level = "debug" // 日志级别
level = zapcore.DebugLevel // 日志级别
max_size = 200 //最大存储大小
max_age = 30 //最大存储时间
max_backups = 7 //#备份数量
)
// 2 初始化Logger对象
func InitLogger() (err error) {
// 创建Core三大件,进行初始化
writeSyncer := getLogWriter(filename, max_size, max_backups, max_age)
encoder := getEncoder()
// 创建核心-->如果是dev模式,就在控制台和文件都打印,否则就只写到文件中
var core zapcore.Core
if mode == "dev" {
// 开发模式,日志输出到终端
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// NewTee创建一个核心,将日志条目复制到两个或多个底层核心中。
core = zapcore.NewTee(
zapcore.NewCore(encoder, writeSyncer, level),
zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), level),
)
} else {
core = zapcore.NewCore(encoder, writeSyncer, level)
}
//core := zapcore.NewCore(encoder, writeSyncer, level)
// 创建 logger 对象
log := zap.New(core, zap.AddCaller())
// 替换全局的 logger, 后续在其他包中只需使用zap.L()调用即可
zap.ReplaceGlobals(log)
return
}
// 获取Encoder,给初始化logger使用的
func getEncoder() zapcore.Encoder {
// 使用zap提供的 NewProductionEncoderConfig
encoderConfig := zap.NewProductionEncoderConfig()
// 设置时间格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 时间的key
encoderConfig.TimeKey = "time"
// 级别
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 显示调用者信息
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// 返回json 格式的 日志编辑器
return zapcore.NewJSONEncoder(encoderConfig)
}
// 获取切割的问题,给初始化logger使用的
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
// 使用 lumberjack 归档切片日志
lumberJackLogger := &lumberjack.Logger{
Filename: filename,
MaxSize: maxSize,
MaxBackups: maxBackup,
MaxAge: maxAge,
}
return zapcore.AddSync(lumberJackLogger)
}
// GinLogger 用于替换gin框架的Logger中间件,不传参数,直接这样写
func GinLogger(c *gin.Context) {
logger := zap.L()
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next() // 执行视图函数
// 视图函数执行完成,统计时间,记录日志
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
// GinRecovery 用于替换gin框架的Recovery中间件,因为传入参数,再包一层
func GinRecovery(stack bool) gin.HandlerFunc {
logger := zap.L()
return func(c *gin.Context) {
defer func() {
// defer 延迟调用,出了异常,处理并恢复异常,记录日志
if err := recover(); err != nil {
// 这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------开始--------
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
//httputil包预先准备好的DumpRequest方法
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// 如果连接已断开,我们无法向其写入状态
c.Error(err.(error))
c.Abort()
return
}
// 这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------结束--------
// 是否打印堆栈信息,使用的是debug.Stack(),传入false,在日志中就没有堆栈信息
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
// 有错误,直接返回给前端错误,前端直接报错
//c.AbortWithStatus(http.StatusInternalServerError)
// 该方式前端不报错
c.String(200,"访问出错了")
}
}()
c.Next()
}
}
- main
package main
import (
"gin_zap_demo/logger"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func main() {
logger.InitLogger()
r:=gin.New()
r.Use(logger.GinLogger,logger.GinRecovery(true))
r.GET("/", func(c *gin.Context) {
zap.L().Error("错误日志")
c.String(200,"hello")
})
r.Run(":8080")
}
三. go-zero 中的 logx
1 . 参考博客, go-zero中的日志组件
四. 日志收集 Logstash
- Logstash是一款轻量级的日志搜集处理框架,可以方便的把分散的、多样化的日志搜集起来,并进行自定义的处理,然后传输到指定的位置,比如某个服务器或者文件
五. go-zero 之 go-stash
- go-stash是go-zero中提供的一个 logstash 的 Go 语言替代版,我们用 go-stash 相比原先的 logstash 节省了2/3的服务器资源