go-logrus 日志框架封装使用
配置
{
"log_level": "debug",
"log_output_dir": "attachment/logs",
"log_dir_max_size_g":5,
"log_dir_max_file_count":20,
"log_file_date_fmt": "20060102",
"log_file_suffix": ".log",
"log_file_prefix": "icnc_run",
"log_file_rotation_hour": 24,
"log_file_rotation_count": 10,
"log_file_max_size_m": 1024,
"log_report_caller": true,
"log_level_report_caller": [
"error"
],
"log_formatter": "text",
"web_log_file_prefix": "icnc_web"
}
package logs
import (
"eastwan.com/xy-icnc/config"
"github.com/astaxie/beego/logs"
)
type logsConfig struct {
//--- 日志级别: Panic,Fatal,Error,Warn,Info,Debug,Trace
LogLevel string `json:"log_level"`
//--- 日志文件输出目录
LogOutputDir string `json:"log_output_dir"`
//--- 输出文件的目录的限制大小
LogDirMaxSizeG float64 `json:"log_dir_max_size_g"`
//--- 输出目录的文件限制个数
LogDirMaxFileCount int `json:"log_dir_max_file_count"`
//--- 运行操作日志文件的前缀
LogFilePrefix string `json:"log_file_prefix"`
//--- 日志文件切割 小时为单位
LogFileRotationHour int `json:"log_file_rotation_hour"`
//--- 输出日志文件的后缀
LogFileSuffix string `json:"log_file_suffix"`
//--- 输出日志文件的时间格式
LogFileDateFmt string `json:"log_file_date_fmt"`
//--- 日志文件最大size 单位m
LogFileMaxSizeM float64 `json:"log_file_max_size_m"`
//--- 日志文件最多个数,达到该数则不会切割
//--- 与LogDirMaxFileCount的区别:LogDirMaxFileCount是指硬盘目录下文件的限制个数,LogFileRotationCount是指logrus内存中存储的文件切割数
LogFileRotationCount int64 `json:"log_file_rotation_count"`
//--- 显示文件和行号
LogReportCaller bool `json:"log_report_caller"`
//--- 指定日志级别才会打印调用文件和行号,不指定的情况下若 LogReportCaller==true,则默认所有级别打印,反之不显示
LogLevelReportCaller []string `json:"log_level_report_caller"`
//--- 日志打印格式 text/json,默认text
LogFormatter string `json:"log_formatter"`
//--- web 运行操作日志文件的前缀
WebLogFilePrefix string `json:"web_log_file_prefix"`
}
var LogsConfig logsConfig
var logsConfigJsonPath = "config/logs.json"
// 加载配置文件
func init() {
logs.Debug("package model,logs init()...")
err := config.LoadConfig(logsConfigJsonPath, &LogsConfig, "json", func() {
InitLogs()
})
if err != nil {
logs.Error("Error read logs.json,%v", err.Error())
return
}
}
func InitLogs() {
InitLogrus()
}
logs
package logs
import (
"fmt"
"github.com/sirupsen/logrus"
"os"
"strings"
)
//Panic,Fatal,Error,Warn,Info,Debug,Trace
var Logger *logrus.Logger
func InitLogrus() {
InitRunLogrus()
InitWebLogrus()
}
func InitRunLogrus() {
//初始化日志
Logger = NewDefaultLogrus()
//打印文件和行号
if LogsConfig.LogReportCaller {
callerHook := GetCallerHook{
Field: "caller",
levels: nil,
}
Logger.AddHook(&callerHook)
}
//(1)文件
fileHook, err := LocalRunHook()
if err != nil {
Logger.Error("config local web file system for Logger error: %v", err)
}
Logger.AddHook(fileHook)
//(2)ES
//esHookRun, errRun := newElasticLogHook(LogsConfig.WebLogEsIndex, LogsConfig.WebLogEsType, LogsConfig.WebLogLevel, LogsConfig.WebAddr)
//if errRun == nil {
// Logger.AddHook(esHookRun)
//} else {
// logrus.Fatal(errRun)
//}
}
func NewDefaultLogrus() *logrus.Logger {
logger := logrus.New()
logLev, err := logrus.ParseLevel(LogsConfig.LogLevel)
logger.SetLevel(logLev)
if err != nil {
logger.SetLevel(logrus.WarnLevel)
}
logger.SetOutput(os.Stdout)
//logger.SetOutput(os.Stderr)
//弃用官方的Caller,由于封装了logrus,打印的级别不是预期效果
//logger.SetReportCaller(true)
// (3) 日志格式
logger.SetFormatter(&logrus.TextFormatter{
//// CallerPrettyfier can be set by the user to modify the content
//// of the function and file keys in the data when ReportCaller is
//// activated. If any of the returned value is the empty string the
//// corresponding key will be removed from fields.
//CallerPrettyfier func(*runtime.Frame) (function string, file string)
//
//ForceColors: true, //show colors
//DisableColors:true,//remove colors
TimestampFormat: "2006-01-02 15:04:05",
})
if LogsConfig.LogFormatter == "json" {
logger.SetFormatter(&logrus.JSONFormatter{
//// CallerPrettyfier can be set by the user to modify the content
//// of the function and file keys in the data when ReportCaller is
//// activated. If any of the returned value is the empty string the
//// corresponding key will be removed from fields.
//CallerPrettyfier func(*runtime.Frame) (function string, file string)
//
//ForceColors: true, //show colors
//DisableColors:true,//remove colors
TimestampFormat: "2006-01-02 15:04:05",
})
}
return logger
}
//日志级别: logrus有7个日志级别,依次是Trace << Debug << Info << Warning << Error << Fatal << Panic,级别越高
//只输出不低于当前级别是日志数据
func WithFields(fields logrus.Fields) *logrus.Logger {
Logger.WithFields(fields)
return Logger
}
func Trace(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Trace(FormatLog(f, v...))
return
}
Logger.Trace(FormatLog(f, v...))
}
func Debug(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Debug(FormatLog(f, v...))
return
}
Logger.Debug(FormatLog(f, v...))
}
func Info(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Info(FormatLog(f, v...))
return
}
Logger.Info(FormatLog(f, v...))
}
func Print(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Print(FormatLog(f, v...))
return
}
Logger.Print(FormatLog(f, v...))
}
func Warn(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Warn(FormatLog(f, v...))
return
}
Logger.Warn(FormatLog(f, v...))
}
//func Warning(f interface{}, v ...interface{}) {
//if Logger==nil{
//logrus.Warning(FormatLog(f, v...))
//}
// Logger.Warning(FormatLog(f, v...))
//}
func Error(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Error(FormatLog(f, v...))
return
}
Logger.Error(FormatLog(f, v...))
}
func Fatal(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Fatal(FormatLog(f, v...))
return
}
Logger.Fatal(FormatLog(f, v...))
}
func Panic(f interface{}, v ...interface{}) {
if Logger == nil {
logrus.Panic(FormatLog(f, v...))
return
}
Logger.Panic(FormatLog(f, v...))
}
func FormatLog(f interface{}, v ...interface{}) string {
var msg string
switch f.(type) {
case string:
msg = f.(string)
if len(v) == 0 {
return msg
}
if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
//format string
} else {
//do not contain format char
msg += strings.Repeat(" %v", len(v))
}
default:
msg = fmt.Sprint(f)
if len(v) == 0 {
return msg
}
msg += strings.Repeat(" %v", len(v))
}
return fmt.Sprintf(msg, v...)
}
func FmtRotateDate(dateFmt string) string {
var fmtStr = dateFmt
//%Y%m%d%H%M
fmtStr = strings.Replace(fmtStr, "2006", "%Y", -1)
fmtStr = strings.Replace(fmtStr, "01", "%m", -1)
fmtStr = strings.Replace(fmtStr, "02", "%d", -1)
fmtStr = strings.Replace(fmtStr, "15", "%H", -1)
fmtStr = strings.Replace(fmtStr, "04", "%M", -1)
fmtStr = strings.Replace(fmtStr, "05", "%S", -1)
return fmtStr
}
hook
package logs
import (
"fmt"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"runtime"
"strings"
"time"
)
const (
Time_Stamp_Format = "2006-01-02 15:04:05"
maximumCallerDepth int = 25
minimumCallerDepth int = 4
)
//local run hook
func LocalRunHook() (*lfshook.LfsHook, error) {
writer, err := rotatelogs.New(
LogsConfig.LogOutputDir+"/"+LogsConfig.LogFilePrefix+"_"+FmtRotateDate(LogsConfig.LogFileDateFmt)+LogsConfig.LogFileSuffix,
//--- 为最新的日志建立软连接,指向最新日志文件,
rotatelogs.WithLinkName(LogsConfig.LogFilePrefix+LogsConfig.LogFileSuffix),
//--- MaxAge and RotationCount cannot be both set 两者不能同时设置
//--- clear 设置文件清理前的最长保存时间 最小分钟为单位
//--- if both are 0, give maxAge a sane default 7 * 24 * time.Hour
//rotatelogs.WithMaxAge(24*time.hour),
//--- number 设置最多切割文件
rotatelogs.WithRotationCount(uint(LogsConfig.LogFileRotationCount)),
//--- rotate 设置日志切割时间间隔 ,默认 24 * time.Hour
rotatelogs.WithRotationTime(time.Duration(LogsConfig.LogFileRotationHour)*time.Hour),
//--- 文件达到多大则切割文件,单位为 bytes WithRotationTime and WithRotationSize 两者任意一个条件达到都会切割
rotatelogs.WithRotationSize(int64(LogsConfig.LogFileMaxSizeM*1024*1024)),
//default: rotatelogs.Local ,you can set rotatelogs.UTC
//rotatelogs.WithClock(rotatelogs.UTC),
//rotatelogs.WithLocation(time.Local),
//--- 当rotatelogs.New()创建的文件存在时,强制创建新的文件 命名为原文件的名称+序号,如a.log存在,则创建创建 a.log.1
//rotatelogs.ForceNewFile(),
//--- 文件切割后执行函数
rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
if e.Type() != rotatelogs.FileRotatedEventType {
return
}
ctx := CleanContext{
Dir: LogsConfig.LogOutputDir,
DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
DirMaxCount: LogsConfig.LogDirMaxFileCount,
}
strategyOne := CleanStrategyOne{}
result, err := NewCleanStrategy(&ctx, &strategyOne).
Clean().
Result()
Warn("文件切割,清理文件策略one已经执行完毕; 结果:%v; 错误:%v", result, err)
}))),
)
if err != nil {
return nil, err
}
lfsHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer,
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
}, &logrus.TextFormatter{
TimestampFormat: Time_Stamp_Format,
})
return lfsHook, err
}
//gin 中间件 hook
func GinHook() (*lfshook.LfsHook, error) {
// 设置 rotatelogs
writer, err := rotatelogs.New(
LogsConfig.LogOutputDir+"/"+LogsConfig.WebLogFilePrefix+"_"+FmtRotateDate(LogsConfig.LogFileDateFmt)+LogsConfig.LogFileSuffix,
rotatelogs.WithLinkName(LogsConfig.WebLogFilePrefix+LogsConfig.LogFileSuffix),
rotatelogs.WithRotationCount(uint(LogsConfig.LogFileRotationCount)),
rotatelogs.WithRotationTime(time.Duration(LogsConfig.LogFileRotationHour)*time.Hour),
rotatelogs.WithRotationSize(int64(LogsConfig.LogFileMaxSizeM*1024*1024)),
rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
if e.Type() != rotatelogs.FileRotatedEventType {
return
}
ctx := CleanContext{
Dir: LogsConfig.LogOutputDir,
DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
DirMaxCount: LogsConfig.LogDirMaxFileCount,
}
strategyOne := CleanStrategyOne{}
result, err := NewCleanStrategy(&ctx, &strategyOne).
Clean().
Result()
Warn("文件切割,清理文件策略one已经执行完毕; 结果:%v; 错误:%v", result, err)
}))),
)
if err != nil {
return nil, err
}
lfsHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer,
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
}, &logrus.TextFormatter{
TimestampFormat: Time_Stamp_Format,
})
return lfsHook, nil
}
//This method may lead to system crash in the case of concurrency.
//Please use it with caution or Control caller level;set config LogLevelReportCaller
//Get Caller hook
type GetCallerHook struct {
Field string
KipPkg string
levels []logrus.Level
}
// Levels implement levels
func (hook *GetCallerHook) Levels() []logrus.Level {
return logrus.AllLevels
}
// Fire implement fire
func (hook *GetCallerHook) Fire(entry *logrus.Entry) error {
if len(LogsConfig.LogLevelReportCaller) <= 0 ||
strings.Join(LogsConfig.LogLevelReportCaller, ",") == "" ||
strings.Contains(strings.ToLower(strings.Join(LogsConfig.LogLevelReportCaller, ",")), entry.Level.String()) {
entry.Caller = hook.getCaller()
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
entry.Data[hook.Field] = fileVal
}
return nil
}
func (hook *GetCallerHook) getCaller() *runtime.Frame {
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)
// If the caller isn't part of this package, we're done
if !strings.Contains(hook.GetKipPkg(), pkg) {
return &f
}
}
// if we got here, we failed to find the caller's context
return nil
}
// getPackageName reduces a fully qualified function name to the package name
// There really ought to be to be a better way...
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
f = f[lastSlash+1:]
break
}
}
return f
}
func (hook GetCallerHook) SetKipPkg(args ...string) {
hook.KipPkg = strings.Join(args, ",")
}
func (hook GetCallerHook) GetKipPkg() string {
if hook.KipPkg == "" {
return "logrus,logs"
}
return hook.KipPkg
}
gin日志中间件
package logs
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"os"
"time"
)
var Wlog *logrus.Logger
func InitWebLogrus(){
// 实例化
Wlog= NewDefaultLogrus()
//不输出到控制台
nullfile, _ := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
Wlog.SetOutput(nullfile)
lfsHook, err := GinHook()
if err != nil {
Wlog.Errorf("config local web file system for Logger error: %v", err)
}
// 设置输出(输出到控制台,使用一个新的Hook输出到文件)
// 新增 Hook
Wlog.AddHook(lfsHook)
}
// 日志记录到文件
func MiddlewareLogger() gin.HandlerFunc {
return func(c *gin.Context) {
// 开始时间
startTime := time.Now()
// 处理请求
c.Next()
// 结束时间
endTime := time.Now()
// 执行时间
latencyTime := endTime.Sub(startTime)
// 请求方式
reqMethod := c.Request.Method
// 请求路由
reqUri := c.Request.RequestURI
// 状态码
statusCode := c.Writer.Status()
// 请求IP
clientIP := c.ClientIP()
// 日志格式
/*Wlog.WithFields(logrus.Fields{
"status_code" : statusCode,
"latency_time" : latencyTime,
"client_ip" : clientIP,
"req_method" : reqMethod,
"req_uri" : reqUri,
}).Info()*/
// 日志格式
Wlog.Infof("| %3d | %13v | %15s | %s | %s |",
statusCode,
latencyTime,
clientIP,
reqMethod,
reqUri,
)
}
}
日志文件清理策略
package logs
import (
"fmt"
"github.com/sirupsen/logrus"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
)
//Open for extension;Closed for modification
//interface
type CleanStrategy interface {
Clean(ctx *CleanContext) ([]string, error)
}
type CleanContext struct {
Dir string `json:"dir"` //日志目录
DirMaxSizeG float64 `json:"max_size_g"` //目录限制大小,单位是G
DirMaxCount int `json:"max_count"` //目录最大文件数
}
type Clean struct {
Ctx *CleanContext
Strategy CleanStrategy
res []string
err error
}
func (c *Clean) Clean() *Clean {
c.res, c.err = c.Strategy.Clean(c.Ctx)
return c
}
func (c *Clean) Result() ([]string, error) {
return c.res, c.err
}
func (c *Clean) Err() error {
return c.err
}
func NewCleanStrategy(ctx *CleanContext, strategy CleanStrategy) *Clean {
return &Clean{
Ctx: ctx,
Strategy: strategy,
}
}
//strategyOne
type CleanStrategyOne struct{}
func (c *CleanStrategyOne) Clean(ctx *CleanContext) ([]string, error) {
Logger.Debug("CleanStrategyOne cleaning......")
var cleanfiles = make([]string, 0)
var err error
var dirfiles = make([]DirFileInfo, 0)
var dirSizeB = float64(0)
var dirSizeG = float64(0)
//打开目录,获取目录大小,目录文件个数
dirInfo, err := os.Stat(ctx.Dir)
if err != nil {
goto RES
}
if ctx.DirMaxSizeG <= 0 && ctx.DirMaxCount <= 0 {
err = fmt.Errorf("DirMaxSizeG/DirMaxCount 其中一个不能小于等于0")
goto RES
}
if !dirInfo.IsDir() {
err = fmt.Errorf("%v不是一个目录", ctx.Dir)
goto RES
}
dirfiles, dirSizeB, err = TPFuncReadDirFiles(ctx.Dir)
//文件排序
sort.Sort(DirFileInfoSort(dirfiles))
//目录限制大小>0 && 目录的大小超过限制大小时
dirSizeG, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", dirSizeB/1e9), 64)
if dirSizeG >= ctx.DirMaxSizeG && ctx.DirMaxSizeG > 0 {
//执行清理
for i := range dirfiles {
dirSizeG = dirSizeG - float64(dirfiles[i].FileInfo.Size())/1e9
errRm := os.Remove(dirfiles[i].Path)
if errRm != nil {
err = fmt.Errorf("%v;清理日志文件(%v)失败:%v", err, dirfiles[i], errRm)
continue
}
if dirSizeG <= ctx.DirMaxSizeG {
break
}
}
}
//目录文件限制个数>0 && 目录的文件个数超过限制个数时
if len(dirfiles) >= ctx.DirMaxCount && ctx.DirMaxCount > 0 {
cleanCount := len(dirfiles) - ctx.DirMaxCount
//执行清理
for i := 0; i < cleanCount; i++ {
errRm := os.Remove(dirfiles[i].Path)
if errRm != nil {
err = fmt.Errorf("%v;清理日志文件(%v)失败:%v", err, dirfiles[i], errRm)
continue
}
//记录
cleanfiles = append(cleanfiles, dirfiles[i].Path)
}
}
Logger.Debug("CleanStrategyOne end......")
RES:
return cleanfiles, err
}
type DirFileInfo struct {
Path string
FileInfo os.FileInfo
}
type DirFileInfoSort []DirFileInfo
func (s DirFileInfoSort) Len() int {
//返回传入数据的总数
return len(s)
}
func (s DirFileInfoSort) Swap(i, j int) {
//两个对象满足Less()则位置对换
//表示执行交换数组中下标为i的数据和下标为j的数据
s[i], s[j] = s[j], s[i]
}
func (s DirFileInfoSort) Less(i, j int) bool {
//按字段比较大小,此处是降序排序
//返回数组中下标为i的数据是否小于下标为j的数据
var iFile = s[i].FileInfo.Name()
var jFile = s[j].FileInfo.Name()
var iFileIndex, jFileIndex int
var iFileT, jFileT time.Time
//--- 格式化i日志文件名称 icnc_run(web)_20201208.log.1
//1.去除前缀
iFile = strings.TrimPrefix(iFile, LogsConfig.LogFilePrefix+"_")
iFile = strings.TrimPrefix(iFile, LogsConfig.WebLogFilePrefix+"_")
//2.剔除类型
iFile = strings.Replace(iFile, LogsConfig.LogFileSuffix, "", -1)
if strings.LastIndex(iFile, ".") >= 0 {
iFileIndex, _ = strconv.Atoi(iFile[strings.LastIndex(iFile, ".")+1:])
iFile = iFile[:strings.LastIndex(iFile, ".")]
}
iFileT, _ = time.ParseInLocation(LogsConfig.LogFileDateFmt, iFile, time.Local)
jFile = strings.TrimPrefix(jFile, LogsConfig.LogFilePrefix+"_")
jFile = strings.TrimPrefix(jFile, LogsConfig.WebLogFilePrefix+"_")
//2.剔除类型
jFile = strings.Replace(jFile, LogsConfig.LogFileSuffix, "", -1)
if strings.LastIndex(jFile, ".") >= 0 {
jFileIndex, _ = strconv.Atoi(jFile[strings.LastIndex(jFile, ".")+1:])
jFile = jFile[:strings.LastIndex(jFile, ".")]
}
jFileT, _ = time.ParseInLocation(LogsConfig.LogFileDateFmt, jFile, time.Local)
if iFile == jFile {
return jFileIndex > iFileIndex
}
return jFileT.After(iFileT)
}
//获取文件夹下所有的文件
func TPFuncReadDirFiles(dir string) ([]DirFileInfo, float64, error) {
var size float64
var files []DirFileInfo
var walkFunc = func(path string, info os.FileInfo, err error) error {
defer func() {
if rec := recover(); rec != nil {
Error("read dir files walkFunc panic info:%v", info)
Error("read dir files walkFunc panic:%v", rec)
}
}()
if !info.IsDir() {
fileInfo := DirFileInfo{
Path: path,
FileInfo: info,
}
files = append(files, fileInfo)
size += float64(info.Size())
}
//fmt.Printf("%s\n", path)
return nil
}
err := filepath.Walk(dir, walkFunc)
return files, size, err
}
//test
func TestCleanStrategy() {
ctx := CleanContext{
Dir: "attachment/logs",
DirMaxSizeG: 5, //5G
DirMaxCount: 10, //10个
}
strategyOne := CleanStrategyOne{}
result, err := NewCleanStrategy(&ctx, &strategyOne).Clean().Result()
//if err != nil {
// logrus.Error(formatLog("TestCleanStrategy strategyOne error:%v", err))
// return
//}
logrus.Info(FormatLog("TestCleanStrategy strategyOne result:%v;error:%v", result, err))
logrus.Info(FormatLog("TestCleanStrategy strategyOne end......"))
}
rotatelogs
writer, err := rotatelogs.New(
LogsConfig.LogOutputDir+"/"+LogsConfig.LogFilePrefix+"_"+FmtRotateDate(LogsConfig.LogFileDateFmt)+LogsConfig.LogFileSuffix,
//--- 为最新的日志建立软连接,指向最新日志文件,
rotatelogs.WithLinkName(LogsConfig.LogFilePrefix+LogsConfig.LogFileSuffix),
//--- MaxAge and RotationCount cannot be both set 两者不能同时设置
//--- clear 设置文件清理前的最长保存时间 最小分钟为单位
//--- if both are 0, give maxAge a sane default 7 * 24 * time.Hour
//rotatelogs.WithMaxAge(24*time.hour),
//--- number 设置最多切割文件
rotatelogs.WithRotationCount(uint(LogsConfig.LogFileRotationCount)),
//--- rotate 设置日志切割时间间隔 ,默认 24 * time.Hour
rotatelogs.WithRotationTime(time.Duration(LogsConfig.LogFileRotationHour)*time.Hour),
//--- 文件达到多大则切割文件,单位为 bytes WithRotationTime and WithRotationSize 两者任意一个条件达到都会切割
rotatelogs.WithRotationSize(int64(LogsConfig.LogFileMaxSizeM*1024*1024)),
//default: rotatelogs.Local ,you can set rotatelogs.UTC
//rotatelogs.WithClock(rotatelogs.UTC),
//rotatelogs.WithLocation(time.Local),
//--- 当rotatelogs.New()创建的文件存在时,强制创建新的文件 命名为原文件的名称+序号,如a.log存在,则创建创建 a.log.1
//rotatelogs.ForceNewFile(),
//--- 文件切割后执行函数
rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
if e.Type() != rotatelogs.FileRotatedEventType {
return
}
ctx := CleanContext{
Dir: LogsConfig.LogOutputDir,
DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
DirMaxCount: LogsConfig.LogDirMaxFileCount,
}
strategyOne := CleanStrategyOne{}
result, err := NewCleanStrategy(&ctx, &strategyOne).
Clean().
Result()
Warn("文件切割,清理文件策略one已经执行完毕; 结果:%v; 错误:%v", result, err)
}))),
)
if err != nil {
return nil, err
}
lfsHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer,
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
}, &logrus.TextFormatter{
TimestampFormat: Time_Stamp_Format,
})