go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/logging/fields.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package logging
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"sort"
    22  )
    23  
    24  const (
    25  	// ErrorKey is a logging field key to use for errors.
    26  	ErrorKey = "error"
    27  )
    28  
    29  // Fields maps string keys to arbitrary values.
    30  //
    31  // Fields can be added to a Context. Fields added to a Context augment those
    32  // in the Context's parent Context, overriding duplicate keys. When Fields are
    33  // added to a Context, they are copied internally for retention.
    34  //
    35  // Fields can also be added directly to a log message by calling its
    36  // logging passthrough methods. This immediate usage avoids the overhead of
    37  // duplicating the fields for retention.
    38  type Fields map[string]any
    39  
    40  // NewFields instantiates a new Fields instance by duplicating the supplied map.
    41  func NewFields(v map[string]any) Fields {
    42  	fields := make(Fields)
    43  	for k, v := range v {
    44  		fields[k] = v
    45  	}
    46  	return fields
    47  }
    48  
    49  // WithError returns a Fields instance containing an error.
    50  func WithError(err error) Fields {
    51  	return Fields{
    52  		ErrorKey: err,
    53  	}
    54  }
    55  
    56  // Copy returns a copy of this Fields with the keys from other overlaid on top
    57  // of this one's.
    58  func (f Fields) Copy(other Fields) Fields {
    59  	if len(f) == 0 && len(other) == 0 {
    60  		return nil
    61  	}
    62  
    63  	ret := make(Fields, len(f)+len(other))
    64  	for k, v := range f {
    65  		ret[k] = v
    66  	}
    67  	for k, v := range other {
    68  		ret[k] = v
    69  	}
    70  	return ret
    71  }
    72  
    73  // SortedEntries processes a Fields object, pruning invisible fields, placing
    74  // the ErrorKey field first, and then sorting the remaining fields by key.
    75  func (f Fields) SortedEntries() (s []*FieldEntry) {
    76  	if len(f) == 0 {
    77  		return nil
    78  	}
    79  	s = make([]*FieldEntry, 0, len(f))
    80  	for k, v := range f {
    81  		s = append(s, &FieldEntry{k, v})
    82  	}
    83  	sort.Sort(fieldEntrySlice(s))
    84  	return
    85  }
    86  
    87  // String returns a string describing the contents of f in a sorted,
    88  // dictionary-like format.
    89  func (f Fields) String() string {
    90  	b := bytes.Buffer{}
    91  	b.WriteRune('{')
    92  	for idx, e := range f.SortedEntries() {
    93  		if idx > 0 {
    94  			b.WriteString(", ")
    95  		}
    96  		b.WriteString(e.String())
    97  	}
    98  	b.WriteRune('}')
    99  	return b.String()
   100  }
   101  
   102  // Debugf is a shorthand method to call the current logger's Errorf method.
   103  func (f Fields) Debugf(ctx context.Context, fmt string, args ...any) {
   104  	Get(SetFields(ctx, f)).LogCall(Debug, 1, fmt, args)
   105  }
   106  
   107  // Infof is a shorthand method to call the current logger's Errorf method.
   108  func (f Fields) Infof(ctx context.Context, fmt string, args ...any) {
   109  	Get(SetFields(ctx, f)).LogCall(Info, 1, fmt, args)
   110  }
   111  
   112  // Warningf is a shorthand method to call the current logger's Errorf method.
   113  func (f Fields) Warningf(ctx context.Context, fmt string, args ...any) {
   114  	Get(SetFields(ctx, f)).LogCall(Warning, 1, fmt, args)
   115  }
   116  
   117  // Errorf is a shorthand method to call the current logger's Errorf method.
   118  func (f Fields) Errorf(ctx context.Context, fmt string, args ...any) {
   119  	Get(SetFields(ctx, f)).LogCall(Error, 1, fmt, args)
   120  }
   121  
   122  // FieldEntry is a static representation of a single key/value entry in a
   123  // Fields.
   124  type FieldEntry struct {
   125  	Key   string      // The field's key.
   126  	Value any // The field's value.
   127  }
   128  
   129  // String returns the string representation of the field entry:
   130  // "<key>":"<value>".
   131  func (e *FieldEntry) String() string {
   132  	value := e.Value
   133  	if s, ok := value.(fmt.Stringer); ok {
   134  		value = s.String()
   135  	}
   136  
   137  	switch v := value.(type) {
   138  	case string:
   139  		return fmt.Sprintf("%q:%q", e.Key, v)
   140  
   141  	case error:
   142  		return fmt.Sprintf("%q:%q", e.Key, v.Error())
   143  
   144  	default:
   145  		return fmt.Sprintf("%q:%#v", e.Key, v)
   146  	}
   147  }
   148  
   149  // fieldEntrySlice is a slice of FieldEntry which implements sort.Interface.
   150  // The error field is placed before any other field; the remaining fields are
   151  // sorted alphabetically.
   152  type fieldEntrySlice []*FieldEntry
   153  
   154  var _ sort.Interface = fieldEntrySlice(nil)
   155  
   156  func (s fieldEntrySlice) Less(i, j int) bool {
   157  	if s[i].Key == ErrorKey {
   158  		return s[j].Key != ErrorKey
   159  	}
   160  	if s[j].Key == ErrorKey {
   161  		return false
   162  	}
   163  	return s[i].Key < s[j].Key
   164  }
   165  
   166  func (s fieldEntrySlice) Swap(i, j int) {
   167  	s[i], s[j] = s[j], s[i]
   168  }
   169  
   170  func (s fieldEntrySlice) Len() int {
   171  	return len(s)
   172  }
   173  
   174  // SetFields adds the additional fields as context for the current Logger. The
   175  // display of these fields depends on the implementation of the Logger. The
   176  // new context will contain the combination of its current Fields, updated with
   177  // the new ones (see Fields.Copy). Specifying the new fields as nil will
   178  // clear the currently set fields.
   179  func SetFields(ctx context.Context, fields Fields) context.Context {
   180  	return context.WithValue(ctx, fieldsKey, GetFields(ctx).Copy(fields))
   181  }
   182  
   183  // SetField is a convenience method for SetFields for a single key/value
   184  // pair.
   185  func SetField(ctx context.Context, key string, value any) context.Context {
   186  	return SetFields(ctx, Fields{key: value})
   187  }
   188  
   189  // GetFields returns the current Fields.
   190  //
   191  // This method is used for logger implementations with the understanding that
   192  // the returned fields must not be mutated.
   193  func GetFields(ctx context.Context) Fields {
   194  	if ret, ok := ctx.Value(fieldsKey).(Fields); ok {
   195  		return ret
   196  	}
   197  	return nil
   198  }