blob: 4345fc5617840426f472a3cc48d983a017e9bebf [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logging
import (
"bytes"
"context"
"fmt"
"sort"
)
const (
// ErrorKey is a logging field key to use for errors.
ErrorKey = "error"
)
// Fields maps string keys to arbitrary values.
//
// Fields can be added to a Context. Fields added to a Context augment those
// in the Context's parent Context, overriding duplicate keys. When Fields are
// added to a Context, they are copied internally for retention.
//
// Fields can also be added directly to a log message by calling its
// logging passthrough methods. This immediate usage avoids the overhead of
// duplicating the fields for retention.
type Fields map[string]any
// NewFields instantiates a new Fields instance by duplicating the supplied map.
func NewFields(v map[string]any) Fields {
fields := make(Fields)
for k, v := range v {
fields[k] = v
}
return fields
}
// WithError returns a Fields instance containing an error.
func WithError(err error) Fields {
return Fields{
ErrorKey: err,
}
}
// Copy returns a copy of this Fields with the keys from other overlaid on top
// of this one's.
func (f Fields) Copy(other Fields) Fields {
if len(f) == 0 && len(other) == 0 {
return nil
}
ret := make(Fields, len(f)+len(other))
for k, v := range f {
ret[k] = v
}
for k, v := range other {
ret[k] = v
}
return ret
}
// SortedEntries processes a Fields object, pruning invisible fields, placing
// the ErrorKey field first, and then sorting the remaining fields by key.
func (f Fields) SortedEntries() (s []*FieldEntry) {
if len(f) == 0 {
return nil
}
s = make([]*FieldEntry, 0, len(f))
for k, v := range f {
s = append(s, &FieldEntry{k, v})
}
sort.Sort(fieldEntrySlice(s))
return
}
// String returns a string describing the contents of f in a sorted,
// dictionary-like format.
func (f Fields) String() string {
b := bytes.Buffer{}
b.WriteRune('{')
for idx, e := range f.SortedEntries() {
if idx > 0 {
b.WriteString(", ")
}
b.WriteString(e.String())
}
b.WriteRune('}')
return b.String()
}
// Debugf is a shorthand method to call the current logger's Errorf method.
func (f Fields) Debugf(ctx context.Context, fmt string, args ...any) {
Get(SetFields(ctx, f)).LogCall(Debug, 1, fmt, args)
}
// Infof is a shorthand method to call the current logger's Errorf method.
func (f Fields) Infof(ctx context.Context, fmt string, args ...any) {
Get(SetFields(ctx, f)).LogCall(Info, 1, fmt, args)
}
// Warningf is a shorthand method to call the current logger's Errorf method.
func (f Fields) Warningf(ctx context.Context, fmt string, args ...any) {
Get(SetFields(ctx, f)).LogCall(Warning, 1, fmt, args)
}
// Errorf is a shorthand method to call the current logger's Errorf method.
func (f Fields) Errorf(ctx context.Context, fmt string, args ...any) {
Get(SetFields(ctx, f)).LogCall(Error, 1, fmt, args)
}
// FieldEntry is a static representation of a single key/value entry in a
// Fields.
type FieldEntry struct {
Key string // The field's key.
Value any // The field's value.
}
// String returns the string representation of the field entry:
// "<key>":"<value>".
func (e *FieldEntry) String() string {
value := e.Value
if s, ok := value.(fmt.Stringer); ok {
value = s.String()
}
switch v := value.(type) {
case string:
return fmt.Sprintf("%q:%q", e.Key, v)
case error:
return fmt.Sprintf("%q:%q", e.Key, v.Error())
default:
return fmt.Sprintf("%q:%#v", e.Key, v)
}
}
// fieldEntrySlice is a slice of FieldEntry which implements sort.Interface.
// The error field is placed before any other field; the remaining fields are
// sorted alphabetically.
type fieldEntrySlice []*FieldEntry
var _ sort.Interface = fieldEntrySlice(nil)
func (s fieldEntrySlice) Less(i, j int) bool {
if s[i].Key == ErrorKey {
return s[j].Key != ErrorKey
}
if s[j].Key == ErrorKey {
return false
}
return s[i].Key < s[j].Key
}
func (s fieldEntrySlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s fieldEntrySlice) Len() int {
return len(s)
}
// SetFields adds the additional fields as context for the current Logger. The
// display of these fields depends on the implementation of the Logger. The
// new context will contain the combination of its current Fields, updated with
// the new ones (see Fields.Copy). Specifying the new fields as nil will
// clear the currently set fields.
func SetFields(ctx context.Context, fields Fields) context.Context {
return context.WithValue(ctx, fieldsKey, GetFields(ctx).Copy(fields))
}
// SetField is a convenience method for SetFields for a single key/value
// pair.
func SetField(ctx context.Context, key string, value any) context.Context {
return SetFields(ctx, Fields{key: value})
}
// GetFields returns the current Fields.
//
// This method is used for logger implementations with the understanding that
// the returned fields must not be mutated.
func GetFields(ctx context.Context) Fields {
if ret, ok := ctx.Value(fieldsKey).(Fields); ok {
return ret
}
return nil
}