...

Source file src/go.uber.org/zap/zaptest/observer/observer.go

Documentation: go.uber.org/zap/zaptest/observer

     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 "go.uber.org/zap/zaptest/observer"
    26  
    27  import (
    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.
    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) 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  
   194  func (co *contextObserver) Sync() error {
   195  	return nil
   196  }
   197  

View as plain text