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 }