@dxos/observability 0.5.9-main.4c63b2f → 0.5.9-main.4cd994e

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 (47) hide show
  1. package/dist/lib/browser/{chunk-Y56RE3XY.mjs → chunk-VHY5QSKY.mjs} +80 -19
  2. package/dist/lib/browser/chunk-VHY5QSKY.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -1
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/{observability-PQXVFXD7.mjs → observability-QSVYYZQL.mjs} +2 -2
  6. package/dist/lib/browser/otel-3B5NJ7JN.mjs +127 -0
  7. package/dist/lib/browser/otel-3B5NJ7JN.mjs.map +7 -0
  8. package/dist/lib/node/{chunk-AUTP6TII.cjs → chunk-2JAUQJ4Y.cjs} +83 -22
  9. package/dist/lib/node/chunk-2JAUQJ4Y.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +15 -15
  11. package/dist/lib/node/index.cjs.map +1 -1
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/{observability-LPC6KPIO.cjs → observability-XBZZCSZG.cjs} +6 -6
  14. package/dist/lib/node/{observability-LPC6KPIO.cjs.map → observability-XBZZCSZG.cjs.map} +2 -2
  15. package/dist/lib/node/otel-WOKFA2PO.cjs +149 -0
  16. package/dist/lib/node/otel-WOKFA2PO.cjs.map +7 -0
  17. package/dist/types/src/cli-observability-secrets.json +3 -1
  18. package/dist/types/src/observability.d.ts +3 -0
  19. package/dist/types/src/observability.d.ts.map +1 -1
  20. package/dist/types/src/observability.js +48 -1
  21. package/dist/types/src/observability.js.map +1 -1
  22. package/dist/types/src/otel/index.d.ts +4 -0
  23. package/dist/types/src/otel/index.d.ts.map +1 -0
  24. package/dist/types/src/otel/index.js +23 -0
  25. package/dist/types/src/otel/index.js.map +1 -0
  26. package/dist/types/src/otel/logs.d.ts +8 -0
  27. package/dist/types/src/otel/logs.d.ts.map +1 -0
  28. package/dist/types/src/otel/logs.js +64 -0
  29. package/dist/types/src/otel/logs.js.map +1 -0
  30. package/dist/types/src/otel/metrics.d.ts +11 -0
  31. package/dist/types/src/otel/metrics.d.ts.map +1 -0
  32. package/dist/types/src/otel/metrics.js +64 -0
  33. package/dist/types/src/otel/metrics.js.map +1 -0
  34. package/dist/types/src/otel/otel.d.ts +10 -0
  35. package/dist/types/src/otel/otel.d.ts.map +1 -0
  36. package/dist/types/src/otel/otel.js +6 -0
  37. package/dist/types/src/otel/otel.js.map +1 -0
  38. package/package.json +23 -15
  39. package/src/cli-observability-secrets.json +3 -1
  40. package/src/observability.ts +55 -0
  41. package/src/otel/index.ts +7 -0
  42. package/src/otel/logs.ts +69 -0
  43. package/src/otel/metrics.ts +79 -0
  44. package/src/otel/otel.ts +11 -0
  45. package/dist/lib/browser/chunk-Y56RE3XY.mjs.map +0 -7
  46. package/dist/lib/node/chunk-AUTP6TII.cjs.map +0 -7
  47. /package/dist/lib/browser/{observability-PQXVFXD7.mjs.map → observability-QSVYYZQL.mjs.map} +0 -0
@@ -0,0 +1,8 @@
1
+ import { type LogProcessor } from '@dxos/log';
2
+ import { type OtelOptions } from './otel';
3
+ export declare class OtelLogs {
4
+ private readonly options;
5
+ constructor(options: OtelOptions);
6
+ getLogProcessor(): LogProcessor;
7
+ }
8
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../../../src/otel/logs.ts"],"names":[],"mappings":"AAWA,OAAO,EAA2C,KAAK,YAAY,EAAa,MAAM,WAAW,CAAC;AAElG,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,WAAW;IAIjD,eAAe,IAAI,YAAY;CAiChC"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2024 DXOS.org
4
+ //
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OtelLogs = void 0;
7
+ const api_1 = require("@opentelemetry/api");
8
+ const api_logs_1 = require("@opentelemetry/api-logs");
9
+ const exporter_logs_otlp_http_1 = require("@opentelemetry/exporter-logs-otlp-http");
10
+ const resources_1 = require("@opentelemetry/resources");
11
+ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
12
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
13
+ const log_1 = require("@dxos/log");
14
+ class OtelLogs {
15
+ constructor(options) {
16
+ this.options = options;
17
+ api_1.diag.setLogger(new api_1.DiagConsoleLogger(), api_1.DiagLogLevel.INFO);
18
+ }
19
+ getLogProcessor() {
20
+ const resource = resources_1.Resource.default().merge(new resources_1.Resource({
21
+ [semantic_conventions_1.SEMRESATTRS_SERVICE_NAME]: this.options.serviceName,
22
+ [semantic_conventions_1.SEMRESATTRS_SERVICE_VERSION]: this.options.serviceVersion,
23
+ }));
24
+ const collectorOptions = {
25
+ url: this.options.endpoint + '/v1/logs',
26
+ headers: {
27
+ Authorization: this.options.authorizationHeader,
28
+ },
29
+ concurrencyLimit: 1, // an optional limit on pending requests
30
+ };
31
+ const logExporter = new exporter_logs_otlp_http_1.OTLPLogExporter(collectorOptions);
32
+ const loggerProvider = new sdk_logs_1.LoggerProvider({ resource });
33
+ loggerProvider.addLogRecordProcessor(new sdk_logs_1.BatchLogRecordProcessor(logExporter));
34
+ // TODO: namespace?
35
+ const logger = loggerProvider.getLogger('dxos-observability', '0.0.0');
36
+ return (config, entry) => {
37
+ const { level, message, context, error } = entry;
38
+ if (!(0, log_1.shouldLog)(entry, config.filters)) {
39
+ return;
40
+ }
41
+ logger.emit({
42
+ severityNumber: convertLevel(level),
43
+ body: { message, error: error !== null && error !== void 0 ? error : undefined, ...context },
44
+ attributes: this.options.getTags(),
45
+ });
46
+ };
47
+ }
48
+ }
49
+ exports.OtelLogs = OtelLogs;
50
+ const convertLevel = (level) => {
51
+ switch (level) {
52
+ case log_1.LogLevel.DEBUG:
53
+ return api_logs_1.SeverityNumber.DEBUG;
54
+ case log_1.LogLevel.INFO:
55
+ return api_logs_1.SeverityNumber.INFO;
56
+ case log_1.LogLevel.WARN:
57
+ return api_logs_1.SeverityNumber.WARN;
58
+ case log_1.LogLevel.ERROR:
59
+ return api_logs_1.SeverityNumber.ERROR;
60
+ default:
61
+ return api_logs_1.SeverityNumber.ERROR;
62
+ }
63
+ };
64
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../../../src/otel/logs.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE;;;AAEF,4CAA2E;AAC3E,sDAAyD;AACzD,oFAAyE;AACzE,wDAAoD;AACpD,sDAAkF;AAClF,8EAA4G;AAE5G,mCAAkG;AAIlG,MAAa,QAAQ;IACnB,YAA6B,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;QAC/C,UAAI,CAAC,SAAS,CAAC,IAAI,uBAAiB,EAAE,EAAE,kBAAY,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED,eAAe;QACb,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;QACF,MAAM,gBAAgB,GAAG;YACvB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,UAAU;YACvC,OAAO,EAAE;gBACP,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB;aAChD;YACD,gBAAgB,EAAE,CAAC,EAAE,wCAAwC;SAC9D,CAAC;QACF,MAAM,WAAW,GAAG,IAAI,yCAAe,CAAC,gBAAgB,CAAC,CAAC;QAC1D,MAAM,cAAc,GAAG,IAAI,yBAAc,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAExD,cAAc,CAAC,qBAAqB,CAAC,IAAI,kCAAuB,CAAC,WAAW,CAAC,CAAC,CAAC;QAE/E,mBAAmB;QACnB,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC;QACvE,OAAO,CAAC,MAAiB,EAAE,KAAe,EAAE,EAAE;YAC5C,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;YACjD,IAAI,CAAC,IAAA,eAAS,EAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,cAAc,EAAE,YAAY,CAAC,KAAK,CAAC;gBACnC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,SAAS,EAAE,GAAG,OAAO,EAAE;gBACxD,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;aACnC,CAAC,CAAC;QACL,CAAC,CAAC;IACJ,CAAC;CACF;AAtCD,4BAsCC;AAED,MAAM,YAAY,GAAG,CAAC,KAAe,EAAkB,EAAE;IACvD,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,cAAQ,CAAC,KAAK;YACjB,OAAO,yBAAc,CAAC,KAAK,CAAC;QAC9B,KAAK,cAAQ,CAAC,IAAI;YAChB,OAAO,yBAAc,CAAC,IAAI,CAAC;QAC7B,KAAK,cAAQ,CAAC,IAAI;YAChB,OAAO,yBAAc,CAAC,IAAI,CAAC;QAC7B,KAAK,cAAQ,CAAC,KAAK;YACjB,OAAO,yBAAc,CAAC,KAAK,CAAC;QAC9B;YACE,OAAO,yBAAc,CAAC,KAAK,CAAC;IAChC,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type OtelOptions } from './otel';
2
+ export declare class OtelMetrics {
3
+ private readonly options;
4
+ private _meterProvider;
5
+ private _gauges;
6
+ constructor(options: OtelOptions);
7
+ gauge(name: string, value: number, tags?: any): void;
8
+ flush(): Promise<void>;
9
+ close(): Promise<void>;
10
+ }
11
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../src/otel/metrics.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAU1C,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,OAAO,CAAuC;gBAEzB,OAAO,EAAE,WAAW;IA0BjD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAqB7C,KAAK;IAIL,KAAK;CAGN"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2024 DXOS.org
4
+ //
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OtelMetrics = void 0;
7
+ const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-otlp-http");
8
+ const resources_1 = require("@opentelemetry/resources");
9
+ const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
10
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
11
+ const EXPORT_INTERVAL = 60 * 1000;
12
+ class OtelMetrics {
13
+ constructor(options) {
14
+ this.options = options;
15
+ this._gauges = new Map();
16
+ // TODO: improve error handling/logging
17
+ // https://github.com/open-telemetry/opentelemetry-js/issues/4823
18
+ const resource = resources_1.Resource.default().merge(new resources_1.Resource({
19
+ [semantic_conventions_1.SEMRESATTRS_SERVICE_NAME]: this.options.serviceName,
20
+ [semantic_conventions_1.SEMRESATTRS_SERVICE_VERSION]: this.options.serviceVersion,
21
+ }));
22
+ const grafanaMetricReader = new sdk_metrics_1.PeriodicExportingMetricReader({
23
+ exporter: new exporter_metrics_otlp_http_1.OTLPMetricExporter({
24
+ url: this.options.endpoint + '/v1/metrics',
25
+ headers: {
26
+ Authorization: this.options.authorizationHeader,
27
+ },
28
+ }),
29
+ exportIntervalMillis: EXPORT_INTERVAL,
30
+ });
31
+ this._meterProvider = new sdk_metrics_1.MeterProvider({
32
+ resource,
33
+ readers: [grafanaMetricReader],
34
+ });
35
+ }
36
+ gauge(name, value, tags) {
37
+ const meter = this._meterProvider.getMeter('dxos-observability');
38
+ const observableGauge = this._gauges.get(name);
39
+ const mergedTags = { ...this.options.getTags(), ...tags };
40
+ if (!observableGauge) {
41
+ const synchronousGauge = {
42
+ gauge: meter.createObservableGauge(name),
43
+ nextValue: value,
44
+ nextTags: mergedTags,
45
+ };
46
+ synchronousGauge.gauge.addCallback((observerResult) => {
47
+ observerResult.observe(synchronousGauge.nextValue, synchronousGauge.nextTags);
48
+ });
49
+ this._gauges.set(name, synchronousGauge);
50
+ }
51
+ else {
52
+ observableGauge.nextTags = mergedTags;
53
+ observableGauge.nextValue = value;
54
+ }
55
+ }
56
+ flush() {
57
+ return this._meterProvider.forceFlush();
58
+ }
59
+ close() {
60
+ return this._meterProvider.shutdown();
61
+ }
62
+ }
63
+ exports.OtelMetrics = OtelMetrics;
64
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../../src/otel/metrics.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE;;;AAGF,0FAA+E;AAC/E,wDAAoD;AACpD,4DAA0F;AAC1F,8EAA4G;AAI5G,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,CAAC;AAQlC,MAAa,WAAW;IAItB,YAA6B,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;QAFzC,YAAO,GAAG,IAAI,GAAG,EAA4B,CAAC;QAGpD,uCAAuC;QACvC,kEAAkE;QAClE,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,mBAAmB,GAAG,IAAI,2CAA6B,CAAC;YAC5D,QAAQ,EAAE,IAAI,+CAAkB,CAAC;gBAC/B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,aAAa;gBAC1C,OAAO,EAAE;oBACP,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB;iBAChD;aACF,CAAC;YACF,oBAAoB,EAAE,eAAe;SACtC,CAAC,CAAC;QAEH,IAAI,CAAC,cAAc,GAAG,IAAI,2BAAa,CAAC;YACtC,QAAQ;YACR,OAAO,EAAE,CAAC,mBAAmB,CAAC;SAC/B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAY,EAAE,KAAa,EAAE,IAAU;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;QACjE,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE/C,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC;QAC1D,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,gBAAgB,GAAG;gBACvB,KAAK,EAAE,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC;gBACxC,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,UAAU;aACrB,CAAC;YACF,gBAAgB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,cAAc,EAAE,EAAE;gBACpD,cAAc,CAAC,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YAChF,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,eAAe,CAAC,QAAQ,GAAG,UAAU,CAAC;YACtC,eAAe,CAAC,SAAS,GAAG,KAAK,CAAC;QACpC,CAAC;IACH,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;IACxC,CAAC;CACF;AA1DD,kCA0DC"}
@@ -0,0 +1,10 @@
1
+ export type OtelOptions = {
2
+ endpoint: string;
3
+ authorizationHeader: string;
4
+ serviceName: string;
5
+ serviceVersion: string;
6
+ getTags: () => {
7
+ [key: string]: string;
8
+ };
9
+ };
10
+ //# sourceMappingURL=otel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../../../../src/otel/otel.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CAC1C,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2024 DXOS.org
4
+ //
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.js","sourceRoot":"","sources":["../../../../src/otel/otel.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/observability",
3
- "version": "0.5.9-main.4c63b2f",
3
+ "version": "0.5.9-main.4cd994e",
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",
@@ -58,6 +58,14 @@
58
58
  "src"
59
59
  ],
60
60
  "dependencies": {
61
+ "@opentelemetry/api": "^1.8.0",
62
+ "@opentelemetry/api-logs": "^0.52.1",
63
+ "@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
64
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.52.0",
65
+ "@opentelemetry/resources": "^1.25.0",
66
+ "@opentelemetry/sdk-logs": "^0.52.1",
67
+ "@opentelemetry/sdk-metrics": "^1.8.0",
68
+ "@opentelemetry/semantic-conventions": "^1.25.0",
61
69
  "@segment/analytics-node": "^2.1.0",
62
70
  "@segment/snippet": "^4.15.3",
63
71
  "@sentry/browser": "^8.7.0",
@@ -67,20 +75,20 @@
67
75
  "js-yaml": "^4.1.0",
68
76
  "localforage": "^1.10.0",
69
77
  "uuid": "^8.3.2",
70
- "@dxos/client-protocol": "0.5.9-main.4c63b2f",
71
- "@dxos/async": "0.5.9-main.4c63b2f",
72
- "@dxos/client-services": "0.5.9-main.4c63b2f",
73
- "@dxos/config": "0.5.9-main.4c63b2f",
74
- "@dxos/context": "0.5.9-main.4c63b2f",
75
- "@dxos/invariant": "0.5.9-main.4c63b2f",
76
- "@dxos/log": "0.5.9-main.4c63b2f",
77
- "@dxos/debug": "0.5.9-main.4c63b2f",
78
- "@dxos/network-manager": "0.5.9-main.4c63b2f",
79
- "@dxos/node-std": "0.5.9-main.4c63b2f",
80
- "@dxos/tracing": "0.5.9-main.4c63b2f",
81
- "@dxos/protocols": "0.5.9-main.4c63b2f",
82
- "@dxos/util": "0.5.9-main.4c63b2f",
83
- "@dxos/react-client": "0.5.9-main.4c63b2f"
78
+ "@dxos/async": "0.5.9-main.4cd994e",
79
+ "@dxos/client-protocol": "0.5.9-main.4cd994e",
80
+ "@dxos/client-services": "0.5.9-main.4cd994e",
81
+ "@dxos/config": "0.5.9-main.4cd994e",
82
+ "@dxos/context": "0.5.9-main.4cd994e",
83
+ "@dxos/invariant": "0.5.9-main.4cd994e",
84
+ "@dxos/log": "0.5.9-main.4cd994e",
85
+ "@dxos/debug": "0.5.9-main.4cd994e",
86
+ "@dxos/node-std": "0.5.9-main.4cd994e",
87
+ "@dxos/network-manager": "0.5.9-main.4cd994e",
88
+ "@dxos/protocols": "0.5.9-main.4cd994e",
89
+ "@dxos/react-client": "0.5.9-main.4cd994e",
90
+ "@dxos/util": "0.5.9-main.4cd994e",
91
+ "@dxos/tracing": "0.5.9-main.4cd994e"
84
92
  },
