...

Source file src/cdr.dev/slog/sloggers/slogtest/t.go

Documentation: cdr.dev/slog/sloggers/slogtest

     1  // Package slogtest contains the slogger for use
     2  // with Go's testing package.
     3  //
     4  // If imported, then all logs that go through the stdlib's
     5  // default logger will go through slog.
     6  package slogtest // import "cdr.dev/slog/sloggers/slogtest"
     7  
     8  import (
     9  	"context"
    10  	"log"
    11  	"os"
    12  	"sync"
    13  	"testing"
    14  
    15  	"cdr.dev/slog"
    16  	"cdr.dev/slog/internal/entryhuman"
    17  	"cdr.dev/slog/sloggers/sloghuman"
    18  )
    19  
    20  // Ensure all stdlib logs go through slog.
    21  func init() {
    22  	l := slog.Make(sloghuman.Sink(os.Stderr))
    23  	log.SetOutput(slog.Stdlib(context.Background(), l, slog.LevelInfo).Writer())
    24  }
    25  
    26  // Options represents the options for the logger returned
    27  // by Make.
    28  type Options struct {
    29  	// IgnoreErrors causes the test logger to not fatal the test
    30  	// on Fatal and not error the test on Error or Critical.
    31  	IgnoreErrors bool
    32  	// SkipCleanup skips adding a t.Cleanup call that prevents the logger from
    33  	// logging after a test has exited. This is necessary because race
    34  	// conditions exist when t.Log is called concurrently of a test exiting. Set
    35  	// to true if you don't need this behavior.
    36  	SkipCleanup bool
    37  }
    38  
    39  // Make creates a Logger that writes logs to tb in a human readable format.
    40  func Make(tb testing.TB, opts *Options) slog.Logger {
    41  	if opts == nil {
    42  		opts = &Options{}
    43  	}
    44  
    45  	sink := &testSink{
    46  		tb:   tb,
    47  		opts: opts,
    48  	}
    49  	if !opts.SkipCleanup {
    50  		tb.Cleanup(func() {
    51  			sink.mu.Lock()
    52  			defer sink.mu.Unlock()
    53  			sink.testDone = true
    54  		})
    55  	}
    56  
    57  	return slog.Make(sink)
    58  }
    59  
    60  type testSink struct {
    61  	tb       testing.TB
    62  	opts     *Options
    63  	mu       sync.RWMutex
    64  	testDone bool
    65  }
    66  
    67  func (ts *testSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
    68  	ts.mu.RLock()
    69  	defer ts.mu.RUnlock()
    70  
    71  	// Don't log after the test this sink was created in has finished.
    72  	if ts.testDone {
    73  		return
    74  	}
    75  
    76  	// The testing package logs to stdout and not stderr.
    77  	s := entryhuman.Fmt(os.Stdout, ent)
    78  
    79  	switch ent.Level {
    80  	case slog.LevelDebug, slog.LevelInfo, slog.LevelWarn:
    81  		ts.tb.Log(s)
    82  	case slog.LevelError, slog.LevelCritical:
    83  		if ts.opts.IgnoreErrors {
    84  			ts.tb.Log(s)
    85  		} else {
    86  			ts.tb.Error(s)
    87  		}
    88  	case slog.LevelFatal:
    89  		ts.tb.Fatal(s)
    90  	}
    91  }
    92  
    93  func (ts *testSink) Sync() {}
    94  
    95  var ctx = context.Background()
    96  
    97  func l(t testing.TB) slog.Logger {
    98  	return Make(t, &Options{SkipCleanup: true})
    99  }
   100  
   101  // Debug logs the given msg and fields to t via t.Log at the debug level.
   102  func Debug(t testing.TB, msg string, fields ...slog.Field) {
   103  	slog.Helper()
   104  	l(t).Debug(ctx, msg, fields...)
   105  }
   106  
   107  // Info logs the given msg and fields to t via t.Log at the info level.
   108  func Info(t testing.TB, msg string, fields ...slog.Field) {
   109  	slog.Helper()
   110  	l(t).Info(ctx, msg, fields...)
   111  }
   112  
   113  // Error logs the given msg and fields to t via t.Error at the error level.
   114  func Error(t testing.TB, msg string, fields ...slog.Field) {
   115  	slog.Helper()
   116  	l(t).Error(ctx, msg, fields...)
   117  }
   118  
   119  // Fatal logs the given msg and fields to t via t.Fatal at the fatal level.
   120  func Fatal(t testing.TB, msg string, fields ...slog.Field) {
   121  	slog.Helper()
   122  	l(t).Fatal(ctx, msg, fields...)
   123  }
   124  

View as plain text