@dxos/observability 0.6.3-main.daaea86 → 0.6.3-main.e6314a6

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 (67) hide show
  1. package/dist/lib/browser/{chunk-7HCG6KYK.mjs → chunk-2CXA7PYK.mjs} +133 -174
  2. package/dist/lib/browser/chunk-2CXA7PYK.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -2
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/observability-MXAPN7J6.mjs +7 -0
  6. package/dist/lib/browser/otel-BUKBDMAL.mjs +275 -0
  7. package/dist/lib/browser/otel-BUKBDMAL.mjs.map +7 -0
  8. package/dist/lib/browser/sentry-log-processor-DVUUOZ6G.mjs +132 -0
  9. package/dist/lib/browser/sentry-log-processor-DVUUOZ6G.mjs.map +7 -0
  10. package/dist/lib/node/{chunk-63FCWS3M.cjs → chunk-HBLKTDQE.cjs} +136 -170
  11. package/dist/lib/node/chunk-HBLKTDQE.cjs.map +7 -0
  12. package/dist/lib/node/index.cjs +15 -16
  13. package/dist/lib/node/index.cjs.map +2 -2
  14. package/dist/lib/node/meta.json +1 -1
  15. package/dist/lib/node/{observability-W7TVZP2V.cjs → observability-4R6M4JMU.cjs} +6 -7
  16. package/dist/lib/node/observability-4R6M4JMU.cjs.map +7 -0
  17. package/dist/lib/node/otel-TSHMOAB4.cjs +276 -0
  18. package/dist/lib/node/otel-TSHMOAB4.cjs.map +7 -0
  19. package/dist/lib/node/sentry-log-processor-H6FUSKZI.cjs +150 -0
  20. package/dist/lib/node/sentry-log-processor-H6FUSKZI.cjs.map +7 -0
  21. package/dist/types/src/cli-observability-secrets.json +3 -1
  22. package/dist/types/src/helpers/browser-observability.d.ts.map +1 -1
  23. package/dist/types/src/helpers/browser-observability.js +1 -0
  24. package/dist/types/src/helpers/browser-observability.js.map +1 -1
  25. package/dist/types/src/observability.d.ts +9 -0
  26. package/dist/types/src/observability.d.ts.map +1 -1
  27. package/dist/types/src/observability.js +90 -13
  28. package/dist/types/src/observability.js.map +1 -1
  29. package/dist/types/src/otel/index.d.ts +5 -0
  30. package/dist/types/src/otel/index.d.ts.map +1 -0
  31. package/dist/types/src/otel/index.js +24 -0
  32. package/dist/types/src/otel/index.js.map +1 -0
  33. package/dist/types/src/otel/logs.d.ts +11 -0
  34. package/dist/types/src/otel/logs.d.ts.map +1 -0
  35. package/dist/types/src/otel/logs.js +72 -0
  36. package/dist/types/src/otel/logs.js.map +1 -0
  37. package/dist/types/src/otel/metrics.d.ts +14 -0
  38. package/dist/types/src/otel/metrics.d.ts.map +1 -0
  39. package/dist/types/src/otel/metrics.js +91 -0
  40. package/dist/types/src/otel/metrics.js.map +1 -0
  41. package/dist/types/src/otel/otel.d.ts +12 -0
  42. package/dist/types/src/otel/otel.d.ts.map +1 -0
  43. package/dist/types/src/otel/otel.js +15 -0
  44. package/dist/types/src/otel/otel.js.map +1 -0
  45. package/dist/types/src/otel/traces-browser.d.ts +8 -0
  46. package/dist/types/src/otel/traces-browser.d.ts.map +1 -0
  47. package/dist/types/src/otel/traces-browser.js +51 -0
  48. package/dist/types/src/otel/traces-browser.js.map +1 -0
  49. package/dist/types/src/otel/traces.d.ts +8 -0
  50. package/dist/types/src/otel/traces.d.ts.map +1 -0
  51. package/dist/types/src/otel/traces.js +44 -0
  52. package/dist/types/src/otel/traces.js.map +1 -0
  53. package/package.json +35 -20
  54. package/src/cli-observability-secrets.json +3 -1
  55. package/src/helpers/browser-observability.ts +1 -0
  56. package/src/observability.ts +107 -14
  57. package/src/otel/index.ts +8 -0
  58. package/src/otel/logs.ts +86 -0
  59. package/src/otel/metrics.ts +111 -0
  60. package/src/otel/otel.ts +21 -0
  61. package/src/otel/traces-browser.ts +59 -0
  62. package/src/otel/traces.ts +57 -0
  63. package/dist/lib/browser/chunk-7HCG6KYK.mjs.map +0 -7
  64. package/dist/lib/browser/observability-IFDZJFY2.mjs +0 -8
  65. package/dist/lib/node/chunk-63FCWS3M.cjs.map +0 -7
  66. package/dist/lib/node/observability-W7TVZP2V.cjs.map +0 -7
  67. /package/dist/lib/browser/{observability-IFDZJFY2.mjs.map → observability-MXAPN7J6.mjs.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traces-browser.js","sourceRoot":"","sources":["../../../../src/otel/traces-browser.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE;;;AAEF,4CAAwD;AACxD,wFAAsF;AACtF,sFAA4E;AAC5E,oEAA0E;AAC1E,wDAAoD;AACpD,kEAA6G;AAC7G,gEAAiE;AACjE,8EAA4G;AAE5G,mCAAgC;AAChC,2CAAuE;AAIvE,MAAa,UAAU;IAErB,YAA6B,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;QAC/C,MAAM,QAAQ,GAAG,oBAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,CACvC,IAAI,oBAAQ,CAAC;YACX,CAAC,+CAAwB,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;YACpD,CAAC,kDAA2B,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;SAC3D,CAAC,CACH,CAAC;QAEF,MAAM,cAAc,GAAG,IAAI,iCAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3D,cAAc,CAAC,gBAAgB,CAAC,IAAI,oCAAmB,CAAC,IAAI,oCAAmB,EAAE,CAAC,CAAC,CAAC;QACpF,cAAc,CAAC,gBAAgB,CAC7B,IAAI,mCAAkB,CACpB,IAAI,4CAAiB,CAAC;YACpB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,YAAY;YACzC,OAAO,EAAE;gBACP,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB;aAChD;YACD,gBAAgB,EAAE,EAAE,EAAE,wCAAwC;SAC/D,CAAC,CACH,CACF,CAAC;QACF,wCAAwC;QACxC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,WAAK,CAAC,SAAS,CAAC,oBAAoB,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACpF,CAAC;IAEM,KAAK;QACV,IAAA,0CAAwB,EAAC;YACvB,gBAAgB,EAAE,CAAC,IAAA,sDAA0B,GAAE,CAAC;SACjD,CAAC,CAAC;QACH,IAAA,SAAG,EAAC,4BAA4B,CAAC,CAAC;QAClC,yBAAe,CAAC,aAAa,CAAC,iBAAiB,CAAC;YAC9C,SAAS,EAAE,CAAC,OAAyB,EAAE,EAAE;gBACvC,IAAA,SAAG,EAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;gBACrC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvD,CAAC;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAxCD,gCAwCC"}
@@ -0,0 +1,8 @@
1
+ import { type OtelOptions } from './otel';
2
+ export declare class OtelTraces {
3
+ private readonly options;
4
+ private _tracer;
5
+ constructor(options: OtelOptions);
6
+ start(): void;
7
+ }
8
+ //# sourceMappingURL=traces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traces.d.ts","sourceRoot":"","sources":["../../../../src/otel/traces.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,qBAAa,UAAU;IAET,OAAO,CAAC,QAAQ,CAAC,OAAO;IADpC,OAAO,CAAC,OAAO,CAAS;gBACK,OAAO,EAAE,WAAW;IAyB1C,KAAK;CASb"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2024 DXOS.org
4
+ //
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OtelTraces = void 0;
7
+ const api_1 = require("@opentelemetry/api");
8
+ const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
9
+ const resources_1 = require("@opentelemetry/resources");
10
+ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
11
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
12
+ const debug_1 = require("debug");
13
+ const tracing_1 = require("@dxos/tracing");
14
+ class OtelTraces {
15
+ constructor(options) {
16
+ this.options = options;
17
+ const resource = resources_1.Resource.default().merge(new resources_1.Resource({
18
+ [semantic_conventions_1.SEMRESATTRS_SERVICE_NAME]: this.options.serviceName,
19
+ [semantic_conventions_1.SEMRESATTRS_SERVICE_VERSION]: this.options.serviceVersion,
20
+ }));
21
+ const tracerProvider = new sdk_trace_base_1.BasicTracerProvider({ resource });
22
+ tracerProvider.addSpanProcessor(new sdk_trace_base_1.SimpleSpanProcessor(new sdk_trace_base_1.ConsoleSpanExporter()));
23
+ tracerProvider.addSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new exporter_trace_otlp_http_1.OTLPTraceExporter({
24
+ url: this.options.endpoint + '/v1/traces',
25
+ headers: {
26
+ Authorization: this.options.authorizationHeader,
27
+ },
28
+ concurrencyLimit: 10, // an optional limit on pending requests
29
+ })));
30
+ tracerProvider.register();
31
+ this._tracer = api_1.trace.getTracer('dxos-observability', this.options.serviceVersion);
32
+ }
33
+ start() {
34
+ (0, debug_1.log)('trace processor registered');
35
+ tracing_1.TRACE_PROCESSOR.remoteTracing.registerProcessor({
36
+ startSpan: (options) => {
37
+ (0, debug_1.log)('begin otel trace', { options });
38
+ return this._tracer.startSpan(options.name, options);
39
+ },
40
+ });
41
+ }
42
+ }
43
+ exports.OtelTraces = OtelTraces;
44
+ //# sourceMappingURL=traces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traces.js","sourceRoot":"","sources":["../../../../src/otel/traces.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE;;;AAEF,4CAAwD;AACxD,sFAA4E;AAC5E,wDAAoD;AACpD,kEAKuC;AACvC,8EAA4G;AAC5G,iCAA4B;AAE5B,2CAAuE;AAIvE,MAAa,UAAU;IAErB,YAA6B,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;QAC/C,MAAM,QAAQ,GAAG,oBAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,CACvC,IAAI,oBAAQ,CAAC;YACX,CAAC,+CAAwB,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;YACpD,CAAC,kDAA2B,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;SAC3D,CAAC,CACH,CAAC;QAEF,MAAM,cAAc,GAAG,IAAI,oCAAmB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7D,cAAc,CAAC,gBAAgB,CAAC,IAAI,oCAAmB,CAAC,IAAI,oCAAmB,EAAE,CAAC,CAAC,CAAC;QACpF,cAAc,CAAC,gBAAgB,CAC7B,IAAI,mCAAkB,CACpB,IAAI,4CAAiB,CAAC;YACpB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,YAAY;YACzC,OAAO,EAAE;gBACP,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB;aAChD;YACD,gBAAgB,EAAE,EAAE,EAAE,wCAAwC;SAC/D,CAAC,CACH,CACF,CAAC;QACF,cAAc,CAAC,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,WAAK,CAAC,SAAS,CAAC,oBAAoB,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACpF,CAAC;IAEM,KAAK;QACV,IAAA,WAAG,EAAC,4BAA4B,CAAC,CAAC;QAClC,yBAAe,CAAC,aAAa,CAAC,iBAAiB,CAAC;YAC9C,SAAS,EAAE,CAAC,OAAyB,EAAE,EAAE;gBACvC,IAAA,WAAG,EAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;gBACrC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvD,CAAC;SACF,CAAC,CAAC;IACL,CAAC;CACF;AApCD,gCAoCC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/observability",
3
- "version": "0.6.3-main.daaea86",
3
+ "version": "0.6.3-main.e6314a6",
4
4
  "description": "Provides a common interface for app and platform observability",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -37,7 +37,8 @@
