Ghost

struxadotcloud/struxa

public
Fork0
CodeIssues0Pull requests0
1branch
struxa.claudeskillsreview-logging-patternsreferencesdrain-pipeline.md
initial commitd4cb9e15 months ago158 lines · 5.0 KB
1# Drain Pipeline Reference
2
3The drain pipeline wraps any adapter to add batching, retry with backoff, and buffer overflow protection. Use it in production to reduce network overhead and handle transient failures.
4
5## When to Recommend
6
7- The user has a drain adapter (Axiom, OTLP, custom) and is deploying to production
8- High-throughput scenarios where one HTTP request per event is wasteful
9- The user needs retry logic for unreliable backends
10- The user is implementing batching manually with `setInterval` and arrays
11
12## Basic Setup
13
14```typescript
15// server/plugins/evlog-drain.ts
16import type { DrainContext
17import { createDrainPipeline }
18import { createAxiomDrain }
19
20export default defineNitroPlugin((nitroApp
21 const pipeline =
22 const drain =
23
24 nitroApp.hooks.hook
25 nitroApp.hooks.hook
26});
27```
28
29**Important:** Always call `drain.flush()
30
31## Full Configuration
32
33```typescript
34const pipeline = createDrainPipeline
35 batch: {
36 size: 50, //
37 intervalMs: 5000, //
38 },
39 retry: {
40 maxAttempts: 3, //
41 backoff: "exponential", //
42 initialDelayMs: 1000, //
43 maxDelayMs: 30000, //
44 },
45 maxBufferSize: 1000, //
46 onDropped: (events,
47 // Called when events are dropped (overflow or retry exhaustion)
48 console.error(`[evlog] Dropped
49 },
50});
51```
52
53## How It Works
54
551. `drain(ctx)` pushes a single event into the buffer
562. When `buffer.length >= batch.size`, the batch is flushed immediately
573. If the batch isn't full, a timer starts; after `intervalMs`, whatever is buffered gets flushed
584. On flush, the drain function receives `T[]` (always an array)
595. If the drain throws, the batch is retried with the configured backoff
606. After `maxAttempts` failures, `onDropped` is called and the batch is discarded
617. If the buffer exceeds `maxBufferSize`, the oldest event is dropped and `onDropped` is called
62
63## Backoff Strategies
64
65| Strategy | Delay Pattern | Best For |
66| ------------- | ----------------- |
67| `exponential` | 1s, 2s, 4s, 8s... | Default. Transient failures needing recovery time
68| `linear` | 1s, 2s, 3s, 4s... | Predictable delay growth
69| `fixed` | 1s, 1s, 1s, 1s... | Rate-limited APIs with known cooldown
70
71## Returned Drain Function API
72
73```typescript
74const drain = pipeline
75
76drain(ctx); //
77await drain.flush()
78drain.pending; //
79```
80
81## Common Patterns
82
83### With multiple adapters
84
85```typescript
86const axiom = createAxiomDrain
87const otlp = createOTLPDrain
88
89const pipeline = createDrainPipeline
90const drain = pipeline
91 await Promise.allSettled
92});
93```
94
95### Custom drain function
96
97```typescript
98const pipeline = createDrainPipeline
99const drain = pipeline
100 await fetch("https://your-service.com/logs
101 method: "POST",
102 headers: { "Content-Type":
103 body: JSON.stringify
104 });
105});
106```
107
108### Low-traffic with longer interval
109
110```typescript
111const pipeline = createDrainPipeline
112 batch: { size: 10
113});
114```
115
116## Anti-Patterns
117
118### Manual batching with setInterval
119
120```typescript
121// ❌ No retry, no overflow protection, no flush on shutdown
122const batch: WideEvent
123setInterval(() => {
124 if (batch.length
125}, 5000)
126```
127
128**Transform to:**
129
130```typescript
131// ✅ Use the pipeline
132const pipeline = createDrainPipeline
133const drain = pipeline
134nitroApp.hooks.hook(
135```
136
137### Missing flush on shutdown
138
139```typescript
140// ❌ Buffered events lost on process exit
141nitroApp.hooks.hook(
142```
143
144**Fix:**
145
146```typescript
147// ✅ Always flush on close
148nitroApp.hooks.hook(
149nitroApp.hooks.hook(
150```
151
152## Review Checklist
153
154- [ ] Pipeline wraps the adapter for production use
155- [ ] `drain.flush()` called on server `close` hook
156- [ ] `onDropped` callback logs or reports dropped events
157- [ ] Batch size and interval are appropriate for the traffic volume
158- [ ] `maxBufferSize` is set to prevent memory leaks under load
}
from
"
evlog
"
;
from
"
evlog/pipeline
"
;
from
"
evlog/axiom
"
;
)
=>
{
createDrainPipeline
<
DrainContext
>
()
;
pipeline
(
createAxiomDrain
())
;
(
"
evlog:drain
"
,
drain
)
;
(
"
close
"
,
()
=>
drain
.
flush
())
;
`
on server
`
close
`
hook. Without it, buffered events are lost when the process exits.
<
DrainContext
>
(
{
Max events per batch (default: 50)
Max wait before flushing partial batch (default: 5000)
Total attempts including first (default: 3)
'exponential' | 'linear' | 'fixed' (default: 'exponential')
Base delay for first retry (default: 1000)
Upper bound for any retry delay (default: 30000)
Max buffered events; oldest dropped on overflow (default: 1000)
error
)
=>
{
${
events
.
length
}
events:
`
,
error
?.
message
)
;
-------------------------------------------------
|
|
|
|
(
myDrainFn
)
;
Push a single event (synchronous, non-blocking)
;
//
Force-flush all buffered events
Number of events currently buffered (readonly)
()
;
()
;
<
DrainContext
>
()
;
(
async
(
batch
)
=>
{
([
axiom
(
batch
)
,
otlp
(
batch
)])
;
<
DrainContext
>
(
{
batch
:
{
size
:
100
}
}
)
;
(
async
(
batch
)
=>
{
"
,
{
"
application/json
"
}
,
(
batch
.
map
(
(
ctx
)
=>
ctx
.
event
))
,
<
DrainContext
>
(
{
,
intervalMs
:
30000
}
,
//
Flush every 30s or 10 events
[]
=
[]
>
0
)
fetch
(
...
)
<
DrainContext
>
()
(
async
(
batch
)
=>
{
await
fetch
(
...
)
}
)
'
close
'
,
()
=>
drain
.
flush
())
"
evlog:drain
"
,
drain
)
;
"
evlog:drain
"
,
drain
)
;
"
close
"
,
()
=>
drain
.
flush
())
;