blob: f77f1308baf29fa3699df5d3cb0eb07763f50d9b [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001// 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.
25package observer // import "go.uber.org/zap/zaptest/observer"
26
27import (
28 "strings"
29 "sync"
30 "time"
31
32 "go.uber.org/zap/internal"
33 "go.uber.org/zap/zapcore"
34)
35
36// ObservedLogs is a concurrency-safe, ordered collection of observed logs.
37type ObservedLogs struct {
38 mu sync.RWMutex
39 logs []LoggedEntry
40}
41
42// Len returns the number of items in the collection.
43func (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.
51func (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.
61func (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.
72func (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.
81func (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.
88func (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.
95func (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.
102func (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.
114func (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.
127func (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
140func (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.
148func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) {
149 ol := &ObservedLogs{}
150 return &contextObserver{
151 LevelEnabler: enab,
152 logs: ol,
153 }, ol
154}
155
156type contextObserver struct {
157 zapcore.LevelEnabler
158 logs *ObservedLogs
159 context []zapcore.Field
160}
161
162var (
163 _ zapcore.Core = (*contextObserver)(nil)
164 _ internal.LeveledEnabler = (*contextObserver)(nil)
165)
166
167func (co *contextObserver) Level() zapcore.Level {
168 return zapcore.LevelOf(co.LevelEnabler)
169}
170
171func (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
178func (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
186func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error {
187 all := make([]zapcore.Field, 0, len(fields)+len(co.context))
188 all = append(all, co.context...)
189 all = append(all, fields...)
190 co.logs.add(LoggedEntry{ent, all})
191 return nil
192}
193
194func (co *contextObserver) Sync() error {
195 return nil
196}