Skip to content

Commit 602e54c

Browse files
committed
Add manual fingers-crossed buffer controls
Let callers flush or discard buffered records through sink methods or a record-driven bufferAction callback. Support category and context selectors while ending the matching lifecycle after each action. Keep isolated buffer identities as structured metadata, and reset global state before forwarding records so errors and synchronous re-entry cannot leave stale buffers. Document the APIs and cover lifecycle edge cases across runtimes. Closes #205 Assisted-by: Codex:gpt-5.6-sol
1 parent 0e5c5dd commit 602e54c

6 files changed

Lines changed: 707 additions & 113 deletions

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ To be released.
1010

1111
### @logtape/logtape
1212

13+
- Added `FingersCrossedSink.flush()` and `FingersCrossedSink.discard()`
14+
methods and the `FingersCrossedOptions.bufferAction` callback for flushing
15+
or discarding isolated buffers when a request or job completes. [[#205]]
1316
- Added the `StreamSinkOptions.closeStream` option to dispose stream sinks
1417
without closing caller-owned streams. [[#203]]
1518

1619
[#203]: https://github.com/dahlia/logtape/issues/203
20+
[#205]: https://github.com/dahlia/logtape/issues/205
1721

1822

1923
Version 2.3.1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Added `FingersCrossedSink.flush()` and `FingersCrossedSink.discard()`
2+
methods and the `FingersCrossedOptions.bufferAction` callback for flushing
3+
or discarding isolated buffers when a request or job completes. [[#205]]

docs/manual/sinks.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,77 @@ await configure({
594594
With both isolations enabled, buffers are only flushed when both the category
595595
relationship matches and the context values are the same.
596596

597+
### Record-driven buffer actions
598+
599+
*This API is available since LogTape 2.4.0.*
600+
601+
Use the `~FingersCrossedOptions.bufferAction` callback when a log record marks
602+
the end of a request, job, or other isolated lifecycle. Return `"flush"` to
603+
emit the matching buffered records and the action record, or `"discard"` to
604+
drop both:
605+
606+
~~~~ typescript twoslash
607+
// @noErrors: 2345
608+
import { fingersCrossed, getConsoleSink } from "@logtape/logtape";
609+
610+
const sink = fingersCrossed(getConsoleSink(), {
611+
isolateByContext: { keys: ["requestId"] },
612+
bufferAction(record) {
613+
const status = record.properties.status;
614+
if (typeof status !== "number") return undefined;
615+
return status >= 500 ? "flush" : "discard";
616+
},
617+
});
618+
~~~~
619+
620+
Returning `undefined` applies the regular `triggerLevel` and `bufferLevel`
621+
behavior. The callback runs before LogTape checks whether the matching buffer
622+
has already been triggered, so a final request record also releases an active
623+
triggered context.
624+
625+
Both callback actions are terminal. After flushing or discarding, the same
626+
isolation key starts a fresh buffer if it appears again. This differs from a
627+
`triggerLevel` match, which flushes the buffer and lets subsequent records for
628+
the triggered isolation pass through directly.
629+
630+
### Manual buffer control
631+
632+
*This API is available since LogTape 2.4.0.*
633+
634+
The sink returned by `fingersCrossed()` implements `FingersCrossedSink`. Use
635+
its `~FingersCrossedSink.flush()` and `~FingersCrossedSink.discard()` methods
636+
when the lifecycle ends outside the logging stream:
637+
638+
~~~~ typescript twoslash
639+
// @noErrors: 2345
640+
import { fingersCrossed, getConsoleSink } from "@logtape/logtape";
641+
642+
const sink = fingersCrossed(getConsoleSink(), {
643+
isolateByContext: { keys: ["requestId"] },
644+
});
645+
646+
// ---cut-before---
647+
function finishRequest(requestId: string, succeeded: boolean) {
648+
if (succeeded) {
649+
// A successful request no longer needs its diagnostic logs.
650+
sink.discard({ context: { requestId } });
651+
} else {
652+
// Emit diagnostic logs when an external failure signal arrives.
653+
sink.flush({ context: { requestId } });
654+
}
655+
}
656+
~~~~
657+
658+
A context-only selector applies to every category for that context. Every key
659+
configured in `~FingersCrossedOptions.isolateByContext` must be present in the
660+
selector. Adding `category` narrows the operation using the configured
661+
category isolation matcher. Omit the selector to flush or discard every
662+
buffer.
663+
664+
Both methods release buffered and triggered state. Calling either method for
665+
an already-triggered isolation resets it, so the next record with the same key
666+
is buffered again.
667+
597668
### Buffer management
598669

599670
The fingers crossed sink provides several mechanisms to manage memory usage

packages/logtape/src/mod.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export {
6969
type AsyncSink,
7070
type ConsoleSinkOptions,
7171
fingersCrossed,
72+
type FingersCrossedBufferAction,
73+
type FingersCrossedBufferSelector,
7274
type FingersCrossedOptions,
75+
type FingersCrossedSink,
7376
fromAsyncSink,
7477
getConsoleSink,
7578
getStreamSink,

0 commit comments

Comments
 (0)