@dxos/observability 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/lib/chunk-log-processor.mjs +28 -0
  2. package/dist/lib/chunk-log-processor.mjs.map +1 -0
  3. package/dist/lib/chunk-logs.mjs +89 -0
  4. package/dist/lib/chunk-logs.mjs.map +1 -0
  5. package/dist/lib/chunk-metrics.mjs +118 -0
  6. package/dist/lib/chunk-metrics.mjs.map +1 -0
  7. package/dist/lib/chunk-otel.mjs +20 -0
  8. package/dist/lib/chunk-otel.mjs.map +1 -0
  9. package/dist/lib/chunk-traces-browser.mjs +145 -0
  10. package/dist/lib/chunk-traces-browser.mjs.map +1 -0
  11. package/dist/lib/index.mjs +977 -0
  12. package/dist/lib/index.mjs.map +1 -0
  13. package/dist/types/src/cli-observability-secrets.json +2 -2
  14. package/dist/types/src/extensions/otel/logs.d.ts +1 -0
  15. package/dist/types/src/extensions/otel/logs.d.ts.map +1 -1
  16. package/dist/types/src/extensions/otel/logs.js +11 -4
  17. package/dist/types/src/extensions/otel/logs.js.map +1 -1
  18. package/dist/types/src/extensions/otel/logs.test.d.ts +2 -0
  19. package/dist/types/src/extensions/otel/logs.test.d.ts.map +1 -0
  20. package/dist/types/src/extensions/otel/logs.test.js +33 -0
  21. package/dist/types/src/extensions/otel/logs.test.js.map +1 -0
  22. package/dist/types/src/extensions/posthog/extension.d.ts.map +1 -1
  23. package/dist/types/src/extensions/posthog/extension.js +4 -0
  24. package/dist/types/src/extensions/posthog/extension.js.map +1 -1
  25. package/dist/types/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +31 -31
  27. package/src/cli-observability-secrets.json +2 -2
  28. package/src/extensions/otel/logs.test.ts +39 -0
  29. package/src/extensions/otel/logs.ts +11 -4
  30. package/src/extensions/posthog/extension.ts +4 -0
  31. package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
  32. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
  33. package/dist/lib/browser/chunk-PSFTIG54.mjs +0 -20
  34. package/dist/lib/browser/chunk-PSFTIG54.mjs.map +0 -7
  35. package/dist/lib/browser/index.mjs +0 -1015
  36. package/dist/lib/browser/index.mjs.map +0 -7
  37. package/dist/lib/browser/log-processor-MZCTEVJC.mjs +0 -46
  38. package/dist/lib/browser/log-processor-MZCTEVJC.mjs.map +0 -7
  39. package/dist/lib/browser/logs-UTNIFYHF.mjs +0 -116
  40. package/dist/lib/browser/logs-UTNIFYHF.mjs.map +0 -7
  41. package/dist/lib/browser/meta.json +0 -1
  42. package/dist/lib/browser/metrics-PRGSYAZJ.mjs +0 -116
  43. package/dist/lib/browser/metrics-PRGSYAZJ.mjs.map +0 -7
  44. package/dist/lib/browser/traces-browser-H4NCYCVZ.mjs +0 -154
  45. package/dist/lib/browser/traces-browser-H4NCYCVZ.mjs.map +0 -7
  46. package/dist/lib/node-esm/chunk-EDDZWPYV.mjs +0 -22
  47. package/dist/lib/node-esm/chunk-EDDZWPYV.mjs.map +0 -7
  48. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  49. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  50. package/dist/lib/node-esm/index.mjs +0 -1021
  51. package/dist/lib/node-esm/index.mjs.map +0 -7
  52. package/dist/lib/node-esm/log-processor-GA24XTJF.mjs +0 -47
  53. package/dist/lib/node-esm/log-processor-GA24XTJF.mjs.map +0 -7
  54. package/dist/lib/node-esm/logs-ARJUVN3T.mjs +0 -117
  55. package/dist/lib/node-esm/logs-ARJUVN3T.mjs.map +0 -7
  56. package/dist/lib/node-esm/meta.json +0 -1
  57. package/dist/lib/node-esm/metrics-DREJOOAC.mjs +0 -117
  58. package/dist/lib/node-esm/metrics-DREJOOAC.mjs.map +0 -7
  59. package/dist/lib/node-esm/traces-74F7JUKF.mjs +0 -125
  60. package/dist/lib/node-esm/traces-74F7JUKF.mjs.map +0 -7
