github.com/Laisky/zap@v1.27.0/zaptest/observer/observer.go (about) 1 // Copyright (c) 2016-2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // Package observer provides a zapcore.Core that keeps an in-memory, 22 // encoding-agnostic representation of log entries. It's useful for 23 // applications that want to unit test their log output without tying their 24 // tests to a particular output encoding. 25 package observer // import "github.com/Laisky/zap/zaptest/observer" 26 27 import ( 28 "strings" 29 "sync" 30 "time" 31 32 "github.com/Laisky/zap/internal" 33 "github.com/Laisky/zap/zapcore" 34 ) 35 36 // ObservedLogs is a concurrency-safe, ordered collection of observed logs. 37 type ObservedLogs struct { 38 mu sync.RWMutex 39 logs []LoggedEntry 40 } 41 42 // Len returns the number of items in the collection. 43 func (o *ObservedLogs) Len() int { 44 o.mu.RLock() 45 n := len(o.logs) 46 o.mu.RUnlock() 47 return n 48 } 49 50 // All returns a copy of all the observed logs. 51 func (o *ObservedLogs) All() []LoggedEntry { 52 o.mu.RLock() 53 ret := make([]LoggedEntry, len(o.logs)) 54 copy(ret, o.logs) 55 o.mu.RUnlock() 56 return ret 57 } 58 59 // TakeAll returns a copy of all the observed logs, and truncates the observed 60 // slice. 61 func (o *ObservedLogs) TakeAll() []LoggedEntry { 62 o.mu.Lock() 63 ret := o.logs 64 o.logs = nil 65 o.mu.Unlock() 66 return ret 67 } 68 69 // AllUntimed returns a copy of all the observed logs, but overwrites the 70 // observed timestamps with time.Time's zero value. This is useful when making 71 // assertions in tests. 72 func (o *ObservedLogs) AllUntimed() []LoggedEntry { 73 ret := o.All() 74 for i := range ret { 75 ret[i].Time = time.Time{} 76 } 77 return ret 78 } 79 80 // FilterLevelExact filters entries to those logged at exactly the given level. 81 func (o *ObservedLogs) FilterLevelExact(level zapcore.Level) *ObservedLogs { 82 return o.Filter(func(e LoggedEntry) bool { 83 return e.Level == level 84 }) 85 } 86 87 // FilterMessage filters entries to those that have the specified message. 88 func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs { 89 return o.Filter(func(e LoggedEntry) bool { 90 return e.Message == msg 91 }) 92 } 93 94 // FilterMessageSnippet filters entries to those that have a message containing the specified snippet. 95 func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs { 96 return o.Filter(func(e LoggedEntry) bool { 97 return strings.Contains(e.Message, snippet) 98 }) 99 } 100 101 // FilterField filters entries to those that have the specified field. 102 func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs { 103 return o.Filter(func(e LoggedEntry) bool { 104 for _, ctxField := range e.Context { 105 if ctxField.Equals(field) { 106 return true 107 } 108 } 109 return false 110 }) 111 } 112 113 // FilterFieldKey filters entries to those that have the specified key. 114 func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs { 115 return o.Filter(func(e LoggedEntry) bool { 116 for _, ctxField := range e.Context { 117 if ctxField.Key == key { 118 return true 119 } 120 } 121 return false 122 }) 123 } 124 125 // Filter returns a copy of this ObservedLogs containing only those entries 126 // for which the provided function returns true. 127 func (o *ObservedLogs) Filter(keep func(LoggedEntry) bool) *ObservedLogs { 128 o.mu.RLock() 129 defer o.mu.RUnlock() 130 131 var filtered []LoggedEntry 132 for _, entry := range o.logs { 133 if keep(entry) { 134 filtered = append(filtered, entry) 135 } 136 } 137 return &ObservedLogs{logs: filtered} 138 } 139 140 func (o *ObservedLogs) add(log LoggedEntry) { 141 o.mu.Lock() 142 o.logs = append(o.logs, log) 143 o.mu.Unlock() 144 } 145 146 // New creates a new Core that buffers logs in memory (without any encoding). 147 // It's particularly useful in tests. 148 func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) { 149 ol := &ObservedLogs{} 150 return &contextObserver{ 151 LevelEnabler: enab, 152 logs: ol, 153 }, ol 154 } 155 156 type contextObserver struct { 157 zapcore.LevelEnabler 158 logs *ObservedLogs 159 context []zapcore.Field 160 } 161 162 var ( 163 _ zapcore.Core = (*contextObserver)(nil) 164 _ internal.LeveledEnabler = (*contextObserver)(nil) 165 ) 166 167 func (co *contextObserver) Level() zapcore.Level { 168 return zapcore.LevelOf(co.LevelEnabler) 169 } 170 171 func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { 172 if co.Enabled(ent.Level) { 173 return ce.AddCore(ent, co) 174 } 175 return ce 176 } 177 178 func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { 179 return &contextObserver{ 180 LevelEnabler: co.LevelEnabler, 181 logs: co.logs, 182 context: append(co.context[:len(co.context):len(co.context)], fields...), 183 } 184 } 185 186 func (co *contextObserver) Fields() []zapcore.Field { 187 return co.context 188 } 189 190 func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { 191 all := make([]zapcore.Field, 0, len(fields)+len(co.context)) 192 all = append(all, co.context...) 193 all = append(all, fields...) 194 co.logs.add(LoggedEntry{ent, all}) 195 return nil 196 } 197 198 func (co *contextObserver) Sync() error { 199 return nil 200 }