37
37
  "./src/datadog/node.ts": "./src/datadog/browser.ts",
38
38
  "./src/segment/node.ts": "./src/segment/browser.ts",
39
39
  "./src/sentry/node.ts": "./src/sentry/browser.ts",
40
- "./testing/testkit/index.ts": "./testing/testkit/browser.ts"
40
+ "./testing/testkit/index.ts": "./testing/testkit/browser.ts",
41
+ "./src/otel/traces.ts": "./src/otel/traces-browser.ts"
41
42
  },
42
43
  "types": "dist/types/src/index.d.ts",
43
44
  "typesVersions": {
@@ -58,35 +59,49 @@
58
59
  "src"
59
60
  ],
60
61
  "dependencies": {
62
+ "@opentelemetry/api": "^1.9.0",
63
+ "@opentelemetry/api-logs": "^0.52.1",
64
+ "@opentelemetry/auto-instrumentations-web": "^0.40.0",
65
+ "@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
66
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.52.0",
67
+ "@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
68
+ "@opentelemetry/instrumentation": "^0.52.1",
69
+ "@opentelemetry/resources": "^1.25.1",
70
+ "@opentelemetry/sdk-logs": "^0.52.1",
71
+ "@opentelemetry/sdk-metrics": "^1.25.1",
72
+ "@opentelemetry/sdk-trace-base": "^1.25.1",
73
+ "@opentelemetry/sdk-trace-web": "^1.25.1",
74
+ "@opentelemetry/semantic-conventions": "^1.25.0",
61
75
  "@segment/analytics-node": "^2.1.0",
62
76
  "@segment/snippet": "^4.15.3",
63
- "@sentry/browser": "^8.7.0",
64
- "@sentry/node": "^8.7.0",
77
+ "@sentry/browser": "^8.8.0",
78
+ "@sentry/node": "^8.8.0",
65
79
  "datadog-metrics": "^0.11.1",
66
80
  "debug": "^4.3.4",
67
81
  "js-yaml": "^4.1.0",
68
82
  "localforage": "^1.10.0",
69
83
  "uuid": "^9.0.0",
70
- "@dxos/async": "0.6.3-main.daaea86",
71
- "@dxos/client": "0.6.3-main.daaea86",
72
- "@dxos/client-protocol": "0.6.3-main.daaea86",
73
- "@dxos/client-services": "0.6.3-main.daaea86",
74
- "@dxos/config": "0.6.3-main.daaea86",
75
- "@dxos/context": "0.6.3-main.daaea86",
76
- "@dxos/debug": "0.6.3-main.daaea86",
77
- "@dxos/invariant": "0.6.3-main.daaea86",
78
- "@dxos/log": "0.6.3-main.daaea86",
79
- "@dxos/network-manager": "0.6.3-main.daaea86",
80
- "@dxos/protocols": "0.6.3-main.daaea86",
81
- "@dxos/node-std": "0.6.3-main.daaea86",
82
- "@dxos/tracing": "0.6.3-main.daaea86",
83
- "@dxos/util": "0.6.3-main.daaea86"
84
+ "@dxos/async": "0.6.3-main.e6314a6",
85
+ "@dxos/client": "0.6.3-main.e6314a6",
86
+ "@dxos/client-protocol": "0.6.3-main.e6314a6",
87
+ "@dxos/client-services": "0.6.3-main.e6314a6",
88
+ "@dxos/context": "0.6.3-main.e6314a6",
89
+ "@dxos/config": "0.6.3-main.e6314a6",
90
+ "@dxos/debug": "0.6.3-main.e6314a6",
91
+ "@dxos/log": "0.6.3-main.e6314a6",
92
+ "@dxos/invariant": "0.6.3-main.e6314a6",
93
+ "@dxos/network-manager": "0.6.3-main.e6314a6",
94
+ "@dxos/protocols": "0.6.3-main.e6314a6",
95
+ "@dxos/node-std": "0.6.3-main.e6314a6",
96
+ "@dxos/util": "0.6.3-main.e6314a6",
97
+ "@dxos/tracing": "0.6.3-main.e6314a6"
84
98
  },
