@dxos/observability 0.8.3 → 0.8.4-main.1068cf700f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-K4VFBKST.mjs +13 -0
- package/dist/lib/browser/chunk-K4VFBKST.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1066 -34
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/log-processor-FDLTDQEM.mjs +45 -0
- package/dist/lib/browser/log-processor-FDLTDQEM.mjs.map +7 -0
- package/dist/lib/browser/logs-ATTRIUTL.mjs +113 -0
- package/dist/lib/browser/logs-ATTRIUTL.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/metrics-PKTV6IGF.mjs +130 -0
- package/dist/lib/browser/metrics-PKTV6IGF.mjs.map +7 -0
- package/dist/lib/browser/traces-browser-XYXBF5ZX.mjs +62 -0
- package/dist/lib/browser/traces-browser-XYXBF5ZX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-FEVP3MK4.mjs +15 -0
- package/dist/lib/node-esm/chunk-FEVP3MK4.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/index.mjs +1070 -34
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/log-processor-TKJVJJSJ.mjs +46 -0
- package/dist/lib/node-esm/log-processor-TKJVJJSJ.mjs.map +7 -0
- package/dist/lib/node-esm/logs-7J45KLM7.mjs +114 -0
- package/dist/lib/node-esm/logs-7J45KLM7.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/metrics-H7DDLYSG.mjs +131 -0
- package/dist/lib/node-esm/metrics-H7DDLYSG.mjs.map +7 -0
- package/dist/lib/node-esm/traces-KMTHMYFX.mjs +44 -0
- package/dist/lib/node-esm/traces-KMTHMYFX.mjs.map +7 -0
- package/dist/types/src/cli-observability-secrets.json +3 -4
- package/dist/types/src/extensions/index.d.ts +3 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/extensions/index.js +6 -0
- package/dist/types/src/extensions/index.js.map +1 -0
- package/dist/types/src/extensions/otel/extension.d.ts +23 -0
- package/dist/types/src/extensions/otel/extension.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/extension.js +124 -0
- package/dist/types/src/extensions/otel/extension.js.map +1 -0
- package/dist/types/src/extensions/otel/index.d.ts +2 -0
- package/dist/types/src/extensions/otel/index.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/index.js +5 -0
- package/dist/types/src/extensions/otel/index.js.map +1 -0
- package/dist/types/src/{otel → extensions/otel}/logs.d.ts +4 -3
- package/dist/types/src/extensions/otel/logs.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/logs.js +104 -0
- package/dist/types/src/extensions/otel/logs.js.map +1 -0
- package/dist/types/src/{otel → extensions/otel}/metrics.d.ts +0 -1
- package/dist/types/src/extensions/otel/metrics.d.ts.map +1 -0
- package/dist/types/src/{otel → extensions/otel}/metrics.js +6 -12
- package/dist/types/src/extensions/otel/metrics.js.map +1 -0
- package/dist/types/src/{otel → extensions/otel}/otel.d.ts +3 -3
- package/dist/types/src/extensions/otel/otel.d.ts.map +1 -0
- package/dist/types/src/{otel → extensions/otel}/otel.js +1 -1
- package/dist/types/src/extensions/otel/otel.js.map +1 -0
- package/dist/types/src/extensions/otel/traces-browser.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/traces-browser.js +44 -0
- package/dist/types/src/extensions/otel/traces-browser.js.map +1 -0
- package/dist/types/src/extensions/otel/traces.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/traces.js +38 -0
- package/dist/types/src/extensions/otel/traces.js.map +1 -0
- package/dist/types/src/extensions/posthog/extension.d.ts +15 -0
- package/dist/types/src/extensions/posthog/extension.d.ts.map +1 -0
- package/dist/types/src/extensions/posthog/extension.js +143 -0
- package/dist/types/src/extensions/posthog/extension.js.map +1 -0
- package/dist/types/src/extensions/posthog/index.d.ts +2 -0
- package/dist/types/src/extensions/posthog/index.d.ts.map +1 -0
- package/dist/types/src/extensions/posthog/index.js +5 -0
- package/dist/types/src/extensions/posthog/index.js.map +1 -0
- package/dist/types/src/extensions/posthog/log-processor.d.ts +3 -0
- package/dist/types/src/extensions/posthog/log-processor.d.ts.map +1 -0
- package/dist/types/src/extensions/posthog/log-processor.js +45 -0
- package/dist/types/src/extensions/posthog/log-processor.js.map +1 -0
- package/dist/types/src/extensions/posthog/log-processor.test.d.ts +2 -0
- package/dist/types/src/extensions/posthog/log-processor.test.d.ts.map +1 -0
- package/dist/types/src/extensions/posthog/log-processor.test.js +146 -0
- package/dist/types/src/extensions/posthog/log-processor.test.js.map +1 -0
- package/dist/types/src/extensions/stub.d.ts +3 -0
- package/dist/types/src/extensions/stub.d.ts.map +1 -0
- package/dist/types/src/extensions/stub.js +16 -0
- package/dist/types/src/extensions/stub.js.map +1 -0
- package/dist/types/src/index.d.ts +4 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/index.js +5 -3
- package/dist/types/src/index.js.map +1 -1
- package/dist/types/src/log-buffer.d.ts +34 -0
- package/dist/types/src/log-buffer.d.ts.map +1 -0
- package/dist/types/src/log-buffer.js +70 -0
- package/dist/types/src/log-buffer.js.map +1 -0
- package/dist/types/src/log-buffer.test.d.ts +2 -0
- package/dist/types/src/log-buffer.test.d.ts.map +1 -0
- package/dist/types/src/log-buffer.test.js +107 -0
- package/dist/types/src/log-buffer.test.js.map +1 -0
- package/dist/types/src/observability-extension.d.ts +74 -0
- package/dist/types/src/observability-extension.d.ts.map +1 -0
- package/dist/types/src/observability-extension.js +5 -0
- package/dist/types/src/observability-extension.js.map +1 -0
- package/dist/types/src/observability.d.ts +32 -110
- package/dist/types/src/observability.d.ts.map +1 -1
- package/dist/types/src/observability.js +176 -455
- package/dist/types/src/observability.js.map +1 -1
- package/dist/types/src/observability.test.d.ts +2 -0
- package/dist/types/src/observability.test.d.ts.map +1 -0
- package/dist/types/src/observability.test.js +312 -0
- package/dist/types/src/observability.test.js.map +1 -0
- package/dist/types/src/providers/client-observability.d.ts +11 -0
- package/dist/types/src/providers/client-observability.d.ts.map +1 -0
- package/dist/types/src/providers/client-observability.js +200 -0
- package/dist/types/src/providers/client-observability.js.map +1 -0
- package/dist/types/src/providers/index.d.ts +4 -0
- package/dist/types/src/providers/index.d.ts.map +1 -0
- package/dist/types/src/providers/index.js +7 -0
- package/dist/types/src/providers/index.js.map +1 -0
- package/dist/types/src/providers/ip-data.d.ts +5 -0
- package/dist/types/src/providers/ip-data.d.ts.map +1 -0
- package/dist/types/src/providers/ip-data.js +55 -0
- package/dist/types/src/providers/ip-data.js.map +1 -0
- package/dist/types/src/providers/storage.d.ts +3 -0
- package/dist/types/src/providers/storage.d.ts.map +1 -0
- package/dist/types/src/providers/storage.js +19 -0
- package/dist/types/src/providers/storage.js.map +1 -0
- package/dist/types/src/storage/browser.d.ts +19 -0
- package/dist/types/src/storage/browser.d.ts.map +1 -0
- package/dist/types/src/storage/browser.js +58 -0
- package/dist/types/src/storage/browser.js.map +1 -0
- package/dist/types/src/storage/index.d.ts +2 -0
- package/dist/types/src/storage/index.d.ts.map +1 -0
- package/{src/segment/index.ts → dist/types/src/storage/index.js} +1 -2
- package/dist/types/src/storage/index.js.map +1 -0
- package/dist/types/src/storage/node.d.ts +26 -0
- package/dist/types/src/storage/node.d.ts.map +1 -0
- package/dist/types/src/storage/node.js +92 -0
- package/dist/types/src/storage/node.js.map +1 -0
- package/dist/types/src/storage/node.test.d.ts +2 -0
- package/dist/types/src/storage/node.test.d.ts.map +1 -0
- package/dist/types/src/storage/node.test.js +103 -0
- package/dist/types/src/storage/node.test.js.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -63
- package/src/cli-observability-secrets.json +3 -4
- package/src/extensions/index.ts +6 -0
- package/src/extensions/otel/extension.ts +178 -0
- package/src/extensions/otel/index.ts +5 -0
- package/src/extensions/otel/logs.ts +134 -0
- package/src/{otel → extensions/otel}/metrics.ts +4 -21
- package/src/{otel → extensions/otel}/otel.ts +4 -4
- package/src/extensions/otel/traces-browser.ts +57 -0
- package/src/extensions/otel/traces.ts +49 -0
- package/src/extensions/posthog/extension.ts +172 -0
- package/src/extensions/posthog/index.ts +5 -0
- package/src/extensions/posthog/log-processor.test.ts +185 -0
- package/src/extensions/posthog/log-processor.ts +54 -0
- package/src/extensions/stub.ts +19 -0
- package/src/index.ts +5 -3
- package/src/log-buffer.test.ts +134 -0
- package/src/log-buffer.ts +101 -0
- package/src/observability-extension.ts +94 -0
- package/src/observability.test.ts +531 -0
- package/src/observability.ts +236 -577
- package/src/providers/client-observability.ts +253 -0
- package/src/providers/index.ts +7 -0
- package/src/providers/ip-data.ts +88 -0
- package/src/providers/storage.ts +23 -0
- package/src/storage/browser.ts +61 -0
- package/src/{sentry → storage}/index.ts +0 -1
- package/src/storage/node.test.ts +130 -0
- package/src/{helpers/node-observability.ts → storage/node.ts} +42 -70
- package/dist/lib/browser/chunk-G6EE7HFV.mjs +0 -147
- package/dist/lib/browser/chunk-G6EE7HFV.mjs.map +0 -7
- package/dist/lib/browser/chunk-JA5VJRKF.mjs +0 -164
- package/dist/lib/browser/chunk-JA5VJRKF.mjs.map +0 -7
- package/dist/lib/browser/chunk-KDP3SESE.mjs +0 -1
- package/dist/lib/browser/chunk-YQJELTRP.mjs +0 -996
- package/dist/lib/browser/chunk-YQJELTRP.mjs.map +0 -7
- package/dist/lib/browser/observability-HDE3I7TA.mjs +0 -10
- package/dist/lib/browser/otel-LHAFLNBQ.mjs +0 -277
- package/dist/lib/browser/otel-LHAFLNBQ.mjs.map +0 -7
- package/dist/lib/browser/segment/index.mjs +0 -11
- package/dist/lib/browser/segment/index.mjs.map +0 -7
- package/dist/lib/browser/sentry/index.mjs +0 -24
- package/dist/lib/browser/sentry/index.mjs.map +0 -7
- package/dist/lib/browser/sentry-log-processor-625AISXI.mjs +0 -146
- package/dist/lib/browser/sentry-log-processor-625AISXI.mjs.map +0 -7
- package/dist/lib/node/chunk-325GAGFA.cjs +0 -213
- package/dist/lib/node/chunk-325GAGFA.cjs.map +0 -7
- package/dist/lib/node/chunk-BZHVFSLF.cjs +0 -1025
- package/dist/lib/node/chunk-BZHVFSLF.cjs.map +0 -7
- package/dist/lib/node/chunk-GIYJMZEQ.cjs +0 -2
- package/dist/lib/node/chunk-GIYJMZEQ.cjs.map +0 -7
- package/dist/lib/node/chunk-MZ3PMDTP.cjs +0 -163
- package/dist/lib/node/chunk-MZ3PMDTP.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -60
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/observability-E2NGRIEN.cjs +0 -32
- package/dist/lib/node/observability-E2NGRIEN.cjs.map +0 -7
- package/dist/lib/node/otel-VF5YNCR3.cjs +0 -278
- package/dist/lib/node/otel-VF5YNCR3.cjs.map +0 -7
- package/dist/lib/node/segment/index.cjs +0 -33
- package/dist/lib/node/segment/index.cjs.map +0 -7
- package/dist/lib/node/sentry/index.cjs +0 -46
- package/dist/lib/node/sentry/index.cjs.map +0 -7
- package/dist/lib/node/sentry-log-processor-CCV4RL7N.cjs +0 -164
- package/dist/lib/node/sentry-log-processor-CCV4RL7N.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-AZMSBUWR.mjs +0 -202
- package/dist/lib/node-esm/chunk-AZMSBUWR.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-H7Y2DDUN.mjs +0 -135
- package/dist/lib/node-esm/chunk-H7Y2DDUN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-M7QJLFGR.mjs +0 -997
- package/dist/lib/node-esm/chunk-M7QJLFGR.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-YJ4KVBWC.mjs +0 -2
- package/dist/lib/node-esm/chunk-YJ4KVBWC.mjs.map +0 -7
- package/dist/lib/node-esm/observability-7BTI46NM.mjs +0 -11
- package/dist/lib/node-esm/observability-7BTI46NM.mjs.map +0 -7
- package/dist/lib/node-esm/otel-AF5TSABC.mjs +0 -260
- package/dist/lib/node-esm/otel-AF5TSABC.mjs.map +0 -7
- package/dist/lib/node-esm/segment/index.mjs +0 -12
- package/dist/lib/node-esm/segment/index.mjs.map +0 -7
- package/dist/lib/node-esm/sentry/index.mjs +0 -25
- package/dist/lib/node-esm/sentry/index.mjs.map +0 -7
- package/dist/lib/node-esm/sentry-log-processor-HPUPCMRG.mjs +0 -147
- package/dist/lib/node-esm/sentry-log-processor-HPUPCMRG.mjs.map +0 -7
- package/dist/types/src/helpers/browser-observability.d.ts +0 -17
- package/dist/types/src/helpers/browser-observability.d.ts.map +0 -1
- package/dist/types/src/helpers/browser-observability.js +0 -140
- package/dist/types/src/helpers/browser-observability.js.map +0 -1
- package/dist/types/src/helpers/common.d.ts +0 -12
- package/dist/types/src/helpers/common.d.ts.map +0 -1
- package/dist/types/src/helpers/common.js +0 -23
- package/dist/types/src/helpers/common.js.map +0 -1
- package/dist/types/src/helpers/index.d.ts +0 -6
- package/dist/types/src/helpers/index.d.ts.map +0 -1
- package/dist/types/src/helpers/index.js +0 -9
- package/dist/types/src/helpers/index.js.map +0 -1
- package/dist/types/src/helpers/map-spaces.d.ts +0 -18
- package/dist/types/src/helpers/map-spaces.d.ts.map +0 -1
- package/dist/types/src/helpers/map-spaces.js +0 -37
- package/dist/types/src/helpers/map-spaces.js.map +0 -1
- package/dist/types/src/helpers/node-observability.d.ts +0 -24
- package/dist/types/src/helpers/node-observability.d.ts.map +0 -1
- package/dist/types/src/helpers/node-observability.js +0 -101
- package/dist/types/src/helpers/node-observability.js.map +0 -1
- package/dist/types/src/helpers/setup-telemetry-listeners.d.ts +0 -4
- package/dist/types/src/helpers/setup-telemetry-listeners.d.ts.map +0 -1
- package/dist/types/src/helpers/setup-telemetry-listeners.js +0 -97
- package/dist/types/src/helpers/setup-telemetry-listeners.js.map +0 -1
- package/dist/types/src/otel/index.d.ts +0 -5
- package/dist/types/src/otel/index.d.ts.map +0 -1
- package/dist/types/src/otel/index.js +0 -8
- package/dist/types/src/otel/index.js.map +0 -1
- package/dist/types/src/otel/logs.d.ts.map +0 -1
- package/dist/types/src/otel/logs.js +0 -71
- package/dist/types/src/otel/logs.js.map +0 -1
- package/dist/types/src/otel/metrics.d.ts.map +0 -1
- package/dist/types/src/otel/metrics.js.map +0 -1
- package/dist/types/src/otel/otel.d.ts.map +0 -1
- package/dist/types/src/otel/otel.js.map +0 -1
- package/dist/types/src/otel/traces-browser.d.ts.map +0 -1
- package/dist/types/src/otel/traces-browser.js +0 -47
- package/dist/types/src/otel/traces-browser.js.map +0 -1
- package/dist/types/src/otel/traces.d.ts.map +0 -1
- package/dist/types/src/otel/traces.js +0 -40
- package/dist/types/src/otel/traces.js.map +0 -1
- package/dist/types/src/segment/base.d.ts +0 -15
- package/dist/types/src/segment/base.d.ts.map +0 -1
- package/dist/types/src/segment/base.js +0 -50
- package/dist/types/src/segment/base.js.map +0 -1
- package/dist/types/src/segment/browser.d.ts +0 -15
- package/dist/types/src/segment/browser.d.ts.map +0 -1
- package/dist/types/src/segment/browser.js +0 -67
- package/dist/types/src/segment/browser.js.map +0 -1
- package/dist/types/src/segment/index.d.ts +0 -3
- package/dist/types/src/segment/index.d.ts.map +0 -1
- package/dist/types/src/segment/index.js +0 -6
- package/dist/types/src/segment/index.js.map +0 -1
- package/dist/types/src/segment/node.d.ts +0 -16
- package/dist/types/src/segment/node.d.ts.map +0 -1
- package/dist/types/src/segment/node.js +0 -83
- package/dist/types/src/segment/node.js.map +0 -1
- package/dist/types/src/segment/types.d.ts +0 -52
- package/dist/types/src/segment/types.d.ts.map +0 -1
- package/dist/types/src/segment/types.js +0 -18
- package/dist/types/src/segment/types.js.map +0 -1
- package/dist/types/src/sentry/browser.d.ts +0 -32
- package/dist/types/src/sentry/browser.d.ts.map +0 -1
- package/dist/types/src/sentry/browser.js +0 -112
- package/dist/types/src/sentry/browser.js.map +0 -1
- package/dist/types/src/sentry/index.d.ts +0 -3
- package/dist/types/src/sentry/index.d.ts.map +0 -1
- package/dist/types/src/sentry/index.js +0 -6
- package/dist/types/src/sentry/index.js.map +0 -1
- package/dist/types/src/sentry/node.d.ts +0 -32
- package/dist/types/src/sentry/node.d.ts.map +0 -1
- package/dist/types/src/sentry/node.js +0 -111
- package/dist/types/src/sentry/node.js.map +0 -1
- package/dist/types/src/sentry/node.node.test.d.ts +0 -2
- package/dist/types/src/sentry/node.node.test.d.ts.map +0 -1
- package/dist/types/src/sentry/node.node.test.js +0 -34
- package/dist/types/src/sentry/node.node.test.js.map +0 -1
- package/dist/types/src/sentry/sentry-log-processor.d.ts +0 -9
- package/dist/types/src/sentry/sentry-log-processor.d.ts.map +0 -1
- package/dist/types/src/sentry/sentry-log-processor.js +0 -149
- package/dist/types/src/sentry/sentry-log-processor.js.map +0 -1
- package/dist/types/src/sentry/sentry.node.test.d.ts +0 -2
- package/dist/types/src/sentry/sentry.node.test.d.ts.map +0 -1
- package/dist/types/src/sentry/sentry.node.test.js +0 -28
- package/dist/types/src/sentry/sentry.node.test.js.map +0 -1
- package/dist/types/src/sentry/types.d.ts +0 -18
- package/dist/types/src/sentry/types.d.ts.map +0 -1
- package/dist/types/src/sentry/types.js +0 -4
- package/dist/types/src/sentry/types.js.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -2
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/dist/types/src/testing/index.js +0 -5
- package/dist/types/src/testing/index.js.map +0 -1
- package/dist/types/src/testing/testkit/browser.d.ts +0 -2
- package/dist/types/src/testing/testkit/browser.d.ts.map +0 -1
- package/dist/types/src/testing/testkit/browser.js +0 -7
- package/dist/types/src/testing/testkit/browser.js.map +0 -1
- package/dist/types/src/testing/testkit/index.d.ts +0 -2
- package/dist/types/src/testing/testkit/index.d.ts.map +0 -1
- package/dist/types/src/testing/testkit/index.js +0 -6
- package/dist/types/src/testing/testkit/index.js.map +0 -1
- package/src/helpers/browser-observability.ts +0 -177
- package/src/helpers/common.ts +0 -38
- package/src/helpers/index.ts +0 -9
- package/src/helpers/map-spaces.ts +0 -48
- package/src/helpers/setup-telemetry-listeners.ts +0 -108
- package/src/otel/index.ts +0 -8
- package/src/otel/logs.ts +0 -100
- package/src/otel/traces-browser.ts +0 -59
- package/src/otel/traces.ts +0 -57
- package/src/segment/base.ts +0 -69
- package/src/segment/browser.ts +0 -68
- package/src/segment/node.ts +0 -94
- package/src/segment/types.ts +0 -57
- package/src/sentry/browser.ts +0 -133
- package/src/sentry/node.node.test.ts +0 -39
- package/src/sentry/node.ts +0 -126
- package/src/sentry/sentry-log-processor.ts +0 -166
- package/src/sentry/sentry.node.test.ts +0 -34
- package/src/sentry/types.ts +0 -22
- package/src/testing/index.ts +0 -5
- package/src/testing/testkit/browser.ts +0 -8
- package/src/testing/testkit/index.ts +0 -7
- package/src/testing/testkit/shims.d.ts +0 -5
- /package/dist/lib/browser/{chunk-KDP3SESE.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
- /package/dist/lib/{browser/observability-HDE3I7TA.mjs.map → node-esm/chunk-HSLMI22Q.mjs.map} +0 -0
- /package/dist/types/src/{otel → extensions/otel}/traces-browser.d.ts +0 -0
- /package/dist/types/src/{otel → extensions/otel}/traces.d.ts +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { Event, scheduleTaskInterval } from '@dxos/async';
|
|
8
|
+
import { type Client, type ClientServices } from '@dxos/client';
|
|
9
|
+
import { type Space } from '@dxos/client/echo';
|
|
10
|
+
import { DeviceKind } from '@dxos/client/halo';
|
|
11
|
+
import { Context } from '@dxos/context';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
import { ConnectionState, type NetworkStatus, Platform } from '@dxos/protocols/proto/dxos/client/services';
|
|
15
|
+
|
|
16
|
+
import { type DataProvider } from '../observability';
|
|
17
|
+
|
|
18
|
+
const SPACE_METRICS_MIN_INTERVAL = 1000 * 60 * 10; // 10 minutes
|
|
19
|
+
const NETWORK_METRICS_MIN_INTERVAL = 1000 * 60 * 10; // 10 minutes
|
|
20
|
+
const RUNTIME_METRICS_MIN_INTERVAL = 1000 * 60 * 10; // 10 minutes
|
|
21
|
+
|
|
22
|
+
// TODO(wittjosiah): Improve privacy of telemetry identifiers.
|
|
23
|
+
// - Identifier should be generated client-side with no attachment to identity.
|
|
24
|
+
// - Identifier can then be reset by user.
|
|
25
|
+
// - Identifier can be synced via HALO to allow for correlation of events bewteen devices.
|
|
26
|
+
// - Identifier should also be stored outside of HALO such that it is available immediately on startup.
|
|
27
|
+
/** Subscribes to identity and device changes and sets observability tags accordingly. */
|
|
28
|
+
export const identityProvider = (clientServices: Partial<ClientServices>): DataProvider =>
|
|
29
|
+
Effect.fn(function* (observability) {
|
|
30
|
+
// TODO(wittjosiah): RPC subscribe returns void; cleanup requires upstream API change.
|
|
31
|
+
clientServices.IdentityService!.queryIdentity().subscribe((idqr) => {
|
|
32
|
+
if (!idqr?.identity?.did) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
observability.identify(idqr.identity.did);
|
|
37
|
+
observability.setTags({ did: idqr.identity.did });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// TODO(wittjosiah): RPC subscribe returns void; cleanup requires upstream API change.
|
|
41
|
+
clientServices.DevicesService!.queryDevices().subscribe((dqr) => {
|
|
42
|
+
if (!dqr?.devices || dqr.devices.length === 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const thisDevice = dqr.devices.find((device) => device.kind === DeviceKind.CURRENT);
|
|
47
|
+
if (!thisDevice) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
observability.setTags({ deviceKey: thisDevice.deviceKey.truncate() });
|
|
52
|
+
if (thisDevice.profile?.label) {
|
|
53
|
+
observability.setTags({ deviceProfile: thisDevice.profile.label });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** Periodically publishes network connection and buffer metrics. */
|
|
59
|
+
export const networkMetricsProvider = (clientServices: Partial<ClientServices>): DataProvider =>
|
|
60
|
+
Effect.fn(function* (observability) {
|
|
61
|
+
const ctx = new Context();
|
|
62
|
+
let lastNetworkStatus: NetworkStatus | undefined;
|
|
63
|
+
|
|
64
|
+
// TODO(nf): support type in debounce()
|
|
65
|
+
const updateSignalMetrics = new Event<NetworkStatus>().debounce(NETWORK_METRICS_MIN_INTERVAL);
|
|
66
|
+
updateSignalMetrics.on(ctx, async () => {
|
|
67
|
+
log('send signal metrics');
|
|
68
|
+
(lastNetworkStatus?.signaling as NetworkStatus.Signal[])?.forEach(({ server, state }) => {
|
|
69
|
+
observability.metrics.gauge('dxos.client.network.signal.connectionState', state, { server });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
let swarmCount = 0;
|
|
73
|
+
const connectionStates = new Map<string, number>();
|
|
74
|
+
for (const state in ConnectionState) {
|
|
75
|
+
connectionStates.set(state, 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let totalReadBufferSize = 0;
|
|
79
|
+
let totalWriteBufferSize = 0;
|
|
80
|
+
let totalChannelBufferSize = 0;
|
|
81
|
+
|
|
82
|
+
lastNetworkStatus?.connectionInfo?.forEach((connectionInfo) => {
|
|
83
|
+
swarmCount++;
|
|
84
|
+
|
|
85
|
+
for (const conn of connectionInfo.connections ?? []) {
|
|
86
|
+
connectionStates.set(conn.state, (connectionStates.get(conn.state) ?? 0) + 1);
|
|
87
|
+
totalReadBufferSize += conn.readBufferSize ?? 0;
|
|
88
|
+
totalWriteBufferSize += conn.writeBufferSize ?? 0;
|
|
89
|
+
for (const stream of conn.streams ?? []) {
|
|
90
|
+
totalChannelBufferSize += stream.writeBufferSize ?? 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
observability.metrics.gauge('dxos.client.network.swarm.count', swarmCount);
|
|
95
|
+
for (const state in ConnectionState) {
|
|
96
|
+
observability.metrics.gauge('dxos.client.network.connection.count', connectionStates.get(state) ?? 0, {
|
|
97
|
+
state,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
observability.metrics.gauge('dxos.client.network.totalReadBufferSize', totalReadBufferSize);
|
|
101
|
+
observability.metrics.gauge('dxos.client.network.totalWriteBufferSize', totalWriteBufferSize);
|
|
102
|
+
observability.metrics.gauge('dxos.client.network.totalChannelBufferSize', totalChannelBufferSize);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
clientServices.NetworkService!.queryStatus().subscribe((networkStatus) => {
|
|
107
|
+
lastNetworkStatus = networkStatus;
|
|
108
|
+
updateSignalMetrics.emit();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
scheduleTaskInterval(ctx, async () => updateSignalMetrics.emit(), NETWORK_METRICS_MIN_INTERVAL);
|
|
112
|
+
|
|
113
|
+
return async () => {
|
|
114
|
+
await ctx.dispose();
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/** Periodically publishes platform and heap memory metrics. */
|
|
119
|
+
export const runtimeMetricsProvider = (clientServices: Partial<ClientServices>): DataProvider =>
|
|
120
|
+
Effect.fn(function* (observability) {
|
|
121
|
+
const ctx = new Context();
|
|
122
|
+
const platform = yield* Effect.promise(() => clientServices.SystemService!.getPlatform());
|
|
123
|
+
invariant(platform, 'platform is required');
|
|
124
|
+
|
|
125
|
+
observability.setTags({
|
|
126
|
+
platformType: Platform.PLATFORM_TYPE[platform.type as number].toLowerCase(),
|
|
127
|
+
platform: platform.platform,
|
|
128
|
+
arch: platform.arch,
|
|
129
|
+
runtime: platform.runtime,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
scheduleTaskInterval(
|
|
133
|
+
ctx,
|
|
134
|
+
async () => {
|
|
135
|
+
if (clientServices.constructor.name === 'WorkerClientServices') {
|
|
136
|
+
const memory = (window.performance as any).memory;
|
|
137
|
+
if (memory) {
|
|
138
|
+
observability.metrics.gauge('dxos.client.runtime.heapTotal', memory.totalJSHeapSize);
|
|
139
|
+
observability.metrics.gauge('dxos.client.runtime.heapUsed', memory.usedJSHeapSize);
|
|
140
|
+
observability.metrics.gauge('dxos.client.runtime.heapSizeLimit', memory.jsHeapSizeLimit);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
clientServices.SystemService?.getPlatform()
|
|
145
|
+
.then((platform) => {
|
|
146
|
+
if (platform.memory) {
|
|
147
|
+
observability.metrics.gauge('dxos.client.services.runtime.rss', platform.memory.rss);
|
|
148
|
+
observability.metrics.gauge('dxos.client.services.runtime.heapTotal', platform.memory.heapTotal);
|
|
149
|
+
observability.metrics.gauge('dxos.client.services.runtime.heapUsed', platform.memory.heapUsed);
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
.catch((error) => log('platform error', { error }));
|
|
153
|
+
},
|
|
154
|
+
RUNTIME_METRICS_MIN_INTERVAL,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return async () => {
|
|
158
|
+
await ctx.dispose();
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/** Periodically publishes space membership, object count, and pipeline progress metrics. */
|
|
163
|
+
export const spacesMetricsProvider = (client: Client): DataProvider =>
|
|
164
|
+
Effect.fn(function* (observability) {
|
|
165
|
+
const ctx = new Context();
|
|
166
|
+
// TODO(nf): update subscription on new spaces
|
|
167
|
+
const spaces = client.spaces.get();
|
|
168
|
+
const subscriptions = new Map<string, { unsubscribe: () => void }>();
|
|
169
|
+
ctx.onDispose(() => subscriptions.forEach((subscription) => subscription.unsubscribe()));
|
|
170
|
+
|
|
171
|
+
const updateSpaceMetrics = new Event<Space>().debounce(SPACE_METRICS_MIN_INTERVAL);
|
|
172
|
+
updateSpaceMetrics.on(ctx, async () => {
|
|
173
|
+
log('send space metrics');
|
|
174
|
+
for (const data of mapSpaces(spaces, { truncateKeys: true })) {
|
|
175
|
+
observability.metrics.gauge('dxos.client.space.members', data.members, { key: data.key });
|
|
176
|
+
observability.metrics.gauge('dxos.client.space.objects', data.objects, { key: data.key });
|
|
177
|
+
observability.metrics.gauge('dxos.client.space.epoch', data.epoch, { key: data.key });
|
|
178
|
+
observability.metrics.gauge('dxos.client.space.currentDataMutations', data.currentDataMutations, {
|
|
179
|
+
key: data.key,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const subscribeToSpaceUpdate = (space: Space) =>
|
|
185
|
+
space.pipeline.subscribe({
|
|
186
|
+
next: () => {
|
|
187
|
+
updateSpaceMetrics.emit();
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
spaces.forEach((space) => {
|
|
192
|
+
subscriptions.set(space.id, subscribeToSpaceUpdate(space));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
client.spaces.subscribe({
|
|
196
|
+
next: async (spaces) => {
|
|
197
|
+
spaces
|
|
198
|
+
.filter((space) => !subscriptions.has(space.id))
|
|
199
|
+
.forEach((space) => {
|
|
200
|
+
subscriptions.set(space.id, subscribeToSpaceUpdate(space));
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
scheduleTaskInterval(ctx, async () => updateSpaceMetrics.emit(), SPACE_METRICS_MIN_INTERVAL);
|
|
206
|
+
|
|
207
|
+
return async () => {
|
|
208
|
+
await ctx.dispose();
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
type MapSpacesOptions = {
|
|
213
|
+
verbose?: boolean;
|
|
214
|
+
truncateKeys?: boolean;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const mapSpaces = (spaces: Space[], options: MapSpacesOptions = { verbose: false, truncateKeys: false }) => {
|
|
218
|
+
return spaces.map((space) => {
|
|
219
|
+
// TODO(burdon): Factor out.
|
|
220
|
+
// TODO(burdon): Agent needs to restart before `ready` is available.
|
|
221
|
+
const { open, ready } = space.internal.data.metrics ?? {};
|
|
222
|
+
const startup = open && ready && ready.getTime() - open.getTime();
|
|
223
|
+
|
|
224
|
+
// TODO(burdon): Get feeds from client-services if verbose (factor out from devtools/diagnostics).
|
|
225
|
+
// const host = client.services.services.DevtoolsHost!;
|
|
226
|
+
const pipeline = space.internal.data.pipeline;
|
|
227
|
+
const startDataMutations = pipeline?.currentEpoch?.subject.assertion.timeframe.totalMessages() ?? 0;
|
|
228
|
+
const epoch = pipeline?.currentEpoch?.subject.assertion.number;
|
|
229
|
+
// const appliedEpoch = pipeline?.appliedEpoch?.subject.assertion.number;
|
|
230
|
+
const currentDataMutations = pipeline?.currentDataTimeframe?.totalMessages() ?? 0;
|
|
231
|
+
const totalDataMutations = pipeline?.targetDataTimeframe?.totalMessages() ?? 0;
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
// TODO(nf): truncate keys for DD?
|
|
235
|
+
key: space.key.truncate(),
|
|
236
|
+
open: space.isOpen,
|
|
237
|
+
members: space.members.get().length,
|
|
238
|
+
objects: space.internal.db.coreDatabase.getAllObjectIds().length,
|
|
239
|
+
startup,
|
|
240
|
+
epoch,
|
|
241
|
+
// appliedEpoch,
|
|
242
|
+
startDataMutations,
|
|
243
|
+
currentDataMutations,
|
|
244
|
+
totalDataMutations,
|
|
245
|
+
|
|
246
|
+
// TODO(burdon): Negative?
|
|
247
|
+
progress: (
|
|
248
|
+
Math.min(Math.abs((currentDataMutations - startDataMutations) / (totalDataMutations - startDataMutations)), 1) *
|
|
249
|
+
100
|
|
250
|
+
).toFixed(0),
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
6
|
+
import * as HttpClient from '@effect/platform/HttpClient';
|
|
7
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Schema from 'effect/Schema';
|
|
10
|
+
// NOTE: localStorage is not available in web workers.
|
|
11
|
+
import * as localForage from 'localforage';
|
|
12
|
+
|
|
13
|
+
import { type Config } from '@dxos/config';
|
|
14
|
+
import { log } from '@dxos/log';
|
|
15
|
+
|
|
16
|
+
import { type DataProvider } from '../observability';
|
|
17
|
+
|
|
18
|
+
const IP_DATA_CACHE_TIMEOUT = 6 * 60 * 60 * 1000; // 6 hours
|
|
19
|
+
|
|
20
|
+
const IPData = Schema.Struct({
|
|
21
|
+
city: Schema.String,
|
|
22
|
+
region: Schema.String,
|
|
23
|
+
country: Schema.String,
|
|
24
|
+
latitude: Schema.optional(Schema.Number),
|
|
25
|
+
longitude: Schema.optional(Schema.Number),
|
|
26
|
+
});
|
|
27
|
+
type IPData = Schema.Schema.Type<typeof IPData>;
|
|
28
|
+
|
|
29
|
+
type CachedIPData = {
|
|
30
|
+
data: IPData;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getIPData = Effect.fn(function* (config: Config) {
|
|
35
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
36
|
+
|
|
37
|
+
// Check cache first.
|
|
38
|
+
const cachedData = yield* Effect.promise(() => localForage.getItem<CachedIPData>('dxos:observability:ipdata'));
|
|
39
|
+
if (cachedData && cachedData.timestamp > Date.now() - IP_DATA_CACHE_TIMEOUT) {
|
|
40
|
+
return cachedData.data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fetch data if not cached.
|
|
44
|
+
const IPDATA_API_KEY = config.get('runtime.app.env.DX_IPDATA_API_KEY');
|
|
45
|
+
if (IPDATA_API_KEY) {
|
|
46
|
+
const data = yield* HttpClientRequest.get(`https://api.ipdata.co?api-key=${IPDATA_API_KEY}`).pipe(
|
|
47
|
+
httpClient.execute,
|
|
48
|
+
Effect.flatMap((res) => res.json),
|
|
49
|
+
Effect.flatMap(Schema.decodeUnknown(IPData)),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Cache data.
|
|
53
|
+
yield* Effect.promise(() =>
|
|
54
|
+
localForage.setItem('dxos:observability:ipdata', {
|
|
55
|
+
data,
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** Fetches IP geolocation data and sets city/region/country tags on the observability instance. */
|
|
65
|
+
export const provider =
|
|
66
|
+
(config: Config): DataProvider =>
|
|
67
|
+
(observability) =>
|
|
68
|
+
Effect.gen(function* () {
|
|
69
|
+
const ipData = yield* getIPData(config);
|
|
70
|
+
if (!ipData) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
observability.setTags({
|
|
75
|
+
city: ipData.city,
|
|
76
|
+
region: ipData.region,
|
|
77
|
+
country: ipData.country,
|
|
78
|
+
latitude: ipData.latitude,
|
|
79
|
+
longitude: ipData.longitude,
|
|
80
|
+
});
|
|
81
|
+
}).pipe(
|
|
82
|
+
Effect.provide(FetchHttpClient.layer),
|
|
83
|
+
Effect.catchAll((err) =>
|
|
84
|
+
Effect.sync(() => {
|
|
85
|
+
log.catch(err);
|
|
86
|
+
}),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Duration from 'effect/Duration';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Fiber from 'effect/Fiber';
|
|
8
|
+
import * as Schedule from 'effect/Schedule';
|
|
9
|
+
|
|
10
|
+
import { type DataProvider } from '../observability';
|
|
11
|
+
|
|
12
|
+
export const provider: DataProvider = Effect.fn(function* (observability) {
|
|
13
|
+
if (typeof navigator !== 'undefined' && navigator.storage?.estimate) {
|
|
14
|
+
const action = Effect.gen(function* () {
|
|
15
|
+
const storageEstimate = yield* Effect.tryPromise(() => navigator.storage.estimate());
|
|
16
|
+
storageEstimate.usage && observability.metrics.gauge('storageUsage', storageEstimate.usage);
|
|
17
|
+
storageEstimate.quota && observability.metrics.gauge('storageQuota', storageEstimate.quota);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const fiber = action.pipe(Effect.repeat(Schedule.fixed(Duration.hours(1))), Effect.runFork);
|
|
21
|
+
return () => Effect.runSync(Fiber.interrupt(fiber));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// NOTE: localStorage is not available in web workers.
|
|
6
|
+
import * as localForage from 'localforage';
|
|
7
|
+
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
|
|
10
|
+
const OBSERVABILITY_DISABLED_KEY = 'observability-disabled';
|
|
11
|
+
const OBSERVABILITY_GROUP_KEY = 'observability-group';
|
|
12
|
+
|
|
13
|
+
/** No-op in browser contexts. */
|
|
14
|
+
export const showObservabilityBanner = () => {
|
|
15
|
+
log.warn('showObservabilityBanner is not supported in browser contexts.');
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param namespace - localForage key prefix used to scope the observability state in browser storage.
|
|
20
|
+
*/
|
|
21
|
+
export const isObservabilityDisabled = async (namespace: string): Promise<boolean> => {
|
|
22
|
+
try {
|
|
23
|
+
return (await localForage.getItem(`${namespace}:${OBSERVABILITY_DISABLED_KEY}`)) === 'true';
|
|
24
|
+
} catch (err) {
|
|
25
|
+
log.catch('Failed to check if observability is disabled, assuming it is', err);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param namespace - localForage key prefix used to scope the observability state in browser storage.
|
|
32
|
+
*/
|
|
33
|
+
export const storeObservabilityDisabled = async (namespace: string, value: boolean) => {
|
|
34
|
+
try {
|
|
35
|
+
await localForage.setItem(`${namespace}:${OBSERVABILITY_DISABLED_KEY}`, String(value));
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log.catch('Failed to store observability disabled', err);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param namespace - localForage key prefix used to scope the observability state in browser storage.
|
|
43
|
+
*/
|
|
44
|
+
export const getObservabilityGroup = async (namespace: string): Promise<string | undefined> => {
|
|
45
|
+
try {
|
|
46
|
+
return (await localForage.getItem(`${namespace}:${OBSERVABILITY_GROUP_KEY}`)) ?? undefined;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log.catch('Failed to get observability group', err);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param namespace - localForage key prefix used to scope the observability state in browser storage.
|
|
54
|
+
*/
|
|
55
|
+
export const storeObservabilityGroup = async (namespace: string, value: string) => {
|
|
56
|
+
try {
|
|
57
|
+
await localForage.setItem(`${namespace}:${OBSERVABILITY_GROUP_KEY}`, value);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
log.catch('Failed to store observability group', err);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
import { validate as validateUuid } from 'uuid';
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
type PersistentObservabilityState,
|
|
16
|
+
getObservabilityGroup,
|
|
17
|
+
isObservabilityDisabled,
|
|
18
|
+
showObservabilityBanner,
|
|
19
|
+
storeObservabilityDisabled,
|
|
20
|
+
storeObservabilityGroup,
|
|
21
|
+
} from './node';
|
|
22
|
+
|
|
23
|
+
let configDir: string;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
configDir = join(tmpdir(), `observability-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
27
|
+
// Clean env vars.
|
|
28
|
+
delete process.env.DX_DISABLE_OBSERVABILITY;
|
|
29
|
+
delete process.env.DX_OBSERVABILITY_GROUP;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
if (existsSync(configDir)) {
|
|
34
|
+
await rm(configDir, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('node storage', () => {
|
|
39
|
+
test('creates config directory if missing', async () => {
|
|
40
|
+
expect(existsSync(configDir)).toBe(false);
|
|
41
|
+
await isObservabilityDisabled(configDir);
|
|
42
|
+
expect(existsSync(configDir)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('initializes state with UUID and writes observability.yml', async () => {
|
|
46
|
+
await isObservabilityDisabled(configDir);
|
|
47
|
+
const content = await readFile(join(configDir, 'observability.yml'), 'utf-8');
|
|
48
|
+
const state = yaml.load(content) as PersistentObservabilityState;
|
|
49
|
+
expect(validateUuid(state.installationId)).toBe(true);
|
|
50
|
+
expect(state.disabled).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('isObservabilityDisabled returns false by default', async () => {
|
|
54
|
+
const disabled = await isObservabilityDisabled(configDir);
|
|
55
|
+
expect(disabled).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('storeObservabilityDisabled persists to file', async () => {
|
|
59
|
+
await storeObservabilityDisabled(configDir, true);
|
|
60
|
+
const content = await readFile(join(configDir, 'observability.yml'), 'utf-8');
|
|
61
|
+
const state = yaml.load(content) as PersistentObservabilityState;
|
|
62
|
+
expect(state.disabled).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('isObservabilityDisabled reads persisted value', async () => {
|
|
66
|
+
await storeObservabilityDisabled(configDir, true);
|
|
67
|
+
const disabled = await isObservabilityDisabled(configDir);
|
|
68
|
+
expect(disabled).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('getObservabilityGroup returns undefined by default', async () => {
|
|
72
|
+
const group = await getObservabilityGroup(configDir);
|
|
73
|
+
expect(group).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('storeObservabilityGroup persists and reads back', async () => {
|
|
77
|
+
await storeObservabilityGroup(configDir, 'beta-testers');
|
|
78
|
+
const group = await getObservabilityGroup(configDir);
|
|
79
|
+
expect(group).toBe('beta-testers');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('respects DX_DISABLE_OBSERVABILITY env var', async () => {
|
|
83
|
+
process.env.DX_DISABLE_OBSERVABILITY = 'true';
|
|
84
|
+
const disabled = await isObservabilityDisabled(configDir);
|
|
85
|
+
expect(disabled).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('respects DX_OBSERVABILITY_GROUP env var on initial creation', async () => {
|
|
89
|
+
process.env.DX_OBSERVABILITY_GROUP = 'alpha';
|
|
90
|
+
const group = await getObservabilityGroup(configDir);
|
|
91
|
+
expect(group).toBe('alpha');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('validates UUID in existing state file', async () => {
|
|
95
|
+
await mkdir(configDir, { recursive: true });
|
|
96
|
+
const state: PersistentObservabilityState = {
|
|
97
|
+
installationId: '550e8400-e29b-41d4-a716-446655440000',
|
|
98
|
+
disabled: false,
|
|
99
|
+
};
|
|
100
|
+
await writeFile(join(configDir, 'observability.yml'), yaml.dump(state), 'utf-8');
|
|
101
|
+
const disabled = await isObservabilityDisabled(configDir);
|
|
102
|
+
expect(disabled).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('reinitializes if UUID is invalid', async () => {
|
|
106
|
+
await mkdir(configDir, { recursive: true });
|
|
107
|
+
const badState = { installationId: 'not-a-uuid', disabled: true };
|
|
108
|
+
await writeFile(join(configDir, 'observability.yml'), yaml.dump(badState), 'utf-8');
|
|
109
|
+
|
|
110
|
+
// Should reinitialize with a fresh UUID.
|
|
111
|
+
await isObservabilityDisabled(configDir);
|
|
112
|
+
const content = await readFile(join(configDir, 'observability.yml'), 'utf-8');
|
|
113
|
+
const state = yaml.load(content) as PersistentObservabilityState;
|
|
114
|
+
expect(validateUuid(state.installationId)).toBe(true);
|
|
115
|
+
// Reinitialized state uses env var defaults, not the persisted bad state.
|
|
116
|
+
expect(state.disabled).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('showObservabilityBanner prints once then is silent', async () => {
|
|
120
|
+
await mkdir(configDir, { recursive: true });
|
|
121
|
+
const bannerCb = vi.fn();
|
|
122
|
+
|
|
123
|
+
await showObservabilityBanner(configDir, bannerCb);
|
|
124
|
+
expect(bannerCb).toHaveBeenCalledTimes(1);
|
|
125
|
+
|
|
126
|
+
bannerCb.mockClear();
|
|
127
|
+
await showObservabilityBanner(configDir, bannerCb);
|
|
128
|
+
expect(bannerCb).not.toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
});
|