243 lines
5.1 KiB
Go
243 lines
5.1 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package floggingtest
|
|
|
|
import (
|
|
"bytes"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/hyperledger/fabric/common/flogging"
|
|
"github.com/hyperledger/fabric/common/flogging/fabenc"
|
|
"github.com/onsi/gomega/gbytes"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/buffer"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
// DefaultFormat is a log encoding format that is mostly compatible with the default
|
|
// log format but excludes colorization and time.
|
|
const DefaultFormat = "[%{module}] %{shortfunc} -> %{level:.4s} %{id:04x} %{message}"
|
|
|
|
type Recorder struct {
|
|
mutex sync.RWMutex
|
|
entries []string
|
|
messages []string
|
|
buffer *gbytes.Buffer
|
|
}
|
|
|
|
func newRecorder() *Recorder {
|
|
return &Recorder{
|
|
buffer: gbytes.NewBuffer(),
|
|
entries: []string{},
|
|
messages: []string{},
|
|
}
|
|
}
|
|
|
|
func (r *Recorder) addEntry(e zapcore.Entry, line *buffer.Buffer) {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
r.buffer.Write(line.Bytes())
|
|
r.entries = append(r.entries, strings.TrimRight(line.String(), "\n"))
|
|
r.messages = append(r.messages, e.Message)
|
|
}
|
|
|
|
func (r *Recorder) Reset() {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
r.buffer = gbytes.NewBuffer()
|
|
r.entries = []string{}
|
|
r.messages = []string{}
|
|
}
|
|
|
|
func (r *Recorder) Buffer() *gbytes.Buffer {
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
return r.buffer
|
|
}
|
|
|
|
func (r *Recorder) Entries() []string {
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
entries := make([]string, len(r.entries), cap(r.entries))
|
|
copy(entries, r.entries)
|
|
return entries
|
|
}
|
|
|
|
func (r *Recorder) EntriesContaining(sub string) []string {
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
matches := []string{}
|
|
for _, entry := range r.entries {
|
|
if strings.Contains(entry, sub) {
|
|
matches = append(matches, entry)
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
|
|
func (r *Recorder) EntriesMatching(regex string) []string {
|
|
re := regexp.MustCompile(regex)
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
matches := []string{}
|
|
for _, entry := range r.entries {
|
|
if re.MatchString(entry) {
|
|
matches = append(matches, entry)
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
|
|
func (r *Recorder) Messages() []string {
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
messages := make([]string, len(r.messages), cap(r.messages))
|
|
copy(messages, r.messages)
|
|
return messages
|
|
}
|
|
|
|
func (r *Recorder) MessagesContaining(sub string) []string {
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
matches := []string{}
|
|
for _, msg := range r.messages {
|
|
if strings.Contains(msg, sub) {
|
|
matches = append(matches, msg)
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
|
|
func (r *Recorder) MessagesMatching(regex string) []string {
|
|
re := regexp.MustCompile(regex)
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
matches := []string{}
|
|
for _, msg := range r.messages {
|
|
if re.MatchString(msg) {
|
|
matches = append(matches, msg)
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
|
|
type RecordingCore struct {
|
|
zapcore.LevelEnabler
|
|
encoder zapcore.Encoder
|
|
recorder *Recorder
|
|
writer zapcore.WriteSyncer
|
|
}
|
|
|
|
func (r *RecordingCore) Write(e zapcore.Entry, fields []zapcore.Field) error {
|
|
buf, err := r.encoder.EncodeEntry(e, fields)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.writer.Write(buf.Bytes())
|
|
r.recorder.addEntry(e, buf)
|
|
|
|
buf.Free()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *RecordingCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
|
if r.Enabled(e.Level) {
|
|
ce = ce.AddCore(e, r)
|
|
}
|
|
if ce != nil && e.Level == zapcore.FatalLevel {
|
|
panic(e.Message)
|
|
}
|
|
return ce
|
|
}
|
|
|
|
func (r *RecordingCore) With(fields []zapcore.Field) zapcore.Core {
|
|
clone := &RecordingCore{
|
|
LevelEnabler: r.LevelEnabler,
|
|
encoder: r.encoder.Clone(),
|
|
recorder: r.recorder,
|
|
writer: r.writer,
|
|
}
|
|
|
|
for _, f := range fields {
|
|
f.AddTo(clone.encoder)
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
func (r *RecordingCore) Sync() error {
|
|
return r.writer.Sync()
|
|
}
|
|
|
|
type TestingWriter struct{ testing.TB }
|
|
|
|
func (t *TestingWriter) Write(buf []byte) (int, error) {
|
|
t.Logf("%s", bytes.TrimRight(buf, "\n"))
|
|
return len(buf), nil
|
|
}
|
|
|
|
func (t *TestingWriter) Sync() error { return nil }
|
|
|
|
type Option func(r *RecordingCore, l *zap.Logger) *zap.Logger
|
|
|
|
func Named(loggerName string) Option {
|
|
return func(r *RecordingCore, l *zap.Logger) *zap.Logger {
|
|
return l.Named(loggerName)
|
|
}
|
|
}
|
|
|
|
func AtLevel(level zapcore.Level) Option {
|
|
return func(r *RecordingCore, l *zap.Logger) *zap.Logger {
|
|
r.LevelEnabler = zap.LevelEnablerFunc(func(l zapcore.Level) bool {
|
|
return level.Enabled(l)
|
|
})
|
|
return l
|
|
}
|
|
}
|
|
|
|
func NewTestLogger(tb testing.TB, options ...Option) (*flogging.FabricLogger, *Recorder) {
|
|
enabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
|
|
return zapcore.DebugLevel.Enabled(l)
|
|
})
|
|
|
|
formatters, err := fabenc.ParseFormat(DefaultFormat)
|
|
if err != nil {
|
|
tb.Fatalf("failed to parse format %s: %s", DefaultFormat, err)
|
|
}
|
|
encoder := fabenc.NewFormatEncoder(formatters...)
|
|
if err != nil {
|
|
tb.Fatalf("failed to create format encoder: %s", err)
|
|
}
|
|
|
|
recorder := newRecorder()
|
|
recordingCore := &RecordingCore{
|
|
LevelEnabler: enabler,
|
|
encoder: encoder,
|
|
recorder: recorder,
|
|
writer: &TestingWriter{TB: tb},
|
|
}
|
|
|
|
zl := zap.New(recordingCore)
|
|
for _, o := range options {
|
|
zl = o(recordingCore, zl)
|
|
}
|
|
|
|
return flogging.NewFabricLogger(zl, zap.AddCaller()), recorder
|
|
}
|