blob: 876d28968e499801753207f2c432b6a0d80b6372 [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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package logging
import (
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})
// String returns a string describing the contents of f in a sorted,
// dictionary-like format.
func (f Fields) String() string {
b := bytes.Buffer{}
for idx, e := range f.SortedEntries() {
if idx > 0 {
b.WriteString(", ")
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())
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