blob: 0f78a9634a777a0c77fdc0b4f5814dc190142724 [file] [log] [blame]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package slog
import (
"runtime"
"time"
)
const nAttrsInline = 5
// A Record holds information about a log event.
type Record struct {
// The time at which the output method (Log, Info, etc.) was called.
time time.Time
// The log message.
message string
// The level of the event.
level Level
// The pc at the time the record was constructed, as determined
// by runtime.Callers using the calldepth argument to NewRecord.
pc uintptr
// Allocation optimization: an inline array sized to hold
// the majority of log calls (based on examination of open-source
// code). The array holds the end of the sequence of Attrs.
tail [nAttrsInline]Attr
// The number of Attrs in tail.
nTail int
// The sequence of Attrs except for the tail, represented as a functional
// list of arrays.
attrs list[[nAttrsInline]Attr]
}
// MakeRecord creates a new Record from the given arguments.
// Use [Record.AddAttr] to add attributes to the Record.
// If calldepth is greater than zero, [Record.SourceLine] will
// return the file and line number at that depth,
// where 1 means the caller of MakeRecord.
//
// MakeRecord is intended for logging APIs that want to support a [Handler] as
// a backend.
func MakeRecord(t time.Time, level Level, msg string, calldepth int) Record {
var p uintptr
if calldepth > 0 {
p = pc(calldepth + 1)
}
return Record{
time: t,
message: msg,
level: level,
pc: p,
}
}
func pc(depth int) uintptr {
var pcs [1]uintptr
runtime.Callers(depth, pcs[:])
return pcs[0]
}
// Time returns the time of the log event.
func (r *Record) Time() time.Time { return r.time }
// Message returns the log message.
func (r *Record) Message() string { return r.message }
// Level returns the level of the log event.
func (r *Record) Level() Level { return r.level }
// SourceLine returns the file and line of the log event.
// If the Record was created without the necessary information,
// or if the location is unavailable, it returns ("", 0).
func (r *Record) SourceLine() (file string, line int) {
fs := runtime.CallersFrames([]uintptr{r.pc})
// TODO: error-checking?
f, _ := fs.Next()
return f.File, f.Line
}
// Attrs returns a copy of the sequence of Attrs in r.
func (r *Record) Attrs() []Attr {
res := make([]Attr, 0, r.attrs.len()*nAttrsInline+r.nTail)
r.attrs = r.attrs.normalize()
for _, f := range r.attrs.front {
res = append(res, f[:]...)
}
for _, a := range r.tail[:r.nTail] {
res = append(res, a)
}
return res
}
// NumAttrs returns the number of Attrs in r.
func (r *Record) NumAttrs() int {
return r.attrs.len()*nAttrsInline + r.nTail
}
// Attr returns the i'th Attr in r.
func (r *Record) Attr(i int) Attr {
if r.attrs.back != nil {
r.attrs = r.attrs.normalize()
}
alen := r.attrs.len() * nAttrsInline
if i < alen {
return r.attrs.at(i / nAttrsInline)[i%nAttrsInline]
}
return r.tail[i-alen]
}
// AddAttr appends an attributes to the record's list of attributes.
// It does not check for duplicate keys.
func (r *Record) AddAttr(a Attr) {
if r.nTail == len(r.tail) {
r.attrs = r.attrs.append(r.tail)
r.nTail = 0
}
r.tail[r.nTail] = a
r.nTail++
}
func (r *Record) addAttrs(attrs []Attr) {
// TODO: be cleverer.
for _, a := range attrs {
r.AddAttr(a)
}
}