@objectstack/observability 10.3.0 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +129 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +120 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -33,8 +33,16 @@ __export(index_exports, {
|
|
|
33
33
|
OBSERVABILITY_ERRORS_SERVICE: () => OBSERVABILITY_ERRORS_SERVICE,
|
|
34
34
|
OBSERVABILITY_METRICS_SERVICE: () => OBSERVABILITY_METRICS_SERVICE,
|
|
35
35
|
OtlpHttpMetricsRegistry: () => OtlpHttpMetricsRegistry,
|
|
36
|
+
PerfTiming: () => PerfTiming,
|
|
36
37
|
RUNTIME_METRICS: () => RUNTIME_METRICS,
|
|
37
|
-
SEMCONV: () => SEMCONV
|
|
38
|
+
SEMCONV: () => SEMCONV,
|
|
39
|
+
currentPerfTiming: () => currentPerfTiming,
|
|
40
|
+
formatServerTiming: () => formatServerTiming,
|
|
41
|
+
measureServerTiming: () => measureServerTiming,
|
|
42
|
+
perfNow: () => perfNow,
|
|
43
|
+
recordServerTiming: () => recordServerTiming,
|
|
44
|
+
runWithPerfTiming: () => runWithPerfTiming,
|
|
45
|
+
startServerTiming: () => startServerTiming
|
|
38
46
|
});
|
|
39
47
|
module.exports = __toCommonJS(index_exports);
|
|
40
48
|
|
|
@@ -444,6 +452,117 @@ var JsonLogger = class _JsonLogger {
|
|
|
444
452
|
}
|
|
445
453
|
}
|
|
446
454
|
};
|
|
455
|
+
|
|
456
|
+
// src/perf-timing.ts
|
|
457
|
+
var import_node_async_hooks = require("async_hooks");
|
|
458
|
+
function perfNow() {
|
|
459
|
+
try {
|
|
460
|
+
return performance.now();
|
|
461
|
+
} catch {
|
|
462
|
+
return Date.now();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
var NAME_UNSAFE = /[^A-Za-z0-9_-]+/g;
|
|
466
|
+
var UNDERSCORE = 95;
|
|
467
|
+
function sanitizeName(name) {
|
|
468
|
+
const collapsed = String(name).replace(NAME_UNSAFE, "_");
|
|
469
|
+
let start = 0;
|
|
470
|
+
let end = collapsed.length;
|
|
471
|
+
while (start < end && collapsed.charCodeAt(start) === UNDERSCORE) start++;
|
|
472
|
+
while (end > start && collapsed.charCodeAt(end - 1) === UNDERSCORE) end--;
|
|
473
|
+
return collapsed.slice(start, end);
|
|
474
|
+
}
|
|
475
|
+
function sanitizeDesc(desc) {
|
|
476
|
+
let out = "";
|
|
477
|
+
for (const ch of String(desc)) {
|
|
478
|
+
const code = ch.codePointAt(0);
|
|
479
|
+
if (code >= 32 && code < 127 && ch !== '"' && ch !== "\\") {
|
|
480
|
+
out += ch;
|
|
481
|
+
} else {
|
|
482
|
+
out += " ";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return out.replace(/ +/g, " ").trim();
|
|
486
|
+
}
|
|
487
|
+
function fmtDur(dur) {
|
|
488
|
+
if (!Number.isFinite(dur)) return "0";
|
|
489
|
+
return String(Math.round(dur * 100) / 100);
|
|
490
|
+
}
|
|
491
|
+
function formatServerTiming(marks) {
|
|
492
|
+
const parts = [];
|
|
493
|
+
for (const m of marks) {
|
|
494
|
+
const name = sanitizeName(m.name);
|
|
495
|
+
if (!name) continue;
|
|
496
|
+
let part = `${name};dur=${fmtDur(m.dur)}`;
|
|
497
|
+
if (m.desc) {
|
|
498
|
+
const desc = sanitizeDesc(m.desc);
|
|
499
|
+
if (desc) part += `;desc="${desc}"`;
|
|
500
|
+
}
|
|
501
|
+
parts.push(part);
|
|
502
|
+
}
|
|
503
|
+
return parts.join(", ");
|
|
504
|
+
}
|
|
505
|
+
var PerfTiming = class {
|
|
506
|
+
constructor() {
|
|
507
|
+
this._marks = [];
|
|
508
|
+
}
|
|
509
|
+
/** Record an already-measured phase. */
|
|
510
|
+
record(name, dur, desc) {
|
|
511
|
+
this._marks.push({ name, dur, desc });
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Begin timing a phase. Returns an idempotent `end()` - the first call
|
|
515
|
+
* records the elapsed duration; later calls are ignored, so it is safe to
|
|
516
|
+
* call from both a success and an error path.
|
|
517
|
+
*/
|
|
518
|
+
start(name, desc) {
|
|
519
|
+
const t0 = perfNow();
|
|
520
|
+
let done = false;
|
|
521
|
+
return () => {
|
|
522
|
+
if (done) return;
|
|
523
|
+
done = true;
|
|
524
|
+
this.record(name, perfNow() - t0, desc);
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/** Time an async (or sync) function, recording its elapsed duration. */
|
|
528
|
+
async measure(name, fn, desc) {
|
|
529
|
+
const end = this.start(name, desc);
|
|
530
|
+
try {
|
|
531
|
+
return await fn();
|
|
532
|
+
} finally {
|
|
533
|
+
end();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/** Snapshot of recorded marks, in record order. */
|
|
537
|
+
marks() {
|
|
538
|
+
return this._marks;
|
|
539
|
+
}
|
|
540
|
+
/** Serialize to a `Server-Timing` header value (`''` when empty). */
|
|
541
|
+
toHeader() {
|
|
542
|
+
return formatServerTiming(this._marks);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
var store = new import_node_async_hooks.AsyncLocalStorage();
|
|
546
|
+
function runWithPerfTiming(timing, fn) {
|
|
547
|
+
return store.run(timing, fn);
|
|
548
|
+
}
|
|
549
|
+
function currentPerfTiming() {
|
|
550
|
+
return store.getStore();
|
|
551
|
+
}
|
|
552
|
+
function recordServerTiming(name, dur, desc) {
|
|
553
|
+
store.getStore()?.record(name, dur, desc);
|
|
554
|
+
}
|
|
555
|
+
function startServerTiming(name, desc) {
|
|
556
|
+
const t = store.getStore();
|
|
557
|
+
if (!t) return () => {
|
|
558
|
+
};
|
|
559
|
+
return t.start(name, desc);
|
|
560
|
+
}
|
|
561
|
+
async function measureServerTiming(name, fn, desc) {
|
|
562
|
+
const t = store.getStore();
|
|
563
|
+
if (!t) return fn();
|
|
564
|
+
return t.measure(name, fn, desc);
|
|
565
|
+
}
|
|
447
566
|
// Annotate the CommonJS export names for ESM import in node:
|
|
448
567
|
0 && (module.exports = {
|
|
449
568
|
ConsoleErrorReporter,
|
|
@@ -459,7 +578,15 @@ var JsonLogger = class _JsonLogger {
|
|
|
459
578
|
OBSERVABILITY_ERRORS_SERVICE,
|
|
460
579
|
OBSERVABILITY_METRICS_SERVICE,
|
|
461
580
|
OtlpHttpMetricsRegistry,
|
|
581
|
+
PerfTiming,
|
|
462
582
|
RUNTIME_METRICS,
|
|
463
|
-
SEMCONV
|
|
583
|
+
SEMCONV,
|
|
584
|
+
currentPerfTiming,
|
|
585
|
+
formatServerTiming,
|
|
586
|
+
measureServerTiming,
|
|
587
|
+
perfNow,
|
|
588
|
+
recordServerTiming,
|
|
589
|
+
runWithPerfTiming,
|
|
590
|
+
startServerTiming
|
|
464
591
|
});
|
|
465
592
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/service-names.ts","../src/semconv.ts","../src/metrics-exporters.ts","../src/error-exporters.ts","../src/loggers.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `@objectstack/observability` — vendor-neutral contracts and exporters\n * for ObjectStack metrics, errors, and logs.\n *\n * @see {@link MetricsRegistry} {@link ErrorReporter} {@link Logger}\n */\n\n// Contracts\nexport type { MetricsRegistry, MetricSample, ErrorReporter, CapturedError, Logger } from './contracts.js';\n\n// Service-registry names (consumed by runtime's ObservabilityServicePlugin and lookup sites)\nexport { OBSERVABILITY_METRICS_SERVICE, OBSERVABILITY_ERRORS_SERVICE } from './service-names.js';\n\n// Semantic conventions\nexport { SEMCONV, RUNTIME_METRICS } from './semconv.js';\n\n// Metric exporters\nexport {\n NoopMetricsRegistry,\n InMemoryMetricsRegistry,\n ConsoleMetricsRegistry,\n OtlpHttpMetricsRegistry,\n type OtlpHttpExporterOptions,\n} from './metrics-exporters.js';\n\n// Error reporters\nexport {\n NoopErrorReporter,\n InMemoryErrorReporter,\n ConsoleErrorReporter,\n} from './error-exporters.js';\n\n// Loggers\nexport {\n NoopLogger,\n ConsoleLogger,\n JsonLogger,\n LOG_LEVELS,\n type LogLevel,\n} from './loggers.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Canonical service-registry names for the host's observability\n * backends. Plugins look these up to discover the configured\n * {@link MetricsRegistry} / {@link ErrorReporter} without each host\n * having to thread observability config through every plugin\n * constructor.\n *\n * See `@objectstack/runtime` → `ObservabilityServicePlugin` for the\n * registration side.\n */\nexport const OBSERVABILITY_METRICS_SERVICE = 'observability:metrics';\nexport const OBSERVABILITY_ERRORS_SERVICE = 'observability:errors';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Semantic conventions — canonical metric names emitted by the\n * framework. Listed here so hosts can wire alerts/dashboards against\n * a stable namespace and so call sites don't sprinkle string\n * literals through the code base.\n *\n * Naming follows Prometheus conventions:\n *\n * - snake_case identifiers.\n * - `_total` suffix for monotonic counters.\n * - `_ms`, `_seconds`, `_bytes` suffixes for histograms / gauges\n * with units.\n *\n * Groups roughly mirror the framework subsystems that emit them.\n * Cloud-specific metrics (DO restarts, Workers Analytics Engine\n * writes, …) do NOT belong here — they are deployment-specific and\n * stay in the deployment repo.\n */\nexport const SEMCONV = {\n // ── HTTP — emitted by `@objectstack/runtime`'s instrumentRouteHandler ──\n /** Counter, labels: `method`, `route`, `status`. */\n httpRequestsTotal: 'http_requests_total',\n /** Histogram (ms), labels: `method`, `route`. */\n httpRequestDurationMs: 'http_request_duration_ms',\n /**\n * Counter, labels: `method`, `route`. Incremented when an\n * in-flight handler throws after the response is sent.\n */\n httpRequestErrorsTotal: 'http_request_errors_total',\n\n // ── Storage — emitted by `@objectstack/service-storage` adapters ──\n /** Counter, labels: `adapter` (`local`|`s3`|…), `op` (`get`|`put`|`delete`|`head`), `result` (`ok`|`error`). */\n storageOperationsTotal: 'storage_operations_total',\n /** Histogram (ms), labels: `adapter`, `op`. */\n storageOperationDurationMs: 'storage_operation_duration_ms',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n storageErrorsTotal: 'storage_errors_total',\n\n // ── Cache — emitted by `@objectstack/service-cache` adapters ──\n /** Counter, labels: `adapter` (`memory`|`redis`), `result` (`hit`|`miss`). */\n cacheLookupsTotal: 'cache_lookups_total',\n /** Counter, labels: `adapter`, `op` (`set`|`delete`|`clear`). */\n cacheWritesTotal: 'cache_writes_total',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n cacheErrorsTotal: 'cache_errors_total',\n\n // ── Package / registry-reader — emitted by `@objectstack/service-package` ──\n /** Counter, labels: `result` (`ok`|`miss`|`error`). */\n registryLookupsTotal: 'registry_lookups_total',\n /** Histogram (ms). */\n registryLookupDurationMs: 'registry_lookup_duration_ms',\n /** Counter, labels: `source` (`r2`|`http`|`local`), `result` (`hit`|`miss`|`error`). */\n registrySourceFetchesTotal: 'registry_source_fetches_total',\n} as const;\n\n/**\n * Backwards-compat alias. `RUNTIME_METRICS` was the original (HTTP-only)\n * constant name shipped from `@objectstack/runtime`; we keep it here so\n * existing code reading `RUNTIME_METRICS.httpRequestsTotal` continues\n * to work after the constants moved into this package.\n */\nexport const RUNTIME_METRICS = {\n httpRequestsTotal: SEMCONV.httpRequestsTotal,\n httpRequestDurationMs: SEMCONV.httpRequestDurationMs,\n httpRequestErrorsTotal: SEMCONV.httpRequestErrorsTotal,\n} as const;\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MetricsRegistry, MetricSample } from './contracts.js';\n\n// ─── Noop ─────────────────────────────────────────────────────────────\n\n/**\n * No-op metrics registry — the default. Discards every observation.\n * Production deployments should swap this for a real registry; tests\n * can use {@link InMemoryMetricsRegistry} to assert emissions.\n */\nexport class NoopMetricsRegistry implements MetricsRegistry {\n counter(): void { }\n histogram(): void { }\n gauge(): void { }\n}\n\n// ─── In-memory (tests + dev inspection) ───────────────────────────────\n\n/**\n * In-memory registry used for tests and local inspection. Stores\n * every observation in insertion order; query via the helpers below\n * or read {@link samples} directly.\n *\n * Not intended for production — unbounded growth.\n */\nexport class InMemoryMetricsRegistry implements MetricsRegistry {\n readonly samples: MetricSample[] = [];\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.samples.push({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n /** Sum of counter increments matching `name` and optionally a label subset. */\n totalCounter(name: string, labelMatch: Record<string, string> = {}): number {\n return this.samples\n .filter(s => s.kind === 'counter' && s.name === name && matchesLabels(s.labels, labelMatch))\n .reduce((acc, s) => acc + s.value, 0);\n }\n\n /** Raw histogram observations matching `name` and optionally a label subset. */\n histogramValues(name: string, labelMatch: Record<string, string> = {}): number[] {\n return this.samples\n .filter(s => s.kind === 'histogram' && s.name === name && matchesLabels(s.labels, labelMatch))\n .map(s => s.value);\n }\n\n /** Last gauge value matching `name` and optionally a label subset, or undefined. */\n lastGauge(name: string, labelMatch: Record<string, string> = {}): number | undefined {\n for (let i = this.samples.length - 1; i >= 0; i--) {\n const s = this.samples[i];\n if (s.kind === 'gauge' && s.name === name && matchesLabels(s.labels, labelMatch)) {\n return s.value;\n }\n }\n return undefined;\n }\n\n /** Clear all recorded samples. */\n reset(): void {\n this.samples.length = 0;\n }\n}\n\nfunction matchesLabels(actual: Record<string, string>, expected: Record<string, string>): boolean {\n for (const [k, v] of Object.entries(expected)) {\n if (actual[k] !== v) return false;\n }\n return true;\n}\n\n// ─── Console (development) ────────────────────────────────────────────\n\n/**\n * Console metrics registry — prints one line per observation. Useful\n * during local development to confirm that instrumentation is firing.\n *\n * Not intended for production: writing every observation to stdout\n * defeats Prometheus / OTLP pipelines and dominates request latency.\n */\nexport class ConsoleMetricsRegistry implements MetricsRegistry {\n constructor(private readonly opts: { sink?: (line: string) => void; prefix?: string } = {}) { }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.emit('counter', name, value, labels);\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('histogram', name, value, labels);\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('gauge', name, value, labels);\n }\n\n private emit(kind: string, name: string, value: number, labels: Record<string, string>): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.log(s); });\n const prefix = this.opts.prefix ?? '[metric]';\n const labelStr = Object.entries(labels)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(' ');\n sink(`${prefix} ${kind} ${name} ${value}${labelStr ? ' ' + labelStr : ''}`);\n } catch {\n // Per contract: never throw from a metric call site.\n }\n }\n}\n\n// ─── OTLP / HTTP (Prometheus via OTel Collector, Grafana Cloud, …) ────\n\n/**\n * Configuration for {@link OtlpHttpMetricsRegistry}.\n */\nexport interface OtlpHttpExporterOptions {\n /**\n * OTLP/HTTP metrics endpoint, e.g. `http://otel-collector:4318/v1/metrics`.\n * The path is appended automatically if missing.\n */\n endpoint: string;\n\n /** Optional headers (Authorization, x-tenant, …). */\n headers?: Record<string, string>;\n\n /**\n * Resource attributes — `service.name`, `service.namespace`,\n * `deployment.environment`, etc. Merged into the OTLP `resource`\n * block on every export.\n */\n resource?: Record<string, string>;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Allows Workers / undici / node-fetch substitution and test\n * doubles.\n */\n fetch?: typeof fetch;\n\n /**\n * Called when an export attempt fails. Default: silently swallow\n * (per the contract that metric emission must not throw / log\n * loudly on the hot path).\n */\n onError?: (error: unknown) => void;\n\n /**\n * Maximum number of samples buffered before {@link OtlpHttpMetricsRegistry.flush}\n * is called automatically. Defaults to 1024.\n */\n maxBufferSize?: number;\n}\n\n/**\n * OTLP/HTTP metrics exporter.\n *\n * Buffers samples in memory and serialises them to the OpenTelemetry\n * Protocol JSON encoding when {@link flush} is called (manually or\n * automatically once the buffer hits the configured size).\n *\n * Intentionally does **not** start an interval timer in the constructor:\n * (a) it makes the exporter usable on Cloudflare Workers where\n * `setInterval` is restricted, and (b) it keeps unit tests deterministic.\n * Long-running hosts should call `flush()` on a schedule\n * (e.g. `setInterval(() => reg.flush(), 10_000)` on Node, or\n * `ctx.waitUntil(reg.flush())` from a Workers fetch handler).\n *\n * Only counters, histograms, and gauges are emitted — no support for\n * exemplars or aggregation temporality switches (the Collector handles\n * those on the upstream side).\n */\nexport class OtlpHttpMetricsRegistry implements MetricsRegistry {\n private buffer: MetricSample[] = [];\n private readonly endpoint: string;\n private readonly headers: Record<string, string>;\n private readonly resource: Record<string, string>;\n private readonly maxBufferSize: number;\n private readonly fetchImpl: typeof fetch;\n private readonly onError: (error: unknown) => void;\n\n constructor(options: OtlpHttpExporterOptions) {\n this.endpoint = normaliseEndpoint(options.endpoint);\n this.headers = options.headers ?? {};\n this.resource = options.resource ?? {};\n this.maxBufferSize = options.maxBufferSize ?? 1024;\n this.fetchImpl = options.fetch ?? (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : noFetch);\n this.onError = options.onError ?? (() => { });\n }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.record({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n private record(sample: MetricSample): void {\n this.buffer.push(sample);\n if (this.buffer.length >= this.maxBufferSize) {\n // Fire-and-forget: do not block the call site.\n void this.flush().catch(this.onError);\n }\n }\n\n /** Snapshot the current buffer (for tests). */\n peek(): readonly MetricSample[] {\n return this.buffer.slice();\n }\n\n /**\n * Send the buffered samples to the OTLP endpoint and clear the\n * buffer. Safe to call concurrently — each invocation takes a\n * snapshot before clearing.\n */\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const samples = this.buffer;\n this.buffer = [];\n try {\n const body = JSON.stringify(serialiseToOtlp(samples, this.resource));\n const res = await this.fetchImpl(this.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...this.headers },\n body,\n });\n if (!res.ok) {\n this.onError(new Error(`OTLP export returned HTTP ${res.status}`));\n }\n } catch (err) {\n this.onError(err);\n }\n }\n}\n\nfunction normaliseEndpoint(raw: string): string {\n const trimmed = raw.replace(/\\/+$/, '');\n if (/\\/v1\\/metrics$/.test(trimmed)) return trimmed;\n return trimmed + '/v1/metrics';\n}\n\nfunction noFetch(): never {\n throw new Error('OtlpHttpMetricsRegistry: no global fetch available; pass options.fetch');\n}\n\n/**\n * Minimal OTLP/JSON metrics serialiser.\n *\n * Implements the subset of <https://opentelemetry.io/docs/specs/otlp/>\n * that we actually emit — sums for counters, gauges for gauges,\n * histograms encoded as bucketless \"summary\"-style histograms with\n * explicit per-sample data points. The Collector accepts this shape\n * and applies its own bucketing.\n *\n * Aggregation temporality is set to DELTA (2) because every flush\n * sends only the samples accumulated since the last flush.\n */\nfunction serialiseToOtlp(samples: MetricSample[], resource: Record<string, string>): unknown {\n const byName = new Map<string, { kind: MetricSample['kind']; points: MetricSample[] }>();\n for (const s of samples) {\n const existing = byName.get(s.name);\n if (existing) existing.points.push(s);\n else byName.set(s.name, { kind: s.kind, points: [s] });\n }\n\n const metrics = Array.from(byName.entries()).map(([name, { kind, points }]) => {\n if (kind === 'counter') {\n return {\n name,\n sum: {\n dataPoints: points.map(p => toNumberPoint(p)),\n aggregationTemporality: 2,\n isMonotonic: true,\n },\n };\n }\n if (kind === 'gauge') {\n return { name, gauge: { dataPoints: points.map(p => toNumberPoint(p)) } };\n }\n // histogram: emit a histogram with a single bucket boundary so the\n // Collector treats it as a recorded distribution.\n return {\n name,\n histogram: {\n aggregationTemporality: 2,\n dataPoints: points.map(p => ({\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n count: '1',\n sum: p.value,\n bucketCounts: ['0', '1'],\n explicitBounds: [p.value],\n })),\n },\n };\n });\n\n return {\n resourceMetrics: [{\n resource: { attributes: toAttributes(resource) },\n scopeMetrics: [{\n scope: { name: '@objectstack/observability', version: '0.1.0' },\n metrics,\n }],\n }],\n };\n}\n\nfunction toNumberPoint(p: MetricSample): unknown {\n return {\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n asDouble: p.value,\n };\n}\n\nfunction toAttributes(labels: Record<string, string>): unknown[] {\n return Object.entries(labels).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { ErrorReporter, CapturedError } from './contracts.js';\n\n/** No-op reporter — the default. */\nexport class NoopErrorReporter implements ErrorReporter {\n captureException(): void { }\n}\n\n/** In-memory reporter for tests. */\nexport class InMemoryErrorReporter implements ErrorReporter {\n readonly captured: CapturedError[] = [];\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n this.captured.push({ error, context, at: Date.now() });\n }\n\n reset(): void {\n this.captured.length = 0;\n }\n}\n\n/**\n * Console error reporter — writes a structured JSON line to stderr per\n * captured exception. Convenient default for local development and for\n * any deployment that ships stderr to a log aggregator (e.g. Loki,\n * fluent-bit) but does not have a dedicated APM.\n *\n * Stack traces are included when the captured value is an `Error`.\n */\nexport class ConsoleErrorReporter implements ErrorReporter {\n constructor(private readonly opts: { sink?: (line: string) => void } = {}) { }\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.error(s); });\n const record: Record<string, unknown> = {\n ts: new Date().toISOString(),\n level: 'error',\n msg: error instanceof Error ? error.message : String(error),\n context,\n };\n if (error instanceof Error && error.stack) {\n record.stack = error.stack;\n }\n sink(JSON.stringify(record));\n } catch {\n // Per contract: error reporting must never throw.\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from './contracts.js';\n\n/** Recognised log levels in increasing severity order. */\nexport const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n fatal: 50,\n};\n\n/** No-op logger — discards every message. */\nexport class NoopLogger implements Logger {\n debug(): void { }\n info(): void { }\n warn(): void { }\n error(): void { }\n fatal(): void { }\n child(): Logger { return this; }\n}\n\n/**\n * Console logger — pretty-printed messages for local development.\n *\n * Not suitable for production where structured JSON is required;\n * use {@link JsonLogger} there instead.\n */\nexport class ConsoleLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n } = {},\n ) { }\n\n private get threshold(): number {\n return LEVEL_PRIORITY[this.opts.level ?? 'info'];\n }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> {\n return this.opts.context ?? {};\n }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new ConsoleLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const merged = { ...this.context, ...(meta ?? {}) };\n const tail = Object.keys(merged).length ? ' ' + JSON.stringify(merged) : '';\n const line = `[${level}] ${msg}${tail}`;\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n\n/**\n * JSON logger — one JSON object per line on stdout (errors on stderr).\n *\n * Matches the shape that Loki / fluent-bit / Cloudflare Logpush ingest\n * by default. Every record contains `ts`, `level`, `msg`, and any\n * accumulated child-context plus per-call `meta`.\n *\n * Use this in production. Use {@link ConsoleLogger} during development\n * for human-friendly output.\n */\nexport class JsonLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n /** Optional fields injected into every record (`service`, `env`, …). */\n base?: Record<string, unknown>;\n /** Wall clock for tests. */\n now?: () => Date;\n } = {},\n ) { }\n\n private get threshold(): number { return LEVEL_PRIORITY[this.opts.level ?? 'info']; }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> { return this.opts.context ?? {}; }\n private get base(): Record<string, unknown> { return this.opts.base ?? {}; }\n private get now(): () => Date { return this.opts.now ?? (() => new Date()); }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new JsonLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const record = {\n ts: this.now().toISOString(),\n level,\n msg,\n ...this.base,\n ...this.context,\n ...(meta ?? {}),\n };\n const line = JSON.stringify(record);\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/service-names.ts","../src/semconv.ts","../src/metrics-exporters.ts","../src/error-exporters.ts","../src/loggers.ts","../src/perf-timing.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `@objectstack/observability` — vendor-neutral contracts and exporters\n * for ObjectStack metrics, errors, and logs.\n *\n * @see {@link MetricsRegistry} {@link ErrorReporter} {@link Logger}\n */\n\n// Contracts\nexport type { MetricsRegistry, MetricSample, ErrorReporter, CapturedError, Logger } from './contracts.js';\n\n// Service-registry names (consumed by runtime's ObservabilityServicePlugin and lookup sites)\nexport { OBSERVABILITY_METRICS_SERVICE, OBSERVABILITY_ERRORS_SERVICE } from './service-names.js';\n\n// Semantic conventions\nexport { SEMCONV, RUNTIME_METRICS } from './semconv.js';\n\n// Metric exporters\nexport {\n NoopMetricsRegistry,\n InMemoryMetricsRegistry,\n ConsoleMetricsRegistry,\n OtlpHttpMetricsRegistry,\n type OtlpHttpExporterOptions,\n} from './metrics-exporters.js';\n\n// Error reporters\nexport {\n NoopErrorReporter,\n InMemoryErrorReporter,\n ConsoleErrorReporter,\n} from './error-exporters.js';\n\n// Loggers\nexport {\n NoopLogger,\n ConsoleLogger,\n JsonLogger,\n LOG_LEVELS,\n type LogLevel,\n} from './loggers.js';\n\n// Per-request performance timing (Server-Timing header)\nexport {\n PerfTiming,\n perfNow,\n formatServerTiming,\n runWithPerfTiming,\n currentPerfTiming,\n recordServerTiming,\n startServerTiming,\n measureServerTiming,\n type ServerTimingMark,\n} from './perf-timing.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Canonical service-registry names for the host's observability\n * backends. Plugins look these up to discover the configured\n * {@link MetricsRegistry} / {@link ErrorReporter} without each host\n * having to thread observability config through every plugin\n * constructor.\n *\n * See `@objectstack/runtime` → `ObservabilityServicePlugin` for the\n * registration side.\n */\nexport const OBSERVABILITY_METRICS_SERVICE = 'observability:metrics';\nexport const OBSERVABILITY_ERRORS_SERVICE = 'observability:errors';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Semantic conventions — canonical metric names emitted by the\n * framework. Listed here so hosts can wire alerts/dashboards against\n * a stable namespace and so call sites don't sprinkle string\n * literals through the code base.\n *\n * Naming follows Prometheus conventions:\n *\n * - snake_case identifiers.\n * - `_total` suffix for monotonic counters.\n * - `_ms`, `_seconds`, `_bytes` suffixes for histograms / gauges\n * with units.\n *\n * Groups roughly mirror the framework subsystems that emit them.\n * Cloud-specific metrics (DO restarts, Workers Analytics Engine\n * writes, …) do NOT belong here — they are deployment-specific and\n * stay in the deployment repo.\n */\nexport const SEMCONV = {\n // ── HTTP — emitted by `@objectstack/runtime`'s instrumentRouteHandler ──\n /** Counter, labels: `method`, `route`, `status`. */\n httpRequestsTotal: 'http_requests_total',\n /** Histogram (ms), labels: `method`, `route`. */\n httpRequestDurationMs: 'http_request_duration_ms',\n /**\n * Counter, labels: `method`, `route`. Incremented when an\n * in-flight handler throws after the response is sent.\n */\n httpRequestErrorsTotal: 'http_request_errors_total',\n\n // ── Storage — emitted by `@objectstack/service-storage` adapters ──\n /** Counter, labels: `adapter` (`local`|`s3`|…), `op` (`get`|`put`|`delete`|`head`), `result` (`ok`|`error`). */\n storageOperationsTotal: 'storage_operations_total',\n /** Histogram (ms), labels: `adapter`, `op`. */\n storageOperationDurationMs: 'storage_operation_duration_ms',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n storageErrorsTotal: 'storage_errors_total',\n\n // ── Cache — emitted by `@objectstack/service-cache` adapters ──\n /** Counter, labels: `adapter` (`memory`|`redis`), `result` (`hit`|`miss`). */\n cacheLookupsTotal: 'cache_lookups_total',\n /** Counter, labels: `adapter`, `op` (`set`|`delete`|`clear`). */\n cacheWritesTotal: 'cache_writes_total',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n cacheErrorsTotal: 'cache_errors_total',\n\n // ── Package / registry-reader — emitted by `@objectstack/service-package` ──\n /** Counter, labels: `result` (`ok`|`miss`|`error`). */\n registryLookupsTotal: 'registry_lookups_total',\n /** Histogram (ms). */\n registryLookupDurationMs: 'registry_lookup_duration_ms',\n /** Counter, labels: `source` (`r2`|`http`|`local`), `result` (`hit`|`miss`|`error`). */\n registrySourceFetchesTotal: 'registry_source_fetches_total',\n} as const;\n\n/**\n * Backwards-compat alias. `RUNTIME_METRICS` was the original (HTTP-only)\n * constant name shipped from `@objectstack/runtime`; we keep it here so\n * existing code reading `RUNTIME_METRICS.httpRequestsTotal` continues\n * to work after the constants moved into this package.\n */\nexport const RUNTIME_METRICS = {\n httpRequestsTotal: SEMCONV.httpRequestsTotal,\n httpRequestDurationMs: SEMCONV.httpRequestDurationMs,\n httpRequestErrorsTotal: SEMCONV.httpRequestErrorsTotal,\n} as const;\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MetricsRegistry, MetricSample } from './contracts.js';\n\n// ─── Noop ─────────────────────────────────────────────────────────────\n\n/**\n * No-op metrics registry — the default. Discards every observation.\n * Production deployments should swap this for a real registry; tests\n * can use {@link InMemoryMetricsRegistry} to assert emissions.\n */\nexport class NoopMetricsRegistry implements MetricsRegistry {\n counter(): void { }\n histogram(): void { }\n gauge(): void { }\n}\n\n// ─── In-memory (tests + dev inspection) ───────────────────────────────\n\n/**\n * In-memory registry used for tests and local inspection. Stores\n * every observation in insertion order; query via the helpers below\n * or read {@link samples} directly.\n *\n * Not intended for production — unbounded growth.\n */\nexport class InMemoryMetricsRegistry implements MetricsRegistry {\n readonly samples: MetricSample[] = [];\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.samples.push({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n /** Sum of counter increments matching `name` and optionally a label subset. */\n totalCounter(name: string, labelMatch: Record<string, string> = {}): number {\n return this.samples\n .filter(s => s.kind === 'counter' && s.name === name && matchesLabels(s.labels, labelMatch))\n .reduce((acc, s) => acc + s.value, 0);\n }\n\n /** Raw histogram observations matching `name` and optionally a label subset. */\n histogramValues(name: string, labelMatch: Record<string, string> = {}): number[] {\n return this.samples\n .filter(s => s.kind === 'histogram' && s.name === name && matchesLabels(s.labels, labelMatch))\n .map(s => s.value);\n }\n\n /** Last gauge value matching `name` and optionally a label subset, or undefined. */\n lastGauge(name: string, labelMatch: Record<string, string> = {}): number | undefined {\n for (let i = this.samples.length - 1; i >= 0; i--) {\n const s = this.samples[i];\n if (s.kind === 'gauge' && s.name === name && matchesLabels(s.labels, labelMatch)) {\n return s.value;\n }\n }\n return undefined;\n }\n\n /** Clear all recorded samples. */\n reset(): void {\n this.samples.length = 0;\n }\n}\n\nfunction matchesLabels(actual: Record<string, string>, expected: Record<string, string>): boolean {\n for (const [k, v] of Object.entries(expected)) {\n if (actual[k] !== v) return false;\n }\n return true;\n}\n\n// ─── Console (development) ────────────────────────────────────────────\n\n/**\n * Console metrics registry — prints one line per observation. Useful\n * during local development to confirm that instrumentation is firing.\n *\n * Not intended for production: writing every observation to stdout\n * defeats Prometheus / OTLP pipelines and dominates request latency.\n */\nexport class ConsoleMetricsRegistry implements MetricsRegistry {\n constructor(private readonly opts: { sink?: (line: string) => void; prefix?: string } = {}) { }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.emit('counter', name, value, labels);\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('histogram', name, value, labels);\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('gauge', name, value, labels);\n }\n\n private emit(kind: string, name: string, value: number, labels: Record<string, string>): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.log(s); });\n const prefix = this.opts.prefix ?? '[metric]';\n const labelStr = Object.entries(labels)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(' ');\n sink(`${prefix} ${kind} ${name} ${value}${labelStr ? ' ' + labelStr : ''}`);\n } catch {\n // Per contract: never throw from a metric call site.\n }\n }\n}\n\n// ─── OTLP / HTTP (Prometheus via OTel Collector, Grafana Cloud, …) ────\n\n/**\n * Configuration for {@link OtlpHttpMetricsRegistry}.\n */\nexport interface OtlpHttpExporterOptions {\n /**\n * OTLP/HTTP metrics endpoint, e.g. `http://otel-collector:4318/v1/metrics`.\n * The path is appended automatically if missing.\n */\n endpoint: string;\n\n /** Optional headers (Authorization, x-tenant, …). */\n headers?: Record<string, string>;\n\n /**\n * Resource attributes — `service.name`, `service.namespace`,\n * `deployment.environment`, etc. Merged into the OTLP `resource`\n * block on every export.\n */\n resource?: Record<string, string>;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Allows Workers / undici / node-fetch substitution and test\n * doubles.\n */\n fetch?: typeof fetch;\n\n /**\n * Called when an export attempt fails. Default: silently swallow\n * (per the contract that metric emission must not throw / log\n * loudly on the hot path).\n */\n onError?: (error: unknown) => void;\n\n /**\n * Maximum number of samples buffered before {@link OtlpHttpMetricsRegistry.flush}\n * is called automatically. Defaults to 1024.\n */\n maxBufferSize?: number;\n}\n\n/**\n * OTLP/HTTP metrics exporter.\n *\n * Buffers samples in memory and serialises them to the OpenTelemetry\n * Protocol JSON encoding when {@link flush} is called (manually or\n * automatically once the buffer hits the configured size).\n *\n * Intentionally does **not** start an interval timer in the constructor:\n * (a) it makes the exporter usable on Cloudflare Workers where\n * `setInterval` is restricted, and (b) it keeps unit tests deterministic.\n * Long-running hosts should call `flush()` on a schedule\n * (e.g. `setInterval(() => reg.flush(), 10_000)` on Node, or\n * `ctx.waitUntil(reg.flush())` from a Workers fetch handler).\n *\n * Only counters, histograms, and gauges are emitted — no support for\n * exemplars or aggregation temporality switches (the Collector handles\n * those on the upstream side).\n */\nexport class OtlpHttpMetricsRegistry implements MetricsRegistry {\n private buffer: MetricSample[] = [];\n private readonly endpoint: string;\n private readonly headers: Record<string, string>;\n private readonly resource: Record<string, string>;\n private readonly maxBufferSize: number;\n private readonly fetchImpl: typeof fetch;\n private readonly onError: (error: unknown) => void;\n\n constructor(options: OtlpHttpExporterOptions) {\n this.endpoint = normaliseEndpoint(options.endpoint);\n this.headers = options.headers ?? {};\n this.resource = options.resource ?? {};\n this.maxBufferSize = options.maxBufferSize ?? 1024;\n this.fetchImpl = options.fetch ?? (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : noFetch);\n this.onError = options.onError ?? (() => { });\n }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.record({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n private record(sample: MetricSample): void {\n this.buffer.push(sample);\n if (this.buffer.length >= this.maxBufferSize) {\n // Fire-and-forget: do not block the call site.\n void this.flush().catch(this.onError);\n }\n }\n\n /** Snapshot the current buffer (for tests). */\n peek(): readonly MetricSample[] {\n return this.buffer.slice();\n }\n\n /**\n * Send the buffered samples to the OTLP endpoint and clear the\n * buffer. Safe to call concurrently — each invocation takes a\n * snapshot before clearing.\n */\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const samples = this.buffer;\n this.buffer = [];\n try {\n const body = JSON.stringify(serialiseToOtlp(samples, this.resource));\n const res = await this.fetchImpl(this.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...this.headers },\n body,\n });\n if (!res.ok) {\n this.onError(new Error(`OTLP export returned HTTP ${res.status}`));\n }\n } catch (err) {\n this.onError(err);\n }\n }\n}\n\nfunction normaliseEndpoint(raw: string): string {\n const trimmed = raw.replace(/\\/+$/, '');\n if (/\\/v1\\/metrics$/.test(trimmed)) return trimmed;\n return trimmed + '/v1/metrics';\n}\n\nfunction noFetch(): never {\n throw new Error('OtlpHttpMetricsRegistry: no global fetch available; pass options.fetch');\n}\n\n/**\n * Minimal OTLP/JSON metrics serialiser.\n *\n * Implements the subset of <https://opentelemetry.io/docs/specs/otlp/>\n * that we actually emit — sums for counters, gauges for gauges,\n * histograms encoded as bucketless \"summary\"-style histograms with\n * explicit per-sample data points. The Collector accepts this shape\n * and applies its own bucketing.\n *\n * Aggregation temporality is set to DELTA (2) because every flush\n * sends only the samples accumulated since the last flush.\n */\nfunction serialiseToOtlp(samples: MetricSample[], resource: Record<string, string>): unknown {\n const byName = new Map<string, { kind: MetricSample['kind']; points: MetricSample[] }>();\n for (const s of samples) {\n const existing = byName.get(s.name);\n if (existing) existing.points.push(s);\n else byName.set(s.name, { kind: s.kind, points: [s] });\n }\n\n const metrics = Array.from(byName.entries()).map(([name, { kind, points }]) => {\n if (kind === 'counter') {\n return {\n name,\n sum: {\n dataPoints: points.map(p => toNumberPoint(p)),\n aggregationTemporality: 2,\n isMonotonic: true,\n },\n };\n }\n if (kind === 'gauge') {\n return { name, gauge: { dataPoints: points.map(p => toNumberPoint(p)) } };\n }\n // histogram: emit a histogram with a single bucket boundary so the\n // Collector treats it as a recorded distribution.\n return {\n name,\n histogram: {\n aggregationTemporality: 2,\n dataPoints: points.map(p => ({\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n count: '1',\n sum: p.value,\n bucketCounts: ['0', '1'],\n explicitBounds: [p.value],\n })),\n },\n };\n });\n\n return {\n resourceMetrics: [{\n resource: { attributes: toAttributes(resource) },\n scopeMetrics: [{\n scope: { name: '@objectstack/observability', version: '0.1.0' },\n metrics,\n }],\n }],\n };\n}\n\nfunction toNumberPoint(p: MetricSample): unknown {\n return {\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n asDouble: p.value,\n };\n}\n\nfunction toAttributes(labels: Record<string, string>): unknown[] {\n return Object.entries(labels).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { ErrorReporter, CapturedError } from './contracts.js';\n\n/** No-op reporter — the default. */\nexport class NoopErrorReporter implements ErrorReporter {\n captureException(): void { }\n}\n\n/** In-memory reporter for tests. */\nexport class InMemoryErrorReporter implements ErrorReporter {\n readonly captured: CapturedError[] = [];\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n this.captured.push({ error, context, at: Date.now() });\n }\n\n reset(): void {\n this.captured.length = 0;\n }\n}\n\n/**\n * Console error reporter — writes a structured JSON line to stderr per\n * captured exception. Convenient default for local development and for\n * any deployment that ships stderr to a log aggregator (e.g. Loki,\n * fluent-bit) but does not have a dedicated APM.\n *\n * Stack traces are included when the captured value is an `Error`.\n */\nexport class ConsoleErrorReporter implements ErrorReporter {\n constructor(private readonly opts: { sink?: (line: string) => void } = {}) { }\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.error(s); });\n const record: Record<string, unknown> = {\n ts: new Date().toISOString(),\n level: 'error',\n msg: error instanceof Error ? error.message : String(error),\n context,\n };\n if (error instanceof Error && error.stack) {\n record.stack = error.stack;\n }\n sink(JSON.stringify(record));\n } catch {\n // Per contract: error reporting must never throw.\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from './contracts.js';\n\n/** Recognised log levels in increasing severity order. */\nexport const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n fatal: 50,\n};\n\n/** No-op logger — discards every message. */\nexport class NoopLogger implements Logger {\n debug(): void { }\n info(): void { }\n warn(): void { }\n error(): void { }\n fatal(): void { }\n child(): Logger { return this; }\n}\n\n/**\n * Console logger — pretty-printed messages for local development.\n *\n * Not suitable for production where structured JSON is required;\n * use {@link JsonLogger} there instead.\n */\nexport class ConsoleLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n } = {},\n ) { }\n\n private get threshold(): number {\n return LEVEL_PRIORITY[this.opts.level ?? 'info'];\n }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> {\n return this.opts.context ?? {};\n }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new ConsoleLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const merged = { ...this.context, ...(meta ?? {}) };\n const tail = Object.keys(merged).length ? ' ' + JSON.stringify(merged) : '';\n const line = `[${level}] ${msg}${tail}`;\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n\n/**\n * JSON logger — one JSON object per line on stdout (errors on stderr).\n *\n * Matches the shape that Loki / fluent-bit / Cloudflare Logpush ingest\n * by default. Every record contains `ts`, `level`, `msg`, and any\n * accumulated child-context plus per-call `meta`.\n *\n * Use this in production. Use {@link ConsoleLogger} during development\n * for human-friendly output.\n */\nexport class JsonLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n /** Optional fields injected into every record (`service`, `env`, …). */\n base?: Record<string, unknown>;\n /** Wall clock for tests. */\n now?: () => Date;\n } = {},\n ) { }\n\n private get threshold(): number { return LEVEL_PRIORITY[this.opts.level ?? 'info']; }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> { return this.opts.context ?? {}; }\n private get base(): Record<string, unknown> { return this.opts.base ?? {}; }\n private get now(): () => Date { return this.opts.now ?? (() => new Date()); }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new JsonLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const record = {\n ts: this.now().toISOString(),\n level,\n msg,\n ...this.base,\n ...this.context,\n ...(meta ?? {}),\n };\n const line = JSON.stringify(record);\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Per-request performance timing - a tiny, dependency-free collector that\n * accumulates named phase durations during a single request and serializes\n * them into the W3C `Server-Timing` response header.\n *\n * @see <https://www.w3.org/TR/server-timing/>\n * @see <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing>\n *\n * Two ways to record:\n *\n * 1. **Explicit collector.** Hold a {@link PerfTiming} instance and call\n * `start()` / `record()` / `measure()` directly. The HTTP adapter owns\n * the instance for a request.\n *\n * 2. **Ambient collector.** Run a request inside {@link runWithPerfTiming}\n * and any framework code on that async call chain records phases via the\n * free functions ({@link measureServerTiming}, {@link startServerTiming},\n * {@link recordServerTiming}) without threading the request object\n * through every layer. When no collector is active the free functions are\n * cheap no-ops, so call sites pay nothing when the feature is off.\n *\n * `Server-Timing` exposes internal phase durations to any client, which is a\n * (mild) information-disclosure surface - it helps an attacker profile the\n * backend. Emission is therefore opt-in (\"perf-tuning mode\"); the collector\n * itself never decides whether to emit, it only measures.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * One recorded phase of a request's server-side processing, serialized as a\n * single member of the `Server-Timing` header.\n */\nexport interface ServerTimingMark {\n /** Metric name. Coerced to a Server-Timing token on record. */\n name: string;\n /** Duration in milliseconds. */\n dur: number;\n /** Optional human-readable description (rendered as the quoted `desc`). */\n desc?: string;\n}\n\n/**\n * Monotonic millisecond clock. Prefers `performance.now()` (monotonic, not\n * affected by wall-clock adjustments); falls back to `Date.now()` on the rare\n * runtime where `performance` is unavailable.\n */\nexport function perfNow(): number {\n try {\n return performance.now();\n } catch {\n return Date.now();\n }\n}\n\n/** Characters not allowed in a Server-Timing metric name (a token). */\nconst NAME_UNSAFE = /[^A-Za-z0-9_-]+/g;\n\nconst UNDERSCORE = 0x5f;\n\n/**\n * Coerce an arbitrary string into a Server-Timing token: non-token runs become\n * a single underscore, then leading/trailing underscores are trimmed.\n *\n * The trim is a linear scan rather than a `/^_+|_+$/g` regex on purpose - the\n * anchored `_+$` quantifier backtracks polynomially on underscore-heavy input\n * (CodeQL js/polynomial-redos), and this name comes from a public-API argument.\n */\nfunction sanitizeName(name: string): string {\n const collapsed = String(name).replace(NAME_UNSAFE, '_');\n let start = 0;\n let end = collapsed.length;\n while (start < end && collapsed.charCodeAt(start) === UNDERSCORE) start++;\n while (end > start && collapsed.charCodeAt(end - 1) === UNDERSCORE) end--;\n return collapsed.slice(start, end);\n}\n\n/**\n * Make a description safe to embed in a quoted-string. Backslashes and double\n * quotes would terminate the quoting; control chars (incl. CR/LF) could forge\n * headers. Collapse anything outside a conservative printable set to a space.\n */\nfunction sanitizeDesc(desc: string): string {\n let out = '';\n for (const ch of String(desc)) {\n const code = ch.codePointAt(0)!;\n // Printable ASCII excluding `\"` (0x22) and `\\` (0x5C); drop the rest.\n if (code >= 0x20 && code < 0x7f && ch !== '\"' && ch !== '\\\\') {\n out += ch;\n } else {\n out += ' ';\n }\n }\n return out.replace(/ +/g, ' ').trim();\n}\n\n/** Round to at most 2 decimals without trailing-zero noise (`12.3`, not `12.30`). */\nfunction fmtDur(dur: number): string {\n if (!Number.isFinite(dur)) return '0';\n return String(Math.round(dur * 100) / 100);\n}\n\n/**\n * Serialize marks into a `Server-Timing` header value. Marks with an empty\n * name after sanitization are dropped (the grammar requires a token). Returns\n * `''` when there is nothing to emit so callers can skip the header.\n */\nexport function formatServerTiming(marks: readonly ServerTimingMark[]): string {\n const parts: string[] = [];\n for (const m of marks) {\n const name = sanitizeName(m.name);\n if (!name) continue;\n let part = `${name};dur=${fmtDur(m.dur)}`;\n if (m.desc) {\n const desc = sanitizeDesc(m.desc);\n if (desc) part += `;desc=\"${desc}\"`;\n }\n parts.push(part);\n }\n return parts.join(', ');\n}\n\n/**\n * Collector for one request's timing phases. Not thread-safe by design - one\n * instance belongs to one request. All methods are allocation-light and never\n * throw on the hot path.\n */\nexport class PerfTiming {\n private readonly _marks: ServerTimingMark[] = [];\n\n /** Record an already-measured phase. */\n record(name: string, dur: number, desc?: string): void {\n this._marks.push({ name, dur, desc });\n }\n\n /**\n * Begin timing a phase. Returns an idempotent `end()` - the first call\n * records the elapsed duration; later calls are ignored, so it is safe to\n * call from both a success and an error path.\n */\n start(name: string, desc?: string): () => void {\n const t0 = perfNow();\n let done = false;\n return () => {\n if (done) return;\n done = true;\n this.record(name, perfNow() - t0, desc);\n };\n }\n\n /** Time an async (or sync) function, recording its elapsed duration. */\n async measure<T>(name: string, fn: () => T | Promise<T>, desc?: string): Promise<T> {\n const end = this.start(name, desc);\n try {\n return await fn();\n } finally {\n end();\n }\n }\n\n /** Snapshot of recorded marks, in record order. */\n marks(): readonly ServerTimingMark[] {\n return this._marks;\n }\n\n /** Serialize to a `Server-Timing` header value (`''` when empty). */\n toHeader(): string {\n return formatServerTiming(this._marks);\n }\n}\n\n// --- Ambient (request-scoped) collector -------------------------------\n\nconst store = new AsyncLocalStorage<PerfTiming>();\n\n/** Run `fn` with `timing` as the ambient collector for the async call chain. */\nexport function runWithPerfTiming<T>(timing: PerfTiming, fn: () => T): T {\n return store.run(timing, fn);\n}\n\n/** The collector for the current request, or `undefined` outside a request. */\nexport function currentPerfTiming(): PerfTiming | undefined {\n return store.getStore();\n}\n\n/** Record a phase on the ambient collector. No-op when none is active. */\nexport function recordServerTiming(name: string, dur: number, desc?: string): void {\n store.getStore()?.record(name, dur, desc);\n}\n\n/**\n * Begin timing a phase on the ambient collector. Returns an `end()` callback;\n * when no collector is active the returned callback is a no-op so call sites\n * stay branch-free.\n */\nexport function startServerTiming(name: string, desc?: string): () => void {\n const t = store.getStore();\n if (!t) return () => {};\n return t.start(name, desc);\n}\n\n/**\n * Time an async function on the ambient collector. When no collector is active\n * the function is awaited with zero timing overhead.\n */\nexport async function measureServerTiming<T>(\n name: string,\n fn: () => T | Promise<T>,\n desc?: string,\n): Promise<T> {\n const t = store.getStore();\n if (!t) return fn();\n return t.measure(name, fn, desc);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -385,4 +385,68 @@ declare class JsonLogger implements Logger {
|
|
|
385
385
|
private emit;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
/**
|
|
389
|
+
* One recorded phase of a request's server-side processing, serialized as a
|
|
390
|
+
* single member of the `Server-Timing` header.
|
|
391
|
+
*/
|
|
392
|
+
interface ServerTimingMark {
|
|
393
|
+
/** Metric name. Coerced to a Server-Timing token on record. */
|
|
394
|
+
name: string;
|
|
395
|
+
/** Duration in milliseconds. */
|
|
396
|
+
dur: number;
|
|
397
|
+
/** Optional human-readable description (rendered as the quoted `desc`). */
|
|
398
|
+
desc?: string;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Monotonic millisecond clock. Prefers `performance.now()` (monotonic, not
|
|
402
|
+
* affected by wall-clock adjustments); falls back to `Date.now()` on the rare
|
|
403
|
+
* runtime where `performance` is unavailable.
|
|
404
|
+
*/
|
|
405
|
+
declare function perfNow(): number;
|
|
406
|
+
/**
|
|
407
|
+
* Serialize marks into a `Server-Timing` header value. Marks with an empty
|
|
408
|
+
* name after sanitization are dropped (the grammar requires a token). Returns
|
|
409
|
+
* `''` when there is nothing to emit so callers can skip the header.
|
|
410
|
+
*/
|
|
411
|
+
declare function formatServerTiming(marks: readonly ServerTimingMark[]): string;
|
|
412
|
+
/**
|
|
413
|
+
* Collector for one request's timing phases. Not thread-safe by design - one
|
|
414
|
+
* instance belongs to one request. All methods are allocation-light and never
|
|
415
|
+
* throw on the hot path.
|
|
416
|
+
*/
|
|
417
|
+
declare class PerfTiming {
|
|
418
|
+
private readonly _marks;
|
|
419
|
+
/** Record an already-measured phase. */
|
|
420
|
+
record(name: string, dur: number, desc?: string): void;
|
|
421
|
+
/**
|
|
422
|
+
* Begin timing a phase. Returns an idempotent `end()` - the first call
|
|
423
|
+
* records the elapsed duration; later calls are ignored, so it is safe to
|
|
424
|
+
* call from both a success and an error path.
|
|
425
|
+
*/
|
|
426
|
+
start(name: string, desc?: string): () => void;
|
|
427
|
+
/** Time an async (or sync) function, recording its elapsed duration. */
|
|
428
|
+
measure<T>(name: string, fn: () => T | Promise<T>, desc?: string): Promise<T>;
|
|
429
|
+
/** Snapshot of recorded marks, in record order. */
|
|
430
|
+
marks(): readonly ServerTimingMark[];
|
|
431
|
+
/** Serialize to a `Server-Timing` header value (`''` when empty). */
|
|
432
|
+
toHeader(): string;
|
|
433
|
+
}
|
|
434
|
+
/** Run `fn` with `timing` as the ambient collector for the async call chain. */
|
|
435
|
+
declare function runWithPerfTiming<T>(timing: PerfTiming, fn: () => T): T;
|
|
436
|
+
/** The collector for the current request, or `undefined` outside a request. */
|
|
437
|
+
declare function currentPerfTiming(): PerfTiming | undefined;
|
|
438
|
+
/** Record a phase on the ambient collector. No-op when none is active. */
|
|
439
|
+
declare function recordServerTiming(name: string, dur: number, desc?: string): void;
|
|
440
|
+
/**
|
|
441
|
+
* Begin timing a phase on the ambient collector. Returns an `end()` callback;
|
|
442
|
+
* when no collector is active the returned callback is a no-op so call sites
|
|
443
|
+
* stay branch-free.
|
|
444
|
+
*/
|
|
445
|
+
declare function startServerTiming(name: string, desc?: string): () => void;
|
|
446
|
+
/**
|
|
447
|
+
* Time an async function on the ambient collector. When no collector is active
|
|
448
|
+
* the function is awaited with zero timing overhead.
|
|
449
|
+
*/
|
|
450
|
+
declare function measureServerTiming<T>(name: string, fn: () => T | Promise<T>, desc?: string): Promise<T>;
|
|
451
|
+
|
|
452
|
+
export { type CapturedError, ConsoleErrorReporter, ConsoleLogger, ConsoleMetricsRegistry, type ErrorReporter, InMemoryErrorReporter, InMemoryMetricsRegistry, JsonLogger, LOG_LEVELS, type LogLevel, type MetricSample, type MetricsRegistry, NoopErrorReporter, NoopLogger, NoopMetricsRegistry, OBSERVABILITY_ERRORS_SERVICE, OBSERVABILITY_METRICS_SERVICE, type OtlpHttpExporterOptions, OtlpHttpMetricsRegistry, PerfTiming, RUNTIME_METRICS, SEMCONV, type ServerTimingMark, currentPerfTiming, formatServerTiming, measureServerTiming, perfNow, recordServerTiming, runWithPerfTiming, startServerTiming };
|
package/dist/index.d.ts
CHANGED
|
@@ -385,4 +385,68 @@ declare class JsonLogger implements Logger {
|
|
|
385
385
|
private emit;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
/**
|
|
389
|
+
* One recorded phase of a request's server-side processing, serialized as a
|
|
390
|
+
* single member of the `Server-Timing` header.
|
|
391
|
+
*/
|
|
392
|
+
interface ServerTimingMark {
|
|
393
|
+
/** Metric name. Coerced to a Server-Timing token on record. */
|
|
394
|
+
name: string;
|
|
395
|
+
/** Duration in milliseconds. */
|
|
396
|
+
dur: number;
|
|
397
|
+
/** Optional human-readable description (rendered as the quoted `desc`). */
|
|
398
|
+
desc?: string;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Monotonic millisecond clock. Prefers `performance.now()` (monotonic, not
|
|
402
|
+
* affected by wall-clock adjustments); falls back to `Date.now()` on the rare
|
|
403
|
+
* runtime where `performance` is unavailable.
|
|
404
|
+
*/
|
|
405
|
+
declare function perfNow(): number;
|
|
406
|
+
/**
|
|
407
|
+
* Serialize marks into a `Server-Timing` header value. Marks with an empty
|
|
408
|
+
* name after sanitization are dropped (the grammar requires a token). Returns
|
|
409
|
+
* `''` when there is nothing to emit so callers can skip the header.
|
|
410
|
+
*/
|
|
411
|
+
declare function formatServerTiming(marks: readonly ServerTimingMark[]): string;
|
|
412
|
+
/**
|
|
413
|
+
* Collector for one request's timing phases. Not thread-safe by design - one
|
|
414
|
+
* instance belongs to one request. All methods are allocation-light and never
|
|
415
|
+
* throw on the hot path.
|
|
416
|
+
*/
|
|
417
|
+
declare class PerfTiming {
|
|
418
|
+
private readonly _marks;
|
|
419
|
+
/** Record an already-measured phase. */
|
|
420
|
+
record(name: string, dur: number, desc?: string): void;
|
|
421
|
+
/**
|
|
422
|
+
* Begin timing a phase. Returns an idempotent `end()` - the first call
|
|
423
|
+
* records the elapsed duration; later calls are ignored, so it is safe to
|
|
424
|
+
* call from both a success and an error path.
|
|
425
|
+
*/
|
|
426
|
+
start(name: string, desc?: string): () => void;
|
|
427
|
+
/** Time an async (or sync) function, recording its elapsed duration. */
|
|
428
|
+
measure<T>(name: string, fn: () => T | Promise<T>, desc?: string): Promise<T>;
|
|
429
|
+
/** Snapshot of recorded marks, in record order. */
|
|
430
|
+
marks(): readonly ServerTimingMark[];
|
|
431
|
+
/** Serialize to a `Server-Timing` header value (`''` when empty). */
|
|
432
|
+
toHeader(): string;
|
|
433
|
+
}
|
|
434
|
+
/** Run `fn` with `timing` as the ambient collector for the async call chain. */
|
|
435
|
+
declare function runWithPerfTiming<T>(timing: PerfTiming, fn: () => T): T;
|
|
436
|
+
/** The collector for the current request, or `undefined` outside a request. */
|
|
437
|
+
declare function currentPerfTiming(): PerfTiming | undefined;
|
|
438
|
+
/** Record a phase on the ambient collector. No-op when none is active. */
|
|
439
|
+
declare function recordServerTiming(name: string, dur: number, desc?: string): void;
|
|
440
|
+
/**
|
|
441
|
+
* Begin timing a phase on the ambient collector. Returns an `end()` callback;
|
|
442
|
+
* when no collector is active the returned callback is a no-op so call sites
|
|
443
|
+
* stay branch-free.
|
|
444
|
+
*/
|
|
445
|
+
declare function startServerTiming(name: string, desc?: string): () => void;
|
|
446
|
+
/**
|
|
447
|
+
* Time an async function on the ambient collector. When no collector is active
|
|
448
|
+
* the function is awaited with zero timing overhead.
|
|
449
|
+
*/
|
|
450
|
+
declare function measureServerTiming<T>(name: string, fn: () => T | Promise<T>, desc?: string): Promise<T>;
|
|
451
|
+
|
|
452
|
+
export { type CapturedError, ConsoleErrorReporter, ConsoleLogger, ConsoleMetricsRegistry, type ErrorReporter, InMemoryErrorReporter, InMemoryMetricsRegistry, JsonLogger, LOG_LEVELS, type LogLevel, type MetricSample, type MetricsRegistry, NoopErrorReporter, NoopLogger, NoopMetricsRegistry, OBSERVABILITY_ERRORS_SERVICE, OBSERVABILITY_METRICS_SERVICE, type OtlpHttpExporterOptions, OtlpHttpMetricsRegistry, PerfTiming, RUNTIME_METRICS, SEMCONV, type ServerTimingMark, currentPerfTiming, formatServerTiming, measureServerTiming, perfNow, recordServerTiming, runWithPerfTiming, startServerTiming };
|
package/dist/index.js
CHANGED
|
@@ -404,6 +404,117 @@ var JsonLogger = class _JsonLogger {
|
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
};
|
|
407
|
+
|
|
408
|
+
// src/perf-timing.ts
|
|
409
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
410
|
+
function perfNow() {
|
|
411
|
+
try {
|
|
412
|
+
return performance.now();
|
|
413
|
+
} catch {
|
|
414
|
+
return Date.now();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
var NAME_UNSAFE = /[^A-Za-z0-9_-]+/g;
|
|
418
|
+
var UNDERSCORE = 95;
|
|
419
|
+
function sanitizeName(name) {
|
|
420
|
+
const collapsed = String(name).replace(NAME_UNSAFE, "_");
|
|
421
|
+
let start = 0;
|
|
422
|
+
let end = collapsed.length;
|
|
423
|
+
while (start < end && collapsed.charCodeAt(start) === UNDERSCORE) start++;
|
|
424
|
+
while (end > start && collapsed.charCodeAt(end - 1) === UNDERSCORE) end--;
|
|
425
|
+
return collapsed.slice(start, end);
|
|
426
|
+
}
|
|
427
|
+
function sanitizeDesc(desc) {
|
|
428
|
+
let out = "";
|
|
429
|
+
for (const ch of String(desc)) {
|
|
430
|
+
const code = ch.codePointAt(0);
|
|
431
|
+
if (code >= 32 && code < 127 && ch !== '"' && ch !== "\\") {
|
|
432
|
+
out += ch;
|
|
433
|
+
} else {
|
|
434
|
+
out += " ";
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return out.replace(/ +/g, " ").trim();
|
|
438
|
+
}
|
|
439
|
+
function fmtDur(dur) {
|
|
440
|
+
if (!Number.isFinite(dur)) return "0";
|
|
441
|
+
return String(Math.round(dur * 100) / 100);
|
|
442
|
+
}
|
|
443
|
+
function formatServerTiming(marks) {
|
|
444
|
+
const parts = [];
|
|
445
|
+
for (const m of marks) {
|
|
446
|
+
const name = sanitizeName(m.name);
|
|
447
|
+
if (!name) continue;
|
|
448
|
+
let part = `${name};dur=${fmtDur(m.dur)}`;
|
|
449
|
+
if (m.desc) {
|
|
450
|
+
const desc = sanitizeDesc(m.desc);
|
|
451
|
+
if (desc) part += `;desc="${desc}"`;
|
|
452
|
+
}
|
|
453
|
+
parts.push(part);
|
|
454
|
+
}
|
|
455
|
+
return parts.join(", ");
|
|
456
|
+
}
|
|
457
|
+
var PerfTiming = class {
|
|
458
|
+
constructor() {
|
|
459
|
+
this._marks = [];
|
|
460
|
+
}
|
|
461
|
+
/** Record an already-measured phase. */
|
|
462
|
+
record(name, dur, desc) {
|
|
463
|
+
this._marks.push({ name, dur, desc });
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Begin timing a phase. Returns an idempotent `end()` - the first call
|
|
467
|
+
* records the elapsed duration; later calls are ignored, so it is safe to
|
|
468
|
+
* call from both a success and an error path.
|
|
469
|
+
*/
|
|
470
|
+
start(name, desc) {
|
|
471
|
+
const t0 = perfNow();
|
|
472
|
+
let done = false;
|
|
473
|
+
return () => {
|
|
474
|
+
if (done) return;
|
|
475
|
+
done = true;
|
|
476
|
+
this.record(name, perfNow() - t0, desc);
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
/** Time an async (or sync) function, recording its elapsed duration. */
|
|
480
|
+
async measure(name, fn, desc) {
|
|
481
|
+
const end = this.start(name, desc);
|
|
482
|
+
try {
|
|
483
|
+
return await fn();
|
|
484
|
+
} finally {
|
|
485
|
+
end();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/** Snapshot of recorded marks, in record order. */
|
|
489
|
+
marks() {
|
|
490
|
+
return this._marks;
|
|
491
|
+
}
|
|
492
|
+
/** Serialize to a `Server-Timing` header value (`''` when empty). */
|
|
493
|
+
toHeader() {
|
|
494
|
+
return formatServerTiming(this._marks);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
var store = new AsyncLocalStorage();
|
|
498
|
+
function runWithPerfTiming(timing, fn) {
|
|
499
|
+
return store.run(timing, fn);
|
|
500
|
+
}
|
|
501
|
+
function currentPerfTiming() {
|
|
502
|
+
return store.getStore();
|
|
503
|
+
}
|
|
504
|
+
function recordServerTiming(name, dur, desc) {
|
|
505
|
+
store.getStore()?.record(name, dur, desc);
|
|
506
|
+
}
|
|
507
|
+
function startServerTiming(name, desc) {
|
|
508
|
+
const t = store.getStore();
|
|
509
|
+
if (!t) return () => {
|
|
510
|
+
};
|
|
511
|
+
return t.start(name, desc);
|
|
512
|
+
}
|
|
513
|
+
async function measureServerTiming(name, fn, desc) {
|
|
514
|
+
const t = store.getStore();
|
|
515
|
+
if (!t) return fn();
|
|
516
|
+
return t.measure(name, fn, desc);
|
|
517
|
+
}
|
|
407
518
|
export {
|
|
408
519
|
ConsoleErrorReporter,
|
|
409
520
|
ConsoleLogger,
|
|
@@ -418,7 +529,15 @@ export {
|
|
|
418
529
|
OBSERVABILITY_ERRORS_SERVICE,
|
|
419
530
|
OBSERVABILITY_METRICS_SERVICE,
|
|
420
531
|
OtlpHttpMetricsRegistry,
|
|
532
|
+
PerfTiming,
|
|
421
533
|
RUNTIME_METRICS,
|
|
422
|
-
SEMCONV
|
|
534
|
+
SEMCONV,
|
|
535
|
+
currentPerfTiming,
|
|
536
|
+
formatServerTiming,
|
|
537
|
+
measureServerTiming,
|
|
538
|
+
perfNow,
|
|
539
|
+
recordServerTiming,
|
|
540
|
+
runWithPerfTiming,
|
|
541
|
+
startServerTiming
|
|
423
542
|
};
|
|
424
543
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/service-names.ts","../src/semconv.ts","../src/metrics-exporters.ts","../src/error-exporters.ts","../src/loggers.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Canonical service-registry names for the host's observability\n * backends. Plugins look these up to discover the configured\n * {@link MetricsRegistry} / {@link ErrorReporter} without each host\n * having to thread observability config through every plugin\n * constructor.\n *\n * See `@objectstack/runtime` → `ObservabilityServicePlugin` for the\n * registration side.\n */\nexport const OBSERVABILITY_METRICS_SERVICE = 'observability:metrics';\nexport const OBSERVABILITY_ERRORS_SERVICE = 'observability:errors';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Semantic conventions — canonical metric names emitted by the\n * framework. Listed here so hosts can wire alerts/dashboards against\n * a stable namespace and so call sites don't sprinkle string\n * literals through the code base.\n *\n * Naming follows Prometheus conventions:\n *\n * - snake_case identifiers.\n * - `_total` suffix for monotonic counters.\n * - `_ms`, `_seconds`, `_bytes` suffixes for histograms / gauges\n * with units.\n *\n * Groups roughly mirror the framework subsystems that emit them.\n * Cloud-specific metrics (DO restarts, Workers Analytics Engine\n * writes, …) do NOT belong here — they are deployment-specific and\n * stay in the deployment repo.\n */\nexport const SEMCONV = {\n // ── HTTP — emitted by `@objectstack/runtime`'s instrumentRouteHandler ──\n /** Counter, labels: `method`, `route`, `status`. */\n httpRequestsTotal: 'http_requests_total',\n /** Histogram (ms), labels: `method`, `route`. */\n httpRequestDurationMs: 'http_request_duration_ms',\n /**\n * Counter, labels: `method`, `route`. Incremented when an\n * in-flight handler throws after the response is sent.\n */\n httpRequestErrorsTotal: 'http_request_errors_total',\n\n // ── Storage — emitted by `@objectstack/service-storage` adapters ──\n /** Counter, labels: `adapter` (`local`|`s3`|…), `op` (`get`|`put`|`delete`|`head`), `result` (`ok`|`error`). */\n storageOperationsTotal: 'storage_operations_total',\n /** Histogram (ms), labels: `adapter`, `op`. */\n storageOperationDurationMs: 'storage_operation_duration_ms',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n storageErrorsTotal: 'storage_errors_total',\n\n // ── Cache — emitted by `@objectstack/service-cache` adapters ──\n /** Counter, labels: `adapter` (`memory`|`redis`), `result` (`hit`|`miss`). */\n cacheLookupsTotal: 'cache_lookups_total',\n /** Counter, labels: `adapter`, `op` (`set`|`delete`|`clear`). */\n cacheWritesTotal: 'cache_writes_total',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n cacheErrorsTotal: 'cache_errors_total',\n\n // ── Package / registry-reader — emitted by `@objectstack/service-package` ──\n /** Counter, labels: `result` (`ok`|`miss`|`error`). */\n registryLookupsTotal: 'registry_lookups_total',\n /** Histogram (ms). */\n registryLookupDurationMs: 'registry_lookup_duration_ms',\n /** Counter, labels: `source` (`r2`|`http`|`local`), `result` (`hit`|`miss`|`error`). */\n registrySourceFetchesTotal: 'registry_source_fetches_total',\n} as const;\n\n/**\n * Backwards-compat alias. `RUNTIME_METRICS` was the original (HTTP-only)\n * constant name shipped from `@objectstack/runtime`; we keep it here so\n * existing code reading `RUNTIME_METRICS.httpRequestsTotal` continues\n * to work after the constants moved into this package.\n */\nexport const RUNTIME_METRICS = {\n httpRequestsTotal: SEMCONV.httpRequestsTotal,\n httpRequestDurationMs: SEMCONV.httpRequestDurationMs,\n httpRequestErrorsTotal: SEMCONV.httpRequestErrorsTotal,\n} as const;\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MetricsRegistry, MetricSample } from './contracts.js';\n\n// ─── Noop ─────────────────────────────────────────────────────────────\n\n/**\n * No-op metrics registry — the default. Discards every observation.\n * Production deployments should swap this for a real registry; tests\n * can use {@link InMemoryMetricsRegistry} to assert emissions.\n */\nexport class NoopMetricsRegistry implements MetricsRegistry {\n counter(): void { }\n histogram(): void { }\n gauge(): void { }\n}\n\n// ─── In-memory (tests + dev inspection) ───────────────────────────────\n\n/**\n * In-memory registry used for tests and local inspection. Stores\n * every observation in insertion order; query via the helpers below\n * or read {@link samples} directly.\n *\n * Not intended for production — unbounded growth.\n */\nexport class InMemoryMetricsRegistry implements MetricsRegistry {\n readonly samples: MetricSample[] = [];\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.samples.push({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n /** Sum of counter increments matching `name` and optionally a label subset. */\n totalCounter(name: string, labelMatch: Record<string, string> = {}): number {\n return this.samples\n .filter(s => s.kind === 'counter' && s.name === name && matchesLabels(s.labels, labelMatch))\n .reduce((acc, s) => acc + s.value, 0);\n }\n\n /** Raw histogram observations matching `name` and optionally a label subset. */\n histogramValues(name: string, labelMatch: Record<string, string> = {}): number[] {\n return this.samples\n .filter(s => s.kind === 'histogram' && s.name === name && matchesLabels(s.labels, labelMatch))\n .map(s => s.value);\n }\n\n /** Last gauge value matching `name` and optionally a label subset, or undefined. */\n lastGauge(name: string, labelMatch: Record<string, string> = {}): number | undefined {\n for (let i = this.samples.length - 1; i >= 0; i--) {\n const s = this.samples[i];\n if (s.kind === 'gauge' && s.name === name && matchesLabels(s.labels, labelMatch)) {\n return s.value;\n }\n }\n return undefined;\n }\n\n /** Clear all recorded samples. */\n reset(): void {\n this.samples.length = 0;\n }\n}\n\nfunction matchesLabels(actual: Record<string, string>, expected: Record<string, string>): boolean {\n for (const [k, v] of Object.entries(expected)) {\n if (actual[k] !== v) return false;\n }\n return true;\n}\n\n// ─── Console (development) ────────────────────────────────────────────\n\n/**\n * Console metrics registry — prints one line per observation. Useful\n * during local development to confirm that instrumentation is firing.\n *\n * Not intended for production: writing every observation to stdout\n * defeats Prometheus / OTLP pipelines and dominates request latency.\n */\nexport class ConsoleMetricsRegistry implements MetricsRegistry {\n constructor(private readonly opts: { sink?: (line: string) => void; prefix?: string } = {}) { }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.emit('counter', name, value, labels);\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('histogram', name, value, labels);\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('gauge', name, value, labels);\n }\n\n private emit(kind: string, name: string, value: number, labels: Record<string, string>): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.log(s); });\n const prefix = this.opts.prefix ?? '[metric]';\n const labelStr = Object.entries(labels)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(' ');\n sink(`${prefix} ${kind} ${name} ${value}${labelStr ? ' ' + labelStr : ''}`);\n } catch {\n // Per contract: never throw from a metric call site.\n }\n }\n}\n\n// ─── OTLP / HTTP (Prometheus via OTel Collector, Grafana Cloud, …) ────\n\n/**\n * Configuration for {@link OtlpHttpMetricsRegistry}.\n */\nexport interface OtlpHttpExporterOptions {\n /**\n * OTLP/HTTP metrics endpoint, e.g. `http://otel-collector:4318/v1/metrics`.\n * The path is appended automatically if missing.\n */\n endpoint: string;\n\n /** Optional headers (Authorization, x-tenant, …). */\n headers?: Record<string, string>;\n\n /**\n * Resource attributes — `service.name`, `service.namespace`,\n * `deployment.environment`, etc. Merged into the OTLP `resource`\n * block on every export.\n */\n resource?: Record<string, string>;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Allows Workers / undici / node-fetch substitution and test\n * doubles.\n */\n fetch?: typeof fetch;\n\n /**\n * Called when an export attempt fails. Default: silently swallow\n * (per the contract that metric emission must not throw / log\n * loudly on the hot path).\n */\n onError?: (error: unknown) => void;\n\n /**\n * Maximum number of samples buffered before {@link OtlpHttpMetricsRegistry.flush}\n * is called automatically. Defaults to 1024.\n */\n maxBufferSize?: number;\n}\n\n/**\n * OTLP/HTTP metrics exporter.\n *\n * Buffers samples in memory and serialises them to the OpenTelemetry\n * Protocol JSON encoding when {@link flush} is called (manually or\n * automatically once the buffer hits the configured size).\n *\n * Intentionally does **not** start an interval timer in the constructor:\n * (a) it makes the exporter usable on Cloudflare Workers where\n * `setInterval` is restricted, and (b) it keeps unit tests deterministic.\n * Long-running hosts should call `flush()` on a schedule\n * (e.g. `setInterval(() => reg.flush(), 10_000)` on Node, or\n * `ctx.waitUntil(reg.flush())` from a Workers fetch handler).\n *\n * Only counters, histograms, and gauges are emitted — no support for\n * exemplars or aggregation temporality switches (the Collector handles\n * those on the upstream side).\n */\nexport class OtlpHttpMetricsRegistry implements MetricsRegistry {\n private buffer: MetricSample[] = [];\n private readonly endpoint: string;\n private readonly headers: Record<string, string>;\n private readonly resource: Record<string, string>;\n private readonly maxBufferSize: number;\n private readonly fetchImpl: typeof fetch;\n private readonly onError: (error: unknown) => void;\n\n constructor(options: OtlpHttpExporterOptions) {\n this.endpoint = normaliseEndpoint(options.endpoint);\n this.headers = options.headers ?? {};\n this.resource = options.resource ?? {};\n this.maxBufferSize = options.maxBufferSize ?? 1024;\n this.fetchImpl = options.fetch ?? (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : noFetch);\n this.onError = options.onError ?? (() => { });\n }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.record({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n private record(sample: MetricSample): void {\n this.buffer.push(sample);\n if (this.buffer.length >= this.maxBufferSize) {\n // Fire-and-forget: do not block the call site.\n void this.flush().catch(this.onError);\n }\n }\n\n /** Snapshot the current buffer (for tests). */\n peek(): readonly MetricSample[] {\n return this.buffer.slice();\n }\n\n /**\n * Send the buffered samples to the OTLP endpoint and clear the\n * buffer. Safe to call concurrently — each invocation takes a\n * snapshot before clearing.\n */\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const samples = this.buffer;\n this.buffer = [];\n try {\n const body = JSON.stringify(serialiseToOtlp(samples, this.resource));\n const res = await this.fetchImpl(this.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...this.headers },\n body,\n });\n if (!res.ok) {\n this.onError(new Error(`OTLP export returned HTTP ${res.status}`));\n }\n } catch (err) {\n this.onError(err);\n }\n }\n}\n\nfunction normaliseEndpoint(raw: string): string {\n const trimmed = raw.replace(/\\/+$/, '');\n if (/\\/v1\\/metrics$/.test(trimmed)) return trimmed;\n return trimmed + '/v1/metrics';\n}\n\nfunction noFetch(): never {\n throw new Error('OtlpHttpMetricsRegistry: no global fetch available; pass options.fetch');\n}\n\n/**\n * Minimal OTLP/JSON metrics serialiser.\n *\n * Implements the subset of <https://opentelemetry.io/docs/specs/otlp/>\n * that we actually emit — sums for counters, gauges for gauges,\n * histograms encoded as bucketless \"summary\"-style histograms with\n * explicit per-sample data points. The Collector accepts this shape\n * and applies its own bucketing.\n *\n * Aggregation temporality is set to DELTA (2) because every flush\n * sends only the samples accumulated since the last flush.\n */\nfunction serialiseToOtlp(samples: MetricSample[], resource: Record<string, string>): unknown {\n const byName = new Map<string, { kind: MetricSample['kind']; points: MetricSample[] }>();\n for (const s of samples) {\n const existing = byName.get(s.name);\n if (existing) existing.points.push(s);\n else byName.set(s.name, { kind: s.kind, points: [s] });\n }\n\n const metrics = Array.from(byName.entries()).map(([name, { kind, points }]) => {\n if (kind === 'counter') {\n return {\n name,\n sum: {\n dataPoints: points.map(p => toNumberPoint(p)),\n aggregationTemporality: 2,\n isMonotonic: true,\n },\n };\n }\n if (kind === 'gauge') {\n return { name, gauge: { dataPoints: points.map(p => toNumberPoint(p)) } };\n }\n // histogram: emit a histogram with a single bucket boundary so the\n // Collector treats it as a recorded distribution.\n return {\n name,\n histogram: {\n aggregationTemporality: 2,\n dataPoints: points.map(p => ({\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n count: '1',\n sum: p.value,\n bucketCounts: ['0', '1'],\n explicitBounds: [p.value],\n })),\n },\n };\n });\n\n return {\n resourceMetrics: [{\n resource: { attributes: toAttributes(resource) },\n scopeMetrics: [{\n scope: { name: '@objectstack/observability', version: '0.1.0' },\n metrics,\n }],\n }],\n };\n}\n\nfunction toNumberPoint(p: MetricSample): unknown {\n return {\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n asDouble: p.value,\n };\n}\n\nfunction toAttributes(labels: Record<string, string>): unknown[] {\n return Object.entries(labels).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { ErrorReporter, CapturedError } from './contracts.js';\n\n/** No-op reporter — the default. */\nexport class NoopErrorReporter implements ErrorReporter {\n captureException(): void { }\n}\n\n/** In-memory reporter for tests. */\nexport class InMemoryErrorReporter implements ErrorReporter {\n readonly captured: CapturedError[] = [];\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n this.captured.push({ error, context, at: Date.now() });\n }\n\n reset(): void {\n this.captured.length = 0;\n }\n}\n\n/**\n * Console error reporter — writes a structured JSON line to stderr per\n * captured exception. Convenient default for local development and for\n * any deployment that ships stderr to a log aggregator (e.g. Loki,\n * fluent-bit) but does not have a dedicated APM.\n *\n * Stack traces are included when the captured value is an `Error`.\n */\nexport class ConsoleErrorReporter implements ErrorReporter {\n constructor(private readonly opts: { sink?: (line: string) => void } = {}) { }\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.error(s); });\n const record: Record<string, unknown> = {\n ts: new Date().toISOString(),\n level: 'error',\n msg: error instanceof Error ? error.message : String(error),\n context,\n };\n if (error instanceof Error && error.stack) {\n record.stack = error.stack;\n }\n sink(JSON.stringify(record));\n } catch {\n // Per contract: error reporting must never throw.\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from './contracts.js';\n\n/** Recognised log levels in increasing severity order. */\nexport const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n fatal: 50,\n};\n\n/** No-op logger — discards every message. */\nexport class NoopLogger implements Logger {\n debug(): void { }\n info(): void { }\n warn(): void { }\n error(): void { }\n fatal(): void { }\n child(): Logger { return this; }\n}\n\n/**\n * Console logger — pretty-printed messages for local development.\n *\n * Not suitable for production where structured JSON is required;\n * use {@link JsonLogger} there instead.\n */\nexport class ConsoleLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n } = {},\n ) { }\n\n private get threshold(): number {\n return LEVEL_PRIORITY[this.opts.level ?? 'info'];\n }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> {\n return this.opts.context ?? {};\n }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new ConsoleLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const merged = { ...this.context, ...(meta ?? {}) };\n const tail = Object.keys(merged).length ? ' ' + JSON.stringify(merged) : '';\n const line = `[${level}] ${msg}${tail}`;\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n\n/**\n * JSON logger — one JSON object per line on stdout (errors on stderr).\n *\n * Matches the shape that Loki / fluent-bit / Cloudflare Logpush ingest\n * by default. Every record contains `ts`, `level`, `msg`, and any\n * accumulated child-context plus per-call `meta`.\n *\n * Use this in production. Use {@link ConsoleLogger} during development\n * for human-friendly output.\n */\nexport class JsonLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n /** Optional fields injected into every record (`service`, `env`, …). */\n base?: Record<string, unknown>;\n /** Wall clock for tests. */\n now?: () => Date;\n } = {},\n ) { }\n\n private get threshold(): number { return LEVEL_PRIORITY[this.opts.level ?? 'info']; }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> { return this.opts.context ?? {}; }\n private get base(): Record<string, unknown> { return this.opts.base ?? {}; }\n private get now(): () => Date { return this.opts.now ?? (() => new Date()); }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new JsonLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const record = {\n ts: this.now().toISOString(),\n level,\n msg,\n ...this.base,\n ...this.context,\n ...(meta ?? {}),\n };\n const line = JSON.stringify(record);\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/service-names.ts","../src/semconv.ts","../src/metrics-exporters.ts","../src/error-exporters.ts","../src/loggers.ts","../src/perf-timing.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Canonical service-registry names for the host's observability\n * backends. Plugins look these up to discover the configured\n * {@link MetricsRegistry} / {@link ErrorReporter} without each host\n * having to thread observability config through every plugin\n * constructor.\n *\n * See `@objectstack/runtime` → `ObservabilityServicePlugin` for the\n * registration side.\n */\nexport const OBSERVABILITY_METRICS_SERVICE = 'observability:metrics';\nexport const OBSERVABILITY_ERRORS_SERVICE = 'observability:errors';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Semantic conventions — canonical metric names emitted by the\n * framework. Listed here so hosts can wire alerts/dashboards against\n * a stable namespace and so call sites don't sprinkle string\n * literals through the code base.\n *\n * Naming follows Prometheus conventions:\n *\n * - snake_case identifiers.\n * - `_total` suffix for monotonic counters.\n * - `_ms`, `_seconds`, `_bytes` suffixes for histograms / gauges\n * with units.\n *\n * Groups roughly mirror the framework subsystems that emit them.\n * Cloud-specific metrics (DO restarts, Workers Analytics Engine\n * writes, …) do NOT belong here — they are deployment-specific and\n * stay in the deployment repo.\n */\nexport const SEMCONV = {\n // ── HTTP — emitted by `@objectstack/runtime`'s instrumentRouteHandler ──\n /** Counter, labels: `method`, `route`, `status`. */\n httpRequestsTotal: 'http_requests_total',\n /** Histogram (ms), labels: `method`, `route`. */\n httpRequestDurationMs: 'http_request_duration_ms',\n /**\n * Counter, labels: `method`, `route`. Incremented when an\n * in-flight handler throws after the response is sent.\n */\n httpRequestErrorsTotal: 'http_request_errors_total',\n\n // ── Storage — emitted by `@objectstack/service-storage` adapters ──\n /** Counter, labels: `adapter` (`local`|`s3`|…), `op` (`get`|`put`|`delete`|`head`), `result` (`ok`|`error`). */\n storageOperationsTotal: 'storage_operations_total',\n /** Histogram (ms), labels: `adapter`, `op`. */\n storageOperationDurationMs: 'storage_operation_duration_ms',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n storageErrorsTotal: 'storage_errors_total',\n\n // ── Cache — emitted by `@objectstack/service-cache` adapters ──\n /** Counter, labels: `adapter` (`memory`|`redis`), `result` (`hit`|`miss`). */\n cacheLookupsTotal: 'cache_lookups_total',\n /** Counter, labels: `adapter`, `op` (`set`|`delete`|`clear`). */\n cacheWritesTotal: 'cache_writes_total',\n /** Counter, labels: `adapter`, `op`, `errorClass`. */\n cacheErrorsTotal: 'cache_errors_total',\n\n // ── Package / registry-reader — emitted by `@objectstack/service-package` ──\n /** Counter, labels: `result` (`ok`|`miss`|`error`). */\n registryLookupsTotal: 'registry_lookups_total',\n /** Histogram (ms). */\n registryLookupDurationMs: 'registry_lookup_duration_ms',\n /** Counter, labels: `source` (`r2`|`http`|`local`), `result` (`hit`|`miss`|`error`). */\n registrySourceFetchesTotal: 'registry_source_fetches_total',\n} as const;\n\n/**\n * Backwards-compat alias. `RUNTIME_METRICS` was the original (HTTP-only)\n * constant name shipped from `@objectstack/runtime`; we keep it here so\n * existing code reading `RUNTIME_METRICS.httpRequestsTotal` continues\n * to work after the constants moved into this package.\n */\nexport const RUNTIME_METRICS = {\n httpRequestsTotal: SEMCONV.httpRequestsTotal,\n httpRequestDurationMs: SEMCONV.httpRequestDurationMs,\n httpRequestErrorsTotal: SEMCONV.httpRequestErrorsTotal,\n} as const;\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MetricsRegistry, MetricSample } from './contracts.js';\n\n// ─── Noop ─────────────────────────────────────────────────────────────\n\n/**\n * No-op metrics registry — the default. Discards every observation.\n * Production deployments should swap this for a real registry; tests\n * can use {@link InMemoryMetricsRegistry} to assert emissions.\n */\nexport class NoopMetricsRegistry implements MetricsRegistry {\n counter(): void { }\n histogram(): void { }\n gauge(): void { }\n}\n\n// ─── In-memory (tests + dev inspection) ───────────────────────────────\n\n/**\n * In-memory registry used for tests and local inspection. Stores\n * every observation in insertion order; query via the helpers below\n * or read {@link samples} directly.\n *\n * Not intended for production — unbounded growth.\n */\nexport class InMemoryMetricsRegistry implements MetricsRegistry {\n readonly samples: MetricSample[] = [];\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.samples.push({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.samples.push({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n /** Sum of counter increments matching `name` and optionally a label subset. */\n totalCounter(name: string, labelMatch: Record<string, string> = {}): number {\n return this.samples\n .filter(s => s.kind === 'counter' && s.name === name && matchesLabels(s.labels, labelMatch))\n .reduce((acc, s) => acc + s.value, 0);\n }\n\n /** Raw histogram observations matching `name` and optionally a label subset. */\n histogramValues(name: string, labelMatch: Record<string, string> = {}): number[] {\n return this.samples\n .filter(s => s.kind === 'histogram' && s.name === name && matchesLabels(s.labels, labelMatch))\n .map(s => s.value);\n }\n\n /** Last gauge value matching `name` and optionally a label subset, or undefined. */\n lastGauge(name: string, labelMatch: Record<string, string> = {}): number | undefined {\n for (let i = this.samples.length - 1; i >= 0; i--) {\n const s = this.samples[i];\n if (s.kind === 'gauge' && s.name === name && matchesLabels(s.labels, labelMatch)) {\n return s.value;\n }\n }\n return undefined;\n }\n\n /** Clear all recorded samples. */\n reset(): void {\n this.samples.length = 0;\n }\n}\n\nfunction matchesLabels(actual: Record<string, string>, expected: Record<string, string>): boolean {\n for (const [k, v] of Object.entries(expected)) {\n if (actual[k] !== v) return false;\n }\n return true;\n}\n\n// ─── Console (development) ────────────────────────────────────────────\n\n/**\n * Console metrics registry — prints one line per observation. Useful\n * during local development to confirm that instrumentation is firing.\n *\n * Not intended for production: writing every observation to stdout\n * defeats Prometheus / OTLP pipelines and dominates request latency.\n */\nexport class ConsoleMetricsRegistry implements MetricsRegistry {\n constructor(private readonly opts: { sink?: (line: string) => void; prefix?: string } = {}) { }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.emit('counter', name, value, labels);\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('histogram', name, value, labels);\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.emit('gauge', name, value, labels);\n }\n\n private emit(kind: string, name: string, value: number, labels: Record<string, string>): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.log(s); });\n const prefix = this.opts.prefix ?? '[metric]';\n const labelStr = Object.entries(labels)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(' ');\n sink(`${prefix} ${kind} ${name} ${value}${labelStr ? ' ' + labelStr : ''}`);\n } catch {\n // Per contract: never throw from a metric call site.\n }\n }\n}\n\n// ─── OTLP / HTTP (Prometheus via OTel Collector, Grafana Cloud, …) ────\n\n/**\n * Configuration for {@link OtlpHttpMetricsRegistry}.\n */\nexport interface OtlpHttpExporterOptions {\n /**\n * OTLP/HTTP metrics endpoint, e.g. `http://otel-collector:4318/v1/metrics`.\n * The path is appended automatically if missing.\n */\n endpoint: string;\n\n /** Optional headers (Authorization, x-tenant, …). */\n headers?: Record<string, string>;\n\n /**\n * Resource attributes — `service.name`, `service.namespace`,\n * `deployment.environment`, etc. Merged into the OTLP `resource`\n * block on every export.\n */\n resource?: Record<string, string>;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Allows Workers / undici / node-fetch substitution and test\n * doubles.\n */\n fetch?: typeof fetch;\n\n /**\n * Called when an export attempt fails. Default: silently swallow\n * (per the contract that metric emission must not throw / log\n * loudly on the hot path).\n */\n onError?: (error: unknown) => void;\n\n /**\n * Maximum number of samples buffered before {@link OtlpHttpMetricsRegistry.flush}\n * is called automatically. Defaults to 1024.\n */\n maxBufferSize?: number;\n}\n\n/**\n * OTLP/HTTP metrics exporter.\n *\n * Buffers samples in memory and serialises them to the OpenTelemetry\n * Protocol JSON encoding when {@link flush} is called (manually or\n * automatically once the buffer hits the configured size).\n *\n * Intentionally does **not** start an interval timer in the constructor:\n * (a) it makes the exporter usable on Cloudflare Workers where\n * `setInterval` is restricted, and (b) it keeps unit tests deterministic.\n * Long-running hosts should call `flush()` on a schedule\n * (e.g. `setInterval(() => reg.flush(), 10_000)` on Node, or\n * `ctx.waitUntil(reg.flush())` from a Workers fetch handler).\n *\n * Only counters, histograms, and gauges are emitted — no support for\n * exemplars or aggregation temporality switches (the Collector handles\n * those on the upstream side).\n */\nexport class OtlpHttpMetricsRegistry implements MetricsRegistry {\n private buffer: MetricSample[] = [];\n private readonly endpoint: string;\n private readonly headers: Record<string, string>;\n private readonly resource: Record<string, string>;\n private readonly maxBufferSize: number;\n private readonly fetchImpl: typeof fetch;\n private readonly onError: (error: unknown) => void;\n\n constructor(options: OtlpHttpExporterOptions) {\n this.endpoint = normaliseEndpoint(options.endpoint);\n this.headers = options.headers ?? {};\n this.resource = options.resource ?? {};\n this.maxBufferSize = options.maxBufferSize ?? 1024;\n this.fetchImpl = options.fetch ?? (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : noFetch);\n this.onError = options.onError ?? (() => { });\n }\n\n counter(name: string, labels: Record<string, string> = {}, value: number = 1): void {\n this.record({ name, kind: 'counter', value, labels, at: Date.now() });\n }\n histogram(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'histogram', value, labels, at: Date.now() });\n }\n gauge(name: string, value: number, labels: Record<string, string> = {}): void {\n this.record({ name, kind: 'gauge', value, labels, at: Date.now() });\n }\n\n private record(sample: MetricSample): void {\n this.buffer.push(sample);\n if (this.buffer.length >= this.maxBufferSize) {\n // Fire-and-forget: do not block the call site.\n void this.flush().catch(this.onError);\n }\n }\n\n /** Snapshot the current buffer (for tests). */\n peek(): readonly MetricSample[] {\n return this.buffer.slice();\n }\n\n /**\n * Send the buffered samples to the OTLP endpoint and clear the\n * buffer. Safe to call concurrently — each invocation takes a\n * snapshot before clearing.\n */\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n const samples = this.buffer;\n this.buffer = [];\n try {\n const body = JSON.stringify(serialiseToOtlp(samples, this.resource));\n const res = await this.fetchImpl(this.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...this.headers },\n body,\n });\n if (!res.ok) {\n this.onError(new Error(`OTLP export returned HTTP ${res.status}`));\n }\n } catch (err) {\n this.onError(err);\n }\n }\n}\n\nfunction normaliseEndpoint(raw: string): string {\n const trimmed = raw.replace(/\\/+$/, '');\n if (/\\/v1\\/metrics$/.test(trimmed)) return trimmed;\n return trimmed + '/v1/metrics';\n}\n\nfunction noFetch(): never {\n throw new Error('OtlpHttpMetricsRegistry: no global fetch available; pass options.fetch');\n}\n\n/**\n * Minimal OTLP/JSON metrics serialiser.\n *\n * Implements the subset of <https://opentelemetry.io/docs/specs/otlp/>\n * that we actually emit — sums for counters, gauges for gauges,\n * histograms encoded as bucketless \"summary\"-style histograms with\n * explicit per-sample data points. The Collector accepts this shape\n * and applies its own bucketing.\n *\n * Aggregation temporality is set to DELTA (2) because every flush\n * sends only the samples accumulated since the last flush.\n */\nfunction serialiseToOtlp(samples: MetricSample[], resource: Record<string, string>): unknown {\n const byName = new Map<string, { kind: MetricSample['kind']; points: MetricSample[] }>();\n for (const s of samples) {\n const existing = byName.get(s.name);\n if (existing) existing.points.push(s);\n else byName.set(s.name, { kind: s.kind, points: [s] });\n }\n\n const metrics = Array.from(byName.entries()).map(([name, { kind, points }]) => {\n if (kind === 'counter') {\n return {\n name,\n sum: {\n dataPoints: points.map(p => toNumberPoint(p)),\n aggregationTemporality: 2,\n isMonotonic: true,\n },\n };\n }\n if (kind === 'gauge') {\n return { name, gauge: { dataPoints: points.map(p => toNumberPoint(p)) } };\n }\n // histogram: emit a histogram with a single bucket boundary so the\n // Collector treats it as a recorded distribution.\n return {\n name,\n histogram: {\n aggregationTemporality: 2,\n dataPoints: points.map(p => ({\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n count: '1',\n sum: p.value,\n bucketCounts: ['0', '1'],\n explicitBounds: [p.value],\n })),\n },\n };\n });\n\n return {\n resourceMetrics: [{\n resource: { attributes: toAttributes(resource) },\n scopeMetrics: [{\n scope: { name: '@objectstack/observability', version: '0.1.0' },\n metrics,\n }],\n }],\n };\n}\n\nfunction toNumberPoint(p: MetricSample): unknown {\n return {\n attributes: toAttributes(p.labels),\n timeUnixNano: String(p.at) + '000000',\n startTimeUnixNano: String(p.at) + '000000',\n asDouble: p.value,\n };\n}\n\nfunction toAttributes(labels: Record<string, string>): unknown[] {\n return Object.entries(labels).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { ErrorReporter, CapturedError } from './contracts.js';\n\n/** No-op reporter — the default. */\nexport class NoopErrorReporter implements ErrorReporter {\n captureException(): void { }\n}\n\n/** In-memory reporter for tests. */\nexport class InMemoryErrorReporter implements ErrorReporter {\n readonly captured: CapturedError[] = [];\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n this.captured.push({ error, context, at: Date.now() });\n }\n\n reset(): void {\n this.captured.length = 0;\n }\n}\n\n/**\n * Console error reporter — writes a structured JSON line to stderr per\n * captured exception. Convenient default for local development and for\n * any deployment that ships stderr to a log aggregator (e.g. Loki,\n * fluent-bit) but does not have a dedicated APM.\n *\n * Stack traces are included when the captured value is an `Error`.\n */\nexport class ConsoleErrorReporter implements ErrorReporter {\n constructor(private readonly opts: { sink?: (line: string) => void } = {}) { }\n\n captureException(error: unknown, context: Record<string, unknown> = {}): void {\n try {\n const sink = this.opts.sink ?? ((s) => { console.error(s); });\n const record: Record<string, unknown> = {\n ts: new Date().toISOString(),\n level: 'error',\n msg: error instanceof Error ? error.message : String(error),\n context,\n };\n if (error instanceof Error && error.stack) {\n record.stack = error.stack;\n }\n sink(JSON.stringify(record));\n } catch {\n // Per contract: error reporting must never throw.\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from './contracts.js';\n\n/** Recognised log levels in increasing severity order. */\nexport const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n fatal: 50,\n};\n\n/** No-op logger — discards every message. */\nexport class NoopLogger implements Logger {\n debug(): void { }\n info(): void { }\n warn(): void { }\n error(): void { }\n fatal(): void { }\n child(): Logger { return this; }\n}\n\n/**\n * Console logger — pretty-printed messages for local development.\n *\n * Not suitable for production where structured JSON is required;\n * use {@link JsonLogger} there instead.\n */\nexport class ConsoleLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n } = {},\n ) { }\n\n private get threshold(): number {\n return LEVEL_PRIORITY[this.opts.level ?? 'info'];\n }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> {\n return this.opts.context ?? {};\n }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new ConsoleLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const merged = { ...this.context, ...(meta ?? {}) };\n const tail = Object.keys(merged).length ? ' ' + JSON.stringify(merged) : '';\n const line = `[${level}] ${msg}${tail}`;\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n\n/**\n * JSON logger — one JSON object per line on stdout (errors on stderr).\n *\n * Matches the shape that Loki / fluent-bit / Cloudflare Logpush ingest\n * by default. Every record contains `ts`, `level`, `msg`, and any\n * accumulated child-context plus per-call `meta`.\n *\n * Use this in production. Use {@link ConsoleLogger} during development\n * for human-friendly output.\n */\nexport class JsonLogger implements Logger {\n constructor(\n private readonly opts: {\n level?: LogLevel;\n context?: Record<string, unknown>;\n sink?: { log: (s: string) => void; error: (s: string) => void };\n /** Optional fields injected into every record (`service`, `env`, …). */\n base?: Record<string, unknown>;\n /** Wall clock for tests. */\n now?: () => Date;\n } = {},\n ) { }\n\n private get threshold(): number { return LEVEL_PRIORITY[this.opts.level ?? 'info']; }\n private get sink() {\n return this.opts.sink ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };\n }\n private get context(): Record<string, unknown> { return this.opts.context ?? {}; }\n private get base(): Record<string, unknown> { return this.opts.base ?? {}; }\n private get now(): () => Date { return this.opts.now ?? (() => new Date()); }\n\n debug(message: string, meta?: Record<string, unknown>): void { this.emit('debug', message, meta); }\n info(message: string, meta?: Record<string, unknown>): void { this.emit('info', message, meta); }\n warn(message: string, meta?: Record<string, unknown>): void { this.emit('warn', message, meta); }\n error(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n fatal(message: string, error?: Error, meta?: Record<string, unknown>): void {\n this.emit('fatal', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });\n }\n child(context: Record<string, unknown>): Logger {\n return new JsonLogger({ ...this.opts, context: { ...this.context, ...context } });\n }\n\n private emit(level: LogLevel, msg: string, meta?: Record<string, unknown>): void {\n if (LEVEL_PRIORITY[level] < this.threshold) return;\n try {\n const record = {\n ts: this.now().toISOString(),\n level,\n msg,\n ...this.base,\n ...this.context,\n ...(meta ?? {}),\n };\n const line = JSON.stringify(record);\n if (level === 'error' || level === 'fatal') this.sink.error(line);\n else this.sink.log(line);\n } catch {\n // Log emission must never throw.\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Per-request performance timing - a tiny, dependency-free collector that\n * accumulates named phase durations during a single request and serializes\n * them into the W3C `Server-Timing` response header.\n *\n * @see <https://www.w3.org/TR/server-timing/>\n * @see <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing>\n *\n * Two ways to record:\n *\n * 1. **Explicit collector.** Hold a {@link PerfTiming} instance and call\n * `start()` / `record()` / `measure()` directly. The HTTP adapter owns\n * the instance for a request.\n *\n * 2. **Ambient collector.** Run a request inside {@link runWithPerfTiming}\n * and any framework code on that async call chain records phases via the\n * free functions ({@link measureServerTiming}, {@link startServerTiming},\n * {@link recordServerTiming}) without threading the request object\n * through every layer. When no collector is active the free functions are\n * cheap no-ops, so call sites pay nothing when the feature is off.\n *\n * `Server-Timing` exposes internal phase durations to any client, which is a\n * (mild) information-disclosure surface - it helps an attacker profile the\n * backend. Emission is therefore opt-in (\"perf-tuning mode\"); the collector\n * itself never decides whether to emit, it only measures.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * One recorded phase of a request's server-side processing, serialized as a\n * single member of the `Server-Timing` header.\n */\nexport interface ServerTimingMark {\n /** Metric name. Coerced to a Server-Timing token on record. */\n name: string;\n /** Duration in milliseconds. */\n dur: number;\n /** Optional human-readable description (rendered as the quoted `desc`). */\n desc?: string;\n}\n\n/**\n * Monotonic millisecond clock. Prefers `performance.now()` (monotonic, not\n * affected by wall-clock adjustments); falls back to `Date.now()` on the rare\n * runtime where `performance` is unavailable.\n */\nexport function perfNow(): number {\n try {\n return performance.now();\n } catch {\n return Date.now();\n }\n}\n\n/** Characters not allowed in a Server-Timing metric name (a token). */\nconst NAME_UNSAFE = /[^A-Za-z0-9_-]+/g;\n\nconst UNDERSCORE = 0x5f;\n\n/**\n * Coerce an arbitrary string into a Server-Timing token: non-token runs become\n * a single underscore, then leading/trailing underscores are trimmed.\n *\n * The trim is a linear scan rather than a `/^_+|_+$/g` regex on purpose - the\n * anchored `_+$` quantifier backtracks polynomially on underscore-heavy input\n * (CodeQL js/polynomial-redos), and this name comes from a public-API argument.\n */\nfunction sanitizeName(name: string): string {\n const collapsed = String(name).replace(NAME_UNSAFE, '_');\n let start = 0;\n let end = collapsed.length;\n while (start < end && collapsed.charCodeAt(start) === UNDERSCORE) start++;\n while (end > start && collapsed.charCodeAt(end - 1) === UNDERSCORE) end--;\n return collapsed.slice(start, end);\n}\n\n/**\n * Make a description safe to embed in a quoted-string. Backslashes and double\n * quotes would terminate the quoting; control chars (incl. CR/LF) could forge\n * headers. Collapse anything outside a conservative printable set to a space.\n */\nfunction sanitizeDesc(desc: string): string {\n let out = '';\n for (const ch of String(desc)) {\n const code = ch.codePointAt(0)!;\n // Printable ASCII excluding `\"` (0x22) and `\\` (0x5C); drop the rest.\n if (code >= 0x20 && code < 0x7f && ch !== '\"' && ch !== '\\\\') {\n out += ch;\n } else {\n out += ' ';\n }\n }\n return out.replace(/ +/g, ' ').trim();\n}\n\n/** Round to at most 2 decimals without trailing-zero noise (`12.3`, not `12.30`). */\nfunction fmtDur(dur: number): string {\n if (!Number.isFinite(dur)) return '0';\n return String(Math.round(dur * 100) / 100);\n}\n\n/**\n * Serialize marks into a `Server-Timing` header value. Marks with an empty\n * name after sanitization are dropped (the grammar requires a token). Returns\n * `''` when there is nothing to emit so callers can skip the header.\n */\nexport function formatServerTiming(marks: readonly ServerTimingMark[]): string {\n const parts: string[] = [];\n for (const m of marks) {\n const name = sanitizeName(m.name);\n if (!name) continue;\n let part = `${name};dur=${fmtDur(m.dur)}`;\n if (m.desc) {\n const desc = sanitizeDesc(m.desc);\n if (desc) part += `;desc=\"${desc}\"`;\n }\n parts.push(part);\n }\n return parts.join(', ');\n}\n\n/**\n * Collector for one request's timing phases. Not thread-safe by design - one\n * instance belongs to one request. All methods are allocation-light and never\n * throw on the hot path.\n */\nexport class PerfTiming {\n private readonly _marks: ServerTimingMark[] = [];\n\n /** Record an already-measured phase. */\n record(name: string, dur: number, desc?: string): void {\n this._marks.push({ name, dur, desc });\n }\n\n /**\n * Begin timing a phase. Returns an idempotent `end()` - the first call\n * records the elapsed duration; later calls are ignored, so it is safe to\n * call from both a success and an error path.\n */\n start(name: string, desc?: string): () => void {\n const t0 = perfNow();\n let done = false;\n return () => {\n if (done) return;\n done = true;\n this.record(name, perfNow() - t0, desc);\n };\n }\n\n /** Time an async (or sync) function, recording its elapsed duration. */\n async measure<T>(name: string, fn: () => T | Promise<T>, desc?: string): Promise<T> {\n const end = this.start(name, desc);\n try {\n return await fn();\n } finally {\n end();\n }\n }\n\n /** Snapshot of recorded marks, in record order. */\n marks(): readonly ServerTimingMark[] {\n return this._marks;\n }\n\n /** Serialize to a `Server-Timing` header value (`''` when empty). */\n toHeader(): string {\n return formatServerTiming(this._marks);\n }\n}\n\n// --- Ambient (request-scoped) collector -------------------------------\n\nconst store = new AsyncLocalStorage<PerfTiming>();\n\n/** Run `fn` with `timing` as the ambient collector for the async call chain. */\nexport function runWithPerfTiming<T>(timing: PerfTiming, fn: () => T): T {\n return store.run(timing, fn);\n}\n\n/** The collector for the current request, or `undefined` outside a request. */\nexport function currentPerfTiming(): PerfTiming | undefined {\n return store.getStore();\n}\n\n/** Record a phase on the ambient collector. No-op when none is active. */\nexport function recordServerTiming(name: string, dur: number, desc?: string): void {\n store.getStore()?.record(name, dur, desc);\n}\n\n/**\n * Begin timing a phase on the ambient collector. Returns an `end()` callback;\n * when no collector is active the returned callback is a no-op so call sites\n * stay branch-free.\n */\nexport function startServerTiming(name: string, desc?: string): () => void {\n const t = store.getStore();\n if (!t) return () => {};\n return t.start(name, desc);\n}\n\n/**\n * Time an async function on the ambient collector. When no collector is active\n * the function is awaited with zero timing overhead.\n */\nexport async function measureServerTiming<T>(\n name: string,\n fn: () => T | Promise<T>,\n desc?: string,\n): Promise<T> {\n const t = store.getStore();\n if (!t) return fn();\n return t.measure(name, fn, desc);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/observability",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.1.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Observability contracts and exporters for ObjectStack — MetricsRegistry, ErrorReporter, Logger plus noop/console/OTLP-HTTP exporters. Deployment-target neutral; runtime and services depend on this so the same instrumentation works on Cloudflare Workers, Node, and self-hosted Kubernetes.",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/spec": "
|
|
17
|
+
"@objectstack/spec": "11.1.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "^26.0.0",
|