github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/common/flogging/floggingtest/logger.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package floggingtest 8 9 import ( 10 "bytes" 11 "regexp" 12 "strings" 13 "sync" 14 "testing" 15 16 "github.com/hyperledger/fabric/common/flogging" 17 "github.com/hyperledger/fabric/common/flogging/fabenc" 18 "github.com/onsi/gomega/gbytes" 19 "go.uber.org/zap" 20 "go.uber.org/zap/buffer" 21 "go.uber.org/zap/zapcore" 22 ) 23 24 // DefaultFormat is a log encoding format that is mostly compatible with the default 25 // log format but excludes colorization and time. 26 const DefaultFormat = "[%{module}] %{shortfunc} -> %{level:.4s} %{id:04x} %{message}" 27 28 type Recorder struct { 29 mutex sync.RWMutex 30 entries []string 31 messages []string 32 buffer *gbytes.Buffer 33 } 34 35 func newRecorder() *Recorder { 36 return &Recorder{ 37 buffer: gbytes.NewBuffer(), 38 entries: []string{}, 39 messages: []string{}, 40 } 41 } 42 43 func (r *Recorder) addEntry(e zapcore.Entry, line *buffer.Buffer) { 44 r.mutex.Lock() 45 defer r.mutex.Unlock() 46 47 r.buffer.Write(line.Bytes()) 48 r.entries = append(r.entries, strings.TrimRight(line.String(), "\n")) 49 r.messages = append(r.messages, e.Message) 50 } 51 52 func (r *Recorder) Reset() { 53 r.mutex.Lock() 54 defer r.mutex.Unlock() 55 r.buffer = gbytes.NewBuffer() 56 r.entries = []string{} 57 r.messages = []string{} 58 } 59 60 func (r *Recorder) Buffer() *gbytes.Buffer { 61 r.mutex.RLock() 62 defer r.mutex.RUnlock() 63 return r.buffer 64 } 65 66 func (r *Recorder) Entries() []string { 67 r.mutex.RLock() 68 defer r.mutex.RUnlock() 69 70 entries := make([]string, len(r.entries), cap(r.entries)) 71 copy(entries, r.entries) 72 return entries 73 } 74 75 func (r *Recorder) EntriesContaining(sub string) []string { 76 r.mutex.RLock() 77 defer r.mutex.RUnlock() 78 79 matches := []string{} 80 for _, entry := range r.entries { 81 if strings.Contains(entry, sub) { 82 matches = append(matches, entry) 83 } 84 } 85 return matches 86 } 87 88 func (r *Recorder) EntriesMatching(regex string) []string { 89 re := regexp.MustCompile(regex) 90 r.mutex.RLock() 91 defer r.mutex.RUnlock() 92 93 matches := []string{} 94 for _, entry := range r.entries { 95 if re.MatchString(entry) { 96 matches = append(matches, entry) 97 } 98 } 99 return matches 100 } 101 102 func (r *Recorder) Messages() []string { 103 r.mutex.RLock() 104 defer r.mutex.RUnlock() 105 106 messages := make([]string, len(r.messages), cap(r.messages)) 107 copy(messages, r.messages) 108 return messages 109 } 110 111 func (r *Recorder) MessagesContaining(sub string) []string { 112 r.mutex.RLock() 113 defer r.mutex.RUnlock() 114 115 matches := []string{} 116 for _, msg := range r.messages { 117 if strings.Contains(msg, sub) { 118 matches = append(matches, msg) 119 } 120 } 121 return matches 122 } 123 124 func (r *Recorder) MessagesMatching(regex string) []string { 125 re := regexp.MustCompile(regex) 126 r.mutex.RLock() 127 defer r.mutex.RUnlock() 128 129 matches := []string{} 130 for _, msg := range r.messages { 131 if re.MatchString(msg) { 132 matches = append(matches, msg) 133 } 134 } 135 return matches 136 } 137 138 type RecordingCore struct { 139 zapcore.LevelEnabler 140 encoder zapcore.Encoder 141 recorder *Recorder 142 writer zapcore.WriteSyncer 143 } 144 145 func (r *RecordingCore) Write(e zapcore.Entry, fields []zapcore.Field) error { 146 buf, err := r.encoder.EncodeEntry(e, fields) 147 if err != nil { 148 return err 149 } 150 151 r.writer.Write(buf.Bytes()) 152 r.recorder.addEntry(e, buf) 153 154 buf.Free() 155 156 return nil 157 } 158 159 func (r *RecordingCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { 160 if r.Enabled(e.Level) { 161 ce = ce.AddCore(e, r) 162 } 163 if ce != nil && e.Level == zapcore.FatalLevel { 164 panic(e.Message) 165 } 166 return ce 167 } 168 169 func (r *RecordingCore) With(fields []zapcore.Field) zapcore.Core { 170 clone := &RecordingCore{ 171 LevelEnabler: r.LevelEnabler, 172 encoder: r.encoder.Clone(), 173 recorder: r.recorder, 174 writer: r.writer, 175 } 176 177 for _, f := range fields { 178 f.AddTo(clone.encoder) 179 } 180 181 return clone 182 } 183 184 func (r *RecordingCore) Sync() error { 185 return r.writer.Sync() 186 } 187 188 type TestingWriter struct{ testing.TB } 189 190 func (t *TestingWriter) Write(buf []byte) (int, error) { 191 t.Logf("%s", bytes.TrimRight(buf, "\n")) 192 return len(buf), nil 193 } 194 195 func (t *TestingWriter) Sync() error { return nil } 196 197 type Option func(r *RecordingCore, l *zap.Logger) *zap.Logger 198 199 func Named(loggerName string) Option { 200 return func(r *RecordingCore, l *zap.Logger) *zap.Logger { 201 return l.Named(loggerName) 202 } 203 } 204 205 func AtLevel(level zapcore.Level) Option { 206 return func(r *RecordingCore, l *zap.Logger) *zap.Logger { 207 r.LevelEnabler = zap.LevelEnablerFunc(func(l zapcore.Level) bool { 208 return level.Enabled(l) 209 }) 210 return l 211 } 212 } 213 214 func NewTestLogger(tb testing.TB, options ...Option) (*flogging.FabricLogger, *Recorder) { 215 enabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool { 216 return zapcore.DebugLevel.Enabled(l) 217 }) 218 219 formatters, err := fabenc.ParseFormat(DefaultFormat) 220 if err != nil { 221 tb.Fatalf("failed to parse format %s: %s", DefaultFormat, err) 222 } 223 encoder := fabenc.NewFormatEncoder(formatters...) 224 if err != nil { 225 tb.Fatalf("failed to create format encoder: %s", err) 226 } 227 228 recorder := newRecorder() 229 recordingCore := &RecordingCore{ 230 LevelEnabler: enabler, 231 encoder: encoder, 232 recorder: recorder, 233 writer: &TestingWriter{TB: tb}, 234 } 235 236 zl := zap.New(recordingCore) 237 for _, o := range options { 238 zl = o(recordingCore, zl) 239 } 240 241 return flogging.NewFabricLogger(zl, zap.AddCaller()), recorder 242 }