Skip to content

Instantly share code, notes, and snippets.

@SolemnJoker
Last active December 7, 2020 03:32
Show Gist options
  • Save SolemnJoker/2721a9bac8dd6edd1d741b09ef20af4f to your computer and use it in GitHub Desktop.
Save SolemnJoker/2721a9bac8dd6edd1d741b09ef20af4f to your computer and use it in GitHub Desktop.
[go logrus] #go #log #logrus

创建log,启用hook,增加log按日期文件切分,自动删除旧日志,记录行号等功能

import(
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
log "github.com/sirupsen/logrus"
)
func initLog() {
	path := "./log/screen-monitor.log"
	vars.Logger = log.New()
	vars.Logger.Hooks.Add(log_hooks.NewContextHook())

	writer, _ := rotatelogs.New(
		path+".%Y%m%d",
		rotatelogs.WithLinkName(path),
		rotatelogs.WithMaxAge(time.Duration(3*30*24)*time.Hour),
		rotatelogs.WithRotationTime(time.Duration(24)*time.Hour),
	)
	vars.Logger.SetOutput(writer)
}
package log_hooks

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	"runtime"
	"strings"
)

// ContextHook for log the call context
type contextHook struct {
	Field  string
	Skip   int
	levels []log.Level
}

// NewContextHook use to make an hook
// 根据上面的推断, 我们递归深度可以设置到5即可.
func NewContextHook(levels ...log.Level) log.Hook {
	hook := contextHook{
		Field:  "line",
		Skip:   5,
		levels: levels,
	}
	if len(hook.levels) == 0 {
		hook.levels = log.AllLevels
	}
	return &hook
}

// Levels implement levels
func (hook contextHook) Levels() []log.Level {
	return log.AllLevels
}

// Fire implement fire
func (hook contextHook) Fire(entry *log.Entry) error {
	entry.Data[hook.Field] = findCaller(hook.Skip)
	return nil
}

// 对caller进行递归查询, 直到找到非log包产生的第一个调用.
// 因为filename我获取到了上层目录名, 因此所有log包的调用的文件名都是 log/...
// 因此通过排除log开头的文件名, 就可以排除所有log包的自己的函数调用
func findCaller(skip int) string {
	file := ""
	line := 0
	for i := 0; i < 10; i++ {
		file, line = getCaller(skip + i)
		if !strings.HasPrefix(file, "log") {
			break
		}
	}
	return fmt.Sprintf("%s:%d", file, line)
}

// 这里其实可以获取函数名称的: fnName := runtime.FuncForPC(pc).Name()
// 但是我觉得有 文件名和行号就够定位问题, 因此忽略了caller返回的第一个值:pc
// 在标准库log里面我们可以选择记录文件的全路径或者文件名, 但是在使用过程成并发最合适的,
// 因为文件的全路径往往很长, 而文件名在多个包中往往有重复, 因此这里选择多取一层, 取到文件所在的上层目录那层.
func getCaller(skip int) (string, int) {
	_, file, line, ok := runtime.Caller(skip)
	//fmt.Println(file)
	//fmt.Println(line)
	if !ok {
		return "", 0
	}
	n := 0
	for i := len(file) - 1; i > 0; i-- {
		if file[i] == '/' {
			n++
			if n >= 2 {
				file = file[i+1:]
				break
			}
		}
	}
	return file, line
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment