@backendkit-labs/observability 0.1.1 → 0.2.1
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.
- package/README.md +371 -367
- package/dist/index.cjs +86 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +85 -33
- package/dist/index.js.map +1 -1
- package/package.json +96 -96
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Injectable, Inject, Optional, Catch, Module, Logger, HttpException, HttpStatus } from '@nestjs/common';
|
|
2
2
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
|
-
import {
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
4
|
import * as winston from 'winston';
|
|
5
5
|
import TransportStream from 'winston-transport';
|
|
6
6
|
import axios from 'axios';
|
|
@@ -8,15 +8,10 @@ import * as http from 'http';
|
|
|
8
8
|
import * as https from 'https';
|
|
9
9
|
import { CircuitBreaker, CircuitBreakerState, CircuitBreakerOpenError } from '@backendkit-labs/circuit-breaker';
|
|
10
10
|
import { Observable } from 'rxjs';
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
11
12
|
import { tap } from 'rxjs/operators';
|
|
12
13
|
|
|
13
14
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
15
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
16
|
-
}) : x)(function(x) {
|
|
17
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
18
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
19
|
-
});
|
|
20
15
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
21
16
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
22
17
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -25,11 +20,10 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
25
20
|
return result;
|
|
26
21
|
};
|
|
27
22
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
28
|
-
|
|
29
|
-
// src/internal/otel.ts
|
|
23
|
+
var _require = createRequire(import.meta.url);
|
|
30
24
|
var otel = null;
|
|
31
25
|
try {
|
|
32
|
-
otel =
|
|
26
|
+
otel = _require("@opentelemetry/api");
|
|
33
27
|
} catch {
|
|
34
28
|
}
|
|
35
29
|
var getTracer = (name) => otel?.trace?.getTracer(name) ?? noopTracer;
|
|
@@ -67,9 +61,9 @@ var CorrelationIdService = class {
|
|
|
67
61
|
run(correlationId, fn) {
|
|
68
62
|
return storage.run(correlationId, fn);
|
|
69
63
|
}
|
|
70
|
-
/** Current correlation ID, or
|
|
64
|
+
/** Current correlation ID, or 'no-context' when called outside a context. */
|
|
71
65
|
get() {
|
|
72
|
-
return storage.getStore() ??
|
|
66
|
+
return storage.getStore() ?? "no-context";
|
|
73
67
|
}
|
|
74
68
|
/**
|
|
75
69
|
* Current correlation ID, or `undefined` when called outside a context.
|
|
@@ -106,12 +100,18 @@ var TRANSPORT_CB_DEFAULTS = {
|
|
|
106
100
|
halfOpenMaxCalls: 1,
|
|
107
101
|
openTimeoutMs: 3e4
|
|
108
102
|
};
|
|
109
|
-
var WinstonHttpTransport = class extends TransportStream {
|
|
103
|
+
var WinstonHttpTransport = class _WinstonHttpTransport extends TransportStream {
|
|
110
104
|
client;
|
|
111
105
|
cb;
|
|
112
106
|
buffer = [];
|
|
113
107
|
batchSize;
|
|
114
108
|
maxBufferSize;
|
|
109
|
+
maxEntryAgeMs = 3e5;
|
|
110
|
+
// 5 min
|
|
111
|
+
fallbackLogger = new Logger(_WinstonHttpTransport.name);
|
|
112
|
+
retryCounts = /* @__PURE__ */ new WeakMap();
|
|
113
|
+
entryTimes = /* @__PURE__ */ new WeakMap();
|
|
114
|
+
maxRetries = 5;
|
|
115
115
|
flushTimer;
|
|
116
116
|
constructor(opts) {
|
|
117
117
|
super(opts);
|
|
@@ -130,11 +130,23 @@ var WinstonHttpTransport = class extends TransportStream {
|
|
|
130
130
|
...opts.headers
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
|
+
this.client.interceptors.response.use(
|
|
134
|
+
(response) => response,
|
|
135
|
+
(error) => {
|
|
136
|
+
if (error.config?.headers?.Authorization) {
|
|
137
|
+
error.config.headers.Authorization = "Bearer ***REDACTED***";
|
|
138
|
+
}
|
|
139
|
+
return Promise.reject(error);
|
|
140
|
+
}
|
|
141
|
+
);
|
|
133
142
|
this.cb = new CircuitBreaker({
|
|
134
143
|
...TRANSPORT_CB_DEFAULTS,
|
|
135
144
|
...opts.circuitBreaker,
|
|
136
145
|
name: "WinstonHttpTransport",
|
|
137
|
-
isFailure: () =>
|
|
146
|
+
isFailure: (error) => {
|
|
147
|
+
const status = error.response?.status;
|
|
148
|
+
return status !== void 0 ? status >= 400 : true;
|
|
149
|
+
}
|
|
138
150
|
});
|
|
139
151
|
this.flushTimer = setInterval(
|
|
140
152
|
() => {
|
|
@@ -146,9 +158,11 @@ var WinstonHttpTransport = class extends TransportStream {
|
|
|
146
158
|
}
|
|
147
159
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
160
|
log(info, callback) {
|
|
149
|
-
|
|
161
|
+
this.emit("logged", info);
|
|
150
162
|
if (this.buffer.length < this.maxBufferSize) {
|
|
151
|
-
|
|
163
|
+
const entry = { ...info };
|
|
164
|
+
this.entryTimes.set(entry, Date.now());
|
|
165
|
+
this.buffer.push(entry);
|
|
152
166
|
}
|
|
153
167
|
if (this.buffer.length >= this.batchSize) {
|
|
154
168
|
void this.flush();
|
|
@@ -162,14 +176,24 @@ var WinstonHttpTransport = class extends TransportStream {
|
|
|
162
176
|
}
|
|
163
177
|
async flush() {
|
|
164
178
|
if (this.buffer.length === 0) return;
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
this.buffer = this.buffer.filter(
|
|
181
|
+
(e) => now - (this.entryTimes.get(e) ?? now) < this.maxEntryAgeMs
|
|
182
|
+
);
|
|
165
183
|
const batch = this.buffer.splice(0, this.batchSize);
|
|
166
184
|
try {
|
|
167
185
|
await this.cb.execute(() => this.client.post("", batch));
|
|
168
186
|
} catch (err) {
|
|
187
|
+
const retryable = batch.filter((entry) => {
|
|
188
|
+
const retries = this.retryCounts.get(entry) ?? 0;
|
|
189
|
+
if (retries >= this.maxRetries) return false;
|
|
190
|
+
this.retryCounts.set(entry, retries + 1);
|
|
191
|
+
return true;
|
|
192
|
+
});
|
|
169
193
|
const room = this.maxBufferSize - this.buffer.length;
|
|
170
|
-
if (room > 0) this.buffer.unshift(...
|
|
194
|
+
if (room > 0) this.buffer.unshift(...retryable.slice(0, room));
|
|
171
195
|
if (!(err instanceof CircuitBreakerOpenError)) {
|
|
172
|
-
|
|
196
|
+
this.fallbackLogger.warn(`flush failed \u2014 re-queued ${retryable.length}/${batch.length} entries`, err.message);
|
|
173
197
|
}
|
|
174
198
|
}
|
|
175
199
|
}
|
|
@@ -186,8 +210,10 @@ var LoggerService = class {
|
|
|
186
210
|
winston.format.timestamp(),
|
|
187
211
|
winston.format.colorize(),
|
|
188
212
|
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
|
189
|
-
const
|
|
190
|
-
|
|
213
|
+
const m = meta;
|
|
214
|
+
const ctxPart = m["context"] ? ` [${String(m["context"])}]` : "";
|
|
215
|
+
const corrPart = typeof m["correlationId"] === "string" && m["correlationId"] !== "no-context" ? ` [${m["correlationId"]}]` : "";
|
|
216
|
+
return `${timestamp} [${level}]${ctxPart}${corrPart} ${message}`;
|
|
191
217
|
})
|
|
192
218
|
)
|
|
193
219
|
})
|
|
@@ -225,9 +251,10 @@ var LoggerService = class {
|
|
|
225
251
|
this.winston.verbose(message, this.buildMeta(context));
|
|
226
252
|
}
|
|
227
253
|
/** Log with additional arbitrary metadata. */
|
|
228
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
229
254
|
logWithMeta(level, message, meta) {
|
|
230
|
-
|
|
255
|
+
const validLevels = ["error", "warn", "info", "debug", "verbose"];
|
|
256
|
+
const safeLevel = validLevels.includes(level) ? level : "info";
|
|
257
|
+
this.winston.log(safeLevel, message, { ...this.buildMeta(), ...meta });
|
|
231
258
|
}
|
|
232
259
|
buildMeta(context) {
|
|
233
260
|
const base = {
|
|
@@ -277,7 +304,10 @@ var MetricsService = class {
|
|
|
277
304
|
...TRANSPORT_CB_DEFAULTS2,
|
|
278
305
|
...m.circuitBreaker,
|
|
279
306
|
name: "MetricsService",
|
|
280
|
-
isFailure: () =>
|
|
307
|
+
isFailure: (error) => {
|
|
308
|
+
const status = error.response?.status;
|
|
309
|
+
return status !== void 0 ? status >= 400 : true;
|
|
310
|
+
},
|
|
281
311
|
onStateChange: (from, to, metrics) => {
|
|
282
312
|
if (to === CircuitBreakerState.OPEN) {
|
|
283
313
|
this.logger.warn(
|
|
@@ -305,6 +335,10 @@ var MetricsService = class {
|
|
|
305
335
|
logger = new Logger(MetricsService.name);
|
|
306
336
|
buffer = [];
|
|
307
337
|
maxBufferSize = 5e3;
|
|
338
|
+
maxEntryAgeMs = 3e5;
|
|
339
|
+
// 5 min
|
|
340
|
+
retryCounts = /* @__PURE__ */ new WeakMap();
|
|
341
|
+
maxRetries = 5;
|
|
308
342
|
flushTimer = null;
|
|
309
343
|
/**
|
|
310
344
|
* Enqueue a metric event. Fire-and-forget; batched and sent on the next
|
|
@@ -330,20 +364,33 @@ var MetricsService = class {
|
|
|
330
364
|
/** Flush on graceful shutdown. */
|
|
331
365
|
async onModuleDestroy() {
|
|
332
366
|
if (this.flushTimer) clearInterval(this.flushTimer);
|
|
333
|
-
|
|
367
|
+
try {
|
|
368
|
+
await this.flush();
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
334
371
|
}
|
|
335
372
|
async flush() {
|
|
336
373
|
if (!this.client || this.buffer.length === 0) return;
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
this.buffer = this.buffer.filter(
|
|
376
|
+
(e) => now - new Date(e.timestamp).getTime() < this.maxEntryAgeMs
|
|
377
|
+
);
|
|
337
378
|
const batch = this.buffer.splice(0, 500);
|
|
338
379
|
try {
|
|
339
380
|
await this.cb.execute(() => this.client.post("", batch));
|
|
340
381
|
} catch (err) {
|
|
382
|
+
const retryable = batch.filter((entry) => {
|
|
383
|
+
const retries = this.retryCounts.get(entry) ?? 0;
|
|
384
|
+
if (retries >= this.maxRetries) return false;
|
|
385
|
+
this.retryCounts.set(entry, retries + 1);
|
|
386
|
+
return true;
|
|
387
|
+
});
|
|
341
388
|
const room = this.maxBufferSize - this.buffer.length;
|
|
342
|
-
if (room > 0) this.buffer.unshift(...
|
|
389
|
+
if (room > 0) this.buffer.unshift(...retryable.slice(0, room));
|
|
343
390
|
if (!(err instanceof CircuitBreakerOpenError)) {
|
|
344
391
|
this.logger.warn(
|
|
345
|
-
`[MetricsService] flush failed \u2014 re-queueing ${batch.length} events`,
|
|
346
|
-
err
|
|
392
|
+
`[MetricsService] flush failed \u2014 re-queueing ${retryable.length}/${batch.length} events`,
|
|
393
|
+
err.message
|
|
347
394
|
);
|
|
348
395
|
}
|
|
349
396
|
}
|
|
@@ -355,6 +402,13 @@ MetricsService = __decorateClass([
|
|
|
355
402
|
__decorateParam(1, Optional())
|
|
356
403
|
], MetricsService);
|
|
357
404
|
var CORRELATION_HEADER = "x-correlation-id";
|
|
405
|
+
var CORRELATION_ID_REGEX = /^[a-zA-Z0-9\-_:]{1,64}$/;
|
|
406
|
+
function sanitizeCorrelationId(raw) {
|
|
407
|
+
if (typeof raw !== "string") return null;
|
|
408
|
+
if (raw.length > 64) return null;
|
|
409
|
+
if (!CORRELATION_ID_REGEX.test(raw)) return null;
|
|
410
|
+
return raw;
|
|
411
|
+
}
|
|
358
412
|
var CorrelationInterceptor = class {
|
|
359
413
|
constructor(correlationSvc) {
|
|
360
414
|
this.correlationSvc = correlationSvc;
|
|
@@ -364,14 +418,11 @@ var CorrelationInterceptor = class {
|
|
|
364
418
|
const req = ctx.switchToHttp().getRequest();
|
|
365
419
|
const res = ctx.switchToHttp().getResponse();
|
|
366
420
|
const incomingId = req.headers?.[CORRELATION_HEADER];
|
|
367
|
-
const correlationId =
|
|
421
|
+
const correlationId = sanitizeCorrelationId(incomingId) ?? randomUUID();
|
|
368
422
|
res.setHeader(CORRELATION_HEADER, correlationId);
|
|
369
423
|
return new Observable((subscriber) => {
|
|
370
424
|
this.correlationSvc.run(correlationId, () => {
|
|
371
|
-
next.handle().
|
|
372
|
-
tap({ error: () => {
|
|
373
|
-
} })
|
|
374
|
-
).subscribe(subscriber);
|
|
425
|
+
next.handle().subscribe(subscriber);
|
|
375
426
|
});
|
|
376
427
|
});
|
|
377
428
|
}
|
|
@@ -479,7 +530,8 @@ var AllExceptionsFilter = class {
|
|
|
479
530
|
}
|
|
480
531
|
if (exception instanceof HttpException) {
|
|
481
532
|
const body = exception.getResponse();
|
|
482
|
-
const
|
|
533
|
+
const rawMessage = typeof body === "string" ? body : body.message ?? exception.message;
|
|
534
|
+
const message = Array.isArray(rawMessage) ? rawMessage.join("; ") : String(rawMessage ?? exception.message);
|
|
483
535
|
return { statusCode: exception.getStatus(), message };
|
|
484
536
|
}
|
|
485
537
|
return {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/otel.ts","../src/correlation/correlation.service.ts","../src/observability.constants.ts","../src/logger/winston-http.transport.ts","../src/logger/logger.service.ts","../src/metrics/metrics.service.ts","../src/interceptors/correlation.interceptor.ts","../src/interceptors/performance.interceptor.ts","../src/filters/all-exceptions.filter.ts","../src/observability.module.ts","../src/decorators/track-performance.decorator.ts"],"names":["transports","Injectable","TRANSPORT_CB_DEFAULTS","http2","https2","axios","CircuitBreaker","CircuitBreakerOpenError","Inject","Optional","randomUUID","tap"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Optional OpenTelemetry shim.\n * If @opentelemetry/api is not installed all operations become no-ops,\n * so the package works without any tracing backend.\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet otel: any = null;\n\ntry {\n // Dynamic require keeps OTel out of the bundle when not installed\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n otel = require('@opentelemetry/api');\n} catch {\n // OTel not installed — spans will be no-ops\n}\n\nexport const isOtelAvailable = (): boolean => otel !== null;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const getTracer = (name: string): any =>\n otel?.trace?.getTracer(name) ?? noopTracer;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const getActiveSpan = (): any =>\n otel ? otel.trace.getSpan(otel.context.active()) : undefined;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const runInOtelContext = async <T>(span: any, fn: () => Promise<T>): Promise<T> => {\n if (!otel || !span) return fn();\n return otel.context.with(otel.trace.setSpan(otel.context.active(), span), fn);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst noopSpan: any = {\n end: () => {},\n setAttribute: () => {},\n setAttributes: () => {},\n recordException: () => {},\n setStatus: () => {},\n spanContext: () => ({ traceId: '', spanId: '' }),\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst noopTracer: any = {\n startSpan: () => noopSpan,\n startActiveSpan: (_name: string, fn: (span: unknown) => unknown) => fn(noopSpan),\n};\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { Injectable } from '@nestjs/common';\nimport { randomUUID } from 'node:crypto';\nimport { getActiveSpan } from '../internal/otel.js';\n\nconst storage = new AsyncLocalStorage<string>();\n\n@Injectable()\nexport class CorrelationIdService {\n /**\n * Run `fn` inside a context that carries `correlationId`.\n * All code executed within `fn` (including async continuations) can call\n * `get()` and receive the same ID without passing it explicitly.\n */\n run<T>(correlationId: string, fn: () => T): T {\n return storage.run(correlationId, fn);\n }\n\n /** Current correlation ID, or a fresh UUID when called outside a context. */\n get(): string {\n return storage.getStore() ?? randomUUID();\n }\n\n /**\n * Current correlation ID, or `undefined` when called outside a context.\n * Prefer `get()` for logging; use this only when you need to distinguish\n * \"no context\" from \"context with a random ID\".\n */\n getOrUndefined(): string | undefined {\n return storage.getStore();\n }\n\n /**\n * Active OTel trace + span IDs when @opentelemetry/api is installed and a\n * span is active; `undefined` otherwise.\n */\n getTraceContext(): { traceId: string; spanId: string } | undefined {\n const span = getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext?.();\n if (!ctx?.traceId) return undefined;\n return { traceId: ctx.traceId, spanId: ctx.spanId };\n }\n}\n","export const OBSERVABILITY_OPTIONS = 'OBSERVABILITY_OPTIONS' as const;\n","import TransportStream, { TransportStreamOptions } from 'winston-transport';\nimport axios, { AxiosInstance } from 'axios';\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport {\n CircuitBreaker,\n CircuitBreakerConfig,\n CircuitBreakerOpenError,\n} from '@backendkit-labs/circuit-breaker';\n\nexport interface WinstonHttpTransportOptions extends TransportStreamOptions {\n /** Full URL of the log-ingest endpoint. */\n url: string;\n\n /** Bearer token sent in `Authorization` header. */\n authToken?: string;\n\n /** Additional static headers merged into every request. */\n headers?: Record<string, string>;\n\n /** Flush batch when it reaches this many entries (default 100). */\n batchSize?: number;\n\n /** Maximum entries held in the in-memory buffer (default 2000). */\n maxBufferSize?: number;\n\n /** Flush interval in ms — also flushes on `close()` (default 5000). */\n flushIntervalMs?: number;\n\n /** Request timeout in ms (default 5000). */\n timeoutMs?: number;\n\n /**\n * Override any circuit breaker config fields.\n * `name` and `isFailure` are set internally and cannot be overridden.\n *\n * Transport defaults: failureThreshold 60%, slidingWindowSize 5,\n * minimumCalls 3, openTimeoutMs 30 000, halfOpenMaxCalls 1.\n */\n circuitBreaker?: Partial<Omit<CircuitBreakerConfig, 'name' | 'isFailure'>>;\n}\n\ninterface LogEntry {\n level: string;\n message: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\nconst TRANSPORT_CB_DEFAULTS: Omit<CircuitBreakerConfig, 'name' | 'isFailure'> = {\n failureThreshold: 60,\n slowCallThreshold: 100,\n slowCallDurationMs: 60_000,\n minimumCalls: 3,\n slidingWindowSize: 5,\n halfOpenMaxCalls: 1,\n openTimeoutMs: 30_000,\n};\n\nexport class WinstonHttpTransport extends TransportStream {\n private readonly client: AxiosInstance;\n private readonly cb: CircuitBreaker;\n private readonly buffer: LogEntry[] = [];\n private readonly batchSize: number;\n private readonly maxBufferSize: number;\n private readonly flushTimer: ReturnType<typeof setInterval>;\n\n constructor(opts: WinstonHttpTransportOptions) {\n super(opts);\n\n this.batchSize = opts.batchSize ?? 100;\n this.maxBufferSize = opts.maxBufferSize ?? 2_000;\n\n const keepAlive = new http.Agent({ keepAlive: true });\n const keepAliveHttps = new https.Agent({ keepAlive: true });\n\n this.client = axios.create({\n baseURL: opts.url,\n timeout: opts.timeoutMs ?? 5_000,\n httpAgent: keepAlive,\n httpsAgent: keepAliveHttps,\n headers: {\n 'Content-Type': 'application/json',\n ...(opts.authToken ? { Authorization: `Bearer ${opts.authToken}` } : {}),\n ...opts.headers,\n },\n });\n\n this.cb = new CircuitBreaker({\n ...TRANSPORT_CB_DEFAULTS,\n ...opts.circuitBreaker,\n name: 'WinstonHttpTransport',\n isFailure: () => true,\n });\n\n this.flushTimer = setInterval(\n () => { void this.flush(); },\n opts.flushIntervalMs ?? 5_000,\n );\n this.flushTimer.unref?.();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n override log(info: any, callback: () => void): void {\n setImmediate(() => this.emit('logged', info));\n\n if (this.buffer.length < this.maxBufferSize) {\n this.buffer.push(info as LogEntry);\n }\n // silently drop when full — buffer-full warn would cause infinite recursion\n\n if (this.buffer.length >= this.batchSize) {\n void this.flush();\n }\n\n callback();\n }\n\n /** Flush remaining buffer on graceful shutdown. */\n override async close(): Promise<void> {\n clearInterval(this.flushTimer);\n await this.flush();\n }\n\n private async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n\n const batch = this.buffer.splice(0, this.batchSize);\n\n try {\n await this.cb.execute(() => this.client.post('', batch));\n } catch (err) {\n // Re-queue the batch (whether CB was open or the request failed)\n const room = this.maxBufferSize - this.buffer.length;\n if (room > 0) this.buffer.unshift(...batch.slice(0, room));\n\n if (!(err instanceof CircuitBreakerOpenError)) {\n // Network errors are worth logging; CB-open state was already surfaced\n // via onStateChange in the CircuitBreaker itself\n console.error(`[WinstonHttpTransport] flush failed — re-queued ${batch.length} entries`, err);\n }\n }\n }\n}\n","import { Injectable, Inject, Optional, LoggerService as NestLoggerService } from '@nestjs/common';\nimport * as winston from 'winston';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { OBSERVABILITY_OPTIONS } from '../observability.constants.js';\nimport { ObservabilityOptions } from '../observability.types.js';\nimport { WinstonHttpTransport } from './winston-http.transport.js';\n\n@Injectable()\nexport class LoggerService implements NestLoggerService {\n private readonly winston: winston.Logger;\n\n constructor(\n @Inject(OBSERVABILITY_OPTIONS)\n private readonly opts: ObservabilityOptions,\n @Optional()\n private readonly correlationSvc?: CorrelationIdService,\n ) {\n const transports: winston.transport[] = [\n new winston.transports.Console({\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.colorize(),\n winston.format.printf(({ level, message, timestamp, ...meta }) => {\n const extra = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';\n return `${timestamp} [${level}] ${message}${extra}`;\n }),\n ),\n }),\n ];\n\n if (opts.http) {\n transports.push(\n new WinstonHttpTransport({\n ...opts.http,\n format: winston.format.json(),\n }),\n );\n }\n\n this.winston = winston.createLogger({\n level: opts.logLevel ?? 'info',\n transports,\n format: winston.format.json(),\n });\n }\n\n log(message: string, context?: string): void {\n this.winston.info(message, this.buildMeta(context));\n }\n\n error(message: string, trace?: string, context?: string): void {\n this.winston.error(message, { ...this.buildMeta(context), trace });\n }\n\n warn(message: string, context?: string): void {\n this.winston.warn(message, this.buildMeta(context));\n }\n\n debug(message: string, context?: string): void {\n this.winston.debug(message, this.buildMeta(context));\n }\n\n verbose(message: string, context?: string): void {\n this.winston.verbose(message, this.buildMeta(context));\n }\n\n /** Log with additional arbitrary metadata. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n logWithMeta(level: string, message: string, meta: Record<string, any>): void {\n this.winston.log(level, message, { ...this.buildMeta(), ...meta });\n }\n\n private buildMeta(context?: string): Record<string, unknown> {\n const base: Record<string, unknown> = {\n service: this.opts.serviceName,\n environment: this.opts.environment ?? 'production',\n correlationId: this.correlationSvc?.get(),\n };\n if (context) base.context = context;\n return base;\n }\n}\n","import { Injectable, Inject, Optional, OnModuleDestroy, Logger } from '@nestjs/common';\nimport axios, { AxiosInstance } from 'axios';\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport {\n CircuitBreaker,\n CircuitBreakerConfig,\n CircuitBreakerOpenError,\n CircuitBreakerState,\n} from '@backendkit-labs/circuit-breaker';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { OBSERVABILITY_OPTIONS } from '../observability.constants.js';\nimport { ObservabilityOptions, MetricEvent } from '../observability.types.js';\n\nconst TRANSPORT_CB_DEFAULTS: Omit<CircuitBreakerConfig, 'name' | 'isFailure'> = {\n failureThreshold: 60,\n slowCallThreshold: 100,\n slowCallDurationMs: 60_000,\n minimumCalls: 3,\n slidingWindowSize: 5,\n halfOpenMaxCalls: 1,\n openTimeoutMs: 30_000,\n};\n\n@Injectable()\nexport class MetricsService implements OnModuleDestroy {\n private readonly client: AxiosInstance | null = null;\n private readonly cb: CircuitBreaker | null = null;\n private readonly logger = new Logger(MetricsService.name);\n private readonly buffer: MetricEvent[] = [];\n private readonly maxBufferSize: number = 5_000;\n private readonly flushTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n @Inject(OBSERVABILITY_OPTIONS)\n private readonly opts: ObservabilityOptions,\n @Optional()\n private readonly correlationSvc?: CorrelationIdService,\n ) {\n if (!opts.metrics) return;\n\n const m = opts.metrics;\n this.maxBufferSize = m.maxBufferSize ?? 5_000;\n\n const keepAlive = new http.Agent({ keepAlive: true });\n const keepAliveHttps = new https.Agent({ keepAlive: true });\n\n this.client = axios.create({\n baseURL: m.url,\n timeout: m.timeoutMs ?? 5_000,\n httpAgent: keepAlive,\n httpsAgent: keepAliveHttps,\n headers: {\n 'Content-Type': 'application/json',\n ...(m.authToken ? { Authorization: `Bearer ${m.authToken}` } : {}),\n ...m.headers,\n },\n });\n\n this.cb = new CircuitBreaker({\n ...TRANSPORT_CB_DEFAULTS,\n ...m.circuitBreaker,\n name: 'MetricsService',\n isFailure: () => true,\n onStateChange: (from, to, metrics) => {\n if (to === CircuitBreakerState.OPEN) {\n this.logger.warn(\n `[MetricsService] circuit breaker OPEN — pausing metric sends for ${(m.circuitBreaker?.openTimeoutMs ?? TRANSPORT_CB_DEFAULTS.openTimeoutMs) / 1_000}s`,\n metrics,\n );\n } else if (to === CircuitBreakerState.CLOSED && from !== CircuitBreakerState.HALF_OPEN) {\n this.logger.log(`[MetricsService] circuit breaker CLOSED — recovered`);\n }\n m.circuitBreaker?.onStateChange?.(from, to, metrics);\n },\n });\n\n this.flushTimer = setInterval(\n () => { void this.flush(); },\n m.flushIntervalMs ?? 10_000,\n );\n this.flushTimer.unref?.();\n }\n\n /**\n * Enqueue a metric event. Fire-and-forget; batched and sent on the next\n * flush interval or when the buffer reaches `maxBufferSize`.\n */\n record(\n name: string,\n value: number,\n options?: { unit?: string; tags?: Record<string, string> },\n ): void {\n if (!this.client) return;\n\n if (this.buffer.length >= this.maxBufferSize) {\n this.logger.warn('[MetricsService] buffer full — dropping metric');\n return;\n }\n\n this.buffer.push({\n name,\n value,\n unit: options?.unit,\n tags: options?.tags,\n timestamp: new Date().toISOString(),\n serviceName: this.opts.serviceName,\n environment: this.opts.environment ?? 'production',\n correlationId: this.correlationSvc?.getOrUndefined(),\n });\n }\n\n /** Flush on graceful shutdown. */\n async onModuleDestroy(): Promise<void> {\n if (this.flushTimer) clearInterval(this.flushTimer);\n await this.flush();\n }\n\n private async flush(): Promise<void> {\n if (!this.client || this.buffer.length === 0) return;\n\n const batch = this.buffer.splice(0, 500);\n\n try {\n await this.cb!.execute(() => this.client!.post('', batch));\n } catch (err) {\n // Re-queue in both cases (CB open or network error)\n const room = this.maxBufferSize - this.buffer.length;\n if (room > 0) this.buffer.unshift(...batch.slice(0, room));\n\n if (!(err instanceof CircuitBreakerOpenError)) {\n this.logger.warn(\n `[MetricsService] flush failed — re-queueing ${batch.length} events`,\n err,\n );\n }\n }\n }\n}\n","import {\n Injectable,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n Inject,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport { randomUUID } from 'node:crypto';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\n\nconst CORRELATION_HEADER = 'x-correlation-id';\n\n@Injectable()\nexport class CorrelationInterceptor implements NestInterceptor {\n constructor(\n @Inject(CorrelationIdService)\n private readonly correlationSvc: CorrelationIdService,\n ) {}\n\n intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {\n const req = ctx.switchToHttp().getRequest<Record<string, unknown>>();\n const res = ctx.switchToHttp().getResponse<{\n setHeader(name: string, value: string): void;\n }>();\n\n const incomingId =\n (req.headers as Record<string, string | undefined>)?.[CORRELATION_HEADER];\n const correlationId = (typeof incomingId === 'string' && incomingId)\n ? incomingId\n : randomUUID();\n\n res.setHeader(CORRELATION_HEADER, correlationId);\n\n return new Observable(subscriber => {\n this.correlationSvc.run(correlationId, () => {\n next.handle().pipe(\n tap({ error: () => {} }),\n ).subscribe(subscriber);\n });\n });\n }\n}\n","import {\n Injectable,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n Inject,\n Optional,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport { LoggerService } from '../logger/logger.service.js';\nimport { MetricsService } from '../metrics/metrics.service.js';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { getActiveSpan } from '../internal/otel.js';\n\n@Injectable()\nexport class PerformanceInterceptor implements NestInterceptor {\n constructor(\n @Inject(LoggerService)\n private readonly logger: LoggerService,\n @Optional()\n @Inject(MetricsService)\n private readonly metrics: MetricsService | undefined,\n @Optional()\n @Inject(CorrelationIdService)\n private readonly correlationSvc: CorrelationIdService | undefined,\n ) {}\n\n intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {\n const start = Date.now();\n const req = ctx.switchToHttp().getRequest<{\n method: string;\n url: string;\n }>();\n const method = req.method ?? 'UNKNOWN';\n const path = req.url ?? 'UNKNOWN';\n\n return next.handle().pipe(\n tap({\n next: () => {\n const durationMs = Date.now() - start;\n this.record(method, path, 'success', durationMs);\n },\n error: () => {\n const durationMs = Date.now() - start;\n this.record(method, path, 'error', durationMs);\n },\n }),\n );\n }\n\n private record(\n method: string,\n path: string,\n outcome: 'success' | 'error',\n durationMs: number,\n ): void {\n const correlationId = this.correlationSvc?.getOrUndefined();\n const span = getActiveSpan();\n\n span?.setAttribute('http.method', method);\n span?.setAttribute('http.target', path);\n span?.setAttribute('http.duration_ms', durationMs);\n\n this.logger.logWithMeta('info', `${method} ${path} [${outcome}] ${durationMs}ms`, {\n method,\n path,\n outcome,\n durationMs,\n correlationId,\n });\n\n this.metrics?.record('http.request.duration', durationMs, {\n unit: 'ms',\n tags: { method, path, outcome },\n });\n }\n}\n","import {\n ExceptionFilter,\n Catch,\n ArgumentsHost,\n HttpException,\n HttpStatus,\n Inject,\n Optional,\n} from '@nestjs/common';\nimport { LoggerService } from '../logger/logger.service.js';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { getActiveSpan } from '../internal/otel.js';\n\nexport type ErrorMapper = (error: unknown) => {\n statusCode: number;\n message: string;\n code?: string;\n} | null;\n\n@Catch()\nexport class AllExceptionsFilter implements ExceptionFilter {\n private readonly mappers: ErrorMapper[] = [];\n\n constructor(\n @Inject(LoggerService)\n private readonly logger: LoggerService,\n @Optional()\n @Inject(CorrelationIdService)\n private readonly correlationSvc: CorrelationIdService | undefined,\n ) {}\n\n /**\n * Register a custom error mapper.\n * Mappers are tried in order; the first non-null result wins.\n * Return `null` to fall through to the next mapper.\n */\n addMapper(mapper: ErrorMapper): this {\n this.mappers.push(mapper);\n return this;\n }\n\n catch(exception: unknown, host: ArgumentsHost): void {\n const ctx = host.switchToHttp();\n const res = ctx.getResponse<{\n status(code: number): { json(body: unknown): void };\n }>();\n\n const mapped = this.resolveError(exception);\n\n const correlationId = this.correlationSvc?.getOrUndefined();\n const span = getActiveSpan();\n span?.recordException(exception instanceof Error ? exception : new Error(String(exception)));\n\n this.logger.error(\n mapped.message,\n exception instanceof Error ? exception.stack : undefined,\n AllExceptionsFilter.name,\n );\n\n res.status(mapped.statusCode).json({\n ok: false,\n statusCode: mapped.statusCode,\n message: mapped.message,\n code: mapped.code,\n correlationId,\n timestamp: new Date().toISOString(),\n });\n }\n\n private resolveError(exception: unknown): {\n statusCode: number;\n message: string;\n code?: string;\n } {\n for (const mapper of this.mappers) {\n const result = mapper(exception);\n if (result !== null) return result;\n }\n\n if (exception instanceof HttpException) {\n const body = exception.getResponse();\n const message =\n typeof body === 'string'\n ? body\n : (body as { message?: string }).message ?? exception.message;\n return { statusCode: exception.getStatus(), message };\n }\n\n return {\n statusCode: HttpStatus.INTERNAL_SERVER_ERROR,\n message: 'Internal server error',\n };\n }\n}\n","import { DynamicModule, Module, Provider } from '@nestjs/common';\nimport { CorrelationIdService } from './correlation/correlation.service.js';\nimport { LoggerService } from './logger/logger.service.js';\nimport { MetricsService } from './metrics/metrics.service.js';\nimport { CorrelationInterceptor } from './interceptors/correlation.interceptor.js';\nimport { PerformanceInterceptor } from './interceptors/performance.interceptor.js';\nimport { AllExceptionsFilter } from './filters/all-exceptions.filter.js';\nimport { OBSERVABILITY_OPTIONS } from './observability.constants.js';\nimport { ObservabilityOptions } from './observability.types.js';\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(options: ObservabilityOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: OBSERVABILITY_OPTIONS,\n useValue: options,\n };\n\n const providers: Provider[] = [\n optionsProvider,\n CorrelationIdService,\n LoggerService,\n MetricsService,\n CorrelationInterceptor,\n PerformanceInterceptor,\n AllExceptionsFilter,\n ];\n\n return {\n module: ObservabilityModule,\n global: true,\n providers,\n exports: [\n CorrelationIdService,\n LoggerService,\n MetricsService,\n CorrelationInterceptor,\n PerformanceInterceptor,\n AllExceptionsFilter,\n ],\n };\n }\n}\n","import { getTracer, runInOtelContext } from '../internal/otel.js';\n\nexport interface TrackPerformanceOptions {\n /** OTel span / log operation name. Defaults to `ClassName.methodName`. */\n operation?: string;\n\n /** Attributes added to the OTel span. */\n attributes?: Record<string, string | number | boolean>;\n}\n\n/**\n * Method decorator that wraps the decorated async method in an OTel span\n * (when @opentelemetry/api is available) and records its duration.\n * Works with regular methods and NestJS service methods.\n *\n * @example\n * \\@TrackPerformance()\n * async processPayment(id: string) { ... }\n */\nexport function TrackPerformance(options: TrackPerformanceOptions = {}): MethodDecorator {\n return function (\n target: object,\n propertyKey: string | symbol,\n descriptor: PropertyDescriptor,\n ): PropertyDescriptor {\n const original = descriptor.value as (...args: unknown[]) => Promise<unknown>;\n const operationName =\n options.operation ?? `${target.constructor.name}.${String(propertyKey)}`;\n\n descriptor.value = async function (...args: unknown[]): Promise<unknown> {\n const tracer = getTracer('@backendkit-labs/observability');\n const span = tracer.startSpan(operationName);\n\n if (options.attributes) {\n span.setAttributes(options.attributes);\n }\n\n try {\n const result = await runInOtelContext(span, () => original.apply(this, args));\n span.setStatus({ code: 1 }); // SpanStatusCode.OK\n return result;\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({ code: 2, message: String(err) }); // SpanStatusCode.ERROR\n throw err;\n } finally {\n span.end();\n }\n };\n\n return descriptor;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/otel.ts","../src/correlation/correlation.service.ts","../src/observability.constants.ts","../src/logger/winston-http.transport.ts","../src/logger/logger.service.ts","../src/metrics/metrics.service.ts","../src/interceptors/correlation.interceptor.ts","../src/interceptors/performance.interceptor.ts","../src/filters/all-exceptions.filter.ts","../src/observability.module.ts","../src/decorators/track-performance.decorator.ts"],"names":["transports","Injectable","TRANSPORT_CB_DEFAULTS","http2","https2","axios","CircuitBreaker","Logger","CircuitBreakerOpenError","Inject","Optional"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Optional OpenTelemetry shim.\n * If @opentelemetry/api is not installed all operations become no-ops,\n * so the package works without any tracing backend.\n */\n\nimport { createRequire } from 'node:module';\n\nconst _require = createRequire(import.meta.url);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet otel: any = null;\n\ntry {\n // Dynamic require keeps OTel out of the bundle when not installed\n otel = _require('@opentelemetry/api');\n} catch {\n // OTel not installed — spans will be no-ops\n}\n\nexport const isOtelAvailable = (): boolean => otel !== null;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const getTracer = (name: string): any =>\n otel?.trace?.getTracer(name) ?? noopTracer;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const getActiveSpan = (): any =>\n otel ? otel.trace.getSpan(otel.context.active()) : undefined;\n\nexport const runInOtelContext = async <T>(span: unknown, fn: () => Promise<T>): Promise<T> => {\n if (!otel || !span) return fn();\n return otel.context.with(otel.trace.setSpan(otel.context.active(), span), fn);\n};\n\nexport interface SpanShim {\n setAttribute(key: string, value: unknown): void;\n setAttributes(attrs: Record<string, unknown>): void;\n recordException(err: Error): void;\n setStatus(status: { code: number; message?: string }): void;\n end(): void;\n spanContext(): { traceId: string; spanId: string };\n}\n\nconst noopSpan: SpanShim = {\n end: () => {},\n setAttribute: () => {},\n setAttributes: () => {},\n recordException: () => {},\n setStatus: () => {},\n spanContext: () => ({ traceId: '', spanId: '' }),\n};\n\nconst noopTracer = {\n startSpan: () => noopSpan,\n startActiveSpan: (_name: string, fn: (span: unknown) => unknown) => fn(noopSpan),\n};\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { Injectable } from '@nestjs/common';\nimport { getActiveSpan } from '../internal/otel.js';\n\nconst storage = new AsyncLocalStorage<string>();\n\n@Injectable()\nexport class CorrelationIdService {\n /**\n * Run `fn` inside a context that carries `correlationId`.\n * All code executed within `fn` (including async continuations) can call\n * `get()` and receive the same ID without passing it explicitly.\n */\n run<T>(correlationId: string, fn: () => T): T {\n return storage.run(correlationId, fn);\n }\n\n /** Current correlation ID, or 'no-context' when called outside a context. */\n get(): string {\n return storage.getStore() ?? 'no-context';\n }\n\n /**\n * Current correlation ID, or `undefined` when called outside a context.\n * Prefer `get()` for logging; use this only when you need to distinguish\n * \"no context\" from \"context with a random ID\".\n */\n getOrUndefined(): string | undefined {\n return storage.getStore();\n }\n\n /**\n * Active OTel trace + span IDs when @opentelemetry/api is installed and a\n * span is active; `undefined` otherwise.\n */\n getTraceContext(): { traceId: string; spanId: string } | undefined {\n const span = getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext?.();\n if (!ctx?.traceId) return undefined;\n return { traceId: ctx.traceId, spanId: ctx.spanId };\n }\n}\n","export const OBSERVABILITY_OPTIONS = 'OBSERVABILITY_OPTIONS' as const;\n","import { Logger } from '@nestjs/common';\nimport TransportStream, { TransportStreamOptions } from 'winston-transport';\nimport axios, { AxiosInstance } from 'axios';\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport {\n CircuitBreaker,\n CircuitBreakerConfig,\n CircuitBreakerOpenError,\n} from '@backendkit-labs/circuit-breaker';\n\nexport interface WinstonHttpTransportOptions extends TransportStreamOptions {\n /** Full URL of the log-ingest endpoint. */\n url: string;\n\n /** Bearer token sent in `Authorization` header. */\n authToken?: string;\n\n /** Additional static headers merged into every request. */\n headers?: Record<string, string>;\n\n /** Flush batch when it reaches this many entries (default 100). */\n batchSize?: number;\n\n /** Maximum entries held in the in-memory buffer (default 2000). */\n maxBufferSize?: number;\n\n /** Flush interval in ms — also flushes on `close()` (default 5000). */\n flushIntervalMs?: number;\n\n /** Request timeout in ms (default 5000). */\n timeoutMs?: number;\n\n /**\n * Override any circuit breaker config fields.\n * `name` and `isFailure` are set internally and cannot be overridden.\n *\n * Transport defaults: failureThreshold 60%, slidingWindowSize 5,\n * minimumCalls 3, openTimeoutMs 30 000, halfOpenMaxCalls 1.\n */\n circuitBreaker?: Partial<Omit<CircuitBreakerConfig, 'name' | 'isFailure'>>;\n}\n\ninterface LogEntry {\n level: string;\n message: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\nconst TRANSPORT_CB_DEFAULTS: Omit<CircuitBreakerConfig, 'name' | 'isFailure'> = {\n failureThreshold: 60,\n slowCallThreshold: 100,\n slowCallDurationMs: 60_000,\n minimumCalls: 3,\n slidingWindowSize: 5,\n halfOpenMaxCalls: 1,\n openTimeoutMs: 30_000,\n};\n\nexport class WinstonHttpTransport extends TransportStream {\n private readonly client: AxiosInstance;\n private readonly cb: CircuitBreaker;\n private buffer: LogEntry[] = [];\n private readonly batchSize: number;\n private readonly maxBufferSize: number;\n private readonly maxEntryAgeMs = 300_000; // 5 min\n private readonly fallbackLogger = new Logger(WinstonHttpTransport.name);\n private readonly retryCounts = new WeakMap<LogEntry, number>();\n private readonly entryTimes = new WeakMap<LogEntry, number>();\n private readonly maxRetries = 5;\n private readonly flushTimer: ReturnType<typeof setInterval>;\n\n constructor(opts: WinstonHttpTransportOptions) {\n super(opts);\n\n this.batchSize = opts.batchSize ?? 100;\n this.maxBufferSize = opts.maxBufferSize ?? 2_000;\n\n const keepAlive = new http.Agent({ keepAlive: true });\n const keepAliveHttps = new https.Agent({ keepAlive: true });\n\n this.client = axios.create({\n baseURL: opts.url,\n timeout: opts.timeoutMs ?? 5_000,\n httpAgent: keepAlive,\n httpsAgent: keepAliveHttps,\n headers: {\n 'Content-Type': 'application/json',\n ...(opts.authToken ? { Authorization: `Bearer ${opts.authToken}` } : {}),\n ...opts.headers,\n },\n });\n\n // Redact sensitive headers from error output\n this.client.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.config?.headers?.Authorization) {\n error.config.headers.Authorization = 'Bearer ***REDACTED***';\n }\n return Promise.reject(error);\n },\n );\n\n this.cb = new CircuitBreaker({\n ...TRANSPORT_CB_DEFAULTS,\n ...opts.circuitBreaker,\n name: 'WinstonHttpTransport',\n isFailure: (error: unknown) => {\n const status = (error as { response?: { status?: number } }).response?.status;\n return status !== undefined ? status >= 400 : true;\n },\n });\n\n this.flushTimer = setInterval(\n () => { void this.flush(); },\n opts.flushIntervalMs ?? 5_000,\n );\n this.flushTimer.unref?.();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n override log(info: any, callback: () => void): void {\n this.emit('logged', info);\n\n if (this.buffer.length < this.maxBufferSize) {\n const entry = { ...info } as LogEntry;\n this.entryTimes.set(entry, Date.now());\n this.buffer.push(entry);\n }\n // silently drop when full — buffer-full warn would cause infinite recursion\n\n if (this.buffer.length >= this.batchSize) {\n void this.flush();\n }\n\n callback();\n }\n\n /** Flush remaining buffer on graceful shutdown. */\n override async close(): Promise<void> {\n clearInterval(this.flushTimer);\n await this.flush();\n }\n\n private async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n\n // Discard entries older than maxEntryAgeMs\n const now = Date.now();\n this.buffer = this.buffer.filter(\n (e) => now - (this.entryTimes.get(e) ?? now) < this.maxEntryAgeMs,\n );\n\n const batch = this.buffer.splice(0, this.batchSize);\n\n try {\n await this.cb.execute(() => this.client.post('', batch));\n } catch (err) {\n // Re-queue with retry limit to prevent infinite re-enqueue\n const retryable = batch.filter(entry => {\n const retries = this.retryCounts.get(entry) ?? 0;\n if (retries >= this.maxRetries) return false;\n this.retryCounts.set(entry, retries + 1);\n return true;\n });\n const room = this.maxBufferSize - this.buffer.length;\n if (room > 0) this.buffer.unshift(...retryable.slice(0, room));\n\n if (!(err instanceof CircuitBreakerOpenError)) {\n this.fallbackLogger.warn(`flush failed — re-queued ${retryable.length}/${batch.length} entries`, (err as Error).message);\n }\n }\n }\n}\n","import { Injectable, Inject, Optional, LoggerService as NestLoggerService } from '@nestjs/common';\nimport * as winston from 'winston';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { OBSERVABILITY_OPTIONS } from '../observability.constants.js';\nimport { ObservabilityOptions } from '../observability.types.js';\nimport { WinstonHttpTransport } from './winston-http.transport.js';\n\n@Injectable()\nexport class LoggerService implements NestLoggerService {\n private readonly winston: winston.Logger;\n\n constructor(\n @Inject(OBSERVABILITY_OPTIONS)\n private readonly opts: ObservabilityOptions,\n @Optional()\n private readonly correlationSvc?: CorrelationIdService,\n ) {\n const transports: winston.transport[] = [\n new winston.transports.Console({\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.colorize(),\n winston.format.printf(({ level, message, timestamp, ...meta }) => {\n const m = meta as Record<string, unknown>;\n const ctxPart = m['context'] ? ` [${String(m['context'])}]` : '';\n const corrPart = typeof m['correlationId'] === 'string' && m['correlationId'] !== 'no-context'\n ? ` [${m['correlationId']}]`\n : '';\n\n return `${timestamp} [${level}]${ctxPart}${corrPart} ${message}`;\n }),\n ),\n }),\n ];\n\n if (opts.http) {\n transports.push(\n new WinstonHttpTransport({\n ...opts.http,\n format: winston.format.json(),\n }),\n );\n }\n\n this.winston = winston.createLogger({\n level: opts.logLevel ?? 'info',\n transports,\n format: winston.format.json(),\n });\n }\n\n log(message: string, context?: string): void {\n this.winston.info(message, this.buildMeta(context));\n }\n\n error(message: string, trace?: string, context?: string): void {\n this.winston.error(message, { ...this.buildMeta(context), trace });\n }\n\n warn(message: string, context?: string): void {\n this.winston.warn(message, this.buildMeta(context));\n }\n\n debug(message: string, context?: string): void {\n this.winston.debug(message, this.buildMeta(context));\n }\n\n verbose(message: string, context?: string): void {\n this.winston.verbose(message, this.buildMeta(context));\n }\n\n /** Log with additional arbitrary metadata. */\n logWithMeta(level: string, message: string, meta: Record<string, unknown>): void {\n const validLevels = ['error', 'warn', 'info', 'debug', 'verbose'];\n const safeLevel = validLevels.includes(level) ? level : 'info';\n this.winston.log(safeLevel, message, { ...this.buildMeta(), ...meta });\n }\n\n private buildMeta(context?: string): Record<string, unknown> {\n const base: Record<string, unknown> = {\n service: this.opts.serviceName,\n environment: this.opts.environment ?? 'production',\n correlationId: this.correlationSvc?.get(),\n };\n if (context) base.context = context;\n return base;\n }\n}\n","import { Injectable, Inject, Optional, OnModuleDestroy, Logger } from '@nestjs/common';\nimport axios, { AxiosInstance } from 'axios';\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport {\n CircuitBreaker,\n CircuitBreakerConfig,\n CircuitBreakerOpenError,\n CircuitBreakerState,\n} from '@backendkit-labs/circuit-breaker';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { OBSERVABILITY_OPTIONS } from '../observability.constants.js';\nimport { ObservabilityOptions, MetricEvent } from '../observability.types.js';\n\nconst TRANSPORT_CB_DEFAULTS: Omit<CircuitBreakerConfig, 'name' | 'isFailure'> = {\n failureThreshold: 60,\n slowCallThreshold: 100,\n slowCallDurationMs: 60_000,\n minimumCalls: 3,\n slidingWindowSize: 5,\n halfOpenMaxCalls: 1,\n openTimeoutMs: 30_000,\n};\n\n@Injectable()\nexport class MetricsService implements OnModuleDestroy {\n private readonly client: AxiosInstance | null = null;\n private readonly cb: CircuitBreaker | null = null;\n private readonly logger = new Logger(MetricsService.name);\n private buffer: MetricEvent[] = [];\n private readonly maxBufferSize: number = 5_000;\n private readonly maxEntryAgeMs = 300_000; // 5 min\n private readonly retryCounts = new WeakMap<MetricEvent, number>();\n private readonly maxRetries = 5;\n private readonly flushTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n @Inject(OBSERVABILITY_OPTIONS)\n private readonly opts: ObservabilityOptions,\n @Optional()\n private readonly correlationSvc?: CorrelationIdService,\n ) {\n if (!opts.metrics) return;\n\n const m = opts.metrics;\n this.maxBufferSize = m.maxBufferSize ?? 5_000;\n\n const keepAlive = new http.Agent({ keepAlive: true });\n const keepAliveHttps = new https.Agent({ keepAlive: true });\n\n this.client = axios.create({\n baseURL: m.url,\n timeout: m.timeoutMs ?? 5_000,\n httpAgent: keepAlive,\n httpsAgent: keepAliveHttps,\n headers: {\n 'Content-Type': 'application/json',\n ...(m.authToken ? { Authorization: `Bearer ${m.authToken}` } : {}),\n ...m.headers,\n },\n });\n\n this.cb = new CircuitBreaker({\n ...TRANSPORT_CB_DEFAULTS,\n ...m.circuitBreaker,\n name: 'MetricsService',\n isFailure: (error: unknown) => {\n const status = (error as { response?: { status?: number } }).response?.status;\n return status !== undefined ? status >= 400 : true;\n },\n onStateChange: (from, to, metrics) => {\n if (to === CircuitBreakerState.OPEN) {\n this.logger.warn(\n `[MetricsService] circuit breaker OPEN — pausing metric sends for ${(m.circuitBreaker?.openTimeoutMs ?? TRANSPORT_CB_DEFAULTS.openTimeoutMs) / 1_000}s`,\n metrics,\n );\n } else if (to === CircuitBreakerState.CLOSED && from !== CircuitBreakerState.HALF_OPEN) {\n this.logger.log(`[MetricsService] circuit breaker CLOSED — recovered`);\n }\n m.circuitBreaker?.onStateChange?.(from, to, metrics);\n },\n });\n\n this.flushTimer = setInterval(\n () => { void this.flush(); },\n m.flushIntervalMs ?? 10_000,\n );\n this.flushTimer.unref?.();\n }\n\n /**\n * Enqueue a metric event. Fire-and-forget; batched and sent on the next\n * flush interval or when the buffer reaches `maxBufferSize`.\n */\n record(\n name: string,\n value: number,\n options?: { unit?: string; tags?: Record<string, string> },\n ): void {\n if (!this.client) return;\n\n if (this.buffer.length >= this.maxBufferSize) {\n this.logger.warn('[MetricsService] buffer full — dropping metric');\n return;\n }\n\n this.buffer.push({\n name,\n value,\n unit: options?.unit,\n tags: options?.tags,\n timestamp: new Date().toISOString(),\n serviceName: this.opts.serviceName,\n environment: this.opts.environment ?? 'production',\n correlationId: this.correlationSvc?.getOrUndefined(),\n });\n }\n\n /** Flush on graceful shutdown. */\n async onModuleDestroy(): Promise<void> {\n if (this.flushTimer) clearInterval(this.flushTimer);\n try {\n await this.flush();\n } catch {\n // Flush already handles errors internally; nothing to do here\n }\n }\n\n private async flush(): Promise<void> {\n if (!this.client || this.buffer.length === 0) return;\n\n // Discard entries older than maxEntryAgeMs\n const now = Date.now();\n this.buffer = this.buffer.filter(\n (e) => now - new Date(e.timestamp).getTime() < this.maxEntryAgeMs,\n );\n\n const batch = this.buffer.splice(0, 500);\n\n try {\n await this.cb!.execute(() => this.client!.post('', batch));\n } catch (err) {\n // Re-queue with retry limit to prevent infinite re-enqueue\n const retryable = batch.filter(entry => {\n const retries = this.retryCounts.get(entry) ?? 0;\n if (retries >= this.maxRetries) return false;\n this.retryCounts.set(entry, retries + 1);\n return true;\n });\n const room = this.maxBufferSize - this.buffer.length;\n if (room > 0) this.buffer.unshift(...retryable.slice(0, room));\n\n if (!(err instanceof CircuitBreakerOpenError)) {\n this.logger.warn(\n `[MetricsService] flush failed — re-queueing ${retryable.length}/${batch.length} events`,\n (err as Error).message,\n );\n }\n }\n }\n}\n","import {\n Injectable,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n Inject,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { randomUUID } from 'node:crypto';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\n\nconst CORRELATION_HEADER = 'x-correlation-id';\n\n/**\n * Only allow characters safe for logs, HTTP headers and JSON.\n * Rejects newlines, control chars, and excessively long values.\n */\nconst CORRELATION_ID_REGEX = /^[a-zA-Z0-9\\-_:]{1,64}$/;\n\nfunction sanitizeCorrelationId(raw: unknown): string | null {\n if (typeof raw !== 'string') return null;\n if (raw.length > 64) return null;\n if (!CORRELATION_ID_REGEX.test(raw)) return null;\n return raw;\n}\n\n@Injectable()\nexport class CorrelationInterceptor implements NestInterceptor {\n constructor(\n @Inject(CorrelationIdService)\n private readonly correlationSvc: CorrelationIdService,\n ) {}\n\n intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {\n const req = ctx.switchToHttp().getRequest<Record<string, unknown>>();\n const res = ctx.switchToHttp().getResponse<{\n setHeader(name: string, value: string): void;\n }>();\n\n const incomingId =\n (req.headers as Record<string, string | undefined>)?.[CORRELATION_HEADER];\n const correlationId = sanitizeCorrelationId(incomingId) ?? randomUUID();\n\n res.setHeader(CORRELATION_HEADER, correlationId);\n\n return new Observable(subscriber => {\n this.correlationSvc.run(correlationId, () => {\n next.handle().subscribe(subscriber);\n });\n });\n }\n}\n","import {\n Injectable,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n Inject,\n Optional,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport { LoggerService } from '../logger/logger.service.js';\nimport { MetricsService } from '../metrics/metrics.service.js';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { getActiveSpan } from '../internal/otel.js';\n\n@Injectable()\nexport class PerformanceInterceptor implements NestInterceptor {\n constructor(\n @Inject(LoggerService)\n private readonly logger: LoggerService,\n @Optional()\n @Inject(MetricsService)\n private readonly metrics: MetricsService | undefined,\n @Optional()\n @Inject(CorrelationIdService)\n private readonly correlationSvc: CorrelationIdService | undefined,\n ) {}\n\n intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {\n const start = Date.now();\n const req = ctx.switchToHttp().getRequest<{\n method: string;\n url: string;\n }>();\n const method = req.method ?? 'UNKNOWN';\n const path = req.url ?? 'UNKNOWN';\n\n return next.handle().pipe(\n tap({\n next: () => {\n const durationMs = Date.now() - start;\n this.record(method, path, 'success', durationMs);\n },\n error: () => {\n const durationMs = Date.now() - start;\n this.record(method, path, 'error', durationMs);\n },\n }),\n );\n }\n\n private record(\n method: string,\n path: string,\n outcome: 'success' | 'error',\n durationMs: number,\n ): void {\n const correlationId = this.correlationSvc?.getOrUndefined();\n const span = getActiveSpan();\n\n span?.setAttribute('http.method', method);\n span?.setAttribute('http.target', path);\n span?.setAttribute('http.duration_ms', durationMs);\n\n this.logger.logWithMeta('info', `${method} ${path} [${outcome}] ${durationMs}ms`, {\n method,\n path,\n outcome,\n durationMs,\n correlationId,\n });\n\n this.metrics?.record('http.request.duration', durationMs, {\n unit: 'ms',\n tags: { method, path, outcome },\n });\n }\n}\n","import {\n ExceptionFilter,\n Catch,\n ArgumentsHost,\n HttpException,\n HttpStatus,\n Inject,\n Optional,\n} from '@nestjs/common';\nimport { LoggerService } from '../logger/logger.service.js';\nimport { CorrelationIdService } from '../correlation/correlation.service.js';\nimport { getActiveSpan } from '../internal/otel.js';\n\nexport type ErrorMapper = (error: unknown) => {\n statusCode: number;\n message: string;\n code?: string;\n} | null;\n\n@Catch()\nexport class AllExceptionsFilter implements ExceptionFilter {\n private readonly mappers: ErrorMapper[] = [];\n\n constructor(\n @Inject(LoggerService)\n private readonly logger: LoggerService,\n @Optional()\n @Inject(CorrelationIdService)\n private readonly correlationSvc: CorrelationIdService | undefined,\n ) {}\n\n /**\n * Register a custom error mapper.\n * Mappers are tried in order; the first non-null result wins.\n * Return `null` to fall through to the next mapper.\n */\n addMapper(mapper: ErrorMapper): this {\n this.mappers.push(mapper);\n return this;\n }\n\n catch(exception: unknown, host: ArgumentsHost): void {\n const ctx = host.switchToHttp();\n const res = ctx.getResponse<{\n status(code: number): { json(body: unknown): void };\n }>();\n\n const mapped = this.resolveError(exception);\n\n const correlationId = this.correlationSvc?.getOrUndefined();\n const span = getActiveSpan();\n span?.recordException(exception instanceof Error ? exception : new Error(String(exception)));\n\n this.logger.error(\n mapped.message,\n exception instanceof Error ? exception.stack : undefined,\n AllExceptionsFilter.name,\n );\n\n res.status(mapped.statusCode).json({\n ok: false,\n statusCode: mapped.statusCode,\n message: mapped.message,\n code: mapped.code,\n correlationId,\n timestamp: new Date().toISOString(),\n });\n }\n\n private resolveError(exception: unknown): {\n statusCode: number;\n message: string;\n code?: string;\n } {\n for (const mapper of this.mappers) {\n const result = mapper(exception);\n if (result !== null) return result;\n }\n\n if (exception instanceof HttpException) {\n const body = exception.getResponse();\n const rawMessage =\n typeof body === 'string'\n ? body\n : (body as { message?: unknown }).message ?? exception.message;\n const message = Array.isArray(rawMessage)\n ? rawMessage.join('; ')\n : String(rawMessage ?? exception.message);\n return { statusCode: exception.getStatus(), message };\n }\n\n return {\n statusCode: HttpStatus.INTERNAL_SERVER_ERROR,\n message: 'Internal server error',\n };\n }\n}\n","import { DynamicModule, Module, Provider } from '@nestjs/common';\nimport { CorrelationIdService } from './correlation/correlation.service.js';\nimport { LoggerService } from './logger/logger.service.js';\nimport { MetricsService } from './metrics/metrics.service.js';\nimport { CorrelationInterceptor } from './interceptors/correlation.interceptor.js';\nimport { PerformanceInterceptor } from './interceptors/performance.interceptor.js';\nimport { AllExceptionsFilter } from './filters/all-exceptions.filter.js';\nimport { OBSERVABILITY_OPTIONS } from './observability.constants.js';\nimport { ObservabilityOptions } from './observability.types.js';\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(options: ObservabilityOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: OBSERVABILITY_OPTIONS,\n useValue: options,\n };\n\n const providers: Provider[] = [\n optionsProvider,\n CorrelationIdService,\n LoggerService,\n MetricsService,\n CorrelationInterceptor,\n PerformanceInterceptor,\n AllExceptionsFilter,\n ];\n\n return {\n module: ObservabilityModule,\n global: true,\n providers,\n exports: [\n CorrelationIdService,\n LoggerService,\n MetricsService,\n CorrelationInterceptor,\n PerformanceInterceptor,\n AllExceptionsFilter,\n ],\n };\n }\n}\n","import { getTracer, runInOtelContext } from '../internal/otel.js';\n\nexport interface TrackPerformanceOptions {\n /** OTel span / log operation name. Defaults to `ClassName.methodName`. */\n operation?: string;\n\n /** Attributes added to the OTel span. */\n attributes?: Record<string, string | number | boolean>;\n}\n\n/**\n * Method decorator that wraps the decorated async method in an OTel span\n * (when @opentelemetry/api is available) and records its duration.\n * Works with regular methods and NestJS service methods.\n *\n * @example\n * \\@TrackPerformance()\n * async processPayment(id: string) { ... }\n */\nexport function TrackPerformance(options: TrackPerformanceOptions = {}): MethodDecorator {\n return function (\n target: object,\n propertyKey: string | symbol,\n descriptor: PropertyDescriptor,\n ): PropertyDescriptor {\n const original = descriptor.value as (...args: unknown[]) => Promise<unknown>;\n const operationName =\n options.operation ?? `${target.constructor.name}.${String(propertyKey)}`;\n\n descriptor.value = async function (...args: unknown[]): Promise<unknown> {\n const tracer = getTracer('@backendkit-labs/observability');\n const span = tracer.startSpan(operationName);\n\n if (options.attributes) {\n span.setAttributes(options.attributes);\n }\n\n try {\n const result = await runInOtelContext(span, () => original.apply(this, args));\n span.setStatus({ code: 1 }); // SpanStatusCode.OK\n return result;\n } catch (err) {\n span.recordException(err instanceof Error ? err : new Error(String(err)));\n span.setStatus({ code: 2, message: String(err) }); // SpanStatusCode.ERROR\n throw err;\n } finally {\n span.end();\n }\n };\n\n return descriptor;\n };\n}\n"]}
|