mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-07-06 13:55:46 +00:00
906639ad7e
* update viper version * removes our last uses of the slice package * fix tests
208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
// 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"
|
|
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
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.
|
|
// 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 NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
|
|
return Record{
|
|
Time: t,
|
|
Message: msg,
|
|
Level: level,
|
|
PC: 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 (r Record) Clone() Record {
|
|
r.back = slices.Clip(r.back) // prevent append from mutating shared array
|
|
return r
|
|
}
|
|
|
|
// NumAttrs returns the number of attributes in the Record.
|
|
func (r Record) NumAttrs() int {
|
|
return r.nFront + len(r.back)
|
|
}
|
|
|
|
// Attrs calls f on each Attr in the Record.
|
|
// Iteration stops if f returns false.
|
|
func (r Record) Attrs(f func(Attr) bool) {
|
|
for i := 0; i < r.nFront; i++ {
|
|
if !f(r.front[i]) {
|
|
return
|
|
}
|
|
}
|
|
for _, a := range r.back {
|
|
if !f(a) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddAttrs appends the given Attrs to the Record's list of Attrs.
|
|
func (r *Record) AddAttrs(attrs ...Attr) {
|
|
n := copy(r.front[r.nFront:], attrs)
|
|
r.nFront += n
|
|
// Check if a copy was modified by slicing past the end
|
|
// and seeing if the Attr there is non-zero.
|
|
if cap(r.back) > len(r.back) {
|
|
end := r.back[:len(r.back)+1][len(r.back)]
|
|
if !end.isEmpty() {
|
|
panic("copies of a slog.Record were both modified")
|
|
}
|
|
}
|
|
r.back = append(r.back, attrs[n:]...)
|
|
}
|
|
|
|
// Add converts the args to Attrs as described in [Logger.Log],
|
|
// then appends the Attrs to the Record's list of Attrs.
|
|
func (r *Record) Add(args ...any) {
|
|
var a Attr
|
|
for len(args) > 0 {
|
|
a, args = argsToAttr(args)
|
|
if r.nFront < len(r.front) {
|
|
r.front[r.nFront] = a
|
|
r.nFront++
|
|
} else {
|
|
if r.back == nil {
|
|
r.back = make([]Attr, 0, countAttrs(args))
|
|
}
|
|
r.back = append(r.back, a)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// countAttrs returns the number of Attrs that would be created from args.
|
|
func countAttrs(args []any) int {
|
|
n := 0
|
|
for i := 0; i < len(args); i++ {
|
|
n++
|
|
if _, ok := args[i].(string); ok {
|
|
i++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
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(args []any) (Attr, []any) {
|
|
switch x := args[0].(type) {
|
|
case string:
|
|
if len(args) == 1 {
|
|
return String(badKey, x), nil
|
|
}
|
|
return Any(x, args[1]), args[2:]
|
|
|
|
case Attr:
|
|
return x, args[1:]
|
|
|
|
default:
|
|
return Any(badKey, x), args[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"`
|
|
}
|
|
|
|
// attrs 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 (s *Source) group() Value {
|
|
var as []Attr
|
|
if s.Function != "" {
|
|
as = append(as, String("function", s.Function))
|
|
}
|
|
if s.File != "" {
|
|
as = append(as, String("file", s.File))
|
|
}
|
|
if s.Line != 0 {
|
|
as = append(as, Int("line", s.Line))
|
|
}
|
|
return GroupValue(as...)
|
|
}
|
|
|
|
// 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 (r Record) source() *Source {
|
|
fs := runtime.CallersFrames([]uintptr{r.PC})
|
|
f, _ := fs.Next()
|
|
return &Source{
|
|
Function: f.Function,
|
|
File: f.File,
|
|
Line: f.Line,
|
|
}
|
|
}
|