summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/logr/logrec.go
blob: 9428aaec7546ed80be8b511040672d22aee03562 (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
183
184
185
186
187
188
189
package logr

import (
	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"
)

var (
	logrPkg string
)

func init() {
	// Calc current package name
	pcs := make([]uintptr, 2)
	_ = runtime.Callers(0, pcs)
	tmp := runtime.FuncForPC(pcs[1]).Name()
	logrPkg = getPackageName(tmp)
}

// 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

	template string
	newline  bool
	args     []interface{}

	stackPC    []uintptr
	stackCount int

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

	// remaining fields calculated by `prep`
	msg    string
	frames []runtime.Frame
}

// NewLogRec creates a new LogRec with the current time and optional stack trace.
func NewLogRec(lvl Level, logger Logger, template string, args []interface{}, incStacktrace bool) *LogRec {
	rec := &LogRec{time: time.Now(), logger: logger, level: lvl, template: template, args: args}
	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 all args and field values to strings, and
// resolves stack trace to frames.
func (rec *LogRec) prep() {
	rec.mux.Lock()
	defer rec.mux.Unlock()

	// resolve args
	if rec.template == "" {
		if rec.newline {
			rec.msg = fmt.Sprintln(rec.args...)
		} else {
			rec.msg = fmt.Sprint(rec.args...)
		}
	} else {
		rec.msg = fmt.Sprintf(rec.template, rec.args...)
	}

	// resolve stack trace
	if rec.stackCount > 0 {
		frames := runtime.CallersFrames(rec.stackPC[:rec.stackCount])
		for {
			f, more := frames.Next()
			rec.frames = append(rec.frames, f)
			if !more {
				break
			}
		}

		// remove leading logr package entries.
		var start int
		for i, frame := range rec.frames {
			pkg := getPackageName(frame.Function)
			if pkg != "" && pkg != logrPkg {
				start = i
				break
			}
		}
		rec.frames = rec.frames[start:]
	}
}

// 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,
		template:   rec.template,
		newline:    rec.newline,
		args:       rec.args,
		msg:        rec.msg,
		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() Fields {
	// no locking needed as this field is not mutated.
	return rec.logger.fields
}

// 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
}

// 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.logr.BorrowBuffer()
	defer rec.logger.logr.ReleaseBuffer(buf)
	buf, _ = f.Format(rec, true, buf)
	return strings.TrimSpace(buf.String())
}

// getPackageName reduces a fully qualified function name to the package name
// By sirupsen: https://github.com/sirupsen/logrus/blob/master/entry.go
func getPackageName(f string) string {
	for {
		lastPeriod := strings.LastIndex(f, ".")
		lastSlash := strings.LastIndex(f, "/")
		if lastPeriod > lastSlash {
			f = f[:lastPeriod]
		} else {
			break
		}
	}
	return f
}