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
|
package logg
import (
"fmt"
"strings"
"time"
)
// assert interface compliance.
var (
_ LevelLogger = (*Entry)(nil)
)
// Entry represents a single log entry at a given log level.
type Entry struct {
logger *logger
Level Level `json:"level"`
Timestamp time.Time `json:"timestamp"`
Fields Fields `json:"fields,omitempty"`
Message string `json:"message"`
fieldsAddedCounter int
}
// NewEntry returns a new entry for `log`.
func NewEntry(log *logger) *Entry {
return &Entry{
logger: log,
}
}
func (e Entry) WithLevel(level Level) *Entry {
e.Level = level
return &e
}
func (e *Entry) WithFields(fielder Fielder) *Entry {
if e.isLevelDisabled() {
return e
}
x := *e
fields := fielder.Fields()
x.fieldsAddedCounter += len(fields)
x.Fields = append(x.Fields, fields...)
if x.fieldsAddedCounter > 100 {
// This operation will eventually also be performed on the final entry,
// do it here to avoid the slice to grow indefinitely.
x.mergeFields()
x.fieldsAddedCounter = 0
}
return &x
}
func (e *Entry) WithField(key string, value any) *Entry {
if e.isLevelDisabled() {
return e
}
return e.WithFields(Fields{{key, value}})
}
func (e *Entry) WithDuration(d time.Duration) *Entry {
if e.isLevelDisabled() {
return e
}
return e.WithField("duration", d.Milliseconds())
}
// WithError returns a new entry with the "error" set to `err`.
//
// The given error may implement .Fielder, if it does the method
// will add all its `.Fields()` into the returned entry.
func (e *Entry) WithError(err error) *Entry {
if err == nil || e.isLevelDisabled() {
return e
}
ctx := e.WithField("error", err.Error())
if s, ok := err.(stackTracer); ok {
frame := s.StackTrace()[0]
name := fmt.Sprintf("%n", frame)
file := fmt.Sprintf("%+s", frame)
line := fmt.Sprintf("%d", frame)
parts := strings.Split(file, "\n\t")
if len(parts) > 1 {
file = parts[1]
}
ctx = ctx.WithField("source", fmt.Sprintf("%s: %s:%s", name, file, line))
}
if f, ok := err.(Fielder); ok {
ctx = ctx.WithFields(f)
}
return ctx
}
func (e *Entry) isLevelDisabled() bool {
return e.Level < e.logger.Level
}
// Log a message at the given level.
func (e *Entry) Log(s fmt.Stringer) {
e.logger.log(e, s)
}
// Log a message at the given level.
func (e *Entry) Logf(format string, a ...any) {
e.logger.log(e, StringFunc(func() string {
return fmt.Sprintf(format, a...)
}))
}
// Clone returns a new Entry with the same fields.
func (e *Entry) Clone() *Entry {
x := *e
x.Fields = make(Fields, len(e.Fields))
copy(x.Fields, e.Fields)
return &x
}
func (e *Entry) reset() {
e.logger = nil
e.Level = 0
e.Fields = e.Fields[:0]
e.Message = ""
e.Timestamp = time.Time{}
}
// Remove any early entries with the same name.
func (e *Entry) mergeFields() {
n := 0
for i, f := range e.Fields {
keep := true
for j := i + 1; j < len(e.Fields); j++ {
if e.Fields[j].Name == f.Name {
keep = false
break
}
}
if keep {
e.Fields[n] = f
n++
}
}
e.Fields = e.Fields[:n]
}
// finalize populates dst with Level and Fields merged from e and Message and Timestamp set.
func (e *Entry) finalize(dst *Entry, msg string) {
dst.Message = msg
dst.Timestamp = e.logger.Clock.Now()
dst.Level = e.Level
if cap(dst.Fields) < len(e.Fields) {
dst.Fields = make(Fields, len(e.Fields))
} else {
dst.Fields = dst.Fields[:len(e.Fields)]
}
copy(dst.Fields, e.Fields)
dst.mergeFields()
}
|