85
93
  "devDependencies": {
86
94
  "@sentry/types": "^8.7.0",
@@ -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
  }
@@ -15,6 +15,7 @@ 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 OtelMetrics } 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
21
  import { SentryLogProcessor } from './sentry/sentry-log-processor';
@@ -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,6 +69,7 @@ export type ObservabilityOptions = {
66
69
  export class Observability {
67
70
  // TODO(wittjosiah): Generic metrics interface.
68
71
  private _metrics?: DatadogMetrics;
72
+ private _otelMetrics?: OtelMetrics;
69
73
  // TODO(wittjosiah): Generic telemetry interface.
70
74
  private _telemetryBatchSize: number;
71
75
  private _telemetry?: SegmentTelemetry;
@@ -135,11 +139,14 @@ export class Observability {
135
139
 
136
140
  process.env.DX_ENVIRONMENT && (mergedSecrets.DX_ENVIRONMENT = process.env.DX_ENVIRONMENT);
137
141
  process.env.DX_RELEASE && (mergedSecrets.DX_RELEASE = process.env.DX_RELEASE);
142
+ // TODO: prefix these with DX_?
138
143
  process.env.SENTRY_DESTINATION && (mergedSecrets.SENTRY_DESTINATION = process.env.SENTRY_DESTINATION);
139
144
  process.env.TELEMETRY_API_KEY && (mergedSecrets.TELEMETRY_API_KEY = process.env.TELEMETRY_API_KEY);
140
145
  process.env.IPDATA_API_KEY && (mergedSecrets.IPDATA_API_KEY = process.env.IPDATA_API_KEY);
141
146
  process.env.DATADOG_API_KEY && (mergedSecrets.DATADOG_API_KEY = process.env.DATADOG_API_KEY);
142
147
  process.env.DATADOG_APP_KEY && (mergedSecrets.DATADOG_APP_KEY = process.env.DATADOG_APP_KEY);
148
+ process.env.DX_OTEL_ENDPOINT && (mergedSecrets.OTEL_ENDPOINT = process.env.DX_OTEL_ENDPOINT);
149
+ process.env.DX_OTEL_AUTHORIZATION && (mergedSecrets.OTEL_AUTHORIZATION = process.env.DX_OTEL_AUTHORIZATION);
143
150
 
144
151
  return mergedSecrets;
145
152
  } else {
@@ -152,6 +159,8 @@ export class Observability {
152
159
  IPDATA_API_KEY: config?.get('runtime.app.env.DX_IPDATA_API_KEY'),
153
160
  DATADOG_API_KEY: config?.get('runtime.app.env.DX_DATADOG_API_KEY'),
154
161
  DATADOG_APP_KEY: config?.get('runtime.app.env.DX_DATADOG_APP_KEY'),
162
+ OTEL_ENDPOINT: config?.get('runtime.app.env.DX_OTEL_ENDPOINT'),
163
+ OTEL_AUTHORIZATION: config?.get('runtime.app.env.DX_OTEL_AUTHORIZATION'),
155
164
  ...secrets,
156
165
  };
157
166
  }
@@ -167,6 +176,9 @@ export class Observability {
167
176
  if (this._telemetry) {
168
177
  await this._telemetry.close();
169
178
  }
179
+ if (this._otelMetrics) {
180
+ await this._otelMetrics.close();
181
+ }
170
182
  await this._ctx.dispose();
171
183
 
172
184
  // TODO(wittjosiah): Remove telemetry, etc. scripts.
@@ -255,6 +267,27 @@ export class Observability {
255
267
  } else {
256
268
  log('datadog disabled');
257
269
  }
270
+
271
+ if (this.enabled && this._secrets.OTEL_ENDPOINT && this._secrets.OTEL_AUTHORIZATION) {
272
+ const { OtelMetrics } = await import('./otel');
273
+ this._otelMetrics = new OtelMetrics({
274
+ endpoint: this._secrets.OTEL_ENDPOINT,
275
+ authorizationHeader: this._secrets.OTEL_AUTHORIZATION,
276
+ serviceName: this._namespace,
277
+ serviceVersion: this.getTag('release')?.value ?? '0.0.0',
278
+ getTags: () =>
279
+ Object.fromEntries(
280
+ Array.from(this._tags)
281
+ .filter(([key, value]) => {
282
+ return value.scope === 'all' || value.scope === 'metrics';
283
+ })
284
+ .map(([key, value]) => [key, value.value]),
285
+ ),
286
+ });
287
+ log('otel metrics enabled');
288
+ } else {
289
+ log('otel metrics disabled');
290
+ }
258
291
  }
259
292
 
260
293
  /**
@@ -264,6 +297,7 @@ export class Observability {
264
297
  */
265
298
  gauge(name: string, value: number | any, extraTags?: any) {
266
299
  this._metrics?.gauge(name, value, extraTags);
300
+ this._otelMetrics?.gauge(name, value, extraTags);
267
301
  }
268
302
 
269
303
  // TODO(nf): Refactor into ObservabilityExtensions.
@@ -489,6 +523,27 @@ export class Observability {
489
523
  } else {
490
524
  log('sentry disabled');
491
525
  }
526
+
527
+ if (this._secrets.OTEL_ENDPOINT && this._secrets.OTEL_AUTHORIZATION && this._mode !== 'disabled') {
528
+ const { OtelLogs } = await import('./otel');
529
+ const logs = new OtelLogs({
530
+ endpoint: this._secrets.OTEL_ENDPOINT,
531
+ authorizationHeader: this._secrets.OTEL_AUTHORIZATION,
532
+ serviceName: this._namespace,
533
+ serviceVersion: this.getTag('release')?.value ?? '0.0.0',
534
+ getTags: () =>
535
+ Object.fromEntries(
536
+ Array.from(this._tags)
537
+ .filter(([key, value]) => {
538
+ return value.scope === 'all' || value.scope === 'errors';
539
+ })
540
+ .map(([key, value]) => [key, value.value]),
541
+ ),
542
+ });
543
+ log.runtimeConfig.processors.push(logs.getLogProcessor());
544
+ } else {
545
+ log('otel logs disabled');
546
+ }
492
547
  }
493
548
 
494
549
  /**
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './otel';
6
+ export * from './logs';
7
+ export * from './metrics';
@@ -0,0 +1,69 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
6
+ import { SeverityNumber } from '@opentelemetry/api-logs';
7
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
8
+ import { Resource } from '@opentelemetry/resources';
9
+ import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
10
+ import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
11
+
12
+ import { type LogConfig, type LogEntry, LogLevel, type LogProcessor, shouldLog } from '@dxos/log';
13
+
14
+ import { type OtelOptions } from './otel';
15
+
16
+ export class OtelLogs {
17
+ constructor(private readonly options: OtelOptions) {
18
+ diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
19
+ }
20
+
21
+ getLogProcessor(): LogProcessor {
22
+ const resource = Resource.default().merge(
23
+ new Resource({
24
+ [SEMRESATTRS_SERVICE_NAME]: this.options.serviceName,
25
+ [SEMRESATTRS_SERVICE_VERSION]: this.options.serviceVersion,
26
+ }),
27
+ );
28
+ const collectorOptions = {
29
+ url: this.options.endpoint + '/v1/logs',
30
+ headers: {
31
+ Authorization: this.options.authorizationHeader,
32
+ },
33
+ concurrencyLimit: 1, // an optional limit on pending requests
34
+ };
35
+ const logExporter = new OTLPLogExporter(collectorOptions);
36
+ const loggerProvider = new LoggerProvider({ resource });
37
+
38
+ loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
39
+
40
+ // TODO: namespace?
41
+ const logger = loggerProvider.getLogger('dxos-observability', '0.0.0');
42
+ return (config: LogConfig, entry: LogEntry) => {
43
+ const { level, message, context, error } = entry;
44
+ if (!shouldLog(entry, config.filters)) {
45
+ return;
46
+ }
47
+ logger.emit({
48
+ severityNumber: convertLevel(level),
49
+ body: { message, error: error ?? undefined, ...context },
50
+ attributes: this.options.getTags(),
51
+ });
52
+ };
53
+ }
54
+ }
55
+
56
+ const convertLevel = (level: LogLevel): SeverityNumber => {
57
+ switch (level) {
58
+ case LogLevel.DEBUG:
59
+ return SeverityNumber.DEBUG;
60
+ case LogLevel.INFO:
61
+ return SeverityNumber.INFO;
62
+ case LogLevel.WARN:
63
+ return SeverityNumber.WARN;
64
+ case LogLevel.ERROR:
65
+ return SeverityNumber.ERROR;
66
+ default:
67
+ return SeverityNumber.ERROR;
68
+ }
69
+ };
@@ -0,0 +1,79 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Attributes, 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 { type OtelOptions } from './otel';
12
+
13
+ const EXPORT_INTERVAL = 60 * 1000;
14
+
15
+ type SynchronousGauge = {
16
+ gauge: ObservableGauge<Attributes>;
17
+ nextValue: number;
18
+ nextTags?: any;
19
+ };
20
+
21
+ export class OtelMetrics {
22
+ private _meterProvider: MeterProvider;
23
+ private _gauges = new Map<string, SynchronousGauge>();
24
+
25
+ constructor(private readonly options: OtelOptions) {
26
+ // TODO: improve error handling/logging
27
+ // https://github.com/open-telemetry/opentelemetry-js/issues/4823
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
+
35
+ const grafanaMetricReader = new PeriodicExportingMetricReader({
36
+ exporter: new OTLPMetricExporter({
37
+ url: this.options.endpoint + '/v1/metrics',
38
+ headers: {
39
+ Authorization: this.options.authorizationHeader,
40
+ },
41
+ }),
42
+ exportIntervalMillis: EXPORT_INTERVAL,
43
+ });
44
+
45
+ this._meterProvider = new MeterProvider({
46
+ resource,
47
+ readers: [grafanaMetricReader],
48
+ });
49
+ }
50
+
51
+ gauge(name: string, value: number, tags?: any) {
52
+ const meter = this._meterProvider.getMeter('dxos-observability');
53
+ const observableGauge = this._gauges.get(name);
54
+
55
+ const mergedTags = { ...this.options.getTags(), ...tags };
56
+ if (!observableGauge) {
57
+ const synchronousGauge = {
58
+ gauge: meter.createObservableGauge(name),
59
+ nextValue: value,
60
+ nextTags: mergedTags,
61
+ };
62
+ synchronousGauge.gauge.addCallback((observerResult) => {
63
+ observerResult.observe(synchronousGauge.nextValue, synchronousGauge.nextTags);
64
+ });
65
+ this._gauges.set(name, synchronousGauge);
66
+ } else {
67
+ observableGauge.nextTags = mergedTags;
68
+ observableGauge.nextValue = value;
69
+ }
70
+ }
71
+
72
+ flush() {
73
+ return this._meterProvider.forceFlush();
74
+ }
75
+
76
+ close() {
77
+ return this._meterProvider.shutdown();
78
+ }
79
+ }
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export type OtelOptions = {
6
+ endpoint: string;
7
+ authorizationHeader: string;
8
+ serviceName: string; // For the Otel API, the name of the entity for which signals (metrics or trace) are collected.
9
+ serviceVersion: string; // For the Otel API, The name of the entity for which signals (metrics or trace) are collected.
10
+ getTags: () => { [key: string]: string };
11
+ };