go-logrus 日志框架封装使用

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,
    })
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容