go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/logging/memlogger/memory.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 memlogger 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "reflect" 23 "sync" 24 25 "go.chromium.org/luci/common/logging" 26 ) 27 28 // LogEntry is a single entry in a MemLogger, containing a message and a 29 // severity. 30 type LogEntry struct { 31 Level logging.Level 32 Msg string 33 Data map[string]any 34 CallDepth int 35 } 36 37 // MemLogger is an implementation of Logger. 38 // Zero value is a valid logger. 39 type MemLogger struct { 40 lock *sync.Mutex 41 data *[]LogEntry 42 fields map[string]any 43 } 44 45 var _ logging.Logger = (*MemLogger)(nil) 46 47 // Debugf implements the logging.Logger interface. 48 func (m *MemLogger) Debugf(format string, args ...any) { 49 m.LogCall(logging.Debug, 1, format, args) 50 } 51 52 // Infof implements the logging.Logger interface. 53 func (m *MemLogger) Infof(format string, args ...any) { 54 m.LogCall(logging.Info, 1, format, args) 55 } 56 57 // Warningf implements the logging.Logger interface. 58 func (m *MemLogger) Warningf(format string, args ...any) { 59 m.LogCall(logging.Warning, 1, format, args) 60 } 61 62 // Errorf implements the logging.Logger interface. 63 func (m *MemLogger) Errorf(format string, args ...any) { 64 m.LogCall(logging.Error, 1, format, args) 65 } 66 67 // LogCall implements the logging.Logger interface. 68 func (m *MemLogger) LogCall(lvl logging.Level, calldepth int, format string, args []any) { 69 if m.lock != nil { 70 m.lock.Lock() 71 defer m.lock.Unlock() 72 } 73 if m.data == nil { 74 m.data = new([]LogEntry) 75 } 76 *m.data = append(*m.data, LogEntry{lvl, fmt.Sprintf(format, args...), m.fields, calldepth + 1}) 77 } 78 79 // Messages returns all of the log messages that this memory logger has 80 // recorded. 81 func (m *MemLogger) Messages() []LogEntry { 82 if m.lock != nil { 83 m.lock.Lock() 84 defer m.lock.Unlock() 85 } 86 if m.data == nil || len(*m.data) == 0 { 87 return nil 88 } 89 ret := make([]LogEntry, len(*m.data)) 90 copy(ret, *m.data) 91 return ret 92 } 93 94 // Reset resets the logged messages recorded so far. 95 func (m *MemLogger) Reset() { 96 if m.lock != nil { 97 m.lock.Lock() 98 defer m.lock.Unlock() 99 } 100 if m.data != nil { 101 *m.data = nil 102 } 103 } 104 105 // GetFunc returns the first matching log entry. 106 func (m *MemLogger) GetFunc(f func(*LogEntry) bool) *LogEntry { 107 if m.lock != nil { 108 m.lock.Lock() 109 defer m.lock.Unlock() 110 } 111 if m.data == nil { 112 return nil 113 } 114 for _, ent := range *m.data { 115 if f(&ent) { 116 clone := ent 117 return &clone 118 } 119 } 120 return nil 121 } 122 123 // Get returns the first matching log entry. 124 // Note that lvl, msg and data have to match the entry precisely. 125 func (m *MemLogger) Get(lvl logging.Level, msg string, data map[string]any) *LogEntry { 126 return m.GetFunc(func(ent *LogEntry) bool { 127 return ent.Level == lvl && ent.Msg == msg && reflect.DeepEqual(data, ent.Data) 128 }) 129 } 130 131 // HasFunc returns true iff the MemLogger contains a matching log message. 132 func (m *MemLogger) HasFunc(f func(*LogEntry) bool) bool { 133 return m.GetFunc(f) != nil 134 } 135 136 // Has returns true iff the MemLogger contains the specified log message. 137 // Note that lvl, msg and data have to match the entry precisely. 138 func (m *MemLogger) Has(lvl logging.Level, msg string, data map[string]any) bool { 139 return m.Get(lvl, msg, data) != nil 140 } 141 142 // Dump dumps the current memory logger contents to the given writer in a 143 // human-readable format. 144 func (m *MemLogger) Dump(w io.Writer) (n int, err error) { 145 amt := 0 146 for i, msg := range m.Messages() { 147 if i == 0 { 148 amt, err = fmt.Fprintf(w, "\nDUMP LOG:\n") 149 n += amt 150 if err != nil { 151 return 152 } 153 } 154 if msg.Data == nil { 155 amt, err = fmt.Fprintf(w, " %s: %s\n", msg.Level, msg.Msg) 156 n += amt 157 if err != nil { 158 return 159 } 160 } else { 161 amt, err = fmt.Fprintf(w, " %s: %s: %s\n", msg.Level, msg.Msg, logging.Fields(msg.Data)) 162 n += amt 163 if err != nil { 164 return 165 } 166 } 167 } 168 return 169 } 170 171 // Use adds a memory backed Logger to Context, with concrete type 172 // *MemLogger. Casting to the concrete type can be used to inspect the 173 // log output after running a test case, for example. 174 func Use(ctx context.Context) context.Context { 175 lock := sync.Mutex{} 176 data := []LogEntry{} 177 return logging.SetFactory(ctx, func(ctx context.Context) logging.Logger { 178 return &MemLogger{&lock, &data, logging.GetFields(ctx)} 179 }) 180 } 181 182 // Reset is a convenience function to reset the current memory logger. 183 // 184 // This will panic if the current logger is not a memory logger. 185 func Reset(ctx context.Context) { 186 logging.Get(ctx).(*MemLogger).Reset() 187 } 188 189 // Dump is a convenience function to dump the current contents of the memory 190 // logger to the writer. 191 // 192 // This will panic if the current logger is not a memory logger. 193 func Dump(ctx context.Context, w io.Writer) (n int, err error) { 194 return logging.Get(ctx).(*MemLogger).Dump(w) 195 } 196 197 // MustDumpStdout is a convenience function to dump the current contents of the 198 // memory logger to stdout. 199 // 200 // This will panic if the current logger is not a memory logger. 201 func MustDumpStdout(ctx context.Context) { 202 _, err := logging.Get(ctx).(*MemLogger).Dump(os.Stdout) 203 if err != nil { 204 panic(err) 205 } 206 }