summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/logr/v2/logrec.go
blob: 76d51b9e16d54fb371ae0dde210e7780e200a1ba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package logr

import (
	"fmt"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

// LogRec collects raw, unformatted data to be logged.
// TODO:  pool these?  how to reliably know when targets are done with them? Copy for each target?
type LogRec struct {
	mux  sync.RWMutex
	time time.Time

	level  Level
	logger Logger

	msg     string
	newline bool
	fields  []Field

	stackPC    []uintptr
	stackCount int

	// flushes Logr and target queues when not nil.
	flush chan struct{}

	// remaining fields calculated by `prep`
	frames    []runtime.Frame
	fieldsAll []Field
	caller    string
}

// NewLogRec creates a new LogRec with the current time and optional stack trace.
func NewLogRec(lvl Level, logger Logger, msg string, fields []Field, incStacktrace bool) *LogRec {
	rec := &LogRec{time: time.Now(), logger: logger, level: lvl, msg: msg, fields: fields}
	if incStacktrace {
		rec.stackPC = make([]uintptr, DefaultMaxStackFrames)
		rec.stackCount = runtime.Callers(2, rec.stackPC)
	}
	return rec
}

// newFlushLogRec creates a LogRec that flushes the Logr queue and
// any target queues that support flushing.
func newFlushLogRec(logger Logger) *LogRec {
	return &LogRec{logger: logger, flush: make(chan struct{})}
}

// prep resolves stack trace to frames.
func (rec *LogRec) prep() {
	rec.mux.Lock()
	defer rec.mux.Unlock()

	// include log rec fields and logger fields added via "With"
	rec.fieldsAll = make([]Field, 0, len(rec.fields)+len(rec.logger.fields))
	rec.fieldsAll = append(rec.fieldsAll, rec.logger.fields...)
	rec.fieldsAll = append(rec.fieldsAll, rec.fields...)

	filter := rec.logger.lgr.options.stackFilter

	// resolve stack trace
	if rec.stackCount > 0 {
		rec.frames = make([]runtime.Frame, 0, rec.stackCount)
		frames := runtime.CallersFrames(rec.stackPC[:rec.stackCount])
		for {
			frame, more := frames.Next()

			// remove all package entries that are in filter.
			pkg := ResolvePackageName(frame.Function)
			if _, ok := filter[pkg]; !ok && pkg != "" {
				rec.frames = append(rec.frames, frame)
			}

			if !more {
				break
			}
		}
	}

	// calc caller if stack trace provided
	if len(rec.frames) > 0 {
		rec.caller = calcCaller(rec.frames)
	}
}

// WithTime returns a shallow copy of the log record while replacing
// the time. This can be used by targets and formatters to adjust
// the time, or take ownership of the log record.
func (rec *LogRec) WithTime(time time.Time) *LogRec {
	rec.mux.RLock()
	defer rec.mux.RUnlock()

	return &LogRec{
		time:       time,
		level:      rec.level,
		logger:     rec.logger,
		msg:        rec.msg,
		newline:    rec.newline,
		fields:     rec.fields,
		stackPC:    rec.stackPC,
		stackCount: rec.stackCount,
		frames:     rec.frames,
	}
}

// Logger returns the `Logger` that created this `LogRec`.
func (rec *LogRec) Logger() Logger {
	return rec.logger
}

// Time returns this log record's time stamp.
func (rec *LogRec) Time() time.Time {
	// no locking needed as this field is not mutated.
	return rec.time
}

// Level returns this log record's Level.
func (rec *LogRec) Level() Level {
	// no locking needed as this field is not mutated.
	return rec.level
}

// Fields returns this log record's Fields.
func (rec *LogRec) Fields() []Field {
	// no locking needed as this field is not mutated.
	return rec.fieldsAll
}

// Msg returns this log record's message text.
func (rec *LogRec) Msg() string {
	rec.mux.RLock()
	defer rec.mux.RUnlock()
	return rec.msg
}

// StackFrames returns this log record's stack frames or
// nil if no stack trace was required.
func (rec *LogRec) StackFrames() []runtime.Frame {
	rec.mux.RLock()
	defer rec.mux.RUnlock()
	return rec.frames
}

// Caller returns this log record's caller info, meaning the file and line
// number where this log record was emitted. Returns empty string if no
// stack trace was provided.
func (rec *LogRec) Caller() string {
	rec.mux.RLock()
	defer rec.mux.RUnlock()
	return rec.caller
}

// String returns a string representation of this log record.
func (rec *LogRec) String() string {
	if rec.flush != nil {
		return "[flusher]"
	}

	f := &DefaultFormatter{}
	buf := rec.logger.lgr.BorrowBuffer()
	defer rec.logger.lgr.ReleaseBuffer(buf)
	buf, _ = f.Format(rec, rec.Level(), buf)
	return strings.TrimSpace(buf.String())
}

func calcCaller(frames []runtime.Frame) string {
	for _, frame := range frames {
		if frame.File == "" {
			continue
		}

		dir, file := filepath.Split(frame.File)
		base := filepath.Base(dir)

		return fmt.Sprintf("%s/%s:%d", base, file, frame.Line)
	}
	return ""
}