@@ -545,6 +545,179 @@ For more details, see `getRotatingFileSink()` function and
545545> flag to use the rotating file sink.
546546
547547
548+ Fingers crossed sink
549+ --------------------
550+
551+ * This API is available since LogTape 1.1.0.*
552+
553+ The fingers crossed sink implements a “fingers crossed” logging pattern where
554+ debug and low-level logs are buffered in memory and only output when
555+ a significant event (like an ` "error" ` ) occurs. This pattern reduces log noise
556+ in normal operations while providing detailed context when issues arise,
557+ making logs more readable and actionable.
558+
559+ ### Basic usage
560+
561+ The simplest way to use the fingers crossed sink is to wrap an existing sink:
562+
563+ ~~~~ typescript twoslash
564+ // @noErrors: 2345
565+ import { configure , fingersCrossed , getConsoleSink } from " @logtape/logtape" ;
566+
567+ await configure ({
568+ sinks: {
569+ console: fingersCrossed (getConsoleSink ()),
570+ },
571+ loggers: [
572+ { category: [], sinks: [" console" ], lowestLevel: " debug" },
573+ ],
574+ });
575+ ~~~~
576+
577+ With this configuration:
578+
579+ - ` "debug" ` , ` "info" ` , and ` "warning" ` logs are buffered in memory
580+ - When an ` "error" ` (or higher) occurs, all buffered logs plus the error are
581+ output
582+ - Subsequent logs pass through directly until the next trigger event
583+
584+ ### Customizing trigger level
585+
586+ You can customize when the buffer is flushed by setting the trigger level:
587+
588+ ~~~~ typescript twoslash
589+ // @noErrors: 2345
590+ import { configure , fingersCrossed , getConsoleSink } from " @logtape/logtape" ;
591+
592+ await configure ({
593+ sinks: {
594+ console: fingersCrossed (getConsoleSink (), {
595+ triggerLevel: " warning" , // Trigger on warning or higher
596+ maxBufferSize: 500 , // Keep last 500 records
597+ }),
598+ },
599+ // Omitted for brevity
600+ });
601+ ~~~~
602+
603+ ### Category isolation
604+
605+ By default, all log records share a single buffer. For applications with
606+ multiple modules or components, you can isolate buffers by category to prevent
607+ one component's errors from flushing logs from unrelated components:
608+
609+ ~~~~ typescript twoslash
610+ // @noErrors: 2345
611+ import { configure , fingersCrossed , getConsoleSink } from " @logtape/logtape" ;
612+
613+ await configure ({
614+ sinks: {
615+ console: fingersCrossed (getConsoleSink (), {
616+ isolateByCategory: " descendant" ,
617+ }),
618+ },
619+ // Omitted for brevity
620+ });
621+ ~~~~
622+
623+ Category isolation modes:
624+
625+ ` "descendant" `
626+ : Flush child category buffers when parent category triggers.
627+ For example, an error in ` ["app"] ` flushes buffers for ` ["app", "auth"] `
628+ and ` ["app", "db"] ` .
629+
630+ ` "ancestor" `
631+ : Flush parent category buffers when child category triggers.
632+ For example, an error in ` ["app", "auth"] ` flushes the ` ["app"] ` buffer.
633+
634+ ` "both" `
635+ : Flush both parent and child category buffers, combining descendant and
636+ ancestor modes.
637+
638+ ### Custom category matching
639+
640+ For advanced use cases, you can provide a custom function to determine which
641+ categories should be flushed:
642+
643+ ~~~~ typescript twoslash
644+ // @noErrors: 2345
645+ import { configure , fingersCrossed , getConsoleSink } from " @logtape/logtape" ;
646+
647+ await configure ({
648+ sinks: {
649+ console: fingersCrossed (getConsoleSink (), {
650+ isolateByCategory : (triggerCategory , bufferedCategory ) => {
651+ // Custom logic: flush if categories share the first element
652+ return triggerCategory [0 ] === bufferedCategory [0 ];
653+ },
654+ }),
655+ },
656+ // Omitted for brevity
657+ });
658+ ~~~~
659+
660+ ### Buffer management
661+
662+ The fingers crossed sink automatically manages buffer size to prevent memory
663+ issues:
664+
665+ ~~~~ typescript twoslash
666+ // @noErrors: 2345
667+ import { configure , fingersCrossed , getConsoleSink } from " @logtape/logtape" ;
668+
669+ await configure ({
670+ sinks: {
671+ console: fingersCrossed (getConsoleSink (), {
672+ maxBufferSize: 1000 , // Keep last 1000 records per buffer
673+ }),
674+ },
675+ // Omitted for brevity
676+ });
677+ ~~~~
678+
679+ When the buffer exceeds the maximum size, the oldest records are automatically
680+ dropped to prevent unbounded memory growth.
681+
682+ ### Use cases
683+
684+ The fingers crossed sink is ideal for:
685+
686+ Production debugging
687+ : Keep detailed debug logs in memory without cluttering output,
688+ only showing them when errors occur to provide context.
689+
690+ Error investigation
691+ : Capture the sequence of events leading up to an error for thorough
692+ investigation.
693+
694+ Log volume management
695+ : Reduce log noise in normal operations while maintaining detailed visibility
696+ during issues.
697+
698+ Component isolation
699+ : Use category isolation to prevent log noise from one component affecting
700+ debugging of another component.
701+
702+ ### Performance considerations
703+
704+ Memory usage
705+ : Buffered logs consume memory. Use appropriate buffer sizes and consider
706+ your application's memory constraints.
707+
708+ Trigger frequency
709+ : Frequent trigger events (like ` "warning" ` s) may reduce the effectiveness of
710+ buffering. Choose trigger levels carefully.
711+
712+ Category isolation overhead
713+ : Category isolation adds some overhead for category matching.
714+ For high-volume logging, consider using a single buffer
715+ if isolation isn't needed.
716+
717+ For more details, see the ` fingersCrossed() ` function and
718+ ` FingersCrossedOptions ` interface in the API reference.
719+
720+
548721Text formatter
549722--------------
550723
0 commit comments