Documentation ¶
Overview ¶
Package slogassert provides a slog Handler that allows testing that expected logging messages were made in your test code.
Normal Usage ¶
Normal usage looks like this:
func TestSomething(t *testing.T) { // This automatically registers a Cleanup function to assert // that all log messages are accounted for. handler := slogassert.New(t, slog.LevelWarn) logger := slog.New(handler) // inject the logger into your test code and run it // Now start asserting things: handler.AssertSomeOf("some log message") // often useful to finish up with an assertion that // all log messages have been accounted for: handler.AssertEmpty() }
A variety of assertions at varying levels of detail are available on the Handler.
Index ¶
- Constants
- func NullHandler() slog.Handler
- func NullLogger() *slog.Logger
- type Handler
- func (h *Handler) Assert(f func(LogMessage) bool) int
- func (h *Handler) AssertEmpty()
- func (h *Handler) AssertMessage(msg string)
- func (h *Handler) AssertPrecise(lmm LogMessageMatch)
- func (h *Handler) AssertSomeMessage(msg string) int
- func (h *Handler) AssertSomePrecise(lmm LogMessageMatch) int
- func (h *Handler) Enabled(_ context.Context, level slog.Level) bool
- func (h *Handler) Fail(msg string, args ...any)
- func (h *Handler) Handle(ctx context.Context, record slog.Record) error
- func (h *Handler) Reset()
- func (h *Handler) Unasserted() []LogMessage
- func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler
- func (h *Handler) WithGroup(name string) slog.Handler
- type LogMessage
- type LogMessageMatch
- type Option
- type Tester
Examples ¶
Constants ¶
const ( // LevelDontCare can be used in a LogMessageMatch to indicate // that the level does not need to match. LevelDontCare = slog.Level(-255000000) )
Variables ¶
This section is empty.
Functions ¶
func NullHandler ¶ added in v0.0.5
NullHandler returns a slog.Handler that does nothing.
func NullLogger ¶ added in v0.0.5
NullLogger returns a *slog.Logger pointed at a NullHandler.
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler implements the slog.Handler interface, with additional methods for testing.
All methods on this Handler are thread-safe.
func New ¶
New creates a new testing logger, logging with the given level.
If wrapped is not nil, Handle calls will be passed down to that handler as well.
It is recommended to generally call defer handler.AssertEmpty() on the result of this call.
func NewDefault ¶ added in v0.3.3
NewDefault is a helper function for tests that creates a slogassert Handler and sets it as the default slog handler Once the test is complete it will attempt to restore the previous handler.
It accepts options to customize the handler, including
- WithLeveler to set the log level
- WithAssertEmpty to assert that the handler is empty at the end of the test
- WithWrapped to wrap the handler with another handler
Example:
func TestExample(t *testing.T) { handler := NewDefault(t, WithLeveler(slog.LevelError)) CodeUnderTest() handler.AssertMessage("expected log message") }
This function MUST NOT be used with t.Parallel(). Doing so will cause unexpected results.
Example ¶
package main import ( "context" "github.com/thejerf/slogassert" "log/slog" "testing" ) // fakeTestingT is a testing.T used in the runnable example to demostrate usage type fakeTestingT struct { *testing.T } func (ft *fakeTestingT) Run(_ string, f func(t *testing.T)) { f(ft.T) } var t = &fakeTestingT{ T: &testing.T{}, } // CodeUnderTest is an example function, used to demonstrate usage. func CodeUnderTest() { slog.ErrorContext(context.Background(), "expected log message") } func main() { t.Run("ensure correct slog message is written", func(t *testing.T) { // update the default logger, and then reset it at the end of the test st := slogassert.NewDefault( t, slogassert.WithLeveler(slog.LevelInfo), // only capture info and above slogassert.WithAssertEmpty(), // ensure that all messages have been captured ) // ... // run the test code CodeUnderTest() // ... // capture and assert that the logged message st.AssertMessage("expected log message") }) }
Output:
func (*Handler) Assert ¶ added in v0.3.0
func (h *Handler) Assert(f func(LogMessage) bool) int
Assert takes in a function that takes a recorded log message and indicates whether or not it is "correct" according to your tests, and should be removed from the slice of unasserted log messages. Essentially all other assertions provided are just ways of populating this.
The passed-in function will be presented only with the remaining unasserted log messages at the time of the call.
func (*Handler) AssertEmpty ¶
func (h *Handler) AssertEmpty()
AssertEmpty asserts that all log messages have now been accounted for and there is nothing left.
A call to this method will be automatically deferred through the testing system if you use New(), but you can also use New
func (*Handler) AssertMessage ¶
AssertMessage asserts a logging message recorded with the giving logging message.
func (*Handler) AssertPrecise ¶
func (h *Handler) AssertPrecise(lmm LogMessageMatch)
AssertPrecise takes a LogMessageMatch and asserts the first log message that matches it.
func (*Handler) AssertSomeMessage ¶
AssertSomeMessage asserts that some logging events were recorded with the given message. The return value is the number of matched messages if there were any. If there was zero, the test fails.
func (*Handler) AssertSomePrecise ¶
func (h *Handler) AssertSomePrecise(lmm LogMessageMatch) int
AssertSomePrecise asserts all the messages in the log that match the LogMessageMatch criteria. The return value is th enumber of matched messages if there were any. (If there aren't any this fails the test.)
func (*Handler) Enabled ¶
Enabled implements slog.Handler, reporting back to slog whether or not the handler is enabled for this level of log message.
func (*Handler) Fail ¶ added in v0.3.0
Fail will print out the remaining unasserted messages and pass the given msg and args to t.Fatalf. This can be used in your custom assertions to fail them out.
func (*Handler) Handle ¶
Handle implements slog.Handler, recording a log message into the root handler.
func (*Handler) Reset ¶
func (h *Handler) Reset()
Reset will simply empty out the log entirely. This can be used in anger to simply make tests pass, or when you legitimately have some logging messages you don't want to bind your tests to (for instance this package's own call to testing/slogtest).
func (*Handler) Unasserted ¶ added in v0.0.7
func (h *Handler) Unasserted() []LogMessage
Unasserted returns all the log messages that are currently unasserted within the slog assert. The returned result is a deep copy. This method does NOT assert them; after a call to this method, if there are any messages an AssertEmpty will still fail.
It is probably superficially tempting to just use this and examine the result with code. However, bear in mind that using the assertion functions in conjuction with the default AssertEmpty on test cleanup already handles making sure everything is asserted. There's a lot of bugs easy to write with direct code examination.
However, sometimes you just need to check the messages with code.
type LogMessage ¶ added in v0.0.7
type LogMessage struct { Message string Level slog.Level Stacktrace string // key is the slash-encoded group path to this value Attrs map[string]slog.Value // this package deliberately ignores this, but passing // testing/slogtest requires us to store this Time time.Time }
LogMessage is a struct for storing the log messages picked up by slogassert's handler.
func (*LogMessage) Print ¶ added in v0.0.7
func (lm *LogMessage) Print(w io.Writer)
Print is a default method that can dump a LogMessage out to a writer; this is used by slogassert to print unasserted log messages.
type LogMessageMatch ¶
type LogMessageMatch struct { Message string Level slog.Level Attrs map[string]any AllAttrsMatch bool }
LogMessageMatch defines a precise message to match.
The Message works as you'd expect; an equality check. It is always checked, so an empty message means to verify that the message logged was empty.
If Level is LevelDontCare, the level won't be matched. Otherwise, it will also be an equality check.
Attrs is a map of string to any. The strings will be the groups for the given attribute, joined together by dots. For instance, an ungrouped key called "url" will be "url". If it is in a "request" group, it will be keyed by "request.url". If that is also in a "webserver" group, the key will be "webserver.request.url". Any dots in the keys themselves will be backslash encoded, so a top-level key called "a.b" will be "a\.b" in this map.
The value is a matcher on the attribute, which may be one of three things.
It can be a function "func (slog.Value) bool", which will be passed the value. If it returns true, it is considered to match; false is considered to be not a match.
It can be a function "func (T) bool", where "T" matches the concrete value behind the Kind of the slog.Value. In that case, the same rules apply. For KindAny, this must be precisely "func(any) bool"; this is done via type switching, not a lot of `reflect` calls, so only and exactly "func (any) bool" will work.
It can be a concrete value, in which case it must be equal to the value contained in the attribute. Type-appropriate equality is used, e.g., time.Time's are compared via time.Equal.
Any other value will result in an error being returned when used to match.
AllAttrsMatch indicate whether the Attrs map must contain matches for all attributes in the match. If true, and there are unmatched attribtues in the log message, the match will fail. If false, extra attributes in the log message won't fail the match.
func (LogMessageMatch) Matches ¶ added in v0.3.3
func (lmm LogMessageMatch) Matches(lm LogMessage) bool
Matches returnes true if the provided LogMessage satisfies LogMessageMatch.
type Option ¶ added in v0.3.3
type Option func(*config)
An Option allows for configuration of the default handler created by NewDefault.
func WithAssertEmpty ¶ added in v0.3.3
func WithAssertEmpty() Option
WithAssertEmpty is a functional option for NewDefault that configures the handler to validated that all messages have been captured and asserted.
func WithLeveler ¶ added in v0.3.3
WithLeveler is a functional option for NewDefault that sets the minimum log level of the default handler. All messages below this level will be ignored.
func WithWrapped ¶ added in v0.3.3
WithWrapped is a functional option for NewDefault that wraps the generated default handler with another handler. If set then handle calls will be passed down to that handler as well.