@contractspec/lib.observability 3.7.6 → 3.7.10
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/AGENTS.md +46 -21
- package/CHANGELOG.md +24 -0
- package/README.md +56 -63
- package/dist/index.d.ts +12 -12
- package/dist/index.js +298 -298
- package/dist/logging/index.js +1 -1
- package/dist/metrics/index.d.ts +1 -1
- package/dist/node/index.js +298 -298
- package/dist/node/logging/index.js +1 -1
- package/dist/node/tracing/middleware.js +24 -24
- package/dist/tracing/middleware.js +24 -24
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -148,241 +148,6 @@ class RootCauseAnalyzer {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
// src/tracing/core.ts
|
|
152
|
-
import {
|
|
153
|
-
SpanStatusCode,
|
|
154
|
-
trace
|
|
155
|
-
} from "@opentelemetry/api";
|
|
156
|
-
var DEFAULT_TRACER_NAME = "@contractspec/lib.observability";
|
|
157
|
-
function getTracer(name = DEFAULT_TRACER_NAME) {
|
|
158
|
-
return trace.getTracer(name);
|
|
159
|
-
}
|
|
160
|
-
async function traceAsync(name, fn, tracerName) {
|
|
161
|
-
const tracer = getTracer(tracerName);
|
|
162
|
-
return tracer.startActiveSpan(name, async (span) => {
|
|
163
|
-
try {
|
|
164
|
-
const result = await fn(span);
|
|
165
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
166
|
-
return result;
|
|
167
|
-
} catch (error) {
|
|
168
|
-
span.recordException(error);
|
|
169
|
-
span.setStatus({
|
|
170
|
-
code: SpanStatusCode.ERROR,
|
|
171
|
-
message: error instanceof Error ? error.message : String(error)
|
|
172
|
-
});
|
|
173
|
-
throw error;
|
|
174
|
-
} finally {
|
|
175
|
-
span.end();
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
function traceSync(name, fn, tracerName) {
|
|
180
|
-
const tracer = getTracer(tracerName);
|
|
181
|
-
return tracer.startActiveSpan(name, (span) => {
|
|
182
|
-
try {
|
|
183
|
-
const result = fn(span);
|
|
184
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
185
|
-
return result;
|
|
186
|
-
} catch (error) {
|
|
187
|
-
span.recordException(error);
|
|
188
|
-
span.setStatus({
|
|
189
|
-
code: SpanStatusCode.ERROR,
|
|
190
|
-
message: error instanceof Error ? error.message : String(error)
|
|
191
|
-
});
|
|
192
|
-
throw error;
|
|
193
|
-
} finally {
|
|
194
|
-
span.end();
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// src/tracing/model-selection.span.ts
|
|
200
|
-
async function traceModelSelection(fn, input) {
|
|
201
|
-
const startMs = performance.now();
|
|
202
|
-
return traceAsync("model.selection", async (span) => {
|
|
203
|
-
const result = await fn();
|
|
204
|
-
const durationMs = performance.now() - startMs;
|
|
205
|
-
span.setAttribute("model.selected", input.modelId);
|
|
206
|
-
span.setAttribute("model.provider", input.providerKey);
|
|
207
|
-
span.setAttribute("model.score", input.score);
|
|
208
|
-
span.setAttribute("model.alternatives_count", input.alternativesCount);
|
|
209
|
-
span.setAttribute("model.selection_duration_ms", durationMs);
|
|
210
|
-
span.setAttribute("model.reason", input.reason);
|
|
211
|
-
if (input.dimension) {
|
|
212
|
-
span.setAttribute("model.dimension", input.dimension);
|
|
213
|
-
}
|
|
214
|
-
if (input.constraints) {
|
|
215
|
-
span.setAttribute("model.constraints", JSON.stringify(input.constraints));
|
|
216
|
-
}
|
|
217
|
-
return result;
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
// src/telemetry/model-selection-telemetry.ts
|
|
221
|
-
class ModelSelectionTelemetry {
|
|
222
|
-
provider;
|
|
223
|
-
eventName;
|
|
224
|
-
constructor(provider, options) {
|
|
225
|
-
this.provider = provider;
|
|
226
|
-
this.eventName = options?.eventName ?? "$model_selection";
|
|
227
|
-
}
|
|
228
|
-
async trackSelection(distinctId, properties) {
|
|
229
|
-
await this.provider.capture({
|
|
230
|
-
distinctId,
|
|
231
|
-
event: this.eventName,
|
|
232
|
-
timestamp: new Date,
|
|
233
|
-
properties: {
|
|
234
|
-
$model_id: properties.modelId,
|
|
235
|
-
$model_provider: properties.providerKey,
|
|
236
|
-
$model_score: properties.score,
|
|
237
|
-
$model_dimension: properties.dimension ?? null,
|
|
238
|
-
$model_reason: properties.reason,
|
|
239
|
-
$model_alternatives_count: properties.alternativesCount,
|
|
240
|
-
$model_cost_estimate_input: properties.costEstimateInput ?? null,
|
|
241
|
-
$model_cost_estimate_output: properties.costEstimateOutput ?? null,
|
|
242
|
-
$model_selection_duration_ms: properties.selectionDurationMs ?? null
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// src/metrics/index.ts
|
|
249
|
-
import {
|
|
250
|
-
metrics
|
|
251
|
-
} from "@opentelemetry/api";
|
|
252
|
-
var DEFAULT_METER_NAME = "@contractspec/lib.observability";
|
|
253
|
-
function getMeter(name = DEFAULT_METER_NAME) {
|
|
254
|
-
return metrics.getMeter(name);
|
|
255
|
-
}
|
|
256
|
-
function createCounter(name, description, meterName) {
|
|
257
|
-
return getMeter(meterName).createCounter(name, { description });
|
|
258
|
-
}
|
|
259
|
-
function createUpDownCounter(name, description, meterName) {
|
|
260
|
-
return getMeter(meterName).createUpDownCounter(name, { description });
|
|
261
|
-
}
|
|
262
|
-
function createHistogram(name, description, meterName) {
|
|
263
|
-
return getMeter(meterName).createHistogram(name, { description });
|
|
264
|
-
}
|
|
265
|
-
var standardMetrics = {
|
|
266
|
-
httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
|
|
267
|
-
httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
|
|
268
|
-
operationErrors: createCounter("operation_errors_total", "Total operation errors"),
|
|
269
|
-
workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// src/logging/index.ts
|
|
273
|
-
import { trace as trace2, context } from "@opentelemetry/api";
|
|
274
|
-
|
|
275
|
-
class Logger {
|
|
276
|
-
serviceName;
|
|
277
|
-
constructor(serviceName) {
|
|
278
|
-
this.serviceName = serviceName;
|
|
279
|
-
}
|
|
280
|
-
log(level, message, meta = {}) {
|
|
281
|
-
const span = trace2.getSpan(context.active());
|
|
282
|
-
const traceId = span?.spanContext().traceId;
|
|
283
|
-
const spanId = span?.spanContext().spanId;
|
|
284
|
-
const entry = {
|
|
285
|
-
timestamp: new Date().toISOString(),
|
|
286
|
-
service: this.serviceName,
|
|
287
|
-
level,
|
|
288
|
-
message,
|
|
289
|
-
traceId,
|
|
290
|
-
spanId,
|
|
291
|
-
...meta
|
|
292
|
-
};
|
|
293
|
-
console.log(JSON.stringify(entry));
|
|
294
|
-
}
|
|
295
|
-
debug(message, meta) {
|
|
296
|
-
this.log("debug", message, meta);
|
|
297
|
-
}
|
|
298
|
-
info(message, meta) {
|
|
299
|
-
this.log("info", message, meta);
|
|
300
|
-
}
|
|
301
|
-
warn(message, meta) {
|
|
302
|
-
this.log("warn", message, meta);
|
|
303
|
-
}
|
|
304
|
-
error(message, meta) {
|
|
305
|
-
this.log("error", message, meta);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
var logger = new Logger(process.env.OTEL_SERVICE_NAME || "unknown-service");
|
|
309
|
-
|
|
310
|
-
// src/tracing/middleware.ts
|
|
311
|
-
function createTracingMiddleware(options = {}) {
|
|
312
|
-
return async (req, next) => {
|
|
313
|
-
const method = req.method;
|
|
314
|
-
const url = new URL(req.url);
|
|
315
|
-
const path = url.pathname;
|
|
316
|
-
standardMetrics.httpRequests.add(1, { method, path });
|
|
317
|
-
const startTime = performance.now();
|
|
318
|
-
return traceAsync(`HTTP ${method} ${path}`, async (span) => {
|
|
319
|
-
span.setAttribute("http.method", method);
|
|
320
|
-
span.setAttribute("http.url", req.url);
|
|
321
|
-
try {
|
|
322
|
-
const response = await next();
|
|
323
|
-
span.setAttribute("http.status_code", response.status);
|
|
324
|
-
const duration = (performance.now() - startTime) / 1000;
|
|
325
|
-
standardMetrics.httpDuration.record(duration, {
|
|
326
|
-
method,
|
|
327
|
-
path,
|
|
328
|
-
status: response.status.toString()
|
|
329
|
-
});
|
|
330
|
-
emitTelemetrySample({
|
|
331
|
-
req,
|
|
332
|
-
res: response,
|
|
333
|
-
span,
|
|
334
|
-
success: true,
|
|
335
|
-
durationMs: duration * 1000,
|
|
336
|
-
options
|
|
337
|
-
});
|
|
338
|
-
return response;
|
|
339
|
-
} catch (error) {
|
|
340
|
-
standardMetrics.operationErrors.add(1, { method, path });
|
|
341
|
-
emitTelemetrySample({
|
|
342
|
-
req,
|
|
343
|
-
span,
|
|
344
|
-
success: false,
|
|
345
|
-
durationMs: performance.now() - startTime,
|
|
346
|
-
error,
|
|
347
|
-
options
|
|
348
|
-
});
|
|
349
|
-
throw error;
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
function emitTelemetrySample({
|
|
355
|
-
req,
|
|
356
|
-
res,
|
|
357
|
-
span,
|
|
358
|
-
success,
|
|
359
|
-
durationMs,
|
|
360
|
-
error,
|
|
361
|
-
options
|
|
362
|
-
}) {
|
|
363
|
-
if (!options.onSample || !options.resolveOperation)
|
|
364
|
-
return;
|
|
365
|
-
const operation = options.resolveOperation({ req, res });
|
|
366
|
-
if (!operation)
|
|
367
|
-
return;
|
|
368
|
-
const sample = {
|
|
369
|
-
operation,
|
|
370
|
-
durationMs,
|
|
371
|
-
success,
|
|
372
|
-
timestamp: new Date,
|
|
373
|
-
errorCode: !success && error instanceof Error ? error.name : success ? undefined : "unknown",
|
|
374
|
-
tenantId: options.tenantResolver?.(req),
|
|
375
|
-
actorId: options.actorResolver?.(req),
|
|
376
|
-
traceId: span.spanContext().traceId,
|
|
377
|
-
metadata: {
|
|
378
|
-
method: req.method,
|
|
379
|
-
path: new URL(req.url).pathname,
|
|
380
|
-
status: res?.status
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
options.onSample(sample);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
151
|
// src/intent/aggregator.ts
|
|
387
152
|
var DEFAULT_WINDOW_MS = 15 * 60 * 1000;
|
|
388
153
|
|
|
@@ -401,11 +166,11 @@ class IntentAggregator {
|
|
|
401
166
|
const minTimestamp = now.getTime() - this.windowMs;
|
|
402
167
|
const windowSamples = this.samples.filter((sample) => sample.timestamp.getTime() >= minTimestamp);
|
|
403
168
|
this.samples.length = 0;
|
|
404
|
-
const
|
|
169
|
+
const metrics = this.aggregateMetrics(windowSamples);
|
|
405
170
|
const sequences = this.buildSequences(windowSamples);
|
|
406
171
|
const timestamps = windowSamples.map((sample) => sample.timestamp.getTime());
|
|
407
172
|
return {
|
|
408
|
-
metrics
|
|
173
|
+
metrics,
|
|
409
174
|
sequences,
|
|
410
175
|
sampleCount: windowSamples.length,
|
|
411
176
|
windowStart: timestamps.length ? new Date(Math.min(...timestamps)) : undefined,
|
|
@@ -620,6 +385,68 @@ class IntentDetector {
|
|
|
620
385
|
}
|
|
621
386
|
}
|
|
622
387
|
|
|
388
|
+
// src/logging/index.ts
|
|
389
|
+
import { context, trace } from "@opentelemetry/api";
|
|
390
|
+
|
|
391
|
+
class Logger {
|
|
392
|
+
serviceName;
|
|
393
|
+
constructor(serviceName) {
|
|
394
|
+
this.serviceName = serviceName;
|
|
395
|
+
}
|
|
396
|
+
log(level, message, meta = {}) {
|
|
397
|
+
const span = trace.getSpan(context.active());
|
|
398
|
+
const traceId = span?.spanContext().traceId;
|
|
399
|
+
const spanId = span?.spanContext().spanId;
|
|
400
|
+
const entry = {
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
service: this.serviceName,
|
|
403
|
+
level,
|
|
404
|
+
message,
|
|
405
|
+
traceId,
|
|
406
|
+
spanId,
|
|
407
|
+
...meta
|
|
408
|
+
};
|
|
409
|
+
console.log(JSON.stringify(entry));
|
|
410
|
+
}
|
|
411
|
+
debug(message, meta) {
|
|
412
|
+
this.log("debug", message, meta);
|
|
413
|
+
}
|
|
414
|
+
info(message, meta) {
|
|
415
|
+
this.log("info", message, meta);
|
|
416
|
+
}
|
|
417
|
+
warn(message, meta) {
|
|
418
|
+
this.log("warn", message, meta);
|
|
419
|
+
}
|
|
420
|
+
error(message, meta) {
|
|
421
|
+
this.log("error", message, meta);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
var logger = new Logger(process.env.OTEL_SERVICE_NAME || "unknown-service");
|
|
425
|
+
|
|
426
|
+
// src/metrics/index.ts
|
|
427
|
+
import {
|
|
428
|
+
metrics
|
|
429
|
+
} from "@opentelemetry/api";
|
|
430
|
+
var DEFAULT_METER_NAME = "@contractspec/lib.observability";
|
|
431
|
+
function getMeter(name = DEFAULT_METER_NAME) {
|
|
432
|
+
return metrics.getMeter(name);
|
|
433
|
+
}
|
|
434
|
+
function createCounter(name, description, meterName) {
|
|
435
|
+
return getMeter(meterName).createCounter(name, { description });
|
|
436
|
+
}
|
|
437
|
+
function createUpDownCounter(name, description, meterName) {
|
|
438
|
+
return getMeter(meterName).createUpDownCounter(name, { description });
|
|
439
|
+
}
|
|
440
|
+
function createHistogram(name, description, meterName) {
|
|
441
|
+
return getMeter(meterName).createHistogram(name, { description });
|
|
442
|
+
}
|
|
443
|
+
var standardMetrics = {
|
|
444
|
+
httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
|
|
445
|
+
httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
|
|
446
|
+
operationErrors: createCounter("operation_errors_total", "Total operation errors"),
|
|
447
|
+
workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
|
|
448
|
+
};
|
|
449
|
+
|
|
623
450
|
// src/pipeline/evolution-pipeline.ts
|
|
624
451
|
import { EventEmitter } from "events";
|
|
625
452
|
class EvolutionPipeline {
|
|
@@ -727,71 +554,41 @@ class LifecycleKpiPipeline {
|
|
|
727
554
|
if (previous !== undefined) {
|
|
728
555
|
this.stageUpDownCounter.add(-1, {
|
|
729
556
|
stage: getStageLabel(previous),
|
|
730
|
-
tenantId
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
this.stageUpDownCounter.add(1, { stage: getStageLabel(stage), tenantId });
|
|
734
|
-
this.currentStageByTenant.set(tenantId, stage);
|
|
735
|
-
this.emitter.emit("event", {
|
|
736
|
-
type: "stage.changed",
|
|
737
|
-
payload: { tenantId, previousStage: previous, nextStage: stage }
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// src/telemetry/posthog-telemetry.ts
|
|
743
|
-
class PosthogTelemetryProvider {
|
|
744
|
-
provider;
|
|
745
|
-
eventPrefix;
|
|
746
|
-
includeMetadata;
|
|
747
|
-
constructor(provider, options = {}) {
|
|
748
|
-
this.provider = provider;
|
|
749
|
-
this.eventPrefix = options.eventPrefix ?? "observability";
|
|
750
|
-
this.includeMetadata = options.includeMetadata ?? false;
|
|
751
|
-
}
|
|
752
|
-
async captureSample(sample) {
|
|
753
|
-
await this.provider.capture({
|
|
754
|
-
distinctId: sample.actorId ?? sample.tenantId ?? "unknown",
|
|
755
|
-
event: `${this.eventPrefix}.operation`,
|
|
756
|
-
timestamp: sample.timestamp,
|
|
757
|
-
properties: {
|
|
758
|
-
operation: sample.operation.name,
|
|
759
|
-
version: sample.operation.version,
|
|
760
|
-
durationMs: sample.durationMs,
|
|
761
|
-
success: sample.success,
|
|
762
|
-
errorCode: sample.errorCode ?? null,
|
|
763
|
-
tenantId: sample.tenantId ?? null,
|
|
764
|
-
traceId: sample.traceId ?? null,
|
|
765
|
-
...this.includeMetadata && sample.metadata ? { metadata: sample.metadata } : {}
|
|
766
|
-
}
|
|
557
|
+
tenantId
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
this.stageUpDownCounter.add(1, { stage: getStageLabel(stage), tenantId });
|
|
561
|
+
this.currentStageByTenant.set(tenantId, stage);
|
|
562
|
+
this.emitter.emit("event", {
|
|
563
|
+
type: "stage.changed",
|
|
564
|
+
payload: { tenantId, previousStage: previous, nextStage: stage }
|
|
767
565
|
});
|
|
768
566
|
}
|
|
769
|
-
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/telemetry/model-selection-telemetry.ts
|
|
570
|
+
class ModelSelectionTelemetry {
|
|
571
|
+
provider;
|
|
572
|
+
eventName;
|
|
573
|
+
constructor(provider, options) {
|
|
574
|
+
this.provider = provider;
|
|
575
|
+
this.eventName = options?.eventName ?? "$model_selection";
|
|
576
|
+
}
|
|
577
|
+
async trackSelection(distinctId, properties) {
|
|
770
578
|
await this.provider.capture({
|
|
771
|
-
distinctId
|
|
772
|
-
event:
|
|
773
|
-
timestamp:
|
|
579
|
+
distinctId,
|
|
580
|
+
event: this.eventName,
|
|
581
|
+
timestamp: new Date,
|
|
774
582
|
properties: {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
totalCalls: metric.totalCalls,
|
|
785
|
-
successRate: metric.successRate,
|
|
786
|
-
errorRate: metric.errorRate,
|
|
787
|
-
averageLatencyMs: metric.averageLatencyMs,
|
|
788
|
-
p95LatencyMs: metric.p95LatencyMs,
|
|
789
|
-
p99LatencyMs: metric.p99LatencyMs,
|
|
790
|
-
maxLatencyMs: metric.maxLatencyMs,
|
|
791
|
-
topErrors: metric.topErrors
|
|
792
|
-
})),
|
|
793
|
-
sequences: snapshot.sequences
|
|
794
|
-
} : {}
|
|
583
|
+
$model_id: properties.modelId,
|
|
584
|
+
$model_provider: properties.providerKey,
|
|
585
|
+
$model_score: properties.score,
|
|
586
|
+
$model_dimension: properties.dimension ?? null,
|
|
587
|
+
$model_reason: properties.reason,
|
|
588
|
+
$model_alternatives_count: properties.alternativesCount,
|
|
589
|
+
$model_cost_estimate_input: properties.costEstimateInput ?? null,
|
|
590
|
+
$model_cost_estimate_output: properties.costEstimateOutput ?? null,
|
|
591
|
+
$model_selection_duration_ms: properties.selectionDurationMs ?? null
|
|
795
592
|
}
|
|
796
593
|
});
|
|
797
594
|
}
|
|
@@ -1102,6 +899,209 @@ function toDate(value) {
|
|
|
1102
899
|
function isRecord(value) {
|
|
1103
900
|
return typeof value === "object" && value !== null;
|
|
1104
901
|
}
|
|
902
|
+
|
|
903
|
+
// src/telemetry/posthog-telemetry.ts
|
|
904
|
+
class PosthogTelemetryProvider {
|
|
905
|
+
provider;
|
|
906
|
+
eventPrefix;
|
|
907
|
+
includeMetadata;
|
|
908
|
+
constructor(provider, options = {}) {
|
|
909
|
+
this.provider = provider;
|
|
910
|
+
this.eventPrefix = options.eventPrefix ?? "observability";
|
|
911
|
+
this.includeMetadata = options.includeMetadata ?? false;
|
|
912
|
+
}
|
|
913
|
+
async captureSample(sample) {
|
|
914
|
+
await this.provider.capture({
|
|
915
|
+
distinctId: sample.actorId ?? sample.tenantId ?? "unknown",
|
|
916
|
+
event: `${this.eventPrefix}.operation`,
|
|
917
|
+
timestamp: sample.timestamp,
|
|
918
|
+
properties: {
|
|
919
|
+
operation: sample.operation.name,
|
|
920
|
+
version: sample.operation.version,
|
|
921
|
+
durationMs: sample.durationMs,
|
|
922
|
+
success: sample.success,
|
|
923
|
+
errorCode: sample.errorCode ?? null,
|
|
924
|
+
tenantId: sample.tenantId ?? null,
|
|
925
|
+
traceId: sample.traceId ?? null,
|
|
926
|
+
...this.includeMetadata && sample.metadata ? { metadata: sample.metadata } : {}
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
async captureSnapshot(snapshot) {
|
|
931
|
+
await this.provider.capture({
|
|
932
|
+
distinctId: "system",
|
|
933
|
+
event: `${this.eventPrefix}.window`,
|
|
934
|
+
timestamp: snapshot.windowEnd ?? new Date,
|
|
935
|
+
properties: {
|
|
936
|
+
sampleCount: snapshot.sampleCount,
|
|
937
|
+
metricsCount: snapshot.metrics.length,
|
|
938
|
+
sequencesCount: snapshot.sequences.length,
|
|
939
|
+
windowStart: snapshot.windowStart?.toISOString() ?? null,
|
|
940
|
+
windowEnd: snapshot.windowEnd?.toISOString() ?? null,
|
|
941
|
+
...this.includeMetadata ? {
|
|
942
|
+
metrics: snapshot.metrics.map((metric) => ({
|
|
943
|
+
operation: metric.operation.name,
|
|
944
|
+
version: metric.operation.version,
|
|
945
|
+
totalCalls: metric.totalCalls,
|
|
946
|
+
successRate: metric.successRate,
|
|
947
|
+
errorRate: metric.errorRate,
|
|
948
|
+
averageLatencyMs: metric.averageLatencyMs,
|
|
949
|
+
p95LatencyMs: metric.p95LatencyMs,
|
|
950
|
+
p99LatencyMs: metric.p99LatencyMs,
|
|
951
|
+
maxLatencyMs: metric.maxLatencyMs,
|
|
952
|
+
topErrors: metric.topErrors
|
|
953
|
+
})),
|
|
954
|
+
sequences: snapshot.sequences
|
|
955
|
+
} : {}
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/tracing/core.ts
|
|
962
|
+
import {
|
|
963
|
+
SpanStatusCode,
|
|
964
|
+
trace as trace2
|
|
965
|
+
} from "@opentelemetry/api";
|
|
966
|
+
var DEFAULT_TRACER_NAME = "@contractspec/lib.observability";
|
|
967
|
+
function getTracer(name = DEFAULT_TRACER_NAME) {
|
|
968
|
+
return trace2.getTracer(name);
|
|
969
|
+
}
|
|
970
|
+
async function traceAsync(name, fn, tracerName) {
|
|
971
|
+
const tracer = getTracer(tracerName);
|
|
972
|
+
return tracer.startActiveSpan(name, async (span) => {
|
|
973
|
+
try {
|
|
974
|
+
const result = await fn(span);
|
|
975
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
976
|
+
return result;
|
|
977
|
+
} catch (error) {
|
|
978
|
+
span.recordException(error);
|
|
979
|
+
span.setStatus({
|
|
980
|
+
code: SpanStatusCode.ERROR,
|
|
981
|
+
message: error instanceof Error ? error.message : String(error)
|
|
982
|
+
});
|
|
983
|
+
throw error;
|
|
984
|
+
} finally {
|
|
985
|
+
span.end();
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
function traceSync(name, fn, tracerName) {
|
|
990
|
+
const tracer = getTracer(tracerName);
|
|
991
|
+
return tracer.startActiveSpan(name, (span) => {
|
|
992
|
+
try {
|
|
993
|
+
const result = fn(span);
|
|
994
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
995
|
+
return result;
|
|
996
|
+
} catch (error) {
|
|
997
|
+
span.recordException(error);
|
|
998
|
+
span.setStatus({
|
|
999
|
+
code: SpanStatusCode.ERROR,
|
|
1000
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1001
|
+
});
|
|
1002
|
+
throw error;
|
|
1003
|
+
} finally {
|
|
1004
|
+
span.end();
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/tracing/model-selection.span.ts
|
|
1010
|
+
async function traceModelSelection(fn, input) {
|
|
1011
|
+
const startMs = performance.now();
|
|
1012
|
+
return traceAsync("model.selection", async (span) => {
|
|
1013
|
+
const result = await fn();
|
|
1014
|
+
const durationMs = performance.now() - startMs;
|
|
1015
|
+
span.setAttribute("model.selected", input.modelId);
|
|
1016
|
+
span.setAttribute("model.provider", input.providerKey);
|
|
1017
|
+
span.setAttribute("model.score", input.score);
|
|
1018
|
+
span.setAttribute("model.alternatives_count", input.alternativesCount);
|
|
1019
|
+
span.setAttribute("model.selection_duration_ms", durationMs);
|
|
1020
|
+
span.setAttribute("model.reason", input.reason);
|
|
1021
|
+
if (input.dimension) {
|
|
1022
|
+
span.setAttribute("model.dimension", input.dimension);
|
|
1023
|
+
}
|
|
1024
|
+
if (input.constraints) {
|
|
1025
|
+
span.setAttribute("model.constraints", JSON.stringify(input.constraints));
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
// src/tracing/middleware.ts
|
|
1031
|
+
function createTracingMiddleware(options = {}) {
|
|
1032
|
+
return async (req, next) => {
|
|
1033
|
+
const method = req.method;
|
|
1034
|
+
const url = new URL(req.url);
|
|
1035
|
+
const path = url.pathname;
|
|
1036
|
+
standardMetrics.httpRequests.add(1, { method, path });
|
|
1037
|
+
const startTime = performance.now();
|
|
1038
|
+
return traceAsync(`HTTP ${method} ${path}`, async (span) => {
|
|
1039
|
+
span.setAttribute("http.method", method);
|
|
1040
|
+
span.setAttribute("http.url", req.url);
|
|
1041
|
+
try {
|
|
1042
|
+
const response = await next();
|
|
1043
|
+
span.setAttribute("http.status_code", response.status);
|
|
1044
|
+
const duration = (performance.now() - startTime) / 1000;
|
|
1045
|
+
standardMetrics.httpDuration.record(duration, {
|
|
1046
|
+
method,
|
|
1047
|
+
path,
|
|
1048
|
+
status: response.status.toString()
|
|
1049
|
+
});
|
|
1050
|
+
emitTelemetrySample({
|
|
1051
|
+
req,
|
|
1052
|
+
res: response,
|
|
1053
|
+
span,
|
|
1054
|
+
success: true,
|
|
1055
|
+
durationMs: duration * 1000,
|
|
1056
|
+
options
|
|
1057
|
+
});
|
|
1058
|
+
return response;
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
standardMetrics.operationErrors.add(1, { method, path });
|
|
1061
|
+
emitTelemetrySample({
|
|
1062
|
+
req,
|
|
1063
|
+
span,
|
|
1064
|
+
success: false,
|
|
1065
|
+
durationMs: performance.now() - startTime,
|
|
1066
|
+
error,
|
|
1067
|
+
options
|
|
1068
|
+
});
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function emitTelemetrySample({
|
|
1075
|
+
req,
|
|
1076
|
+
res,
|
|
1077
|
+
span,
|
|
1078
|
+
success,
|
|
1079
|
+
durationMs,
|
|
1080
|
+
error,
|
|
1081
|
+
options
|
|
1082
|
+
}) {
|
|
1083
|
+
if (!options.onSample || !options.resolveOperation)
|
|
1084
|
+
return;
|
|
1085
|
+
const operation = options.resolveOperation({ req, res });
|
|
1086
|
+
if (!operation)
|
|
1087
|
+
return;
|
|
1088
|
+
const sample = {
|
|
1089
|
+
operation,
|
|
1090
|
+
durationMs,
|
|
1091
|
+
success,
|
|
1092
|
+
timestamp: new Date,
|
|
1093
|
+
errorCode: !success && error instanceof Error ? error.name : success ? undefined : "unknown",
|
|
1094
|
+
tenantId: options.tenantResolver?.(req),
|
|
1095
|
+
actorId: options.actorResolver?.(req),
|
|
1096
|
+
traceId: span.spanContext().traceId,
|
|
1097
|
+
metadata: {
|
|
1098
|
+
method: req.method,
|
|
1099
|
+
path: new URL(req.url).pathname,
|
|
1100
|
+
status: res?.status
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
options.onSample(sample);
|
|
1104
|
+
}
|
|
1105
1105
|
export {
|
|
1106
1106
|
traceSync,
|
|
1107
1107
|
traceModelSelection,
|
package/dist/logging/index.js
CHANGED
package/dist/metrics/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Counter, type Histogram, type Meter, type UpDownCounter } from '@opentelemetry/api';
|
|
2
2
|
export declare function getMeter(name?: string): Meter;
|
|
3
3
|
export declare function createCounter(name: string, description?: string, meterName?: string): Counter;
|
|
4
4
|
export declare function createUpDownCounter(name: string, description?: string, meterName?: string): UpDownCounter;
|