85
99
  "devDependencies": {
86
- "@sentry/types": "^8.7.0",
100
+ "@sentry/types": "^8.8.0",
87
101
  "@types/debug": "^4.1.10",
88
102
  "@types/js-yaml": "^4.0.5",
89
- "sentry-testkit": "^5.0.5"
103
+ "sentry-testkit": "^5.0.5",
104
+ "zone.js": ">=0.11.4 <0.12.0-0 || >=0.13.0 <0.14.0-0 || >=0.14.0 <0.15.0-0"
90
105
  },
91
106
  "publishConfig": {
92
107
  "access": "public"
@@ -3,5 +3,7 @@
3
3
  "TELEMETRY_API_KEY": "B00QG6PtJJrJ0VVFe0H5a6bcUUShKyZM",
4
4
  "IPDATA_API_KEY": "73dfdecdf979c18f07d50cf841bbdd9e589f237256326ac8cca23786",
5
5
  "DATADOG_API_KEY": null,
6
- "DATADOG_APP_KEY": null
6
+ "DATADOG_APP_KEY": null,
7
+ "OTEL_ENDPOINT": null,
8
+ "OTEL_AUTHORIZATION": null
7
9
  }
@@ -155,6 +155,7 @@ export const initializeAppObservability = async ({
155
155
  // TODO(nf): should provide capability to init Sentry earlier in booting process to capture errors during initialization.
156
156
 
157
157
  await observability.initialize();
158
+ observability.startErrorLogs();
158
159
 
159
160
  const ipData = await getIPData(config);
160
161
 
@@ -15,9 +15,10 @@ import { isNode } from '@dxos/util';
15
15
  import buildSecrets from './cli-observability-secrets.json';
16
16
  import { type DatadogMetrics } from './datadog';
17
17
  import { type IPData, getTelemetryIdentifier, mapSpaces } from './helpers';
18
+ import { type OtelLogs, type OtelMetrics, type OtelTraces } from './otel';
18
19
  import { type SegmentTelemetry, type EventOptions, type PageOptions } from './segment';
19
20
  import { type InitOptions, type captureException as SentryCaptureException } from './sentry';
20
- import { SentryLogProcessor } from './sentry/sentry-log-processor';
21
+ import { type SentryLogProcessor } from './sentry/sentry-log-processor';
21
22
 
22
23
  const SPACE_METRICS_MIN_INTERVAL = 1000 * 60; // 1 minute
23
24
  const SPACE_TELEMETRY_MIN_INTERVAL = 1000 * 60 * 60; // 1 hour
@@ -33,6 +34,8 @@ export type ObservabilitySecrets = {
33
34
  IPDATA_API_KEY: string | null;
34
35
  DATADOG_API_KEY: string | null;
35
36
  DATADOG_APP_KEY: string | null;
37
+ OTEL_ENDPOINT: string | null;
38
+ OTEL_AUTHORIZATION: string | null;
36
39
  };
37
40
 
38
41
  export type Mode = 'basic' | 'full' | 'disabled';
@@ -66,10 +69,14 @@ export type ObservabilityOptions = {
66
69
  export class Observability {
67
70
  // TODO(wittjosiah): Generic metrics interface.
68
71
  private _metrics?: DatadogMetrics;
72
+ private _otelMetrics?: OtelMetrics;
73
+ private _otelTraces?: OtelTraces;
69
74
  // TODO(wittjosiah): Generic telemetry interface.
70
75
  private _telemetryBatchSize: number;
71
76
  private _telemetry?: SegmentTelemetry;
72
77
  // TODO(wittjosiah): Generic error logging interface.
78
+ private _sentryLogProcessor?: SentryLogProcessor;
79
+ private _otelLogs?: OtelLogs;
73
80
  private _errorReportingOptions?: InitOptions;
74
81
  private _captureException?: typeof SentryCaptureException;
75
82
  private _captureUserFeedback?: (name: string, email: string, message: string) => Promise<void>;
@@ -135,11 +142,14 @@ export class Observability {
135
142
 
136
143
  process.env.DX_ENVIRONMENT && (mergedSecrets.DX_ENVIRONMENT = process.env.DX_ENVIRONMENT);
137
144
  process.env.DX_RELEASE && (mergedSecrets.DX_RELEASE = process.env.DX_RELEASE);
145
+ // TODO: prefix these with DX_?
138
146
  process.env.SENTRY_DESTINATION && (mergedSecrets.SENTRY_DESTINATION = process.env.SENTRY_DESTINATION);
139
147
  process.env.TELEMETRY_API_KEY && (mergedSecrets.TELEMETRY_API_KEY = process.env.TELEMETRY_API_KEY);
140
148
  process.env.IPDATA_API_KEY && (mergedSecrets.IPDATA_API_KEY = process.env.IPDATA_API_KEY);
141
149
  process.env.DATADOG_API_KEY && (mergedSecrets.DATADOG_API_KEY = process.env.DATADOG_API_KEY);
142
150
  process.env.DATADOG_APP_KEY && (mergedSecrets.DATADOG_APP_KEY = process.env.DATADOG_APP_KEY);
151
+ process.env.DX_OTEL_ENDPOINT && (mergedSecrets.OTEL_ENDPOINT = process.env.DX_OTEL_ENDPOINT);
152
+ process.env.DX_OTEL_AUTHORIZATION && (mergedSecrets.OTEL_AUTHORIZATION = process.env.DX_OTEL_AUTHORIZATION);
143
153
 
144
154
  return mergedSecrets;
145
155
  } else {
@@ -152,6 +162,8 @@ export class Observability {
152
162
  IPDATA_API_KEY: config?.get('runtime.app.env.DX_IPDATA_API_KEY'),
153
163
  DATADOG_API_KEY: config?.get('runtime.app.env.DX_DATADOG_API_KEY'),
154
164
  DATADOG_APP_KEY: config?.get('runtime.app.env.DX_DATADOG_APP_KEY'),
165
+ OTEL_ENDPOINT: config?.get('runtime.app.env.DX_OTEL_ENDPOINT'),
166
+ OTEL_AUTHORIZATION: config?.get('runtime.app.env.DX_OTEL_AUTHORIZATION'),
155
167
  ...secrets,
156
168
  };
157
169
  }
@@ -161,12 +173,16 @@ export class Observability {
161
173
  await this._initMetrics();
162
174
  await this._initTelemetry();
163
175
  await this._initErrorLogs();
176
+ await this._initTraces();
164
177
  }
165
178
 
166
179
  async close() {
167
- if (this._telemetry) {
168
- await this._telemetry.close();
169
- }
180
+ const closes: Promise<void>[] = [];
181
+ this._telemetry && closes.push(this._telemetry.close());
182
+ this._otelMetrics && closes.push(this._otelMetrics.close());
183
+ this._otelLogs && closes.push(this._otelLogs.close());
184
+
185
+ await Promise.all(closes);
170
186
  await this._ctx.dispose();
171
187
 
172
188
  // TODO(wittjosiah): Remove telemetry, etc. scripts.
@@ -258,6 +274,27 @@ export class Observability {
258
274
  } else {
259
275
  log('datadog disabled');
260
276
  }
277
+
278
+ if (this.enabled && this._secrets.OTEL_ENDPOINT && this._secrets.OTEL_AUTHORIZATION) {
279
+ const { OtelMetrics } = await import('./otel');
280
+ this._otelMetrics = new OtelMetrics({
281
+ endpoint: this._secrets.OTEL_ENDPOINT,
282
+ authorizationHeader: this._secrets.OTEL_AUTHORIZATION,
283
+ serviceName: this._namespace,
284
+ serviceVersion: this.getTag('release')?.value ?? '0.0.0',
285
+ getTags: () =>
286
+ Object.fromEntries(
287
+ Array.from(this._tags)
288
+ .filter(([key, value]) => {
289
+ return value.scope === 'all' || value.scope === 'metrics';
290
+ })
291
+ .map(([key, value]) => [key, value.value]),
292
+ ),
293
+ });
294
+ log('otel metrics enabled');
295
+ } else {
296
+ log('otel metrics disabled');
297
+ }
261
298
  }
262
299
 
263
300
  /**
@@ -267,6 +304,7 @@ export class Observability {
267
304
  */
268
305
  gauge(name: string, value: number | any, extraTags?: any) {
269
306
  this._metrics?.gauge(name, value, extraTags);
307
+ this._otelMetrics?.gauge(name, value, extraTags);
270
308
  }
271
309
 
272
310
  // TODO(nf): Refactor into ObservabilityExtensions.
@@ -399,14 +437,20 @@ export class Observability {
399
437
  scheduleTaskInterval(
400
438
  this._ctx,
401
439
  async () => {
402
- log('platform');
440
+ if (client.services.constructor.name === 'WorkerClientServices') {
441
+ const memory = (window.performance as any).memory;
442
+ if (memory) {
443
+ this.gauge('dxos.client.runtime.heapTotal', memory.totalJSHeapSize);
444
+ this.gauge('dxos.client.runtime.heapUsed', memory.usedJSHeapSize);
445
+ this.gauge('dxos.client.runtime.heapSizeLimit', memory.jsHeapSizeLimit);
446
+ }
447
+ }
403
448
  client.services.services.SystemService?.getPlatform()
404
449
  .then((platform) => {
405
- log('platform', { platform });
406
450
  if (platform.memory) {
407
- this.gauge('dxos.client.runtime.rss', platform.memory.rss);
408
- this.gauge('dxos.client.runtime.heapTotal', platform.memory.heapTotal);
409
- this.gauge('dxos.client.runtime.heapUsed', platform.memory.heapUsed);
451
+ this.gauge('dxos.client.services.runtime.rss', platform.memory.rss);
452
+ this.gauge('dxos.client.services.runtime.heapTotal', platform.memory.heapTotal);
453
+ this.gauge('dxos.client.services.runtime.heapUsed', platform.memory.heapUsed);
410
454
  }
411
455
  })
412
456
  .catch((error) => log('platform error', { error }));
@@ -464,6 +508,7 @@ export class Observability {
464
508
  private async _initErrorLogs() {
465
509
  if (this._secrets.SENTRY_DESTINATION && this._mode !== 'disabled') {
466
510
  const { captureException, captureUserFeedback, init, setTag } = await import('./sentry');
511
+ const { SentryLogProcessor } = await import('./sentry/sentry-log-processor');
467
512
  this._captureException = captureException;
468
513
  this._captureUserFeedback = captureUserFeedback;
469
514
 
@@ -474,17 +519,14 @@ export class Observability {
474
519
  dest: this._secrets.SENTRY_DESTINATION,
475
520
  options: this._errorReportingOptions,
476
521
  });
477
-
478
- const logProcessor = new SentryLogProcessor();
522
+ this._sentryLogProcessor = new SentryLogProcessor();
479
523
  init({
480
524
  ...this._errorReportingOptions,
481
525
  destination: this._secrets.SENTRY_DESTINATION,
482
526
  scrubFilenames: this._mode !== 'full',
483
- onError: (event) => logProcessor.addLogBreadcrumbsTo(event),
527
+ onError: (event) => this._sentryLogProcessor!.addLogBreadcrumbsTo(event),
484
528
  });
485
-
486
529
  // TODO(nf): set platform at instantiation? needed for node.
487
- log.runtimeConfig.processors.push(logProcessor.logProcessor);
488
530
 
489
531
  // TODO(nf): is this different than passing as properties in options?
490
532
  this._tags.forEach((v, k) => {
@@ -495,6 +537,57 @@ export class Observability {
495
537
  } else {
496
538
  log('sentry disabled');
497
539
  }
540
+
541
+ if (this._secrets.OTEL_ENDPOINT && this._secrets.OTEL_AUTHORIZATION && this._mode !== 'disabled') {
542
+ const { OtelLogs } = await import('./otel');
543
+ this._otelLogs = new OtelLogs({
544
+ endpoint: this._secrets.OTEL_ENDPOINT,
545
+ authorizationHeader: this._secrets.OTEL_AUTHORIZATION,
546
+ serviceName: this._namespace,
547
+ serviceVersion: this.getTag('release')?.value ?? '0.0.0',
548
+ getTags: () =>
549
+ Object.fromEntries(
550
+ Array.from(this._tags)
551
+ .filter(([key, value]) => {
552
+ return value.scope === 'all' || value.scope === 'errors';
553
+ })
554
+ .map(([key, value]) => [key, value.value]),
555
+ ),
556
+ });
557
+ log('otel logs enabled', { namespace: this._namespace });
558
+ } else {
559
+ log('otel logs disabled');
560
+ }
561
+ }
562
+
563
+ startErrorLogs() {
564
+ this._sentryLogProcessor && log.runtimeConfig.processors.push(this._sentryLogProcessor.logProcessor);
565
+ this._otelLogs && log.runtimeConfig.processors.push(this._otelLogs.logProcessor);
566
+ }
567
+
568
+ startTraces() {
569
+ this._otelTraces && this._otelTraces.start();
570
+ }
571
+
572
+ // TODO(nf): refactor init based on providers and their capabilities
573
+ private async _initTraces() {
574
+ if (this._secrets.OTEL_ENDPOINT && this._secrets.OTEL_AUTHORIZATION && this._mode !== 'disabled') {
575
+ const { OtelTraces } = await import('./otel');
576
+ this._otelTraces = new OtelTraces({
577
+ endpoint: this._secrets.OTEL_ENDPOINT,
578
+ authorizationHeader: this._secrets.OTEL_AUTHORIZATION,
579
+ serviceName: this._namespace,
580
+ serviceVersion: this.getTag('release')?.value ?? '0.0.0',
581
+ getTags: () =>
582
+ Object.fromEntries(
583
+ Array.from(this._tags)
584
+ .filter(([key, value]) => {
585
+ return value.scope === 'all' || value.scope === 'metrics';
586
+ })
587
+ .map(([key, value]) => [key, value.value]),
588
+ ),
589
+ });
590
+ }
498
591
  }
499
592
 
500
593
  /**
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './otel';
6
+ export * from './logs';
7
+ export * from './metrics';
8
+ export * from './traces';
@@ -0,0 +1,86 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { SeverityNumber } from '@opentelemetry/api-logs';
6
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
7
+ import { Resource } from '@opentelemetry/resources';
8
+ import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';
9
+ import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
10
+
11
+ import {
12
+ getContextFromEntry,
13
+ getRelativeFilename,
14
+ type LogConfig,
15
+ type LogEntry,
16
+ LogLevel,
17
+ type LogProcessor,
18
+ shouldLog,
19
+ } from '@dxos/log';
20
+ import { jsonlogify } from '@dxos/util';
21
+
22
+ import { type OtelOptions, setDiagLogger } from './otel';
23
+
24
+ export class OtelLogs {
25
+ private _loggerProvider: LoggerProvider;
26
+ constructor(private readonly options: OtelOptions) {
27
+ setDiagLogger(options.consoleDiagLogLevel);
28
+ const resource = Resource.default().merge(
29
+ new Resource({
30
+ [SEMRESATTRS_SERVICE_NAME]: this.options.serviceName,
31
+ [SEMRESATTRS_SERVICE_VERSION]: this.options.serviceVersion,
32
+ }),
33
+ );
34
+ const logExporter = new OTLPLogExporter({
35
+ url: this.options.endpoint + '/v1/logs',
36
+ headers: {
37
+ Authorization: this.options.authorizationHeader,
38
+ },
39
+ concurrencyLimit: 10, // an optional limit on pending requests
40
+ });
41
+ this._loggerProvider = new LoggerProvider({ resource });
42
+ this._loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
43
+ }
44
+
45
+ public readonly logProcessor: LogProcessor = (config: LogConfig, entry: LogEntry) => {
46
+ const logger = this._loggerProvider.getLogger('dxos-observability', this.options.serviceVersion);
47
+
48
+ if (!shouldLog(entry, config.captureFilters) || entry.meta?.S?.remoteSessionId) {
49
+ return;
50
+ }
51
+ const record = {
52
+ ...entry,
53
+ ...(entry.meta ? { meta: { file: getRelativeFilename(entry.meta.F), line: entry.meta.L } } : {}),
54
+ context: jsonlogify(getContextFromEntry(entry)),
55
+ };
56
+
57
+ logger.emit({
58
+ severityNumber: convertLevel(entry.level),
59
+ body: JSON.stringify(record),
60
+ attributes: this.options.getTags(),
61
+ });
62
+ };
63
+
64
+ flush() {
65
+ return this._loggerProvider.forceFlush();
66
+ }
67
+
68
+ close() {
69
+ return this._loggerProvider.shutdown();
70
+ }
71
+ }
72
+
73
+ const convertLevel = (level: LogLevel): SeverityNumber => {
74
+ switch (level) {
75
+ case LogLevel.DEBUG:
76
+ return SeverityNumber.DEBUG;
77
+ case LogLevel.INFO:
78
+ return SeverityNumber.INFO;
79
+ case LogLevel.WARN:
80
+ return SeverityNumber.WARN;
81
+ case LogLevel.ERROR:
82
+ return SeverityNumber.ERROR;
83
+ default:
84
+ return SeverityNumber.ERROR;
85
+ }
86
+ };
@@ -0,0 +1,111 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Attributes, type Meter, type ObservableGauge } from '@opentelemetry/api';
6
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
7
+ import { Resource } from '@opentelemetry/resources';
8
+ import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
9
+ import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
10
+
11
+ import { log } from '@dxos/log';
12
+ import { TRACE_PROCESSOR, type MetricData } from '@dxos/tracing';
13
+
14
+ import { type OtelOptions, setDiagLogger } from './otel';
15
+
16
+ const EXPORT_INTERVAL = 60 * 1000;
17
+
18
+ type SynchronousGauge = {
19
+ gauge: ObservableGauge<Attributes>;
20
+ nextValue: number;
21
+ nextTags?: any;
22
+ };
23
+
24
+ export class OtelMetrics {
25
+ private _meterProvider: MeterProvider;
26
+ private _meter: Meter;
27
+ private _gauges = new Map<string, SynchronousGauge>();
28
+
29
+ constructor(private readonly options: OtelOptions) {
30
+ // TODO: improve error handling/logging
31
+ // https://github.com/open-telemetry/opentelemetry-js/issues/4823
32
+ setDiagLogger(options.consoleDiagLogLevel);
33
+ const resource = Resource.default().merge(
34
+ new Resource({
35
+ [SEMRESATTRS_SERVICE_NAME]: this.options.serviceName,
36
+ [SEMRESATTRS_SERVICE_VERSION]: this.options.serviceVersion,
37
+ }),
38
+ );
39
+
40
+ const grafanaMetricReader = new PeriodicExportingMetricReader({
41
+ exporter: new OTLPMetricExporter({
42
+ url: this.options.endpoint + '/v1/metrics',
43
+ headers: {
44
+ Authorization: this.options.authorizationHeader,
45
+ },
46
+ }),
47
+ exportIntervalMillis: EXPORT_INTERVAL,
48
+ });
49
+
50
+ this._meterProvider = new MeterProvider({
51
+ resource,
52
+ readers: [grafanaMetricReader],
53
+ });
54
+ this._meter = this._meterProvider.getMeter('dxos-observability');
55
+
56
+ const metrics = {
57
+ // TODO: update metrics names and remove prefix?
58
+ increment: (name: string, value?: number, data?: MetricData) => {
59
+ this.increment(name, value, convertTags(data));
60
+ },
61
+ distribution: (name: string, value: number, data?: MetricData) => {
62
+ this.distribution(name, value, convertTags(data));
63
+ },
64
+ set: (name: string, value: number | string, data?: MetricData) => {
65
+ // Not implemented, not part of Otel spec.
66
+ },
67
+ gauge: (name: string, value: number, data?: MetricData) => {
68
+ this.gauge(name, value, convertTags(data));
69
+ },
70
+ };
71
+
72
+ TRACE_PROCESSOR.remoteMetrics.registerProcessor(metrics);
73
+ }
74
+
75
+ gauge(name: string, value: number, tags?: any) {
76
+ const gauge = this._meter.createGauge(name);
77
+ log('otel gauge', { name, value, tags: { ...this.options.getTags(), ...tags } });
78
+ gauge.record(value, { ...this.options.getTags(), ...tags });
79
+ }
80
+
81
+ increment(name: string, value?: number, tags?: any) {
82
+ const counter = this._meter.createCounter(name);
83
+ log('otel counter', { name, value, tags: { ...this.options.getTags(), ...tags } });
84
+ counter.add(value ?? 1, { ...this.options.getTags(), ...tags });
85
+ }
86
+
87
+ distribution(name: string, value: number, tags?: any) {
88
+ const distribution = this._meter.createHistogram(name);
89
+ log('otel distribution', { name, value, tags: { ...this.options.getTags(), ...tags } });
90
+ distribution.record(value, { ...this.options.getTags(), ...tags });
91
+ }
92
+
93
+ flush() {
94
+ return this._meterProvider.forceFlush();
95
+ }
96
+
97
+ close() {
98
+ return this._meterProvider.shutdown();
99
+ }
100
+ }
101
+
102
+ const convertTags = (data?: MetricData) => {
103
+ if (data && data.tags) {
104
+ return Object.entries(data.tags).reduce<{ [key: string]: any }>((obj, [key, value]) => {
105
+ obj[key] = value;
106
+ return obj;
107
+ }, {});
108
+ } else {
109
+ return {};
110
+ }
111
+ };
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
6
+
7
+ export type OtelOptions = {
8
+ endpoint: string;
9
+ authorizationHeader: string;
10
+ serviceName: string; // For the Otel API, the name of the entity for which signals (metrics or trace) are collected.
11
+ serviceVersion: string; // For the Otel API, The name of the entity for which signals (metrics or trace) are collected.
12
+ getTags: () => { [key: string]: string };
13
+ consoleDiagLogLevel?: string;
14
+ };
15
+
16
+ export const setDiagLogger = (level?: string) => {
17
+ const logLevel = DiagLogLevel[level as keyof typeof DiagLogLevel];
18
+ if (logLevel) {
19
+ diag.setLogger(new DiagConsoleLogger(), logLevel);
20
+ }
21
+ };