@bufferlog/sdk-node 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/dist/config.d.ts +97 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +38 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/context/async-context.d.ts +86 -0
  8. package/dist/context/async-context.d.ts.map +1 -0
  9. package/dist/context/async-context.js +90 -0
  10. package/dist/context/async-context.js.map +1 -0
  11. package/dist/control-plane/policy-fetcher.d.ts +73 -0
  12. package/dist/control-plane/policy-fetcher.d.ts.map +1 -0
  13. package/dist/control-plane/policy-fetcher.js +116 -0
  14. package/dist/control-plane/policy-fetcher.js.map +1 -0
  15. package/dist/control-plane/telemetry-reporter.d.ts +73 -0
  16. package/dist/control-plane/telemetry-reporter.d.ts.map +1 -0
  17. package/dist/control-plane/telemetry-reporter.js +133 -0
  18. package/dist/control-plane/telemetry-reporter.js.map +1 -0
  19. package/dist/core/buffer-manager.d.ts +83 -0
  20. package/dist/core/buffer-manager.d.ts.map +1 -0
  21. package/dist/core/buffer-manager.js +119 -0
  22. package/dist/core/buffer-manager.js.map +1 -0
  23. package/dist/core/log-event.d.ts +72 -0
  24. package/dist/core/log-event.d.ts.map +1 -0
  25. package/dist/core/log-event.js +78 -0
  26. package/dist/core/log-event.js.map +1 -0
  27. package/dist/core/ring-buffer.d.ts +60 -0
  28. package/dist/core/ring-buffer.d.ts.map +1 -0
  29. package/dist/core/ring-buffer.js +120 -0
  30. package/dist/core/ring-buffer.js.map +1 -0
  31. package/dist/flash/adapters/datadog.d.ts +40 -0
  32. package/dist/flash/adapters/datadog.d.ts.map +1 -0
  33. package/dist/flash/adapters/datadog.js +67 -0
  34. package/dist/flash/adapters/datadog.js.map +1 -0
  35. package/dist/flash/adapters/splunk.d.ts +46 -0
  36. package/dist/flash/adapters/splunk.d.ts.map +1 -0
  37. package/dist/flash/adapters/splunk.js +71 -0
  38. package/dist/flash/adapters/splunk.js.map +1 -0
  39. package/dist/flash/adapters/stdout.d.ts +25 -0
  40. package/dist/flash/adapters/stdout.d.ts.map +1 -0
  41. package/dist/flash/adapters/stdout.js +29 -0
  42. package/dist/flash/adapters/stdout.js.map +1 -0
  43. package/dist/flash/adapters/types.d.ts +25 -0
  44. package/dist/flash/adapters/types.d.ts.map +1 -0
  45. package/dist/flash/adapters/types.js +10 -0
  46. package/dist/flash/adapters/types.js.map +1 -0
  47. package/dist/flash/flash-controller.d.ts +78 -0
  48. package/dist/flash/flash-controller.d.ts.map +1 -0
  49. package/dist/flash/flash-controller.js +157 -0
  50. package/dist/flash/flash-controller.js.map +1 -0
  51. package/dist/index.d.ts +126 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +185 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/integrations/pino.d.ts +37 -0
  56. package/dist/integrations/pino.d.ts.map +1 -0
  57. package/dist/integrations/pino.js +86 -0
  58. package/dist/integrations/pino.js.map +1 -0
  59. package/dist/integrations/winston.d.ts +61 -0
  60. package/dist/integrations/winston.d.ts.map +1 -0
  61. package/dist/integrations/winston.js +120 -0
  62. package/dist/integrations/winston.js.map +1 -0
  63. package/dist/middleware/express.d.ts +47 -0
  64. package/dist/middleware/express.d.ts.map +1 -0
  65. package/dist/middleware/express.js +71 -0
  66. package/dist/middleware/express.js.map +1 -0
  67. package/dist/middleware/fastify.d.ts +32 -0
  68. package/dist/middleware/fastify.d.ts.map +1 -0
  69. package/dist/middleware/fastify.js +91 -0
  70. package/dist/middleware/fastify.js.map +1 -0
  71. package/package.json +82 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BufferLog.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # @bufferlog/sdk-node
