Source File
record.go
Belonging Package
log/slog
// 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 (
)
const nAttrsInline = 5
// A Record holds information about a log event.
// Copies of a Record share state.
// Do not modify a Record after handing out a copy to it.
// Call [NewRecord] to create a new Record.
// Use [Record.Clone] to create a copy with no shared state.
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 program counter at the time the record was constructed, as determined
// by runtime.Callers. If zero, no program counter is available.
//
// The only valid use for this value is as an argument to
// [runtime.CallersFrames]. In particular, it must not be passed to
// [runtime.FuncForPC].
PC uintptr
// Allocation optimization: an inline array sized to hold
// the majority of log calls (based on examination of open-source
// code). It holds the start of the list of Attrs.
front [nAttrsInline]Attr
// The number of Attrs in front.
nFront int
// The list of Attrs except for those in front.
// Invariants:
// - len(back) > 0 iff nFront == len(front)
// - Unused array elements are zero. Used to detect mistakes.
back []Attr
}
// NewRecord creates a [Record] from the given arguments.
// Use [Record.AddAttrs] to add attributes to the Record.
//
// NewRecord is intended for logging APIs that want to support a [Handler] as
// a backend.
func ( time.Time, Level, string, uintptr) Record {
return Record{
Time: ,
Message: ,
Level: ,
PC: ,
}
}
// Clone returns a copy of the record with no shared state.
// The original record and the clone can both be modified
// without interfering with each other.
func ( Record) () Record {
.back = slices.Clip(.back) // prevent append from mutating shared array
return
}
// NumAttrs returns the number of attributes in the [Record].
func ( Record) () int {
return .nFront + len(.back)
}
// Attrs calls f on each Attr in the [Record].
// Iteration stops if f returns false.
func ( Record) ( func(Attr) bool) {
for := 0; < .nFront; ++ {
if !(.front[]) {
return
}
}
for , := range .back {
if !() {
return
}
}
}
// AddAttrs appends the given Attrs to the [Record]'s list of Attrs.
// It omits empty groups.
func ( *Record) ( ...Attr) {
var int
for = 0; < len() && .nFront < len(.front); ++ {
:= []
if .Value.isEmptyGroup() {
continue
}
.front[.nFront] =
.nFront++
}
// Check if a copy was modified by slicing past the end
// and seeing if the Attr there is non-zero.
if cap(.back) > len(.back) {
:= .back[:len(.back)+1][len(.back)]
if !.isEmpty() {
// Don't panic; copy and muddle through.
.back = slices.Clip(.back)
.back = append(.back, String("!BUG", "AddAttrs unsafely called on copy of Record made without using Record.Clone"))
}
}
:= countEmptyGroups([:])
.back = slices.Grow(.back, len([:])-)
for , := range [:] {
if !.Value.isEmptyGroup() {
.back = append(.back, )
}
}
}
// Add converts the args to Attrs as described in [Logger.Log],
// then appends the Attrs to the [Record]'s list of Attrs.
// It omits empty groups.
func ( *Record) ( ...any) {
var Attr
for len() > 0 {
, = argsToAttr()
if .Value.isEmptyGroup() {
continue
}
if .nFront < len(.front) {
.front[.nFront] =
.nFront++
} else {
if .back == nil {
.back = make([]Attr, 0, countAttrs()+1)
}
.back = append(.back, )
}
}
}
// countAttrs returns the number of Attrs that would be created from args.
func countAttrs( []any) int {
:= 0
for := 0; < len(); ++ {
++
if , := [].(string); {
++
}
}
return
}
const badKey = "!BADKEY"
// argsToAttr turns a prefix of the nonempty args slice into an Attr
// and returns the unconsumed portion of the slice.
// If args[0] is an Attr, it returns it.
// If args[0] is a string, it treats the first two elements as
// a key-value pair.
// Otherwise, it treats args[0] as a value with a missing key.
func argsToAttr( []any) (Attr, []any) {
switch x := [0].(type) {
case string:
if len() == 1 {
return String(badKey, ), nil
}
return Any(, [1]), [2:]
case Attr:
return , [1:]
default:
return Any(badKey, ), [1:]
}
}
// Source describes the location of a line of source code.
type Source struct {
// Function is the package path-qualified function name containing the
// source line. If non-empty, this string uniquely identifies a single
// function in the program. This may be the empty string if not known.
Function string `json:"function"`
// File and Line are the file name and line number (1-based) of the source
// line. These may be the empty string and zero, respectively, if not known.
File string `json:"file"`
Line int `json:"line"`
}
// group returns the non-zero fields of s as a slice of attrs.
// It is similar to a LogValue method, but we don't want Source
// to implement LogValuer because it would be resolved before
// the ReplaceAttr function was called.
func ( *Source) () Value {
var []Attr
if .Function != "" {
= append(, String("function", .Function))
}
if .File != "" {
= append(, String("file", .File))
}
if .Line != 0 {
= append(, Int("line", .Line))
}
return GroupValue(...)
}
// source returns a Source for the log event.
// If the Record was created without the necessary information,
// or if the location is unavailable, it returns a non-nil *Source
// with zero fields.
func ( Record) () *Source {
:= runtime.CallersFrames([]uintptr{.PC})
, := .Next()
return &Source{
Function: .Function,
File: .File,
Line: .Line,
}
}
The pages are generated with Golds v0.7.3. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |