@typokit/otel 0.1.4
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/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/log-bridge.d.ts +22 -0
- package/dist/log-bridge.d.ts.map +1 -0
- package/dist/log-bridge.js +128 -0
- package/dist/log-bridge.js.map +1 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +100 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +62 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +260 -0
- package/dist/metrics.js.map +1 -0
- package/dist/redact.d.ts +7 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +46 -0
- package/dist/redact.js.map +1 -0
- package/dist/tracing.d.ts +94 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +292 -0
- package/dist/tracing.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -0
- package/src/index.test.ts +310 -0
- package/src/index.ts +45 -0
- package/src/log-bridge.test.ts +511 -0
- package/src/log-bridge.ts +150 -0
- package/src/logger.ts +143 -0
- package/src/metrics.test.ts +373 -0
- package/src/metrics.ts +337 -0
- package/src/redact.ts +52 -0
- package/src/tracing.test.ts +425 -0
- package/src/tracing.ts +377 -0
- package/src/types.ts +145 -0
package/src/tracing.ts
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SpanData,
|
|
3
|
+
SpanStatus,
|
|
4
|
+
SpanExporter,
|
|
5
|
+
TracingConfig,
|
|
6
|
+
TelemetryConfig,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
|
|
9
|
+
// ─── ID Generation ───────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function randomHex(bytes: number): string {
|
|
12
|
+
const arr = new Uint8Array(bytes);
|
|
13
|
+
const cryptoObj = (
|
|
14
|
+
globalThis as unknown as {
|
|
15
|
+
crypto?: { getRandomValues(a: Uint8Array): Uint8Array };
|
|
16
|
+
}
|
|
17
|
+
).crypto;
|
|
18
|
+
if (cryptoObj?.getRandomValues) {
|
|
19
|
+
cryptoObj.getRandomValues(arr);
|
|
20
|
+
} else {
|
|
21
|
+
for (let i = 0; i < bytes; i++) {
|
|
22
|
+
arr[i] = Math.floor(Math.random() * 256);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Generate a 32-char hex trace ID */
|
|
29
|
+
export function generateTraceId(): string {
|
|
30
|
+
return randomHex(16);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Generate a 16-char hex span ID */
|
|
34
|
+
export function generateSpanId(): string {
|
|
35
|
+
return randomHex(8);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Console Span Exporter ───────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/** Exports spans to stdout as structured JSON (dev mode) */
|
|
41
|
+
export class ConsoleSpanExporter implements SpanExporter {
|
|
42
|
+
export(spans: SpanData[]): void {
|
|
43
|
+
const proc = (
|
|
44
|
+
globalThis as unknown as {
|
|
45
|
+
process?: { stdout?: { write(s: string): void } };
|
|
46
|
+
}
|
|
47
|
+
).process;
|
|
48
|
+
for (const span of spans) {
|
|
49
|
+
const output = JSON.stringify({ type: "span", ...span });
|
|
50
|
+
if (proc?.stdout?.write) {
|
|
51
|
+
proc.stdout.write(output + "\n");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** No-op exporter that silently discards spans */
|
|
58
|
+
export class NoopSpanExporter implements SpanExporter {
|
|
59
|
+
export(_spans: SpanData[]): void {
|
|
60
|
+
// intentionally empty
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Exports spans to an OTLP-compatible HTTP endpoint */
|
|
65
|
+
export class OtlpSpanExporter implements SpanExporter {
|
|
66
|
+
private readonly endpoint: string;
|
|
67
|
+
|
|
68
|
+
constructor(endpoint?: string) {
|
|
69
|
+
this.endpoint = endpoint ?? "http://localhost:4318/v1/traces";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export(spans: SpanData[]): void {
|
|
73
|
+
// Best-effort fire-and-forget POST to OTLP endpoint
|
|
74
|
+
const fetchFn = (
|
|
75
|
+
globalThis as unknown as {
|
|
76
|
+
fetch?: (url: string, init: unknown) => Promise<unknown>;
|
|
77
|
+
}
|
|
78
|
+
).fetch;
|
|
79
|
+
if (fetchFn) {
|
|
80
|
+
const payload = {
|
|
81
|
+
resourceSpans: [
|
|
82
|
+
{
|
|
83
|
+
resource: { attributes: [] },
|
|
84
|
+
scopeSpans: [
|
|
85
|
+
{
|
|
86
|
+
scope: { name: "@typokit/otel" },
|
|
87
|
+
spans: spans.map((s) => ({
|
|
88
|
+
traceId: s.traceId,
|
|
89
|
+
spanId: s.spanId,
|
|
90
|
+
parentSpanId: s.parentSpanId,
|
|
91
|
+
name: s.name,
|
|
92
|
+
kind: s.kind === "server" ? 2 : 1,
|
|
93
|
+
startTimeUnixNano:
|
|
94
|
+
new Date(s.startTime).getTime() * 1_000_000,
|
|
95
|
+
endTimeUnixNano: s.endTime
|
|
96
|
+
? new Date(s.endTime).getTime() * 1_000_000
|
|
97
|
+
: undefined,
|
|
98
|
+
status: {
|
|
99
|
+
code: s.status === "ok" ? 1 : s.status === "error" ? 2 : 0,
|
|
100
|
+
},
|
|
101
|
+
attributes: Object.entries(s.attributes).map(
|
|
102
|
+
([key, value]) => ({
|
|
103
|
+
key,
|
|
104
|
+
value:
|
|
105
|
+
typeof value === "string"
|
|
106
|
+
? { stringValue: value }
|
|
107
|
+
: typeof value === "number"
|
|
108
|
+
? { intValue: value }
|
|
109
|
+
: { boolValue: value },
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
})),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
fetchFn(this.endpoint, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { "Content-Type": "application/json" },
|
|
122
|
+
body: JSON.stringify(payload),
|
|
123
|
+
}).catch(() => {
|
|
124
|
+
// Silently ignore export failures
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Span ────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/** A mutable span representing one phase of request processing */
|
|
133
|
+
export class Span {
|
|
134
|
+
readonly traceId: string;
|
|
135
|
+
readonly spanId: string;
|
|
136
|
+
readonly parentSpanId?: string;
|
|
137
|
+
readonly name: string;
|
|
138
|
+
readonly kind: "server" | "internal";
|
|
139
|
+
readonly startTime: string;
|
|
140
|
+
readonly attributes: Record<string, string | number | boolean>;
|
|
141
|
+
private _endTime?: string;
|
|
142
|
+
private _durationMs?: number;
|
|
143
|
+
private _status: SpanStatus = "unset";
|
|
144
|
+
|
|
145
|
+
constructor(options: {
|
|
146
|
+
traceId: string;
|
|
147
|
+
spanId?: string;
|
|
148
|
+
parentSpanId?: string;
|
|
149
|
+
name: string;
|
|
150
|
+
kind: "server" | "internal";
|
|
151
|
+
attributes?: Record<string, string | number | boolean>;
|
|
152
|
+
}) {
|
|
153
|
+
this.traceId = options.traceId;
|
|
154
|
+
this.spanId = options.spanId ?? generateSpanId();
|
|
155
|
+
this.parentSpanId = options.parentSpanId;
|
|
156
|
+
this.name = options.name;
|
|
157
|
+
this.kind = options.kind;
|
|
158
|
+
this.startTime = new Date().toISOString();
|
|
159
|
+
this.attributes = { ...options.attributes };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Set a key-value attribute on the span */
|
|
163
|
+
setAttribute(key: string, value: string | number | boolean): void {
|
|
164
|
+
this.attributes[key] = value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Mark the span as successful and end it */
|
|
168
|
+
setOk(): void {
|
|
169
|
+
this._status = "ok";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Mark the span as errored with an optional message */
|
|
173
|
+
setError(message?: string): void {
|
|
174
|
+
this._status = "error";
|
|
175
|
+
if (message) {
|
|
176
|
+
this.attributes["error.message"] = message;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** End the span and record its duration */
|
|
181
|
+
end(): void {
|
|
182
|
+
this._endTime = new Date().toISOString();
|
|
183
|
+
this._durationMs =
|
|
184
|
+
new Date(this._endTime).getTime() - new Date(this.startTime).getTime();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Whether this span has been ended */
|
|
188
|
+
get ended(): boolean {
|
|
189
|
+
return this._endTime !== undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
get status(): SpanStatus {
|
|
193
|
+
return this._status;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Convert to a plain data object for export */
|
|
197
|
+
toData(): SpanData {
|
|
198
|
+
return {
|
|
199
|
+
traceId: this.traceId,
|
|
200
|
+
spanId: this.spanId,
|
|
201
|
+
...(this.parentSpanId !== undefined
|
|
202
|
+
? { parentSpanId: this.parentSpanId }
|
|
203
|
+
: {}),
|
|
204
|
+
name: this.name,
|
|
205
|
+
kind: this.kind,
|
|
206
|
+
startTime: this.startTime,
|
|
207
|
+
...(this._endTime !== undefined ? { endTime: this._endTime } : {}),
|
|
208
|
+
...(this._durationMs !== undefined
|
|
209
|
+
? { durationMs: this._durationMs }
|
|
210
|
+
: {}),
|
|
211
|
+
status: this._status,
|
|
212
|
+
attributes: { ...this.attributes },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── Tracer ──────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
/** Resolves a TelemetryConfig into a normalized TracingConfig */
|
|
220
|
+
export function resolveTracingConfig(
|
|
221
|
+
telemetry?: TelemetryConfig,
|
|
222
|
+
): TracingConfig {
|
|
223
|
+
if (!telemetry) {
|
|
224
|
+
return { enabled: true, exporter: "console" };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof telemetry.tracing === "boolean") {
|
|
228
|
+
return {
|
|
229
|
+
enabled: telemetry.tracing,
|
|
230
|
+
exporter: telemetry.exporter ?? "console",
|
|
231
|
+
endpoint: telemetry.endpoint,
|
|
232
|
+
serviceName: telemetry.serviceName,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (typeof telemetry.tracing === "object") {
|
|
237
|
+
return {
|
|
238
|
+
enabled: telemetry.tracing.enabled ?? true,
|
|
239
|
+
exporter: telemetry.tracing.exporter ?? telemetry.exporter ?? "console",
|
|
240
|
+
endpoint: telemetry.tracing.endpoint ?? telemetry.endpoint,
|
|
241
|
+
serviceName: telemetry.tracing.serviceName ?? telemetry.serviceName,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
enabled: true,
|
|
247
|
+
exporter: telemetry.exporter ?? "console",
|
|
248
|
+
endpoint: telemetry.endpoint,
|
|
249
|
+
serviceName: telemetry.serviceName,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Creates the appropriate SpanExporter from config */
|
|
254
|
+
export function createExporter(config: TracingConfig): SpanExporter {
|
|
255
|
+
if (!config.enabled) {
|
|
256
|
+
return new NoopSpanExporter();
|
|
257
|
+
}
|
|
258
|
+
if (config.exporter === "otlp") {
|
|
259
|
+
return new OtlpSpanExporter(config.endpoint);
|
|
260
|
+
}
|
|
261
|
+
return new ConsoleSpanExporter();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Tracer creates and manages spans for a single request trace.
|
|
266
|
+
* Each request gets its own Tracer instance with a unique traceId.
|
|
267
|
+
*/
|
|
268
|
+
export class Tracer {
|
|
269
|
+
readonly traceId: string;
|
|
270
|
+
private readonly spans: Span[] = [];
|
|
271
|
+
private readonly exporter: SpanExporter;
|
|
272
|
+
private readonly serviceName: string;
|
|
273
|
+
private readonly enabled: boolean;
|
|
274
|
+
private rootSpan?: Span;
|
|
275
|
+
|
|
276
|
+
constructor(options?: {
|
|
277
|
+
traceId?: string;
|
|
278
|
+
exporter?: SpanExporter;
|
|
279
|
+
serviceName?: string;
|
|
280
|
+
enabled?: boolean;
|
|
281
|
+
}) {
|
|
282
|
+
this.traceId = options?.traceId ?? generateTraceId();
|
|
283
|
+
this.exporter = options?.exporter ?? new NoopSpanExporter();
|
|
284
|
+
this.serviceName = options?.serviceName ?? "typokit";
|
|
285
|
+
this.enabled = options?.enabled ?? true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Start a root span for the incoming request */
|
|
289
|
+
startRootSpan(
|
|
290
|
+
name: string,
|
|
291
|
+
attributes?: Record<string, string | number | boolean>,
|
|
292
|
+
): Span {
|
|
293
|
+
const span = new Span({
|
|
294
|
+
traceId: this.traceId,
|
|
295
|
+
name,
|
|
296
|
+
kind: "server",
|
|
297
|
+
attributes: {
|
|
298
|
+
"service.name": this.serviceName,
|
|
299
|
+
...attributes,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
this.rootSpan = span;
|
|
303
|
+
this.spans.push(span);
|
|
304
|
+
return span;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Start a child span under the root (or specified parent) */
|
|
308
|
+
startSpan(
|
|
309
|
+
name: string,
|
|
310
|
+
options?: {
|
|
311
|
+
parentSpanId?: string;
|
|
312
|
+
attributes?: Record<string, string | number | boolean>;
|
|
313
|
+
},
|
|
314
|
+
): Span {
|
|
315
|
+
if (!this.enabled) {
|
|
316
|
+
// Return a no-op span that won't be exported
|
|
317
|
+
return new Span({
|
|
318
|
+
traceId: this.traceId,
|
|
319
|
+
name,
|
|
320
|
+
kind: "internal",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const parentId = options?.parentSpanId ?? this.rootSpan?.spanId;
|
|
325
|
+
const span = new Span({
|
|
326
|
+
traceId: this.traceId,
|
|
327
|
+
parentSpanId: parentId,
|
|
328
|
+
name,
|
|
329
|
+
kind: "internal",
|
|
330
|
+
attributes: options?.attributes,
|
|
331
|
+
});
|
|
332
|
+
this.spans.push(span);
|
|
333
|
+
return span;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** End all open spans and export them */
|
|
337
|
+
flush(): void {
|
|
338
|
+
// End any spans that haven't been ended
|
|
339
|
+
for (const span of this.spans) {
|
|
340
|
+
if (!span.ended) {
|
|
341
|
+
span.end();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (this.enabled && this.spans.length > 0) {
|
|
346
|
+
this.exporter.export(this.spans.map((s) => s.toData()));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Get all collected spans as data objects */
|
|
351
|
+
getSpans(): SpanData[] {
|
|
352
|
+
return this.spans.map((s) => s.toData());
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Get the root span if one was started */
|
|
356
|
+
getRootSpan(): Span | undefined {
|
|
357
|
+
return this.rootSpan;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Creates a Tracer for an incoming request, configured from TelemetryConfig.
|
|
363
|
+
* This is the main entry point for request-level tracing.
|
|
364
|
+
*/
|
|
365
|
+
export function createRequestTracer(
|
|
366
|
+
telemetry?: TelemetryConfig,
|
|
367
|
+
exporterOverride?: SpanExporter,
|
|
368
|
+
): Tracer {
|
|
369
|
+
const config = resolveTracingConfig(telemetry);
|
|
370
|
+
const exporter = exporterOverride ?? createExporter(config);
|
|
371
|
+
|
|
372
|
+
return new Tracer({
|
|
373
|
+
exporter,
|
|
374
|
+
serviceName: config.serviceName,
|
|
375
|
+
enabled: config.enabled,
|
|
376
|
+
});
|
|
377
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Log levels ordered by severity
|
|
2
|
+
export const LOG_LEVELS = [
|
|
3
|
+
"trace",
|
|
4
|
+
"debug",
|
|
5
|
+
"info",
|
|
6
|
+
"warn",
|
|
7
|
+
"error",
|
|
8
|
+
"fatal",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type LogLevel = (typeof LOG_LEVELS)[number];
|
|
12
|
+
|
|
13
|
+
/** Metadata automatically attached to every log entry */
|
|
14
|
+
export interface LogMetadata {
|
|
15
|
+
traceId?: string;
|
|
16
|
+
route?: string;
|
|
17
|
+
phase?: string;
|
|
18
|
+
requestId?: string;
|
|
19
|
+
serverAdapter?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A single structured log entry */
|
|
23
|
+
export interface LogEntry {
|
|
24
|
+
level: LogLevel;
|
|
25
|
+
message: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
data?: Record<string, unknown>;
|
|
28
|
+
traceId?: string;
|
|
29
|
+
route?: string;
|
|
30
|
+
phase?: string;
|
|
31
|
+
requestId?: string;
|
|
32
|
+
serverAdapter?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Configuration for the logging system */
|
|
36
|
+
export interface LoggingConfig {
|
|
37
|
+
/** Minimum log level (default: "info" in production, "debug" in development) */
|
|
38
|
+
level?: LogLevel;
|
|
39
|
+
/** Glob-style paths to redact from log output (e.g., "*.password", "authorization") */
|
|
40
|
+
redact?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A sink that receives structured log entries */
|
|
44
|
+
export interface LogSink {
|
|
45
|
+
write(entry: LogEntry): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Tracing Types ───────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** Status of a span */
|
|
51
|
+
export type SpanStatus = "ok" | "error" | "unset";
|
|
52
|
+
|
|
53
|
+
/** A single span representing a phase of request processing */
|
|
54
|
+
export interface SpanData {
|
|
55
|
+
traceId: string;
|
|
56
|
+
spanId: string;
|
|
57
|
+
parentSpanId?: string;
|
|
58
|
+
name: string;
|
|
59
|
+
kind: "server" | "internal";
|
|
60
|
+
startTime: string;
|
|
61
|
+
endTime?: string;
|
|
62
|
+
durationMs?: number;
|
|
63
|
+
status: SpanStatus;
|
|
64
|
+
attributes: Record<string, string | number | boolean>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Configuration for the tracing system */
|
|
68
|
+
export interface TracingConfig {
|
|
69
|
+
/** Enable tracing (default: true in dev) */
|
|
70
|
+
enabled?: boolean;
|
|
71
|
+
/** Exporter type: 'console' for dev, 'otlp' for collectors */
|
|
72
|
+
exporter?: "console" | "otlp";
|
|
73
|
+
/** OTel Collector endpoint (default: http://localhost:4318) */
|
|
74
|
+
endpoint?: string;
|
|
75
|
+
/** Service name for OTel resource */
|
|
76
|
+
serviceName?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Full telemetry configuration for createApp() */
|
|
80
|
+
export interface TelemetryConfig {
|
|
81
|
+
tracing?: boolean | TracingConfig;
|
|
82
|
+
metrics?: boolean | MetricsConfig;
|
|
83
|
+
exporter?: "console" | "otlp";
|
|
84
|
+
endpoint?: string;
|
|
85
|
+
serviceName?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Interface for exporting completed spans */
|
|
89
|
+
export interface SpanExporter {
|
|
90
|
+
export(spans: SpanData[]): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Metrics Types ───────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/** Configuration for the metrics system */
|
|
96
|
+
export interface MetricsConfig {
|
|
97
|
+
/** Enable metrics collection (default: true) */
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
/** Exporter type: 'console' for dev, 'otlp' for collectors */
|
|
100
|
+
exporter?: "console" | "otlp";
|
|
101
|
+
/** OTel Collector endpoint (default: http://localhost:4318) */
|
|
102
|
+
endpoint?: string;
|
|
103
|
+
/** Service name for OTel resource */
|
|
104
|
+
serviceName?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Labels applied to all request metrics */
|
|
108
|
+
export interface MetricLabels {
|
|
109
|
+
route: string;
|
|
110
|
+
method: string;
|
|
111
|
+
status: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A single histogram data point */
|
|
115
|
+
export interface HistogramDataPoint {
|
|
116
|
+
labels: MetricLabels;
|
|
117
|
+
value: number;
|
|
118
|
+
timestamp: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A single gauge data point */
|
|
122
|
+
export interface GaugeDataPoint {
|
|
123
|
+
labels: Partial<MetricLabels>;
|
|
124
|
+
value: number;
|
|
125
|
+
timestamp: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A single counter data point */
|
|
129
|
+
export interface CounterDataPoint {
|
|
130
|
+
labels: MetricLabels;
|
|
131
|
+
value: number;
|
|
132
|
+
timestamp: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Collected metric data for export */
|
|
136
|
+
export interface MetricData {
|
|
137
|
+
name: string;
|
|
138
|
+
type: "histogram" | "gauge" | "counter";
|
|
139
|
+
dataPoints: (HistogramDataPoint | GaugeDataPoint | CounterDataPoint)[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Interface for exporting collected metrics */
|
|
143
|
+
export interface MetricExporter {
|
|
144
|
+
export(metrics: MetricData[]): void;
|
|
145
|
+
}
|