@@ -0,0 +1,28 @@
1
+ import { InvariantViolation } from "@dxos/invariant";
2
+ import { LogLevel, getContextFromEntry, shouldLog } from "@dxos/log";
3
+ import posthog from "posthog-js";
4
+ //#region src/extensions/posthog/log-processor.ts
5
+ var logProcessor = (config, entry) => {
6
+ if (!shouldLog(entry, config.captureFilters) || entry.meta?.S?.remoteSessionId) return;
7
+ let capturedError = entry.error;
8
+ if (capturedError == null && entry.level === LogLevel.ERROR) capturedError = Object.values(entry.context ?? {}).find((v) => v instanceof Error);
9
+ if (!capturedError) return;
10
+ const additionalProperties = {};
11
+ const { filename, line } = entry.computedMeta;
12
+ if (filename !== void 0 && line !== void 0) additionalProperties.transaction = `${filename}:${line}`;
13
+ if (entry.meta?.S?.hostSessionId) {
14
+ additionalProperties.service_host_issue = true;
15
+ additionalProperties.service_host_session = entry.meta.S.hostSessionId;
16
+ }
17
+ if (entry.meta?.S?.uptimeSeconds != null) additionalProperties.uptime_seconds = entry.meta.S.uptimeSeconds;
18
+ if (capturedError instanceof InvariantViolation) additionalProperties.invariant_violation = true;
19
+ const context = getContextFromEntry(entry);
20
+ if (context) {
21
+ for (const [key, value] of Object.entries(context)) if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") additionalProperties[key] = value;
22
+ }
23
+ posthog.captureException(capturedError, additionalProperties);
24
+ };
25
+ //#endregion
26
+ export { logProcessor };
27
+
28
+ //# sourceMappingURL=chunk-log-processor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-log-processor.mjs","names":[],"sources":["../../src/extensions/posthog/log-processor.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport posthog from 'posthog-js';\n\nimport { InvariantViolation } from '@dxos/invariant';\nimport { type LogConfig, type LogEntry, LogLevel, type LogProcessor, getContextFromEntry, shouldLog } from '@dxos/log';\n\nexport const logProcessor: LogProcessor = (config: LogConfig, entry: LogEntry) => {\n // Don't forward logs from remote sessions.\n if (!shouldLog(entry, config.captureFilters) || entry.meta?.S?.remoteSessionId) {\n return;\n }\n\n let capturedError = entry.error;\n if (capturedError == null && entry.level === LogLevel.ERROR) {\n capturedError = Object.values(entry.context ?? {}).find((v): v is Error => v instanceof Error);\n }\n\n if (!capturedError) {\n return;\n }\n\n const additionalProperties: Record<string, string | boolean | number> = {};\n const { filename, line } = entry.computedMeta;\n if (filename !== undefined && line !== undefined) {\n additionalProperties.transaction = `${filename}:${line}`;\n }\n if (entry.meta?.S?.hostSessionId) {\n additionalProperties.service_host_issue = true;\n additionalProperties.service_host_session = entry.meta.S.hostSessionId;\n }\n if (entry.meta?.S?.uptimeSeconds != null) {\n additionalProperties.uptime_seconds = entry.meta.S.uptimeSeconds;\n }\n\n if (capturedError instanceof InvariantViolation) {\n additionalProperties.invariant_violation = true;\n }\n\n // Forward primitive context values so callers can attach queryable attributes (e.g. fatal_dialog: true).\n const context = getContextFromEntry(entry);\n if (context) {\n for (const [key, value] of Object.entries(context)) {\n if (typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number') {\n additionalProperties[key] = value;\n }\n }\n }\n\n posthog.captureException(capturedError, additionalProperties);\n};\n"],"mappings":";;;;AASA,IAAa,gBAA8B,QAAmB,UAAoB;CAEhF,IAAI,CAAC,UAAU,OAAO,OAAO,cAAc,KAAK,MAAM,MAAM,GAAG,iBAC7D;CAGF,IAAI,gBAAgB,MAAM;CAC1B,IAAI,iBAAiB,QAAQ,MAAM,UAAU,SAAS,OACpD,gBAAgB,OAAO,OAAO,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,MAAkB,aAAa,KAAK;CAG/F,IAAI,CAAC,eACH;CAGF,MAAM,uBAAkE,CAAC;CACzE,MAAM,EAAE,UAAU,SAAS,MAAM;CACjC,IAAI,aAAa,KAAA,KAAa,SAAS,KAAA,GACrC,qBAAqB,cAAc,GAAG,SAAS,GAAG;CAEpD,IAAI,MAAM,MAAM,GAAG,eAAe;EAChC,qBAAqB,qBAAqB;EAC1C,qBAAqB,uBAAuB,MAAM,KAAK,EAAE;CAC3D;CACA,IAAI,MAAM,MAAM,GAAG,iBAAiB,MAClC,qBAAqB,iBAAiB,MAAM,KAAK,EAAE;CAGrD,IAAI,yBAAyB,oBAC3B,qBAAqB,sBAAsB;CAI7C,MAAM,UAAU,oBAAoB,KAAK;CACzC,IAAI;OACG,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAC9E,qBAAqB,OAAO;CAAA;CAKlC,QAAQ,iBAAiB,eAAe,oBAAoB;AAC9D"}
@@ -0,0 +1,89 @@
1
+ import { n as setDiagLogger, t as resolveOtlpUrl } from "./chunk-otel.mjs";
2
+ import { LogLevel, getContextFromEntry, getRelativeFilename } from "@dxos/log";
3
+ import { ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
4
+ import { SeverityNumber } from "@opentelemetry/api-logs";
5
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
6
+ import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
7
+ //#region src/extensions/otel/logs.ts
8
+ var FLATTEN_DEPTH = 1;
9
+ var OtelLogs = class {
10
+ options;
11
+ _loggerProvider;
12
+ constructor(options) {
13
+ this.options = options;
14
+ setDiagLogger(options.consoleDiagLogLevel);
15
+ const logExporter = new OTLPLogExporter({
16
+ url: resolveOtlpUrl(this.options.endpoint + "/v1/logs"),
17
+ headers: this.options.headers,
18
+ concurrencyLimit: 10
19
+ });
20
+ this._loggerProvider = new LoggerProvider({
21
+ resource: this.options.resource,
22
+ processors: [new BatchLogRecordProcessor({ exporter: logExporter })]
23
+ });
24
+ }
25
+ logProcessor = (_config, entry) => {
26
+ const logger = this._loggerProvider.getLogger("dxos-observability", this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString());
27
+ if (entry.level < this.options.logLevel || !this.options.includeSharedWorkerLogs && entry.meta?.S?.remoteSessionId) return;
28
+ const attributes = {
29
+ ...this.options.getTags(),
30
+ ...entry.meta ? { meta: {
31
+ file: getRelativeFilename(entry.meta.F),
32
+ line: entry.meta.L
33
+ } } : {},
34
+ ...entry.error ? { error: entry.error.stack } : {},
35
+ ...stringifyValues(getContextFromEntry(entry), "ctx_")
36
+ };
37
+ logger.emit({
38
+ severityNumber: convertLevel(entry.level),
39
+ body: entry.message,
40
+ attributes
41
+ });
42
+ };
43
+ flush() {
44
+ return this._loggerProvider.forceFlush();
45
+ }
46
+ close() {
47
+ return this._loggerProvider.shutdown();
48
+ }
49
+ };
50
+ /** Maps {@link LogLevel} to OpenTelemetry {@link SeverityNumber}. */
51
+ var convertLevel = (level) => {
52
+ switch (level) {
53
+ case LogLevel.TRACE: return SeverityNumber.TRACE;
54
+ case LogLevel.DEBUG: return SeverityNumber.DEBUG;
55
+ case LogLevel.VERBOSE: return SeverityNumber.INFO;
56
+ case LogLevel.INFO: return SeverityNumber.INFO;
57
+ case LogLevel.WARN: return SeverityNumber.WARN;
58
+ case LogLevel.ERROR: return SeverityNumber.ERROR;
59
+ default: return SeverityNumber.UNSPECIFIED;
60
+ }
61
+ };
62
+ var errorToString = (error) => error.stack ?? `${error.name}: ${error.message}`;
63
+ var safeStringify = (value) => {
64
+ try {
65
+ return JSON.stringify(value, (_key, nested) => nested instanceof Error ? errorToString(nested) : nested);
66
+ } catch {
67
+ return "[Circular]";
68
+ }
69
+ };
70
+ var stringifyValues = (object, keyPrefix, depth = 1) => {
71
+ if (!object) return {};
72
+ const result = {};
73
+ for (const [key, value] of Object.entries(object)) {
74
+ if (value === void 0) continue;
75
+ const newKey = keyPrefix ? `${keyPrefix}${key}` : key;
76
+ if (typeof value === "object") if (value instanceof Error) result[newKey] = errorToString(value);
77
+ else if (!value || Array.isArray(value) || depth > FLATTEN_DEPTH) result[newKey] = safeStringify(value);
78
+ else {
79
+ const flattened = stringifyValues(value, `${newKey}_`, depth + 1);
80
+ for (const [flattenedKey, flattenedValue] of Object.entries(flattened)) result[flattenedKey] = flattenedValue;
81
+ }
82
+ else result[newKey] = String(value);
83
+ }
84
+ return result;
85
+ };
86
+ //#endregion
87
+ export { OtelLogs, stringifyValues };
88
+
89
+ //# sourceMappingURL=chunk-logs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-logs.mjs","names":[],"sources":["../../src/extensions/otel/logs.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';\nimport { ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\n\nimport {\n type LogConfig,\n type LogEntry,\n LogLevel,\n type LogProcessor,\n getContextFromEntry,\n getRelativeFilename,\n} from '@dxos/log';\n\nimport { type OtelOptions, resolveOtlpUrl, setDiagLogger } from './otel';\n\nconst FLATTEN_DEPTH = 1;\n\nexport type OtelLogOptions = OtelOptions & {\n logLevel: LogLevel;\n /**\n * Include logs forwarded from the shared worker via LoggingService.\n *\n * @deprecated\n */\n includeSharedWorkerLogs?: boolean;\n};\n\nexport class OtelLogs {\n private _loggerProvider: LoggerProvider;\n constructor(private readonly options: OtelLogOptions) {\n setDiagLogger(options.consoleDiagLogLevel);\n const logExporter = new OTLPLogExporter({\n url: resolveOtlpUrl(this.options.endpoint + '/v1/logs'),\n headers: this.options.headers,\n concurrencyLimit: 10, // an optional limit on pending requests\n });\n this._loggerProvider = new LoggerProvider({\n resource: this.options.resource,\n processors: [new BatchLogRecordProcessor({ exporter: logExporter })],\n });\n }\n\n public readonly logProcessor: LogProcessor = (_config: LogConfig, entry: LogEntry) => {\n const logger = this._loggerProvider.getLogger(\n 'dxos-observability',\n this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString(),\n );\n\n if (\n entry.level < this.options.logLevel ||\n (!this.options.includeSharedWorkerLogs && entry.meta?.S?.remoteSessionId)\n ) {\n return;\n }\n\n const attributes = {\n ...this.options.getTags(),\n ...(entry.meta ? { meta: { file: getRelativeFilename(entry.meta.F), line: entry.meta.L } } : {}),\n ...(entry.error ? { error: entry.error.stack } : {}),\n ...stringifyValues(getContextFromEntry(entry), 'ctx_'),\n };\n\n logger.emit({\n severityNumber: convertLevel(entry.level),\n body: entry.message,\n attributes,\n });\n };\n\n flush(): Promise<void> {\n return this._loggerProvider.forceFlush();\n }\n\n close(): Promise<void> {\n return this._loggerProvider.shutdown();\n }\n}\n\n/** Maps {@link LogLevel} to OpenTelemetry {@link SeverityNumber}. */\nconst convertLevel = (level: LogLevel): SeverityNumber => {\n switch (level) {\n case LogLevel.TRACE:\n return SeverityNumber.TRACE;\n case LogLevel.DEBUG:\n return SeverityNumber.DEBUG;\n case LogLevel.VERBOSE:\n return SeverityNumber.INFO;\n case LogLevel.INFO:\n return SeverityNumber.INFO;\n case LogLevel.WARN:\n return SeverityNumber.WARN;\n case LogLevel.ERROR:\n return SeverityNumber.ERROR;\n default:\n return SeverityNumber.UNSPECIFIED;\n }\n};\n\nconst errorToString = (error: Error): string => error.stack ?? `${error.name}: ${error.message}`;\n\nconst safeStringify = (value: unknown): string => {\n try {\n // Replace errors as they are encountered; `JSON.stringify` alone renders them as `{}`.\n return JSON.stringify(value, (_key, nested) => (nested instanceof Error ? errorToString(nested) : nested));\n } catch {\n return '[Circular]';\n }\n};\n\n// TODO(wittjosiah): Reconcile logging utils w/ EDGE.\nexport const stringifyValues = (object: object | undefined, keyPrefix?: string, depth: number = 1) => {\n if (!object) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(object)) {\n if (value === undefined) {\n continue;\n }\n const newKey = keyPrefix ? `${keyPrefix}${key}` : key;\n if (typeof value === 'object') {\n if (value instanceof Error) {\n // `message`/`stack` are non-enumerable, so the flatten path below would silently\n // reduce a logged error to nothing.\n result[newKey] = errorToString(value);\n } else if (!value || Array.isArray(value) || depth > FLATTEN_DEPTH) {\n result[newKey] = safeStringify(value);\n } else {\n const flattened = stringifyValues(value, `${newKey}_`, depth + 1);\n for (const [flattenedKey, flattenedValue] of Object.entries(flattened)) {\n result[flattenedKey] = flattenedValue;\n }\n }\n } else {\n result[newKey] = String(value);\n }\n }\n return result;\n};\n"],"mappings":";;;;;;;AAoBA,IAAM,gBAAgB;AAYtB,IAAa,WAAb,MAAsB;CAES;CAD7B;CACA,YAAY,SAA0C;EAAzB,KAAA,UAAA;EAC3B,cAAc,QAAQ,mBAAmB;EACzC,MAAM,cAAc,IAAI,gBAAgB;GACtC,KAAK,eAAe,KAAK,QAAQ,WAAW,UAAU;GACtD,SAAS,KAAK,QAAQ;GACtB,kBAAkB;EACpB,CAAC;EACD,KAAK,kBAAkB,IAAI,eAAe;GACxC,UAAU,KAAK,QAAQ;GACvB,YAAY,CAAC,IAAI,wBAAwB,EAAE,UAAU,YAAY,CAAC,CAAC;EACrE,CAAC;CACH;CAEA,gBAA8C,SAAoB,UAAoB;EACpF,MAAM,SAAS,KAAK,gBAAgB,UAClC,sBACA,KAAK,QAAQ,SAAS,WAAW,qBAAqB,EAAE,SAAS,CACnE;EAEA,IACE,MAAM,QAAQ,KAAK,QAAQ,YAC1B,CAAC,KAAK,QAAQ,2BAA2B,MAAM,MAAM,GAAG,iBAEzD;EAGF,MAAM,aAAa;GACjB,GAAG,KAAK,QAAQ,QAAQ;GACxB,GAAI,MAAM,OAAO,EAAE,MAAM;IAAE,MAAM,oBAAoB,MAAM,KAAK,CAAC;IAAG,MAAM,MAAM,KAAK;GAAE,EAAE,IAAI,CAAC;GAC9F,GAAI,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,MAAM,IAAI,CAAC;GAClD,GAAG,gBAAgB,oBAAoB,KAAK,GAAG,MAAM;EACvD;EAEA,OAAO,KAAK;GACV,gBAAgB,aAAa,MAAM,KAAK;GACxC,MAAM,MAAM;GACZ;EACF,CAAC;CACH;CAEA,QAAuB;EACrB,OAAO,KAAK,gBAAgB,WAAW;CACzC;CAEA,QAAuB;EACrB,OAAO,KAAK,gBAAgB,SAAS;CACvC;AACF;;AAGA,IAAM,gBAAgB,UAAoC;CACxD,QAAQ,OAAR;EACE,KAAK,SAAS,OACZ,OAAO,eAAe;EACxB,KAAK,SAAS,OACZ,OAAO,eAAe;EACxB,KAAK,SAAS,SACZ,OAAO,eAAe;EACxB,KAAK,SAAS,MACZ,OAAO,eAAe;EACxB,KAAK,SAAS,MACZ,OAAO,eAAe;EACxB,KAAK,SAAS,OACZ,OAAO,eAAe;EACxB,SACE,OAAO,eAAe;CAC1B;AACF;AAEA,IAAM,iBAAiB,UAAyB,MAAM,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM;AAEvF,IAAM,iBAAiB,UAA2B;CAChD,IAAI;EAEF,OAAO,KAAK,UAAU,QAAQ,MAAM,WAAY,kBAAkB,QAAQ,cAAc,MAAM,IAAI,MAAO;CAC3G,QAAQ;EACN,OAAO;CACT;AACF;AAGA,IAAa,mBAAmB,QAA4B,WAAoB,QAAgB,MAAM;CACpG,IAAI,CAAC,QACH,OAAO,CAAC;CAEV,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EACjD,IAAI,UAAU,KAAA,GACZ;EAEF,MAAM,SAAS,YAAY,GAAG,YAAY,QAAQ;EAClD,IAAI,OAAO,UAAU,UACnB,IAAI,iBAAiB,OAGnB,OAAO,UAAU,cAAc,KAAK;OAC/B,IAAI,CAAC,SAAS,MAAM,QAAQ,KAAK,KAAK,QAAQ,eACnD,OAAO,UAAU,cAAc,KAAK;OAC/B;GACL,MAAM,YAAY,gBAAgB,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC;GAChE,KAAK,MAAM,CAAC,cAAc,mBAAmB,OAAO,QAAQ,SAAS,GACnE,OAAO,gBAAgB;EAE3B;OAEA,OAAO,UAAU,OAAO,KAAK;CAEjC;CACA,OAAO;AACT"}
@@ -0,0 +1,118 @@
1
+ import { n as setDiagLogger, t as resolveOtlpUrl } from "./chunk-otel.mjs";
2
+ import { log } from "@dxos/log";
3
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
4
+ import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
5
+ import { TRACE_PROCESSOR } from "@dxos/tracing";
6
+ //#region src/extensions/otel/metrics.ts
7
+ var __dxlog_file = "/__w/dxos/dxos/packages/sdk/observability/src/extensions/otel/metrics.ts";
8
+ var EXPORT_INTERVAL = 60 * 1e3;
9
+ var OtelMetrics = class {
10
+ options;
11
+ _meterProvider;
12
+ _meter;
13
+ constructor(options) {
14
+ this.options = options;
15
+ setDiagLogger(options.consoleDiagLogLevel);
16
+ const grafanaMetricReader = new PeriodicExportingMetricReader({
17
+ exporter: new OTLPMetricExporter({
18
+ url: resolveOtlpUrl(this.options.endpoint + "/v1/metrics"),
19
+ headers: this.options.headers
20
+ }),
21
+ exportIntervalMillis: EXPORT_INTERVAL
22
+ });
23
+ this._meterProvider = new MeterProvider({
24
+ resource: this.options.resource,
25
+ readers: [grafanaMetricReader]
26
+ });
27
+ this._meter = this._meterProvider.getMeter("dxos-observability");
28
+ TRACE_PROCESSOR.remoteMetrics.registerProcessor({
29
+ increment: (name, value, data) => {
30
+ this.increment(name, value, convertTags(data));
31
+ },
32
+ distribution: (name, value, data) => {
33
+ this.distribution(name, value, convertTags(data));
34
+ },
35
+ set: (name, value, data) => {},
36
+ gauge: (name, value, data) => {
37
+ this.gauge(name, value, convertTags(data));
38
+ }
39
+ });
40
+ }
41
+ gauge(name, value, tags) {
42
+ const gauge = this._meter.createGauge(name);
43
+ log("otel gauge", {
44
+ name,
45
+ value,
46
+ tags: {
47
+ ...this.options.getTags(),
48
+ ...tags
49
+ }
50
+ }, {
51
+ "~LogMeta": "~LogMeta",
52
+ F: __dxlog_file,
53
+ L: 60,
54
+ S: this
55
+ });
56
+ gauge.record(value, {
57
+ ...this.options.getTags(),
58
+ ...tags
59
+ });
60
+ }
61
+ increment(name, value, tags) {
62
+ const counter = this._meter.createCounter(name);
63
+ log("otel counter", {
64
+ name,
65
+ value,
66
+ tags: {
67
+ ...this.options.getTags(),
68
+ ...tags
69
+ }
70
+ }, {
71
+ "~LogMeta": "~LogMeta",
72
+ F: __dxlog_file,
73
+ L: 66,
74
+ S: this
75
+ });
76
+ counter.add(value ?? 1, {
77
+ ...this.options.getTags(),
78
+ ...tags
79
+ });
80
+ }
81
+ distribution(name, value, tags) {
82
+ const distribution = this._meter.createHistogram(name);
83
+ log("otel distribution", {
84
+ name,
85
+ value,
86
+ tags: {
87
+ ...this.options.getTags(),
88
+ ...tags
89
+ }
90
+ }, {
91
+ "~LogMeta": "~LogMeta",
92
+ F: __dxlog_file,
93
+ L: 72,
94
+ S: this
95
+ });
96
+ distribution.record(value, {
97
+ ...this.options.getTags(),
98
+ ...tags
99
+ });
100
+ }
101
+ flush() {
102
+ return this._meterProvider.forceFlush();
103
+ }
104
+ close() {
105
+ return this._meterProvider.shutdown();
106
+ }
107
+ };
108
+ var convertTags = (data) => {
109
+ if (data && data.tags) return Object.entries(data.tags).reduce((obj, [key, value]) => {
110
+ obj[key] = value;
111
+ return obj;
112
+ }, {});
113
+ else return {};
114
+ };
115
+ //#endregion
116
+ export { OtelMetrics };
117
+
118
+ //# sourceMappingURL=chunk-metrics.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-metrics.mjs","names":[],"sources":["../../src/extensions/otel/metrics.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Meter } from '@opentelemetry/api';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\n\nimport { log } from '@dxos/log';\nimport { type MetricData, TRACE_PROCESSOR } from '@dxos/tracing';\n\nimport { type OtelOptions, resolveOtlpUrl, setDiagLogger } from './otel';\n\nconst EXPORT_INTERVAL = 60 * 1000;\n\nexport class OtelMetrics {\n private _meterProvider: MeterProvider;\n private _meter: Meter;\n\n constructor(private readonly options: OtelOptions) {\n // TODO: improve error handling/logging\n // https://github.com/open-telemetry/opentelemetry-js/issues/4823\n setDiagLogger(options.consoleDiagLogLevel);\n\n const grafanaMetricReader = new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: resolveOtlpUrl(this.options.endpoint + '/v1/metrics'),\n headers: this.options.headers,\n }),\n exportIntervalMillis: EXPORT_INTERVAL,\n });\n\n this._meterProvider = new MeterProvider({\n resource: this.options.resource,\n readers: [grafanaMetricReader],\n });\n this._meter = this._meterProvider.getMeter('dxos-observability');\n\n const metrics = {\n // TODO: update metrics names and remove prefix?\n increment: (name: string, value?: number, data?: MetricData) => {\n this.increment(name, value, convertTags(data));\n },\n distribution: (name: string, value: number, data?: MetricData) => {\n this.distribution(name, value, convertTags(data));\n },\n set: (name: string, value: number | string, data?: MetricData) => {\n // Not implemented, not part of Otel spec.\n },\n gauge: (name: string, value: number, data?: MetricData) => {\n this.gauge(name, value, convertTags(data));\n },\n };\n\n TRACE_PROCESSOR.remoteMetrics.registerProcessor(metrics);\n }\n\n gauge(name: string, value: number, tags?: any): void {\n const gauge = this._meter.createGauge(name);\n log('otel gauge', { name, value, tags: { ...this.options.getTags(), ...tags } });\n gauge.record(value, { ...this.options.getTags(), ...tags });\n }\n\n increment(name: string, value?: number, tags?: any): void {\n const counter = this._meter.createCounter(name);\n log('otel counter', { name, value, tags: { ...this.options.getTags(), ...tags } });\n counter.add(value ?? 1, { ...this.options.getTags(), ...tags });\n }\n\n distribution(name: string, value: number, tags?: any): void {\n const distribution = this._meter.createHistogram(name);\n log('otel distribution', { name, value, tags: { ...this.options.getTags(), ...tags } });\n distribution.record(value, { ...this.options.getTags(), ...tags });\n }\n\n flush(): Promise<void> {\n return this._meterProvider.forceFlush();\n }\n\n close(): Promise<void> {\n return this._meterProvider.shutdown();\n }\n}\n\nconst convertTags = (data?: MetricData) => {\n if (data && data.tags) {\n return Object.entries(data.tags).reduce<{ [key: string]: any }>((obj, [key, value]) => {\n obj[key] = value;\n return obj;\n }, {});\n } else {\n return {};\n }\n};\n"],"mappings":";;;;;;;AAaA,IAAM,kBAAkB,KAAK;AAE7B,IAAa,cAAb,MAAyB;CAIM;CAH7B;CACA;CAEA,YAAY,SAAuC;EAAtB,KAAA,UAAA;EAG3B,cAAc,QAAQ,mBAAmB;EAEzC,MAAM,sBAAsB,IAAI,8BAA8B;GAC5D,UAAU,IAAI,mBAAmB;IAC/B,KAAK,eAAe,KAAK,QAAQ,WAAW,aAAa;IACzD,SAAS,KAAK,QAAQ;GACxB,CAAC;GACD,sBAAsB;EACxB,CAAC;EAED,KAAK,iBAAiB,IAAI,cAAc;GACtC,UAAU,KAAK,QAAQ;GACvB,SAAS,CAAC,mBAAmB;EAC/B,CAAC;EACD,KAAK,SAAS,KAAK,eAAe,SAAS,oBAAoB;EAkB/D,gBAAgB,cAAc,kBAAkB;GAd9C,YAAY,MAAc,OAAgB,SAAsB;IAC9D,KAAK,UAAU,MAAM,OAAO,YAAY,IAAI,CAAC;GAC/C;GACA,eAAe,MAAc,OAAe,SAAsB;IAChE,KAAK,aAAa,MAAM,OAAO,YAAY,IAAI,CAAC;GAClD;GACA,MAAM,MAAc,OAAwB,SAAsB,CAElE;GACA,QAAQ,MAAc,OAAe,SAAsB;IACzD,KAAK,MAAM,MAAM,OAAO,YAAY,IAAI,CAAC;GAC3C;EAG8C,CAAO;CACzD;CAEA,MAAM,MAAc,OAAe,MAAkB;EACnD,MAAM,QAAQ,KAAK,OAAO,YAAY,IAAI;EAC1C,IAAI,cAAc;GAAE;GAAM;GAAO,MAAM;IAAE,GAAG,KAAK,QAAQ,QAAQ;IAAG,GAAG;GAAK;EAAE,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;EAC/E,MAAM,OAAO,OAAO;GAAE,GAAG,KAAK,QAAQ,QAAQ;GAAG,GAAG;EAAK,CAAC;CAC5D;CAEA,UAAU,MAAc,OAAgB,MAAkB;EACxD,MAAM,UAAU,KAAK,OAAO,cAAc,IAAI;EAC9C,IAAI,gBAAgB;GAAE;GAAM;GAAO,MAAM;IAAE,GAAG,KAAK,QAAQ,QAAQ;IAAG,GAAG;GAAK;EAAE,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;EACjF,QAAQ,IAAI,SAAS,GAAG;GAAE,GAAG,KAAK,QAAQ,QAAQ;GAAG,GAAG;EAAK,CAAC;CAChE;CAEA,aAAa,MAAc,OAAe,MAAkB;EAC1D,MAAM,eAAe,KAAK,OAAO,gBAAgB,IAAI;EACrD,IAAI,qBAAqB;GAAE;GAAM;GAAO,MAAM;IAAE,GAAG,KAAK,QAAQ,QAAQ;IAAG,GAAG;GAAK;EAAE,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;EACtF,aAAa,OAAO,OAAO;GAAE,GAAG,KAAK,QAAQ,QAAQ;GAAG,GAAG;EAAK,CAAC;CACnE;CAEA,QAAuB;EACrB,OAAO,KAAK,eAAe,WAAW;CACxC;CAEA,QAAuB;EACrB,OAAO,KAAK,eAAe,SAAS;CACtC;AACF;AAEA,IAAM,eAAe,SAAsB;CACzC,IAAI,QAAQ,KAAK,MACf,OAAO,OAAO,QAAQ,KAAK,IAAI,CAAC,CAAC,QAAgC,KAAK,CAAC,KAAK,WAAW;EACrF,IAAI,OAAO;EACX,OAAO;CACT,GAAG,CAAC,CAAC;MAEL,OAAO,CAAC;AAEZ"}
@@ -0,0 +1,20 @@
1
+ import { DiagConsoleLogger, DiagLogLevel, diag } from "@opentelemetry/api";
2
+ //#region src/extensions/otel/otel.ts
3
+ var setDiagLogger = (level) => {
4
+ const logLevel = DiagLogLevel[level];
5
+ if (logLevel) diag.setLogger(new DiagConsoleLogger(), logLevel);
6
+ };
7
+ /**
8
+ * Resolve a possibly-relative OTLP exporter URL to an absolute URL.
9
+ * The OpenTelemetry browser HTTP exporter validates user-provided URLs with `new URL(url)`,
10
+ * which throws on relative paths like `/api/otel/v1/logs`. In a browser context we resolve
11
+ * against `window.location.origin`; outside the browser we return the input unchanged.
12
+ */
13
+ var resolveOtlpUrl = (url) => {
14
+ if (typeof window !== "undefined" && url.startsWith("/")) return new URL(url, window.location.origin).toString();
15
+ return url;
16
+ };
17
+ //#endregion
18
+ export { setDiagLogger as n, resolveOtlpUrl as t };
19
+
20
+ //# sourceMappingURL=chunk-otel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-otel.mjs","names":[],"sources":["../../src/extensions/otel/otel.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport { type Resource } from '@opentelemetry/resources';\n\nexport type OtelOptions = {\n endpoint: string;\n headers: Record<string, string>;\n resource: Resource;\n getTags: () => { [key: string]: string };\n consoleDiagLogLevel?: string;\n};\n\nexport const setDiagLogger = (level?: string) => {\n const logLevel = DiagLogLevel[level as keyof typeof DiagLogLevel];\n if (logLevel) {\n diag.setLogger(new DiagConsoleLogger(), logLevel);\n }\n};\n\n/**\n * Resolve a possibly-relative OTLP exporter URL to an absolute URL.\n * The OpenTelemetry browser HTTP exporter validates user-provided URLs with `new URL(url)`,\n * which throws on relative paths like `/api/otel/v1/logs`. In a browser context we resolve\n * against `window.location.origin`; outside the browser we return the input unchanged.\n */\nexport const resolveOtlpUrl = (url: string): string => {\n if (typeof window !== 'undefined' && url.startsWith('/')) {\n return new URL(url, window.location.origin).toString();\n }\n return url;\n};\n"],"mappings":";;AAeA,IAAa,iBAAiB,UAAmB;CAC/C,MAAM,WAAW,aAAa;CAC9B,IAAI,UACF,KAAK,UAAU,IAAI,kBAAkB,GAAG,QAAQ;AAEpD;;;;;;;AAQA,IAAa,kBAAkB,QAAwB;CACrD,IAAI,OAAO,WAAW,eAAe,IAAI,WAAW,GAAG,GACrD,OAAO,IAAI,IAAI,KAAK,OAAO,SAAS,MAAM,CAAC,CAAC,SAAS;CAEvD,OAAO;AACT"}
@@ -0,0 +1,145 @@
1
+ import { t as resolveOtlpUrl } from "./chunk-otel.mjs";
2
+ import { log } from "@dxos/log";
3
+ import { ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
4
+ import { ROOT_CONTEXT, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
5
+ import { TRACE_ALL_KEY, TRACE_PROCESSOR } from "@dxos/tracing";
6
+ import { getWebAutoInstrumentations } from "@opentelemetry/auto-instrumentations-web";
7
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
8
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
9
+ import { registerInstrumentations } from "@opentelemetry/instrumentation";
10
+ import { AlwaysOnSampler, BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
11
+ import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
12
+ //#region src/extensions/otel/span-processors.ts
13
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/sdk/observability/src/extensions/otel/span-processors.ts";
14
+ /**
15
+ * Injects dynamic tags as attributes on every span.
16
+ *
17
+ * This is an `onStart`-only processor; `onEnd`/`shutdown`/`forceFlush` are no-ops
18
+ * because this processor never holds pending work (delegates export to downstream
19
+ * processors like BatchSpanProcessor).
20
+ *
21
+ * Use for tags that are not known at provider construction (e.g. identity
22
+ * resolved asynchronously after login). For values known at startup, prefer
23
+ * putting them on the `Resource` — see `extension.ts`.
24
+ *
25
+ * Failures in the tag provider are swallowed so they can never break span
26
+ * recording (the processor is best-effort and runs on the startSpan hot path).
27
+ */
28
+ var TagInjectorSpanProcessor = class {
29
+ _getTags;
30
+ constructor(_getTags) {
31
+ this._getTags = _getTags;
32
+ }
33
+ onStart(span, _parentContext) {
34
+ let tags;
35
+ try {
36
+ tags = this._getTags();
37
+ } catch (err) {
38
+ log.catch(err, void 0, {
39
+ "~LogMeta": "~LogMeta",
40
+ F: __dxlog_file$1,
41
+ L: 32,
42
+ S: this
43
+ });
44
+ return;
45
+ }
46
+ for (const [key, value] of Object.entries(tags)) span.setAttribute(key, value);
47
+ }
48
+ onEnd(_span) {}
49
+ async shutdown() {}
50
+ async forceFlush() {}
51
+ };
52
+ //#endregion
53
+ //#region src/extensions/otel/traces-browser.ts
54
+ var __dxlog_file = "/__w/dxos/dxos/packages/sdk/observability/src/extensions/otel/traces-browser.ts";
55
+ var OtelTraces = class {
56
+ options;
57
+ _tracer;
58
+ _tracerProvider;
59
+ constructor(options) {
60
+ this.options = options;
61
+ propagation.setGlobalPropagator(new W3CTraceContextPropagator());
62
+ const forceTraceAll = typeof localStorage !== "undefined" && localStorage.getItem(TRACE_ALL_KEY) === "true";
63
+ this._tracerProvider = new WebTracerProvider({
64
+ resource: this.options.resource,
65
+ sampler: new ParentBasedSampler({ root: forceTraceAll ? new AlwaysOnSampler() : new TraceIdRatioBasedSampler(.3) }),
66
+ spanProcessors: [new TagInjectorSpanProcessor(this.options.getTags), new BatchSpanProcessor(new OTLPTraceExporter({
67
+ url: resolveOtlpUrl(this.options.endpoint + "/v1/traces"),
68
+ headers: this.options.headers,
69
+ concurrencyLimit: 10
70
+ }), { scheduledDelayMillis: 5e3 })]
71
+ });
72
+ trace.setGlobalTracerProvider(this._tracerProvider);
73
+ this._tracer = trace.getTracer("dxos-observability", this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString());
74
+ }
75
+ /**
76
+ * Forcibly flush the BatchSpanProcessor. Call before process exit / page unload
77
+ * to avoid losing queued spans (which manifests as "Missing Span" in SigNoz —
78
+ * their already-exported children reference a parent that never made it to OTLP).
79
+ */
80
+ async flush() {
81
+ await this._tracerProvider.forceFlush();
82
+ }
83
+ /**
84
+ * Flush + shut down the tracer provider via `WebTracerProvider.shutdown()`,
85
+ * which forces a final export then terminates all span processors.
86
+ *
87
+ * Terminal and effectively one-shot: safe to call after `flush()`, but
88
+ * `flush()` MUST NOT be called after `close()` — shutdown stops further
89
+ * exporting, so subsequent `close()`/`flush()` calls resolve without
90
+ * emitting new spans.
91
+ */
92
+ async close() {
93
+ await this._tracerProvider.shutdown();
94
+ }
95
+ start() {
96
+ log("trace processor registered", void 0, {
97
+ "~LogMeta": "~LogMeta",
98
+ F: __dxlog_file,
99
+ L: 90,
100
+ S: this
101
+ });
102
+ registerInstrumentations({ instrumentations: [getWebAutoInstrumentations({
103
+ "@opentelemetry/instrumentation-fetch": {
104
+ enabled: false,
105
+ ignoreUrls: [/api\.ipdata\.co/]
106
+ },
107
+ "@opentelemetry/instrumentation-document-load": { enabled: false },
108
+ "@opentelemetry/instrumentation-xml-http-request": { enabled: false },
109
+ "@opentelemetry/instrumentation-user-interaction": { enabled: false }
110
+ })] });
111
+ const tracer = this._tracer;
112
+ TRACE_PROCESSOR.tracingBackend = { startSpan: (options) => {
113
+ log("begin otel trace", { options }, {
114
+ "~LogMeta": "~LogMeta",
115
+ F: __dxlog_file,
116
+ L: 107,
117
+ S: this
118
+ });
119
+ const parentCtx = options.parentContext ? propagation.extract(ROOT_CONTEXT, {
120
+ traceparent: options.parentContext.traceparent,
121
+ tracestate: options.parentContext.tracestate ?? ""
122
+ }) : context.active();
123
+ const span = tracer.startSpan(options.name, options, parentCtx);
124
+ const sc = span.spanContext();
125
+ return {
126
+ end: (endTime) => span.end(endTime),
127
+ setError: (err) => {
128
+ if (err instanceof Error) span.recordException(err);
129
+ span.setStatus({
130
+ code: SpanStatusCode.ERROR,
131
+ message: err instanceof Error ? err.message : String(err)
132
+ });
133
+ },
134
+ spanContext: sc && sc.traceId && sc.spanId ? {
135
+ traceparent: `00-${sc.traceId}-${sc.spanId}-${(sc.traceFlags ?? 0).toString(16).padStart(2, "0")}`,
136
+ tracestate: sc.traceState?.serialize()
137
+ } : void 0
138
+ };
139
+ } };
140
+ }
141
+ };
142
+ //#endregion
143
+ export { OtelTraces };
144
+
145
+ //# sourceMappingURL=chunk-traces-browser.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-traces-browser.mjs","names":[],"sources":["../../src/extensions/otel/span-processors.ts","../../src/extensions/otel/traces-browser.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Context } from '@opentelemetry/api';\nimport { type ReadableSpan, type Span, type SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport { log } from '@dxos/log';\n\n/**\n * Injects dynamic tags as attributes on every span.\n *\n * This is an `onStart`-only processor; `onEnd`/`shutdown`/`forceFlush` are no-ops\n * because this processor never holds pending work (delegates export to downstream\n * processors like BatchSpanProcessor).\n *\n * Use for tags that are not known at provider construction (e.g. identity\n * resolved asynchronously after login). For values known at startup, prefer\n * putting them on the `Resource` — see `extension.ts`.\n *\n * Failures in the tag provider are swallowed so they can never break span\n * recording (the processor is best-effort and runs on the startSpan hot path).\n */\nexport class TagInjectorSpanProcessor implements SpanProcessor {\n constructor(private readonly _getTags: () => Record<string, string>) {}\n\n onStart(span: Span, _parentContext: Context): void {\n let tags: Record<string, string>;\n try {\n tags = this._getTags();\n } catch (err) {\n log.catch(err);\n return;\n }\n for (const [key, value] of Object.entries(tags)) {\n span.setAttribute(key, value);\n }\n }\n\n onEnd(_span: ReadableSpan): void {}\n\n async shutdown(): Promise<void> {}\n\n async forceFlush(): Promise<void> {}\n}\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport {\n ROOT_CONTEXT,\n SpanStatusCode,\n type Tracer,\n context as otelContext,\n propagation,\n trace,\n} from '@opentelemetry/api';\nimport { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';\nimport { W3CTraceContextPropagator } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport {\n AlwaysOnSampler,\n BatchSpanProcessor,\n ParentBasedSampler,\n TraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-base';\nimport { WebTracerProvider } from '@opentelemetry/sdk-trace-web';\nimport { ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\n\nimport { log } from '@dxos/log';\nimport { type RemoteSpan, type StartSpanOptions, TRACE_ALL_KEY, TRACE_PROCESSOR } from '@dxos/tracing';\n\nimport { type OtelOptions, resolveOtlpUrl } from './otel';\nimport { TagInjectorSpanProcessor } from './span-processors';\n\nexport class OtelTraces {\n private _tracer: Tracer;\n private readonly _tracerProvider: WebTracerProvider;\n\n constructor(private readonly options: OtelOptions) {\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n\n const forceTraceAll = typeof localStorage !== 'undefined' && localStorage.getItem(TRACE_ALL_KEY) === 'true';\n\n this._tracerProvider = new WebTracerProvider({\n resource: this.options.resource,\n sampler: new ParentBasedSampler({\n root: forceTraceAll ? new AlwaysOnSampler() : new TraceIdRatioBasedSampler(0.3),\n }),\n spanProcessors: [\n new TagInjectorSpanProcessor(this.options.getTags),\n new BatchSpanProcessor(\n new OTLPTraceExporter({\n url: resolveOtlpUrl(this.options.endpoint + '/v1/traces'),\n headers: this.options.headers,\n concurrencyLimit: 10,\n }),\n { scheduledDelayMillis: 5_000 },\n ),\n ],\n });\n\n trace.setGlobalTracerProvider(this._tracerProvider);\n\n this._tracer = trace.getTracer(\n 'dxos-observability',\n this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString(),\n );\n }\n\n /**\n * Forcibly flush the BatchSpanProcessor. Call before process exit / page unload\n * to avoid losing queued spans (which manifests as \"Missing Span\" in SigNoz —\n * their already-exported children reference a parent that never made it to OTLP).\n */\n public async flush(): Promise<void> {\n await this._tracerProvider.forceFlush();\n }\n\n /**\n * Flush + shut down the tracer provider via `WebTracerProvider.shutdown()`,\n * which forces a final export then terminates all span processors.\n *\n * Terminal and effectively one-shot: safe to call after `flush()`, but\n * `flush()` MUST NOT be called after `close()` — shutdown stops further\n * exporting, so subsequent `close()`/`flush()` calls resolve without\n * emitting new spans.\n */\n public async close(): Promise<void> {\n await this._tracerProvider.shutdown();\n }\n\n public start(): void {\n log('trace processor registered');\n\n registerInstrumentations({\n instrumentations: [\n getWebAutoInstrumentations({\n '@opentelemetry/instrumentation-fetch': { enabled: false, ignoreUrls: [/api\\.ipdata\\.co/] },\n '@opentelemetry/instrumentation-document-load': { enabled: false },\n '@opentelemetry/instrumentation-xml-http-request': { enabled: false },\n '@opentelemetry/instrumentation-user-interaction': { enabled: false },\n }),\n ],\n });\n\n const tracer = this._tracer;\n\n TRACE_PROCESSOR.tracingBackend = {\n startSpan: (options: StartSpanOptions): RemoteSpan => {\n log('begin otel trace', { options });\n const parentCtx = options.parentContext\n ? propagation.extract(ROOT_CONTEXT, {\n traceparent: options.parentContext.traceparent,\n tracestate: options.parentContext.tracestate ?? '',\n })\n : otelContext.active();\n\n const span = tracer.startSpan(options.name, options, parentCtx);\n\n const sc = span.spanContext();\n const spanContext =\n sc && sc.traceId && sc.spanId\n ? {\n traceparent: `00-${sc.traceId}-${sc.spanId}-${(sc.traceFlags ?? 0).toString(16).padStart(2, '0')}`,\n tracestate: sc.traceState?.serialize(),\n }\n : undefined;\n\n return {\n end: (endTime?: number) => span.end(endTime),\n setError: (err: unknown) => {\n if (err instanceof Error) {\n span.recordException(err);\n }\n span.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n },\n spanContext,\n };\n },\n };\n }\n}\n"],"mappings":"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"}