@@ -91,12 +91,13 @@ bun add @logtape/testing
9191
9292~~~~ typescript twoslash
9393// @noErrors: 2307
94+ import { after , before , test } from " node:test" ;
9495import { configure , getLogger , reset } from " @logtape/logtape" ;
95- import { createLogRecorder } from " @logtape/testing" ;
96+ import { createLogRecorder } from " @logtape/testing/recorder " ;
9697
9798const recorder = createLogRecorder ();
9899
99- try {
100+ before ( async () => {
100101 await configure ({
101102 sinks: {
102103 recorder: recorder .sink , // [!code highlight]
@@ -110,7 +111,11 @@ try {
110111 { category: [" logtape" , " meta" ], sinks: [] },
111112 ],
112113 });
114+ });
115+
116+ after (reset );
113117
118+ test (" case" , () => {
114119 getLogger ([" my-lib" ]).info (" User {userId} logged in." , {
115120 userId: 123 ,
116121 });
@@ -121,9 +126,7 @@ try {
121126 message: " User 123 logged in." ,
122127 properties: { userId: 123 },
123128 });
124- } finally {
125- await reset ();
126- }
129+ });
127130~~~~
128131
129132The recorder stores records in sink call order. It snapshots lazy callback
@@ -145,6 +148,81 @@ sinks, still call `await dispose()` or `await reset()` as usual.
145148[ *@logtape/testing* ] : https://jsr.io/@logtape/testing
146149
147150
151+ Failure log reporter
152+ --------------------
153+
154+ * This API is available since LogTape 2.3.0.*
155+
156+ When logs are useful only after a test fails, use
157+ ` createFailureLogReporter() ` from the [ * @logtape/testing * ] package. It
158+ buffers records while the wrapped callback runs, discards them when the
159+ callback succeeds, and reports them to a sink when the callback throws or
160+ rejects:
161+
162+ ~~~~ typescript twoslash
163+ // @noErrors: 2307
164+ import { AsyncLocalStorage } from " node:async_hooks" ;
165+ import { after , before , test } from " node:test" ;
166+ import { configure , getLogger , reset } from " @logtape/logtape" ;
167+ import { createFailureLogReporter } from " @logtape/testing/reporter" ;
168+
169+ const reporter = createFailureLogReporter ({
170+ lowestLevel: " debug" ,
171+ });
172+
173+ before (async () => {
174+ await configure ({
175+ contextLocalStorage: new AsyncLocalStorage (),
176+ sinks: {},
177+ loggers: [
178+ { category: [" logtape" , " meta" ], sinks: [] },
179+ ],
180+ });
181+ });
182+
183+ after (reset );
184+
185+ test (" case" , reporter .wrap (async () => {
186+ getLogger ([" my-lib" ]).debug (" Fixture state: {state}" , {
187+ state: " ready" ,
188+ });
189+
190+ // Run assertions. The debug log is printed only if this callback fails.
191+ }));
192+ ~~~~
193+
194+ The reporter uses scoped configuration, so it does not call ` configure() ` or
195+ ` reset() ` for each wrapped callback and does not mutate process-wide logger
196+ routing while a test is running. The process-wide configuration still must
197+ provide ` ~Config.contextLocalStorage ` , because scoped configuration needs it to
198+ isolate the callback's logging policy.
199+
200+ Use ` ~FailureLogReporter.wrap() ` when passing a callback to a test runner. It
201+ preserves callback parameters such as a test context or fixtures, and it always
202+ returns an async callback. Use ` ~FailureLogReporter.run() ` when you want to
203+ invoke the callback directly:
204+
205+ ~~~~ typescript twoslash
206+ // @noErrors: 2307
207+ import { createFailureLogReporter } from " @logtape/testing/reporter" ;
208+
209+ const reporter = createFailureLogReporter ({
210+ lowestLevel: " debug" ,
211+ mode: " on-failure" ,
212+ });
213+
214+ await reporter .run (async () => {
215+ // Logs emitted here are reported only if this callback fails.
216+ });
217+ ~~~~
218+
219+ Set ` mode: "always" ` to report buffered records even when the callback passes,
220+ or ` mode: "never" ` to suppress reporting while keeping the shared wrapper in
221+ place. By default the reporter writes formatted records to the console; pass
222+ ` sink ` to report records elsewhere, or ` formatter ` to customize the default
223+ console output.
224+
225+
148226Buffer sink
149227-----------
150228
0 commit comments