@arcote.tech/arc-otel 0.7.6

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.
@@ -0,0 +1,39 @@
1
+ /** Keys matching this pattern are dropped wholesale, regardless of nesting. */
2
+ export declare const DEFAULT_REDACT_KEY_PATTERN: RegExp;
3
+ /** Truncate string values longer than this; backends typically cap span
4
+ * attributes around 8-16 KB total, so 2 KB per value is generous but safe. */
5
+ export declare const DEFAULT_MAX_STRING_LEN = 2048;
6
+ /** Serialized objects/arrays beyond this size are truncated. Some backends
7
+ * silently drop spans whose attributes exceed their limit (Tempo ~16 KB). */
8
+ export declare const DEFAULT_MAX_JSON_LEN = 4096;
9
+ export interface SanitizeOptions {
10
+ /** Override the default redaction pattern. Combined via OR. */
11
+ redactKeyPattern?: RegExp;
12
+ /** Max string length (per value). Default 2048. */
13
+ maxStringLen?: number;
14
+ /** Max serialized object/array length (per value). Default 4096. */
15
+ maxJsonLen?: number;
16
+ }
17
+ /**
18
+ * Build a flat attribute map suitable for OpenTelemetry's setAttributes().
19
+ *
20
+ * - Removes any key matching the redact pattern (case-insensitive).
21
+ * - Recursively walks objects/arrays; nested redacted keys are dropped too.
22
+ * - Primitive values (string/number/boolean) pass through.
23
+ * - Objects/arrays are JSON-stringified with size cap.
24
+ * - Strings beyond the cap are truncated with an explicit suffix.
25
+ * - undefined/null values are dropped (OTel rejects them anyway).
26
+ *
27
+ * The output is intentionally a flat `Record<string, primitive>` because the
28
+ * OTel SDK only persists primitive attribute values.
29
+ */
30
+ export declare function sanitizeAttrs(input: Record<string, unknown> | null | undefined, opts?: SanitizeOptions): Record<string, string | number | boolean>;
31
+ /**
32
+ * Strip password from a Postgres-style connection string for safe logging.
33
+ * Returns the URL unchanged when no password component is present, and
34
+ * "[unparseable]" if the input is not a URL at all.
35
+ *
36
+ * postgresql://arc:secret@db:5432/arc → postgresql://arc:***@db:5432/arc
37
+ */
38
+ export declare function redactConnectionString(url: string | undefined | null): string;
39
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAUA,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,QAC2D,CAAC;AAEnG;+EAC+E;AAC/E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;8EAC8E;AAC9E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EACjD,IAAI,GAAE,eAAoB,GACzB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAa3C;AA6CD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAS7E"}
@@ -0,0 +1,99 @@
1
+ import { SpanKind, type Attributes, type Span, type Tracer } from "@opentelemetry/api";
2
+ import { type Logger } from "@opentelemetry/api-logs";
3
+ import { type Meter } from "@opentelemetry/api";
4
+ import { type SanitizeOptions } from "./sanitize";
5
+ export type ObservabilityMode = "development" | "production" | "disabled";
6
+ export interface TelemetryConfig {
7
+ /** OTel `service.name` resource attribute. Required. */
8
+ serviceName: string;
9
+ /** Side this runs on. Determines default sampler + propagator setup in init. */
10
+ environment: "client" | "server";
11
+ /** OTLP HTTP endpoint, e.g. `http://otel-collector:4318`. Init functions
12
+ * append signal-specific paths (`/v1/traces`, `/v1/logs`, `/v1/metrics`). */
13
+ endpoint?: string;
14
+ /** When false, all telemetry APIs are no-ops. Default true. */
15
+ enabled?: boolean;
16
+ /** Head-based sample rate at the SDK level. Default 1.0 (server) / 0.1 (client).
17
+ * Collector tail-sampling can still drop or upgrade these. */
18
+ sampleRate?: number;
19
+ /** development = verbose attrs + 100% sample; production = strict redaction
20
+ * + low sample. `disabled` is equivalent to enabled=false. */
21
+ mode?: ObservabilityMode;
22
+ /** Override the auto default for `includePayloads` (true in dev, false elsewhere). */
23
+ includePayloads?: boolean;
24
+ /** Extra debug logging to stdout during init. Off by default. */
25
+ debug?: boolean;
26
+ /** Override sanitization defaults (regex / size caps). */
27
+ sanitize?: SanitizeOptions;
28
+ }
29
+ /** Span options. Same shape as OTel's but with optional Arc-friendly defaults. */
30
+ export interface SpanOptions {
31
+ kind?: SpanKind;
32
+ attributes?: Record<string, unknown>;
33
+ /** Skip sanitization for `attributes`. Use only when the caller has filtered
34
+ * payloads themselves AND the values fit the size cap. */
35
+ unsafeAttrs?: boolean;
36
+ }
37
+ export declare class ArcTelemetry {
38
+ readonly config: Required<Pick<TelemetryConfig, "enabled" | "sampleRate" | "mode" | "debug">> & TelemetryConfig;
39
+ private tracer;
40
+ private logger;
41
+ private meter;
42
+ private histograms;
43
+ private counters;
44
+ constructor(config: TelemetryConfig);
45
+ /** Called by init-server / init-browser after the SDK provider is wired. */
46
+ attach(opts: {
47
+ tracer: Tracer;
48
+ logger?: Logger;
49
+ meter?: Meter;
50
+ }): void;
51
+ /** Whether the SDK is wired and exporting. False during construction or when disabled. */
52
+ get active(): boolean;
53
+ /** True in dev mode by default, false in prod. Instrumentation uses this to
54
+ * decide whether to attach raw payloads as attributes. */
55
+ shouldIncludePayloads(): boolean;
56
+ /**
57
+ * Run `fn` inside an active span. The span auto-records exceptions, sets
58
+ * ERROR status on throw, and ends in the finally block.
59
+ *
60
+ * When telemetry is disabled or unattached, the span argument passed to
61
+ * `fn` is the OTel non-recording span. Calls on it (setAttribute, etc.)
62
+ * are no-ops, so callers don't need to null-check.
63
+ */
64
+ startSpan<T>(name: string, fn: (span: Span) => T | Promise<T>, options?: SpanOptions): Promise<T>;
65
+ /**
66
+ * Manually create a span without an active scope. Caller is responsible
67
+ * for ending it. Useful for fire-and-forget instrumentation (e.g. a
68
+ * long-lived WebSocket connection).
69
+ */
70
+ createSpan(name: string, options?: SpanOptions): Span;
71
+ /** Get the currently active span (if any) for adding attributes / events
72
+ * outside the immediate `startSpan` callback. */
73
+ getCurrentSpan(): Span | undefined;
74
+ /** Run `fn` with `parent` as the active span. Lets us bridge async
75
+ * boundaries where AsyncLocalStorage doesn't help (e.g. WS message
76
+ * dispatch, where the parent came over the wire as `traceparent`). */
77
+ withSpan<T>(parent: Span, fn: () => T): T;
78
+ /**
79
+ * Extract a W3C trace context from a carrier (HTTP headers, WS message
80
+ * payload) and run `fn` inside it. Convenience wrapper so callers don't
81
+ * need to import from `@opentelemetry/api` directly. Returns whatever
82
+ * `fn` returns. Safe when telemetry is disabled — falls through to
83
+ * `fn(activeContext)`.
84
+ */
85
+ runWithExtractedContext<T>(carrier: Record<string, unknown> | Headers | null | undefined, fn: () => T): T;
86
+ /** Mark a span as errored. Safe to call with non-Error throwables. */
87
+ recordError(span: Span, error: unknown): void;
88
+ /** Attach attributes to the currently active span. No-op when none exists. */
89
+ addAttributes(attrs: Record<string, unknown>, unsafeAttrs?: boolean): void;
90
+ log(level: "debug" | "info" | "warn" | "error", body: string, attrs?: Record<string, unknown>, unsafeAttrs?: boolean): void;
91
+ /** Increment a counter by `value` (default 1). Counter is created on first use. */
92
+ incrementCounter(name: string, value?: number, attrs?: Attributes): void;
93
+ /** Record a value into a histogram (e.g. latency ms). Created lazily. */
94
+ recordHistogram(name: string, value: number, attrs?: Attributes): void;
95
+ /** Convenience: record `Date.now() - start` into a histogram. */
96
+ measureSince(name: string, start: number, attrs?: Attributes): void;
97
+ private toAttributes;
98
+ }
99
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { ArcTelemetry } from "./telemetry";
2
+ interface ReadTxLike {
3
+ find(store: string, options: unknown): Promise<unknown[]>;
4
+ }
5
+ interface ReadWriteTxLike extends ReadTxLike {
6
+ remove(store: string, id: unknown): Promise<void>;
7
+ set(store: string, data: unknown): Promise<void>;
8
+ commit(): Promise<void>;
9
+ }
10
+ interface AdapterLike {
11
+ readWriteTransaction(stores?: string[]): ReadWriteTxLike;
12
+ readTransaction(stores?: string[]): ReadTxLike;
13
+ executeReinitTables(dataStorage: unknown): Promise<void>;
14
+ destroy?(): Promise<void>;
15
+ }
16
+ /**
17
+ * Wrap a `DatabaseAdapter`-shaped object so every transaction's operations
18
+ * emit child spans of the active context. Safe to call with an
19
+ * already-wrapped adapter (no double-wrap detection — the OTel overhead of
20
+ * a duplicate span is negligible if it ever happened, and adapter identity
21
+ * checks would couple us to specific implementations).
22
+ *
23
+ * The returned adapter has the SAME structural type as the input; pass it
24
+ * straight where the original would go. No-op when `telemetry` is disabled.
25
+ */
26
+ export declare function wrapDbAdapter<A extends AdapterLike>(adapter: A, telemetry: ArcTelemetry | undefined, dbSystem: "postgresql" | "sqlite"): A;
27
+ export {};
28
+ //# sourceMappingURL=wrap-db-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-db-adapter.d.ts","sourceRoot":"","sources":["../src/wrap-db-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAqBhD,UAAU,UAAU;IAClB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC3D;AACD,UAAU,eAAgB,SAAQ,UAAU;IAC1C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AACD,UAAU,WAAW;IACnB,oBAAoB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC;IACzD,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IAC/C,mBAAmB,CAAC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,EACjD,OAAO,EAAE,CAAC,EACV,SAAS,EAAE,YAAY,GAAG,SAAS,EACnC,QAAQ,EAAE,YAAY,GAAG,QAAQ,GAChC,CAAC,CAqFH"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@arcote.tech/arc-otel",
3
+ "version": "0.7.6",
4
+ "description": "OpenTelemetry instrumentation primitives for the Arc framework — server + browser SDK init, span helpers, PII-safe attribute sanitization, W3C Trace Context propagation",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./server": {
17
+ "import": "./dist/init-server.js",
18
+ "types": "./dist/init-server.d.ts"
19
+ },
20
+ "./browser": {
21
+ "import": "./dist/init-browser.js",
22
+ "types": "./dist/init-browser.d.ts"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "build": "bun build ./src/index.ts ./src/init-server.ts ./src/init-browser.ts --outdir ./dist --target node --format esm --external @opentelemetry/* && bun run build:types",
27
+ "build:types": "tsc --emitDeclarationOnly --declaration --outDir ./dist",
28
+ "dev": "bun build ./src/index.ts ./src/init-server.ts ./src/init-browser.ts --outdir ./dist --target node --format esm --watch --external @opentelemetry/*"
29
+ },
30
+ "dependencies": {
31
+ "@opentelemetry/api": "^1.9.0",
32
+ "@opentelemetry/api-logs": "^0.57.0",
33
+ "@opentelemetry/core": "^1.30.0",
34
+ "@opentelemetry/exporter-logs-otlp-http": "^0.57.0",
35
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.57.0",
36
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
37
+ "@opentelemetry/resources": "^1.30.0",
38
+ "@opentelemetry/sdk-logs": "^0.57.0",
39
+ "@opentelemetry/sdk-metrics": "^1.30.0",
40
+ "@opentelemetry/sdk-trace-base": "^1.30.0",
41
+ "@opentelemetry/sdk-trace-node": "^1.30.0",
42
+ "@opentelemetry/sdk-trace-web": "^1.30.0",
43
+ "@opentelemetry/semantic-conventions": "^1.27.0"
44
+ },
45
+ "peerDependencies": {
46
+ "@arcote.tech/arc": "^0.7.6"
47
+ },
48
+ "devDependencies": {
49
+ "typescript": "^5.0.0"
50
+ }
51
+ }