1package log
2
3import (
4	"fmt"
5	"runtime"
6	"strings"
7
8	"github.com/sirupsen/logrus"
9)
10
11// CallerHook for log the calling file and line of the fine
12type CallerHook struct {
13	Field  string
14	Skip   int
15	levels []logrus.Level
16}
17
18// NewCallerHook use to make a hook
19func NewCallerHook(levels ...logrus.Level) logrus.Hook {
20	hook := CallerHook{
21		Field:  "source",
22		Skip:   7,
23		levels: levels,
24	}
25	if len(hook.levels) == 0 {
26		hook.levels = logrus.AllLevels
27	}
28	return &hook
29}
30
31// Levels implement applied hook to which levels
32func (h *CallerHook) Levels() []logrus.Level {
33	return logrus.AllLevels
34}
35
36// Fire logs the information of context (filename and line)
37func (h *CallerHook) Fire(entry *logrus.Entry) error {
38	entry.Data[h.Field] = findCaller(h.Skip)
39	return nil
40}
41
42// findCaller ignores the caller relevant to logrus or fslog then find out the exact caller
43func findCaller(skip int) string {
44	file := ""
45	line := 0
46	for i := 0; i < 10; i++ {
47		file, line = getCaller(skip + i)
48		if !strings.HasPrefix(file, "logrus") && strings.Index(file, "log.go") < 0 {
49			break
50		}
51	}
52	return fmt.Sprintf("%s:%d", file, line)
53}
54
55func getCaller(skip int) (string, int) {
56	_, file, line, ok := runtime.Caller(skip)
57	// fmt.Println(file,":",line)
58	if !ok {
59		return "", 0
60	}
61	n := 0
62	for i := len(file) - 1; i > 0; i-- {
63		if file[i] == '/' {
64			n++
65			if n >= 2 {
66				file = file[i+1:]
67				break
68			}
69		}
70	}
71	return file, line
72}
73