2
+
3
+ > **Buffer logs in memory. Flush only on errors. Save 90%+ on APM costs.**
4
+
5
+ `@bufferlog/sdk-node` is a lightweight SDK that wraps your existing logging library (Winston, Pino) and buffers all debug/info logs in memory per-request. If the request succeeds, the logs are safely discarded. If an error occurs (such as an HTTP 5xx or `logger.error`), the preceding log context is automatically flushed to your APM (Datadog, Splunk, etc.), giving you complete visibility without the cost of storing successful request logs.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @bufferlog/sdk-node
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ### 1. Express + Winston Setup
20
+
21
+ ```typescript
22
+ import express from 'express';
23
+ import winston from 'winston';
24
+ import { BufferLog, StdOutAdapter, DatadogAdapter } from '@bufferlog/sdk-node';
25
+
26
+ // 1. Create a BufferLog instance
27
+ const bufferlog = BufferLog.create({
28
+ bufferCapacity: 100,
29
+ adapters: [
30
+ new DatadogAdapter({ apiKey: process.env.DD_API_KEY! }),
31
+ new StdOutAdapter({ pretty: true })
32
+ ],
33
+ });
34
+
35
+ // 2. Create a Winston logger with the BufferLog transport
36
+ const logger = winston.createLogger({
37
+ level: 'debug',
38
+ transports: [bufferlog.winstonTransport()],
39
+ });
40
+
41
+ // 3. Apply the Express middleware
42
+ const app = express();
43
+ app.use(bufferlog.expressMiddleware());
44
+
45
+ // 4. Log normally - BufferLog handles context tracking and routing
46
+ app.get('/api/data', (req, res) => {
47
+ logger.info('Querying database...'); // Buffered (discarded if HTTP 2xx)
48
+ logger.debug('SQL: SELECT * FROM ...'); // Buffered (discarded if HTTP 2xx)
49
+ res.json({ status: 'ok' }); // Success -> logs are discarded ($0 cost)
50
+ });
51
+
52
+ app.get('/api/fail', (req, res) => {
53
+ logger.info('Starting transaction...'); // Buffered
54
+ logger.error('Database connection lost'); // Triggers Flush! Context + error is sent to APM
55
+ res.status(500).json({ error: true });
56
+ });
57
+ ```
58
+
59
+ ### 2. Fastify + Pino Setup
60
+
61
+ ```typescript
62
+ import Fastify from 'fastify';
63
+ import pino from 'pino';
64
+ import { BufferLog, StdOutAdapter } from '@bufferlog/sdk-node';
65
+
66
+ // 1. Create a BufferLog instance
67
+ const bufferlog = BufferLog.create({
68
+ bufferCapacity: 100,
69
+ adapters: [new StdOutAdapter({ pretty: true })],
70
+ });
71
+
72
+ // 2. Wrap Pino logger with BufferLog destination
73
+ const logger = pino(
74
+ { level: 'debug' },
75
+ bufferlog.pinoDestination()
76
+ );
77
+
78
+ const fastify = Fastify();
79
+
80
+ // 3. Register Fastify plugin and pass the required options
81
+ fastify.register(bufferlog.fastifyPlugin(), bufferlog.fastifyPluginOptions());
82
+
83
+ fastify.get('/api/data', async (request, reply) => {
84
+ logger.info('Handling fastify request');
85
+ return { ok: true };
86
+ });
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Configuration Options
92
+
93
+ Pass configuration options when calling `BufferLog.create(...)`:
94
+
95
+ ```typescript
96
+ BufferLog.create({
97
+ // Max logs to retain in memory per request. Oldest are overwritten when full.
98
+ bufferCapacity: 100,
99
+
100
+ // Log levels that immediately trigger a flush to APM.
101
+ // Defaults to: ['error', 'fatal', 'crit', 'emerg']
102
+ flushOnLevels: ['error', 'fatal'],
103
+
104
+ // HTTP response status codes that trigger a flush.
105
+ // Defaults to: [500, 501, 502, 503, 504]
106
+ flushOnStatusCodes: [500, 502, 503, 504],
107
+
108
+ // Downstream targets where flushed logs are shipped.
109
+ adapters: [
110
+ new DatadogAdapter({ apiKey: process.env.DD_API_KEY! }),
111
+ new SplunkAdapter({ token: process.env.SPLUNK_TOKEN!, url: process.env.SPLUNK_URL! }),
112
+ new StdOutAdapter()
113
+ ],
114
+
115
+ // Globally enable or disable buffering behavior.
116
+ enabled: true,
117
+
118
+ // If true, logs are sent directly to stdout/stderr if downstream APM adapters fail.
119
+ failOpen: true,
120
+
121
+ // Scrub sensitive PII data before logs enter the memory buffer.
122
+ scrubber: (msg, metadata) => {
123
+ return {
124
+ message: msg.replace(/password=\S+/g, 'password=***'),
125
+ metadata,
126
+ };
127
+ }
128
+ });
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Local Metrics & ROI Tracking
134
+
135
+ You can inspect the effectiveness and cost savings of BufferLog dynamically:
136
+
137
+ ```typescript
138
+ const metrics = bufferlog.getMetrics();
139
+ console.log(metrics);
140
+ /*
141
+ Output:
142
+ {
143
+ buffers: {
144
+ created: 1000, // Total requests processed
145
+ discarded: 995, // Requests where logs were successfully discarded
146
+ active: 0 // Active requests currently in flight
147
+ },
148
+ flash: {
149
+ flushCount: 5, // Total flushes triggered
150
+ eventsFlushed: 45, // Total log lines shipped to APMs
151
+ adapterErrors: 0 // Connection/adapter errors
152
+ }
153
+ }
154
+ */
155
+ ```
156
+ In this scenario, **99.5% of logs were safely discarded**, resulting in massive ingest savings!
157
+
158
+ ---
159
+
160
+ ## Control Plane Integration
161
+
162
+ Connect the SDK to the BufferLog Control Plane for remote policy management and usage tracking. Policies can be changed from the dashboard without redeploying your application.
163
+
164
+ ```typescript
165
+ const bufferlog = BufferLog.create({
166
+ adapters: [new DatadogAdapter({ apiKey: process.env.DD_API_KEY! })],
167
+ controlPlane: {
168
+ url: 'https://control.bufferlog.io', // or http://localhost:3001 for local dev
169
+ apiKey: process.env.BUFFERLOG_API_KEY!,
170
+ pollIntervalMs: 60_000, // How often to fetch policy updates (default: 60s)
171
+ telemetryIntervalMs: 60_000, // How often to push metrics (default: 60s)
172
+ },
173
+ });
174
+ ```
175
+
176
+ When connected, the SDK will:
177
+ - **Poll for policy updates** — buffer capacity, sampling rate, flush triggers, and bypass rules can all be changed remotely.
178
+ - **Push usage metrics** — counters (never log data) are sent to power the ROI dashboard.
179
+
180
+ If the control plane is unreachable, the SDK continues operating with its last known configuration.
181
+
182
+ ---
183
+
184
+ ## Graceful Shutdown
185
+
186
+ When your application is shutting down, call `shutdown()` to stop background timers and send a final telemetry report:
187
+
188
+ ```typescript
189
+ process.on('SIGTERM', async () => {
190
+ await bufferlog.shutdown();
191
+ process.exit(0);
192
+ });
193
+ ```
194
+
@@ -0,0 +1,97 @@
1
+ /**
2
+ * SDK Configuration — Defines all tunable parameters for BufferLog.
3
+ *
4
+ * Design notes:
5
+ * - All fields are optional with sensible defaults
6
+ * - Configuration is frozen after initialization to prevent mutation
7
+ */
8
+ import type { DownstreamAdapter } from './flash/adapters/types.js';
9
+ /**
10
+ * User-facing configuration options for the BufferLog SDK.
11
+ */
12
+ export interface BufferLogConfig {
13
+ /**
14
+ * Maximum number of log events per request buffer.
15
+ * When full, the oldest log is overwritten (ring buffer behavior).
16
+ * @default 100
17
+ */
18
+ bufferCapacity?: number;
19
+ /**
20
+ * Log levels that trigger a buffer flush.
21
+ * By default, ERROR and FATAL trigger a flush.
22
+ * @default ['error', 'fatal']
23
+ */
24
+ flushOnLevels?: string[];
25
+ /**
26
+ * HTTP status codes that trigger a buffer flush on response.
27
+ * @default [500, 501, 502, 503, 504]
28
+ */
29
+ flushOnStatusCodes?: number[];
30
+ /**
31
+ * Downstream adapters to send flushed logs to.
32
+ * At least one adapter should be configured; falls back to stdout if empty.
33
+ */
34
+ adapters?: DownstreamAdapter[];
35
+ /**
36
+ * Enable/disable BufferLog entirely.
37
+ * When disabled, all logs pass through to the original logger unmodified.
38
+ * @default true
39
+ */
40
+ enabled?: boolean;
41
+ /**
42
+ * Enable fail-open behavior: if BufferLog encounters an internal error,
43
+ * forward logs directly to stderr/stdout instead of dropping them.
44
+ * @default true
45
+ */
46
+ failOpen?: boolean;
47
+ /**
48
+ * Optional PII scrubbing function applied to each log event before buffering.
49
+ * Receives the log message and metadata; should return sanitized versions.
50
+ */
51
+ scrubber?: (message: string, metadata?: Record<string, unknown>) => {
52
+ message: string;
53
+ metadata?: Record<string, unknown>;
54
+ };
55
+ /**
56
+ * Connect to the BufferLog Control Plane for dynamic policy
57
+ * updates and telemetry reporting.
58
+ * If not set, the SDK operates in standalone mode with static config.
59
+ */
60
+ controlPlane?: {
61
+ /** Control plane base URL, e.g. "https://control.bufferlog.io" */
62
+ url: string;
63
+ /** API key for authentication */
64
+ apiKey: string;
65
+ /** Policy polling interval in ms. @default 60000 */
66
+ pollIntervalMs?: number;
67
+ /** Telemetry reporting interval in ms. @default 60000 */
68
+ telemetryIntervalMs?: number;
69
+ };
70
+ }
71
+ /**
72
+ * Resolved configuration with all defaults applied.
73
+ * Immutable after construction.
74
+ */
75
+ export interface ResolvedConfig {
76
+ readonly bufferCapacity: number;
77
+ readonly flushOnLevels: ReadonlySet<string>;
78
+ readonly flushOnStatusCodes: ReadonlySet<number>;
79
+ readonly adapters: readonly DownstreamAdapter[];
80
+ readonly enabled: boolean;
81
+ readonly failOpen: boolean;
82
+ readonly scrubber?: (message: string, metadata?: Record<string, unknown>) => {
83
+ message: string;
84
+ metadata?: Record<string, unknown>;
85
+ };
86
+ readonly controlPlane?: {
87
+ readonly url: string;
88
+ readonly apiKey: string;
89
+ readonly pollIntervalMs: number;
90
+ readonly telemetryIntervalMs: number;
91
+ };
92
+ }
93
+ /**
94
+ * Merge user config with defaults and freeze the result.
95
+ */
96
+ export declare function resolveConfig(userConfig?: BufferLogConfig): ResolvedConfig;
97
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAE/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK;QAClE,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;IAEF;;;;OAIG;IACH,YAAY,CAAC,EAAE;QACb,kEAAkE;QAClE,GAAG,EAAE,MAAM,CAAC;QACZ,iCAAiC;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,oDAAoD;QACpD,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,yDAAyD;QACzD,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK;QAC3E,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;IACF,QAAQ,CAAC,YAAY,CAAC,EAAE;QACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;KACtC,CAAC;CACH;AAWD;;GAEG;AACH,wBAAgB,aAAa,CAAC,UAAU,GAAE,eAAoB,GAAG,cAAc,CAsB9E"}
package/dist/config.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SDK Configuration — Defines all tunable parameters for BufferLog.
3
+ *
4
+ * Design notes:
5
+ * - All fields are optional with sensible defaults
6
+ * - Configuration is frozen after initialization to prevent mutation
7
+ */
8
+ /** Default configuration values. */
9
+ const DEFAULTS = {
10
+ bufferCapacity: 100,
11
+ flushOnLevels: ['error', 'fatal', 'crit', 'emerg'],
12
+ flushOnStatusCodes: [500, 501, 502, 503, 504],
13
+ enabled: true,
14
+ failOpen: true,
15
+ };
16
+ /**
17
+ * Merge user config with defaults and freeze the result.
18
+ */
19
+ export function resolveConfig(userConfig = {}) {
20
+ return Object.freeze({
21
+ bufferCapacity: userConfig.bufferCapacity ?? DEFAULTS.bufferCapacity,
22
+ flushOnLevels: new Set((userConfig.flushOnLevels ?? DEFAULTS.flushOnLevels).map((l) => l.toLowerCase())),
23
+ flushOnStatusCodes: new Set(userConfig.flushOnStatusCodes ?? DEFAULTS.flushOnStatusCodes),
24
+ adapters: Object.freeze(userConfig.adapters ?? []),
25
+ enabled: userConfig.enabled ?? DEFAULTS.enabled,
26
+ failOpen: userConfig.failOpen ?? DEFAULTS.failOpen,
27
+ scrubber: userConfig.scrubber,
28
+ controlPlane: userConfig.controlPlane
29
+ ? Object.freeze({
30
+ url: userConfig.controlPlane.url,
31
+ apiKey: userConfig.controlPlane.apiKey,
32
+ pollIntervalMs: userConfig.controlPlane.pollIntervalMs ?? 60_000,
33
+ telemetryIntervalMs: userConfig.controlPlane.telemetryIntervalMs ?? 60_000,
34
+ })
35
+ : undefined,
36
+ });
37
+ }
38
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiGH,oCAAoC;AACpC,MAAM,QAAQ,GAAG;IACf,cAAc,EAAE,GAAG;IACnB,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;IAClD,kBAAkB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IAC7C,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,IAAI;CACN,CAAC;AAEX;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,aAA8B,EAAE;IAC5D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,cAAc,EAAE,UAAU,CAAC,cAAc,IAAI,QAAQ,CAAC,cAAc;QACpE,aAAa,EAAE,IAAI,GAAG,CACpB,CAAC,UAAU,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CACjF;QACD,kBAAkB,EAAE,IAAI,GAAG,CACzB,UAAU,CAAC,kBAAkB,IAAI,QAAQ,CAAC,kBAAkB,CAC7D;QACD,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC;QAClD,OAAO,EAAE,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO;QAC/C,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;QAClD,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,YAAY,EAAE,UAAU,CAAC,YAAY;YACnC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;gBACZ,GAAG,EAAE,UAAU,CAAC,YAAY,CAAC,GAAG;gBAChC,MAAM,EAAE,UAAU,CAAC,YAAY,CAAC,MAAM;gBACtC,cAAc,EAAE,UAAU,CAAC,YAAY,CAAC,cAAc,IAAI,MAAM;gBAChE,mBAAmB,EAAE,UAAU,CAAC,YAAY,CAAC,mBAAmB,IAAI,MAAM;aAC3E,CAAC;YACJ,CAAC,CAAC,SAAS;KACd,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Async Context Propagation — AsyncLocalStorage wrapper for request-scoped buffers.
3
+ *
4
+ * This is the "technical moat" of BufferLog for Node.js.
5
+ * Uses AsyncLocalStorage (stable since Node 16) to transparently propagate
6
+ * the request's log buffer through:
7
+ * - async/await chains
8
+ * - Promise.then() callbacks
9
+ * - setTimeout / setImmediate
10
+ * - EventEmitter handlers
11
+ * - Stream callbacks
12
+ *
13
+ * Design notes:
14
+ * - Stores the entire RequestContext (not just contextId) to avoid map lookups on the hot path
15
+ * - Returns `undefined` when accessed outside a request scope → signals fail-open
16
+ * - The `run()` method is the only way to create a scope, ensuring cleanup is deterministic
17
+ */
18
+ import type { RingBuffer } from '../core/ring-buffer.js';
19
+ import type { LogEvent } from '../core/log-event.js';
20
+ /**
21
+ * Per-request context stored in AsyncLocalStorage.
22
+ */
23
+ export interface RequestContext {
24
+ /** Unique identifier for this request */
25
+ readonly contextId: string;
26
+ /** The ring buffer holding this request's log events */
27
+ readonly buffer: RingBuffer<LogEvent>;
28
+ /** Timestamp when the request scope was created (for latency tracking) */
29
+ readonly startTime: number;
30
+ /** Optional: arbitrary key-value pairs to include with flushed logs */
31
+ readonly tags?: Record<string, string>;
32
+ }
33
+ /**
34
+ * Singleton async context manager for BufferLog.
35
+ *
36
+ * Usage:
37
+ * ```ts
38
+ * BufferLogContext.run(contextId, buffer, () => {
39
+ * // All async operations in this callback (and their children)
40
+ * // can access the buffer via BufferLogContext.current()
41
+ * });
42
+ * ```
43
+ */
44
+ export declare class BufferLogContext {
45
+ private static readonly storage;
46
+ /**
47
+ * Execute a function within a request-scoped context.
48
+ * All async operations spawned within `fn` will inherit this context.
49
+ *
50
+ * @param contextId Unique request identifier
51
+ * @param buffer The ring buffer for this request
52
+ * @param fn The function to execute within the context
53
+ * @param tags Optional tags to attach to the context
54
+ * @returns The return value of `fn`
55
+ */
56
+ static run<T>(contextId: string, buffer: RingBuffer<LogEvent>, fn: () => T, tags?: Record<string, string>): T;
57
+ /**
58
+ * Get the current request context, if one exists.
59
+ * Returns `undefined` if called outside a `run()` scope.
60
+ *
61
+ * This is the hot-path accessor — called on every log statement.
62
+ * AsyncLocalStorage.getStore() is O(1) and highly optimized in V8.
63
+ */
64
+ static current(): RequestContext | undefined;
65
+ /**
66
+ * Get the current request's buffer directly.
67
+ * Convenience method to avoid `BufferLogContext.current()?.buffer`.
68
+ */
69
+ static currentBuffer(): RingBuffer<LogEvent> | undefined;
70
+ /**
71
+ * Get the current context ID.
72
+ * Convenience method for log correlation.
73
+ */
74
+ static currentContextId(): string | undefined;
75
+ /**
76
+ * Check if we're currently inside a BufferLog request scope.
77
+ */
78
+ static isActive(): boolean;
79
+ /**
80
+ * Disable the async local storage instance.
81
+ * Calling this will cause all subsequent `current()` calls to return `undefined`.
82
+ * Used in shutdown/cleanup scenarios.
83
+ */
84
+ static disable(): void;
85
+ }
86
+ //# sourceMappingURL=async-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.d.ts","sourceRoot":"","sources":["../../src/context/async-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEtC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAA2C;IAE1E;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,CAAC,CAAC,EACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,EAAE,EAAE,MAAM,CAAC,EACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,CAAC;IAWJ;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,IAAI,cAAc,GAAG,SAAS;IAI5C;;;OAGG;IACH,MAAM,CAAC,aAAa,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,SAAS;IAIxD;;;OAGG;IACH,MAAM,CAAC,gBAAgB,IAAI,MAAM,GAAG,SAAS;IAI7C;;OAEG;IACH,MAAM,CAAC,QAAQ,IAAI,OAAO;IAI1B;;;;OAIG;IACH,MAAM,CAAC,OAAO,IAAI,IAAI;CAGvB"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Async Context Propagation — AsyncLocalStorage wrapper for request-scoped buffers.
3
+ *
4
+ * This is the "technical moat" of BufferLog for Node.js.
5
+ * Uses AsyncLocalStorage (stable since Node 16) to transparently propagate
6
+ * the request's log buffer through:
7
+ * - async/await chains
8
+ * - Promise.then() callbacks
9
+ * - setTimeout / setImmediate
10
+ * - EventEmitter handlers
11
+ * - Stream callbacks
12
+ *
13
+ * Design notes:
14
+ * - Stores the entire RequestContext (not just contextId) to avoid map lookups on the hot path
15
+ * - Returns `undefined` when accessed outside a request scope → signals fail-open
16
+ * - The `run()` method is the only way to create a scope, ensuring cleanup is deterministic
17
+ */
18
+ import { AsyncLocalStorage } from 'node:async_hooks';
19
+ /**
20
+ * Singleton async context manager for BufferLog.
21
+ *
22
+ * Usage:
23
+ * ```ts
24
+ * BufferLogContext.run(contextId, buffer, () => {
25
+ * // All async operations in this callback (and their children)
26
+ * // can access the buffer via BufferLogContext.current()
27
+ * });
28
+ * ```
29
+ */
30
+ export class BufferLogContext {
31
+ static storage = new AsyncLocalStorage();
32
+ /**
33
+ * Execute a function within a request-scoped context.
34
+ * All async operations spawned within `fn` will inherit this context.
35
+ *
36
+ * @param contextId Unique request identifier
37
+ * @param buffer The ring buffer for this request
38
+ * @param fn The function to execute within the context
39
+ * @param tags Optional tags to attach to the context
40
+ * @returns The return value of `fn`
41
+ */
42
+ static run(contextId, buffer, fn, tags) {
43
+ const ctx = {
44
+ contextId,
45
+ buffer,
46
+ startTime: Date.now(),
47
+ ...(tags && { tags }),
48
+ };
49
+ return this.storage.run(ctx, fn);
50
+ }
51
+ /**
52
+ * Get the current request context, if one exists.
53
+ * Returns `undefined` if called outside a `run()` scope.
54
+ *
55
+ * This is the hot-path accessor — called on every log statement.
56
+ * AsyncLocalStorage.getStore() is O(1) and highly optimized in V8.
57
+ */
58
+ static current() {
59
+ return this.storage.getStore();
60
+ }
61
+ /**
62
+ * Get the current request's buffer directly.
63
+ * Convenience method to avoid `BufferLogContext.current()?.buffer`.
64
+ */
65
+ static currentBuffer() {
66
+ return this.storage.getStore()?.buffer;
67
+ }
68
+ /**
69
+ * Get the current context ID.
70
+ * Convenience method for log correlation.
71
+ */
72
+ static currentContextId() {
73
+ return this.storage.getStore()?.contextId;
74
+ }
75
+ /**
76
+ * Check if we're currently inside a BufferLog request scope.
77
+ */
78
+ static isActive() {
79
+ return this.storage.getStore() !== undefined;
80
+ }
81
+ /**
82
+ * Disable the async local storage instance.
83
+ * Calling this will cause all subsequent `current()` calls to return `undefined`.
84
+ * Used in shutdown/cleanup scenarios.
85
+ */
86
+ static disable() {
87
+ this.storage.disable();
88
+ }
89
+ }
90
+ //# sourceMappingURL=async-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.js","sourceRoot":"","sources":["../../src/context/async-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAqBrD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAU,OAAO,GAAG,IAAI,iBAAiB,EAAkB,CAAC;IAE1E;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,CACR,SAAiB,EACjB,MAA4B,EAC5B,EAAW,EACX,IAA6B;QAE7B,MAAM,GAAG,GAAmB;YAC1B,SAAS;YACT,MAAM;YACN,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,GAAG,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC;SACtB,CAAC;QAEF,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,OAAO;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,aAAa;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,gBAAgB;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,QAAQ;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,SAAS,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,OAAO;QACZ,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Policy Fetcher — Periodically polls the BufferLog Control Plane
3
+ * for dynamic configuration updates.
4
+ *
5
+ * The SDK calls GET /api/v1/policy every `intervalMs` (default 60s).
6
+ * If the server returns updated policies, the fetcher invokes the
7
+ * `onUpdate` callback so the BufferLog instance can apply changes
8
+ * without requiring a redeploy.
9
+ *
10
+ * Design notes:
11
+ * - Fail-safe: if the control plane is unreachable, the SDK continues
12
+ * operating with its last known config. No logs are lost.
13
+ * - Uses AbortController for clean shutdown.
14
+ * - Sends SDK version and runtime info in headers for server-side analytics.
15
+ */
16
+ export interface PolicyFetcherOptions {
17
+ /** Control plane base URL, e.g. "https://control.bufferlog.io" */
18
+ controlPlaneUrl: string;
19
+ /** API key for authentication */
20
+ apiKey: string;
21
+ /** Polling interval in milliseconds. @default 60000 */
22
+ intervalMs?: number;
23
+ /** Callback invoked when a new policy is received */
24
+ onUpdate?: (policy: RemotePolicy) => void;
25
+ /** Callback invoked when a fetch fails */
26
+ onError?: (error: Error) => void;
27
+ }
28
+ export interface RemotePolicy {
29
+ bufferCapacity: number;
30
+ flushOnLevels: string[];
31
+ flushOnStatusCodes: number[];
32
+ enabled: boolean;
33
+ samplingRate: number;
34
+ bypassRules: Array<{
35
+ field: string;
36
+ value: string;
37
+ action: string;
38
+ }>;
39
+ }
40
+ export declare class PolicyFetcher {
41
+ private readonly url;
42
+ private readonly apiKey;
43
+ private readonly intervalMs;
44
+ private readonly onUpdate?;
45
+ private readonly onError?;
46
+ private timer;
47
+ private abortController;
48
+ private _lastPolicy;
49
+ private _fetchCount;
50
+ private _errorCount;
51
+ constructor(options: PolicyFetcherOptions);
52
+ /**
53
+ * Start polling. Fetches immediately, then every `intervalMs`.
54
+ */
55
+ start(): void;
56
+ /**
57
+ * Stop polling and cancel any in-flight request.
58
+ */
59
+ stop(): void;
60
+ /**
61
+ * Perform a single fetch. Can be called manually to force a refresh.
62
+ */
63
+ fetch(): Promise<RemotePolicy | null>;
64
+ /** Last successfully fetched policy */
65
+ get lastPolicy(): RemotePolicy | null;
66
+ /** Total successful fetches */
67
+ get fetchCount(): number;
68
+ /** Total fetch errors */
69
+ get errorCount(): number;
70
+ /** Whether the fetcher is currently running */
71
+ get isRunning(): boolean;
72
+ }
73
+ //# sourceMappingURL=policy-fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-fetcher.d.ts","sourceRoot":"","sources":["../../src/control-plane/policy-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;IAExB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IAEf,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAE1C,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAyB;IAElD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAK;gBAEZ,OAAO,EAAE,oBAAoB;IASzC;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb;;OAEG;IACH,IAAI,IAAI,IAAI;IAWZ;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAsC3C,uCAAuC;IACvC,IAAI,UAAU,IAAI,YAAY,GAAG,IAAI,CAEpC;IAED,+BAA+B;IAC/B,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,yBAAyB;IACzB,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,+CAA+C;IAC/C,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF"}