1// Copyright (c) 2016 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 repesentation 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. 25package observer // import "go.uber.org/zap/zaptest/observer" 26 27import ( 28 "strings" 29 "sync" 30 "time" 31 32 "go.uber.org/zap/zapcore" 33) 34 35// ObservedLogs is a concurrency-safe, ordered collection of observed logs. 36type ObservedLogs struct { 37 mu sync.RWMutex 38 logs []LoggedEntry 39} 40 41// Len returns the number of items in the collection. 42func (o *ObservedLogs) Len() int { 43 o.mu.RLock() 44 n := len(o.logs) 45 o.mu.RUnlock() 46 return n 47} 48 49// All returns a copy of all the observed logs. 50func (o *ObservedLogs) All() []LoggedEntry { 51 o.mu.RLock() 52 ret := make([]LoggedEntry, len(o.logs)) 53 for i := range o.logs { 54 ret[i] = o.logs[i] 55 } 56 o.mu.RUnlock() 57 return ret 58} 59 60// TakeAll returns a copy of all the observed logs, and truncates the observed 61// slice. 62func (o *ObservedLogs) TakeAll() []LoggedEntry { 63 o.mu.Lock() 64 ret := o.logs 65 o.logs = nil 66 o.mu.Unlock() 67 return ret 68} 69 70// AllUntimed returns a copy of all the observed logs, but overwrites the 71// observed timestamps with time.Time's zero value. This is useful when making 72// assertions in tests. 73func (o *ObservedLogs) AllUntimed() []LoggedEntry { 74 ret := o.All() 75 for i := range ret { 76 ret[i].Time = time.Time{} 77 } 78 return ret 79} 80 81// FilterMessage filters entries to those that have the specified message. 82func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs { 83 return o.filter(func(e LoggedEntry) bool { 84 return e.Message == msg 85 }) 86} 87 88// FilterMessageSnippet filters entries to those that have a message containing the specified snippet. 89func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs { 90 return o.filter(func(e LoggedEntry) bool { 91 return strings.Contains(e.Message, snippet) 92 }) 93} 94 95// FilterField filters entries to those that have the specified field. 96func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs { 97 return o.filter(func(e LoggedEntry) bool { 98 for _, ctxField := range e.Context { 99 if ctxField.Equals(field) { 100 return true 101 } 102 } 103 return false 104 }) 105} 106 107func (o *ObservedLogs) filter(match func(LoggedEntry) bool) *ObservedLogs { 108 o.mu.RLock() 109 defer o.mu.RUnlock() 110 111 var filtered []LoggedEntry 112 for _, entry := range o.logs { 113 if match(entry) { 114 filtered = append(filtered, entry) 115 } 116 } 117 return &ObservedLogs{logs: filtered} 118} 119 120func (o *ObservedLogs) add(log LoggedEntry) { 121 o.mu.Lock() 122 o.logs = append(o.logs, log) 123 o.mu.Unlock() 124} 125 126// New creates a new Core that buffers logs in memory (without any encoding). 127// It's particularly useful in tests. 128func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) { 129 ol := &ObservedLogs{} 130 return &contextObserver{ 131 LevelEnabler: enab, 132 logs: ol, 133 }, ol 134} 135 136type contextObserver struct { 137 zapcore.LevelEnabler 138 logs *ObservedLogs 139 context []zapcore.Field 140} 141 142func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { 143 if co.Enabled(ent.Level) { 144 return ce.AddCore(ent, co) 145 } 146 return ce 147} 148 149func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { 150 return &contextObserver{ 151 LevelEnabler: co.LevelEnabler, 152 logs: co.logs, 153 context: append(co.context[:len(co.context):len(co.context)], fields...), 154 } 155} 156 157func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { 158 all := make([]zapcore.Field, 0, len(fields)+len(co.context)) 159 all = append(all, co.context...) 160 all = append(all, fields...) 161 co.logs.add(LoggedEntry{ent, all}) 162 return nil 163} 164 165func (co *contextObserver) Sync() error { 166 return nil 167} 168