@dxos/observability 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/lib/chunk-log-processor.mjs +28 -0
  2. package/dist/lib/chunk-log-processor.mjs.map +1 -0
  3. package/dist/lib/chunk-logs.mjs +89 -0
  4. package/dist/lib/chunk-logs.mjs.map +1 -0
  5. package/dist/lib/chunk-metrics.mjs +118 -0
  6. package/dist/lib/chunk-metrics.mjs.map +1 -0
  7. package/dist/lib/chunk-otel.mjs +20 -0
  8. package/dist/lib/chunk-otel.mjs.map +1 -0
  9. package/dist/lib/chunk-traces-browser.mjs +145 -0
  10. package/dist/lib/chunk-traces-browser.mjs.map +1 -0
  11. package/dist/lib/index.mjs +977 -0
  12. package/dist/lib/index.mjs.map +1 -0
  13. package/dist/types/src/cli-observability-secrets.json +2 -2
  14. package/dist/types/src/extensions/otel/logs.d.ts +1 -0
  15. package/dist/types/src/extensions/otel/logs.d.ts.map +1 -1
  16. package/dist/types/src/extensions/otel/logs.js +11 -4
  17. package/dist/types/src/extensions/otel/logs.js.map +1 -1
  18. package/dist/types/src/extensions/otel/logs.test.d.ts +2 -0
  19. package/dist/types/src/extensions/otel/logs.test.d.ts.map +1 -0
  20. package/dist/types/src/extensions/otel/logs.test.js +33 -0
  21. package/dist/types/src/extensions/otel/logs.test.js.map +1 -0
  22. package/dist/types/src/extensions/posthog/extension.d.ts.map +1 -1
  23. package/dist/types/src/extensions/posthog/extension.js +4 -0
  24. package/dist/types/src/extensions/posthog/extension.js.map +1 -1
  25. package/dist/types/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +31 -31
  27. package/src/cli-observability-secrets.json +2 -2
  28. package/src/extensions/otel/logs.test.ts +39 -0
  29. package/src/extensions/otel/logs.ts +11 -4
  30. package/src/extensions/posthog/extension.ts +4 -0
  31. package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
  32. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
  33. package/dist/lib/browser/chunk-PSFTIG54.mjs +0 -20
  34. package/dist/lib/browser/chunk-PSFTIG54.mjs.map +0 -7
  35. package/dist/lib/browser/index.mjs +0 -1015
  36. package/dist/lib/browser/index.mjs.map +0 -7
  37. package/dist/lib/browser/log-processor-MZCTEVJC.mjs +0 -46
  38. package/dist/lib/browser/log-processor-MZCTEVJC.mjs.map +0 -7
  39. package/dist/lib/browser/logs-UTNIFYHF.mjs +0 -116
  40. package/dist/lib/browser/logs-UTNIFYHF.mjs.map +0 -7
  41. package/dist/lib/browser/meta.json +0 -1
  42. package/dist/lib/browser/metrics-PRGSYAZJ.mjs +0 -116
  43. package/dist/lib/browser/metrics-PRGSYAZJ.mjs.map +0 -7
  44. package/dist/lib/browser/traces-browser-H4NCYCVZ.mjs +0 -154
  45. package/dist/lib/browser/traces-browser-H4NCYCVZ.mjs.map +0 -7
  46. package/dist/lib/node-esm/chunk-EDDZWPYV.mjs +0 -22
  47. package/dist/lib/node-esm/chunk-EDDZWPYV.mjs.map +0 -7
  48. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  49. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  50. package/dist/lib/node-esm/index.mjs +0 -1021
  51. package/dist/lib/node-esm/index.mjs.map +0 -7
  52. package/dist/lib/node-esm/log-processor-GA24XTJF.mjs +0 -47
  53. package/dist/lib/node-esm/log-processor-GA24XTJF.mjs.map +0 -7
  54. package/dist/lib/node-esm/logs-ARJUVN3T.mjs +0 -117
  55. package/dist/lib/node-esm/logs-ARJUVN3T.mjs.map +0 -7
  56. package/dist/lib/node-esm/meta.json +0 -1
  57. package/dist/lib/node-esm/metrics-DREJOOAC.mjs +0 -117
  58. package/dist/lib/node-esm/metrics-DREJOOAC.mjs.map +0 -7
  59. package/dist/lib/node-esm/traces-74F7JUKF.mjs +0 -125
  60. package/dist/lib/node-esm/traces-74F7JUKF.mjs.map +0 -7
@@ -0,0 +1,977 @@
1
+ import * as Array from "effect/Array";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Function from "effect/Function";
4
+ import { Event, SubscriptionList, scheduleTaskInterval } from "@dxos/async";
5
+ import { invariant } from "@dxos/invariant";
6
+ import { LogLevel, log } from "@dxos/log";
7
+ import * as localForage from "localforage";
8
+ import { compositeKey, isNode, isNonNullable } from "@dxos/util";
9
+ import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
10
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
11
+ import * as Match from "effect/Match";
12
+ import * as Option from "effect/Option";
13
+ import * as Ref from "effect/Ref";
14
+ import { resolveTelemetryTag } from "@dxos/config";
15
+ import { DeviceKind } from "@dxos/client/halo";
16
+ import { Context } from "@dxos/context";
17
+ import { ConnectionState, Platform } from "@dxos/protocols/proto/dxos/client/services";
18
+ import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
19
+ import * as HttpClient from "@effect/platform/HttpClient";
20
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
21
+ import * as Schema from "effect/Schema";
22
+ import * as Duration from "effect/Duration";
23
+ import * as Fiber from "effect/Fiber";
24
+ import * as Schedule from "effect/Schedule";
25
+ //#region \0rolldown/runtime.js
26
+ var __defProp = Object.defineProperty;
27
+ var __exportAll = (all, no_symbols) => {
28
+ let target = {};
29
+ for (var name in all) __defProp(target, name, {
30
+ get: all[name],
31
+ enumerable: true
32
+ });
33
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
34
+ return target;
35
+ };
36
+ //#endregion
37
+ //#region src/storage/browser.ts
38
+ var __dxlog_file$5 = "/__w/dxos/dxos/packages/sdk/observability/src/storage/browser.ts";
39
+ var OBSERVABILITY_DISABLED_KEY = "observability-disabled";
40
+ var OBSERVABILITY_GROUP_KEY = "observability-group";
41
+ var OTEL_LOG_LEVEL_KEY = "otel-log-level";
42
+ /** No-op in browser contexts. */
43
+ var showObservabilityBanner = () => {
44
+ log.warn("showObservabilityBanner is not supported in browser contexts.", void 0, {
45
+ "~LogMeta": "~LogMeta",
46
+ F: __dxlog_file$5,
47
+ L: 17,
48
+ S: void 0
49
+ });
50
+ };
51
+ /**
52
+ * @param namespace - localForage key prefix used to scope the observability state in browser storage.
53
+ */
54
+ var isObservabilityDisabled = async (namespace) => {
55
+ try {
56
+ return await localForage.getItem(compositeKey(namespace, OBSERVABILITY_DISABLED_KEY)) === "true";
57
+ } catch (err) {
58
+ log.catch("Failed to check if observability is disabled, assuming it is", err, {
59
+ "~LogMeta": "~LogMeta",
60
+ F: __dxlog_file$5,
61
+ L: 27,
62
+ S: void 0
63
+ });
64
+ return true;
65
+ }
66
+ };
67
+ /**
68
+ * @param namespace - localForage key prefix used to scope the observability state in browser storage.
69
+ */
70
+ var storeObservabilityDisabled = async (namespace, value) => {
71
+ try {
72
+ await localForage.setItem(compositeKey(namespace, OBSERVABILITY_DISABLED_KEY), String(value));
73
+ } catch (err) {
74
+ log.catch("Failed to store observability disabled", err, {
75
+ "~LogMeta": "~LogMeta",
76
+ F: __dxlog_file$5,
77
+ L: 39,
78
+ S: void 0
79
+ });
80
+ }
81
+ try {
82
+ if (typeof localStorage !== "undefined") localStorage.setItem(`${namespace}/${OBSERVABILITY_DISABLED_KEY}`, String(value));
83
+ } catch {}
84
+ };
85
+ /**
86
+ * @param namespace - localForage key prefix used to scope the observability state in browser storage.
87
+ */
88
+ var getObservabilityGroup = async (namespace) => {
89
+ try {
90
+ return await localForage.getItem(compositeKey(namespace, OBSERVABILITY_GROUP_KEY)) ?? void 0;
91
+ } catch (err) {
92
+ log.catch("Failed to get observability group", err, {
93
+ "~LogMeta": "~LogMeta",
94
+ F: __dxlog_file$5,
95
+ L: 59,
96
+ S: void 0
97
+ });
98
+ }
99
+ };
100
+ /**
101
+ * @param namespace - localForage key prefix used to scope the observability state in browser storage.
102
+ */
103
+ var storeObservabilityGroup = async (namespace, value) => {
104
+ try {
105
+ await localForage.setItem(compositeKey(namespace, OBSERVABILITY_GROUP_KEY), value);
106
+ } catch (err) {
107
+ log.catch("Failed to store observability group", err, {
108
+ "~LogMeta": "~LogMeta",
109
+ F: __dxlog_file$5,
110
+ L: 70,
111
+ S: void 0
112
+ });
113
+ }
114
+ };
115
+ /**
116
+ * @param namespace - localForage key prefix used to scope the observability state in browser storage.
117
+ */
118
+ var getOtelLogLevel = async (namespace) => {
119
+ try {
120
+ return await localForage.getItem(compositeKey(namespace, OTEL_LOG_LEVEL_KEY));
121
+ } catch (err) {
122
+ log.catch("Failed to get OTEL log level", err, {
123
+ "~LogMeta": "~LogMeta",
124
+ F: __dxlog_file$5,
125
+ L: 81,
126
+ S: void 0
127
+ });
128
+ return null;
129
+ }
130
+ };
131
+ /**
132
+ * @param namespace - localForage key prefix used to scope the observability state in browser storage.
133
+ */
134
+ var storeOtelLogLevel = async (namespace, value) => {
135
+ try {
136
+ if (value === null) await localForage.removeItem(compositeKey(namespace, OTEL_LOG_LEVEL_KEY));
137
+ else await localForage.setItem(compositeKey(namespace, OTEL_LOG_LEVEL_KEY), value);
138
+ } catch (err) {
139
+ log.catch("Failed to store OTEL log level", err, {
140
+ "~LogMeta": "~LogMeta",
141
+ F: __dxlog_file$5,
142
+ L: 97,
143
+ S: void 0
144
+ });
145
+ }
146
+ };
147
+ //#endregion
148
+ //#region src/observability.ts
149
+ var observability_exports = /* @__PURE__ */ __exportAll({
150
+ addDataProvider: () => addDataProvider,
151
+ addExtension: () => addExtension,
152
+ getObservabilityGroup: () => getObservabilityGroup,
153
+ getOtelLogLevel: () => getOtelLogLevel,
154
+ initialize: () => initialize,
155
+ isObservabilityDisabled: () => isObservabilityDisabled,
156
+ make: () => make,
157
+ showObservabilityBanner: () => showObservabilityBanner,
158
+ storeObservabilityDisabled: () => storeObservabilityDisabled,
159
+ storeObservabilityGroup: () => storeObservabilityGroup,
160
+ storeOtelLogLevel: () => storeOtelLogLevel
161
+ });
162
+ var __dxlog_file$4 = "/__w/dxos/dxos/packages/sdk/observability/src/observability.ts";
163
+ var ObservabilityImpl = class {
164
+ _initialized = false;
165
+ _extensions = [];
166
+ _dataProviders = [];
167
+ _subscriptions = new SubscriptionList();
168
+ initialize() {
169
+ if (this._initialized) return Effect.succeed(void 0);
170
+ const initializedExtensions = [];
171
+ return Effect.gen(this, function* () {
172
+ for (const extension of this._extensions) {
173
+ if (extension.initialize) yield* extension.initialize();
174
+ initializedExtensions.push(extension);
175
+ }
176
+ const cleanups = yield* Effect.all(this._dataProviders.map((provider) => provider(this)));
177
+ this._subscriptions.add(...cleanups.filter((cleanup) => cleanup !== void 0));
178
+ this._initialized = true;
179
+ }).pipe(Effect.catchAll((error) => Effect.gen(this, function* () {
180
+ log.catch(error, void 0, {
181
+ "~LogMeta": "~LogMeta",
182
+ F: __dxlog_file$4,
183
+ L: 85,
184
+ S: this
185
+ });
186
+ for (const extension of initializedExtensions) if (extension.close) yield* extension.close().pipe(Effect.catchAll(() => Effect.succeed(void 0)));
187
+ this._subscriptions.clear();
188
+ })));
189
+ }
190
+ close() {
191
+ return Effect.gen(this, function* () {
192
+ this._subscriptions.clear();
193
+ this._dataProviders.length = 0;
194
+ for (const extension of this._extensions) if (extension.close) yield* extension.close();
195
+ this._initialized = false;
196
+ });
197
+ }
198
+ enable() {
199
+ return Effect.gen(this, function* () {
200
+ for (const extension of this._extensions) if (extension.enable) yield* extension.enable();
201
+ });
202
+ }
203
+ disable() {
204
+ return Effect.gen(this, function* () {
205
+ for (const extension of this._extensions) if (extension.disable) yield* extension.disable();
206
+ });
207
+ }
208
+ flush() {
209
+ return Effect.gen(this, function* () {
210
+ for (const extension of this._extensions) if (extension.flush) yield* extension.flush();
211
+ });
212
+ }
213
+ _addExtension(extension) {
214
+ invariant(!this._initialized, "Observability is already initialized", {
215
+ "~LogMeta": "~LogMeta",
216
+ F: __dxlog_file$4,
217
+ L: 142,
218
+ S: this,
219
+ A: ["!this._initialized", "'Observability is already initialized'"]
220
+ });
221
+ this._extensions.push(extension);
222
+ }
223
+ _addDataProvider(dataProvider) {
224
+ invariant(!this._initialized, "Observability is already initialized", {
225
+ "~LogMeta": "~LogMeta",
226
+ F: __dxlog_file$4,
227
+ L: 147,
228
+ S: this,
229
+ A: ["!this._initialized", "'Observability is already initialized'"]
230
+ });
231
+ this._dataProviders.push(dataProvider);
232
+ }
233
+ /**
234
+ * Adds a data provider and initializes it.
235
+ */
236
+ addDataProvider(dataProvider) {
237
+ return Effect.gen(this, function* () {
238
+ this._dataProviders.push(dataProvider);
239
+ const cleanup = yield* dataProvider(this);
240
+ if (cleanup) this._subscriptions.add(cleanup);
241
+ });
242
+ }
243
+ identify(distinctId, attributes, setOnceAttributes) {
244
+ for (const extension of this._extensions) extension.identify?.(distinctId, attributes, setOnceAttributes);
245
+ }
246
+ alias(distinctId, previousId) {
247
+ for (const extension of this._extensions) extension.alias?.(distinctId, previousId);
248
+ }
249
+ setTags(tags, kind) {
250
+ for (const extension of this._extensions) {
251
+ if (kind && !extension.apis.some((api) => api.kind === kind)) continue;
252
+ const processedTags = Object.fromEntries(Object.entries(tags).filter((entry) => entry[1] !== void 0).map(([key, value]) => [key, value.toString()]));
253
+ extension.setTags?.(processedTags);
254
+ }
255
+ }
256
+ get enabled() {
257
+ return this._extensions.every((extension) => extension.enabled);
258
+ }
259
+ get errors() {
260
+ return { captureException: (error, attributes) => {
261
+ for (const extension of this._getExtensions("errors")) extension.captureException(error, attributes);
262
+ } };
263
+ }
264
+ get events() {
265
+ return { captureEvent: (event, attributes) => {
266
+ for (const extension of this._getExtensions("events")) extension.captureEvent(event, attributes);
267
+ } };
268
+ }
269
+ get feedback() {
270
+ return { captureUserFeedback: async (form) => {
271
+ let eventUuid;
272
+ for (const extension of this._getExtensions("feedback")) eventUuid = await extension.captureUserFeedback(form) ?? eventUuid;
273
+ return eventUuid;
274
+ } };
275
+ }
276
+ isAvailable(kind) {
277
+ const apis = this._getExtensions(kind);
278
+ if (apis.length === 0) return Effect.succeed(false);
279
+ return Effect.gen(this, function* () {
280
+ for (const api of apis) if (yield* api.isAvailable()) return true;
281
+ return false;
282
+ });
283
+ }
284
+ get metrics() {
285
+ return {
286
+ gauge: (name, value, attributes) => {
287
+ for (const extension of this._getExtensions("metrics")) extension.gauge(name, value, attributes);
288
+ },
289
+ increment: (name, value, attributes) => {
290
+ for (const extension of this._getExtensions("metrics")) extension.increment(name, value, attributes);
291
+ },
292
+ distribution: (name, value, attributes) => {
293
+ for (const extension of this._getExtensions("metrics")) extension.distribution(name, value, attributes);
294
+ }
295
+ };
296
+ }
297
+ _getExtensions(kind) {
298
+ return Function.pipe(this._extensions, Array.flatMap((extension) => extension.apis), Array.filter((api) => api.kind === kind));
299
+ }
300
+ };
301
+ var make = () => Effect.succeed(new ObservabilityImpl());
302
+ var addExtension = (_extension) => Effect.fn(function* (_observability) {
303
+ const observability = yield* _observability;
304
+ const extension = yield* _extension;
305
+ invariant("_addExtension" in observability && typeof observability._addExtension === "function", void 0, {
306
+ "~LogMeta": "~LogMeta",
307
+ F: __dxlog_file$4,
308
+ L: 278,
309
+ S: this,
310
+ A: ["'_addExtension' in observability && typeof observability._addExtension === 'function'", ""]
311
+ });
312
+ observability._addExtension(extension);
313
+ return observability;
314
+ });
315
+ var addDataProvider = (dataProvider) => Effect.fn(function* (_observability) {
316
+ const observability = yield* _observability;
317
+ invariant("_addDataProvider" in observability && typeof observability._addDataProvider === "function", void 0, {
318
+ "~LogMeta": "~LogMeta",
319
+ F: __dxlog_file$4,
320
+ L: 286,
321
+ S: this,
322
+ A: ["'_addDataProvider' in observability && typeof observability._addDataProvider === 'function'", ""]
323
+ });
324
+ observability._addDataProvider(dataProvider);
325
+ return observability;
326
+ });
327
+ var initialize = Effect.fn(function* (_observability) {
328
+ const observability = yield* _observability;
329
+ yield* observability.initialize();
330
+ return observability;
331
+ });
332
+ var cli_observability_secrets_default = {
333
+ POSTHOG_API_KEY: null,
334
+ IPDATA_API_KEY: null,
335
+ OTEL_ENDPOINT: null,
336
+ OTEL_HEADERS: null
337
+ };
338
+ //#endregion
339
+ //#region src/extensions/stub.ts
340
+ var stubExtension = {
341
+ initialize: () => Effect.succeed(void 0),
342
+ enable: () => Effect.succeed(void 0),
343
+ disable: () => Effect.succeed(void 0),
344
+ flush: () => Effect.succeed(void 0),
345
+ setTags: () => void 0,
346
+ get enabled() {
347
+ return true;
348
+ },
349
+ apis: []
350
+ };
351
+ //#endregion
352
+ //#region src/extensions/otel/extension.ts
353
+ var __dxlog_file$3 = "/__w/dxos/dxos/packages/sdk/observability/src/extensions/otel/extension.ts";
354
+ /** Create an OTEL-backed observability extension for logs, metrics, and/or traces. */
355
+ var extensions$1 = Effect.fn(function* ({ serviceName, serviceVersion, environment, config, endpoint: _endpoint, headers: _headers, logs: logsEnabled = false, logLevel = LogLevel.INFO, metrics: metricsEnabled = false, traces: tracesEnabled = false }) {
356
+ const { OtelLogs } = yield* Effect.promise(() => import("./chunk-logs.mjs"));
357
+ const { OtelMetrics } = yield* Effect.promise(() => import("./chunk-metrics.mjs"));
358
+ const { OtelTraces } = yield* Effect.promise(() => import("./chunk-traces-browser.mjs"));
359
+ const disabled = (yield* Effect.promise(() => isObservabilityDisabled(serviceName))) || isObservabilityDisabledSync(serviceName);
360
+ const storedLogLevel = yield* Effect.promise(() => getOtelLogLevel(serviceName));
361
+ const resolvedLogLevel = storedLogLevel != null ? LogLevel[storedLogLevel.toUpperCase()] ?? logLevel : logLevel;
362
+ const enabledRef = yield* Ref.make(!disabled);
363
+ const tags = /* @__PURE__ */ new Map();
364
+ const rawEndpoint = isNode() ? process.env.DX_OTEL_ENDPOINT ?? _endpoint ?? cli_observability_secrets_default.OTEL_ENDPOINT : config.values.runtime?.app?.env?.DX_OTEL_ENDPOINT ?? _endpoint;
365
+ const endpoint = !isNode() && rawEndpoint?.startsWith("/") ? resolveRelativeEndpoint(rawEndpoint) : rawEndpoint;
366
+ const headers = _headers ?? Match.value(isNode()).pipe(Match.when(true, () => Option.fromNullable(process.env.DX_OTEL_HEADERS ?? cli_observability_secrets_default.OTEL_HEADERS)), Match.when(false, () => Option.fromNullable(config.values.runtime?.app?.env?.DX_OTEL_HEADERS)), Match.exhaustive, Option.map((raw) => parseHeaders(raw)), Option.getOrElse(() => void 0));
367
+ if (!endpoint) {
368
+ log.info("Missing OTEL_ENDPOINT", void 0, {
369
+ "~LogMeta": "~LogMeta",
370
+ F: __dxlog_file$3,
371
+ L: 85,
372
+ S: this
373
+ });
374
+ return stubExtension;
375
+ }
376
+ const resolvedHeaders = headers ?? {};
377
+ const resolvedEndpoint = !isNode() && endpoint.startsWith("/") ? `${globalThis.location.origin}${endpoint}` : endpoint;
378
+ const clientTag = resolveTelemetryTag(config);
379
+ if (clientTag) tags.set("ctx.tag", clientTag);
380
+ const resource = defaultResource().merge(resourceFromAttributes({
381
+ [ATTR_SERVICE_NAME]: serviceName,
382
+ [ATTR_SERVICE_VERSION]: serviceVersion,
383
+ "session.id": crypto.randomUUID(),
384
+ "deployment.environment": environment,
385
+ "dxos.process.type": detectProcessType(),
386
+ ...clientTag ? { "ctx.tag": clientTag } : {}
387
+ }));
388
+ const logs = logsEnabled ? new OtelLogs({
389
+ endpoint: resolvedEndpoint,
390
+ headers: resolvedHeaders,
391
+ resource,
392
+ getTags: () => Object.fromEntries(tags),
393
+ logLevel: resolvedLogLevel
394
+ }) : void 0;
395
+ const metrics = metricsEnabled ? new OtelMetrics({
396
+ endpoint: resolvedEndpoint,
397
+ headers: resolvedHeaders,
398
+ resource,
399
+ getTags: () => Object.fromEntries(tags)
400
+ }) : void 0;
401
+ const traces = tracesEnabled ? new OtelTraces({
402
+ endpoint: resolvedEndpoint,
403
+ headers: resolvedHeaders,
404
+ resource,
405
+ getTags: () => Object.fromEntries(tags)
406
+ }) : void 0;
407
+ return {
408
+ initialize: () => Effect.sync(() => {
409
+ if (disabled) return;
410
+ if (logs) log.runtimeConfig.processors.push(logs.logProcessor);
411
+ if (traces) traces.start();
412
+ }),
413
+ enable: Effect.fn(function* () {
414
+ yield* Effect.promise(() => storeObservabilityDisabled(serviceName, false));
415
+ yield* Ref.update(enabledRef, () => true);
416
+ }),
417
+ disable: Effect.fn(function* () {
418
+ yield* Effect.promise(() => storeObservabilityDisabled(serviceName, true));
419
+ yield* Ref.update(enabledRef, () => false);
420
+ }),
421
+ close: () => Effect.promise(async () => {
422
+ const results = await Promise.allSettled([logs?.close(), metrics?.close()]);
423
+ for (const result of results) if (result.status === "rejected") log.catch(result.reason, void 0, {
424
+ "~LogMeta": "~LogMeta",
425
+ F: __dxlog_file$3,
426
+ L: 183,
427
+ S: this
428
+ });
429
+ await traces?.close();
430
+ }),
431
+ flush: () => Effect.promise(async () => {
432
+ const results = await Promise.allSettled([logs?.flush(), metrics?.flush()]);
433
+ for (const result of results) if (result.status === "rejected") log.catch(result.reason, void 0, {
434
+ "~LogMeta": "~LogMeta",
435
+ F: __dxlog_file$3,
436
+ L: 196,
437
+ S: this
438
+ });
439
+ await traces?.flush();
440
+ }),
441
+ setTags: (incomingTags) => {
442
+ for (const [key, value] of Object.entries(incomingTags)) tags.set(key, value);
443
+ },
444
+ get enabled() {
445
+ return Ref.get(enabledRef).pipe(Effect.runSync);
446
+ },
447
+ apis: [
448
+ {
449
+ kind: "logs",
450
+ isAvailable: () => Effect.succeed(!!logs)
451
+ },
452
+ metrics ? {
453
+ kind: "metrics",
454
+ isAvailable: () => Effect.succeed(true),
455
+ gauge: (name, value, tags) => metrics.gauge(name, value, tags),
456
+ increment: (name, value, tags) => metrics.increment(name, value, tags),
457
+ distribution: (name, value, tags) => metrics.distribution(name, value, tags)
458
+ } : void 0,
459
+ traces ? {
460
+ kind: "traces",
461
+ isAvailable: () => Effect.succeed(true)
462
+ } : void 0
463
+ ].filter(isNonNullable)
464
+ };
465
+ });
466
+ /**
467
+ * Synchronous best-effort check for observability opt-out.
468
+ * Prevents telemetry from being emitted before the async storage check completes.
469
+ */
470
+ var isObservabilityDisabledSync = (serviceName) => {
471
+ if (isNode()) return process.env.DX_DISABLE_OBSERVABILITY === "true";
472
+ try {
473
+ if (typeof localStorage !== "undefined") return localStorage.getItem(`${serviceName}/observability-disabled`) === "true";
474
+ } catch {}
475
+ return false;
476
+ };
477
+ /** Best-effort detection of the JavaScript execution context type. */
478
+ var detectProcessType = () => {
479
+ if (isNode()) return "node";
480
+ if (typeof window !== "undefined") return "browser";
481
+ if (typeof globalThis.ServiceWorkerGlobalScope !== "undefined") return "service-worker";
482
+ if (typeof globalThis.SharedWorkerGlobalScope !== "undefined") return "shared-worker";
483
+ return "dedicated-worker";
484
+ };
485
+ /**
486
+ * Resolves a relative OTLP endpoint path against the current global origin.
487
+ * Workers expose the same `globalThis.location` as the page that spawned them;
488
+ * if neither is available we fall back to the original path and let the OTLP
489
+ * exporter surface the configuration error.
490
+ */
491
+ var resolveRelativeEndpoint = (path) => {
492
+ const origin = globalThis.location?.origin;
493
+ return origin ? `${origin}${path}` : path;
494
+ };
495
+ var parseHeaders = (unparsedHeaders) => {
496
+ return unparsedHeaders.split(";").reduce((acc, header) => {
497
+ const [key, ...rest] = header.split(":");
498
+ if (key && rest.length > 0) acc[key.trim().toLowerCase()] = rest.join(":").trim();
499
+ return acc;
500
+ }, {});
501
+ };
502
+ //#endregion
503
+ //#region src/extensions/otel/index.ts
504
+ var otel_exports = /* @__PURE__ */ __exportAll({ extensions: () => extensions$1 });
505
+ //#endregion
506
+ //#region src/extensions/posthog/extension.ts
507
+ var __dxlog_file$2 = "/__w/dxos/dxos/packages/sdk/observability/src/extensions/posthog/extension.ts";
508
+ /** Upload serialized logs to the feedback-logs endpoint. Returns the R2 key on success. */
509
+ var uploadLogs = async (body) => {
510
+ try {
511
+ const response = await fetch("/api/feedback-logs", {
512
+ method: "POST",
513
+ headers: { "Content-Type": "application/x-ndjson" },
514
+ body
515
+ });
516
+ if (!response.ok) {
517
+ log.warn("feedback log upload failed", { status: response.status }, {
518
+ "~LogMeta": "~LogMeta",
519
+ F: __dxlog_file$2,
520
+ L: 45,
521
+ S: void 0
522
+ });
523
+ return;
524
+ }
525
+ const { key } = await response.json();
526
+ return key;
527
+ } catch (err) {
528
+ log.warn("feedback log upload error", { error: err }, {
529
+ "~LogMeta": "~LogMeta",
530
+ F: __dxlog_file$2,
531
+ L: 51,
532
+ S: void 0
533
+ });
534
+ return;
535
+ }
536
+ };
537
+ /** Create a PostHog-backed observability extension for events, errors, and feedback. */
538
+ var extensions = Effect.fn(function* ({ config, release, environment, posthog: posthogConfig, logStore, feedbackLogMaxSize }) {
539
+ if (typeof window === "undefined") {
540
+ log("PostHog is being stubbed because it is running in a worker.", void 0, {
541
+ "~LogMeta": "~LogMeta",
542
+ F: __dxlog_file$2,
543
+ L: 66,
544
+ S: this
545
+ });
546
+ return stubExtension;
547
+ }
548
+ const feedbackSurveyId = config.get("runtime.app.env.DX_POSTHOG_FEEDBACK_SURVEY_ID");
549
+ const apiKey = config.get("runtime.app.env.DX_POSTHOG_API_KEY");
550
+ const api_host = config.get("runtime.app.env.DX_POSTHOG_API_HOST");
551
+ if (!apiKey || !api_host) {
552
+ log.info("Missing POSTHOG_API_KEY or POSTHOG_API_HOST", void 0, {
553
+ "~LogMeta": "~LogMeta",
554
+ F: __dxlog_file$2,
555
+ L: 74,
556
+ S: this
557
+ });
558
+ return stubExtension;
559
+ }
560
+ const { default: posthog } = yield* Effect.promise(() => import("posthog-js"));
561
+ const { logProcessor } = yield* Effect.promise(() => import("./chunk-log-processor.mjs"));
562
+ let feedbackSurveyAvailable = null;
563
+ let unregisterPosthogProcessors;
564
+ const checkFeedbackSurveyAvailable = () => feedbackSurveyId ? Effect.promise(() => {
565
+ if (feedbackSurveyAvailable !== null) return Promise.resolve(feedbackSurveyAvailable);
566
+ return new Promise((resolve) => {
567
+ posthog.getSurveys((surveys) => {
568
+ const found = surveys.some((s) => s.id === feedbackSurveyId);
569
+ feedbackSurveyAvailable = found;
570
+ resolve(found);
571
+ });
572
+ });
573
+ }) : Effect.succeed(false);
574
+ return {
575
+ initialize: () => Effect.sync(() => {
576
+ posthog.init(apiKey, {
577
+ api_host,
578
+ mask_all_text: true,
579
+ capture_exceptions: true,
580
+ cross_subdomain_cookie: false,
581
+ ...posthogConfig
582
+ });
583
+ if (release || environment) posthog.register({
584
+ ...release ? { release } : {},
585
+ ...environment ? { environment } : {}
586
+ });
587
+ unregisterPosthogProcessors?.();
588
+ const removePosthogLog = log.addProcessor(logProcessor, void 0, {
589
+ "~LogMeta": "~LogMeta",
590
+ F: __dxlog_file$2,
591
+ L: 118,
592
+ S: this
593
+ });
594
+ unregisterPosthogProcessors = () => {
595
+ removePosthogLog();
596
+ };
597
+ }),
598
+ close: () => Effect.sync(() => {
599
+ unregisterPosthogProcessors?.();
600
+ unregisterPosthogProcessors = void 0;
601
+ }),
602
+ enable: () => Effect.sync(() => posthog.opt_in_capturing()),
603
+ disable: () => Effect.sync(() => posthog.opt_out_capturing()),
604
+ identify: (distinctId, attributes, setOnceAttributes) => {
605
+ posthog.identify(distinctId, attributes, setOnceAttributes);
606
+ },
607
+ alias: (distinctId, previousId) => {
608
+ posthog.alias(distinctId, previousId);
609
+ },
610
+ setTags: (tags) => {
611
+ posthog.register_for_session(tags);
612
+ },
613
+ get enabled() {
614
+ return posthog.is_capturing();
615
+ },
616
+ apis: [
617
+ {
618
+ kind: "events",
619
+ isAvailable: () => Effect.succeed(true),
620
+ captureEvent: (event, attributes) => {
621
+ posthog.capture(event, attributes);
622
+ }
623
+ },
624
+ {
625
+ kind: "errors",
626
+ isAvailable: () => Effect.succeed(true),
627
+ captureException: (error, attributes) => {
628
+ posthog.captureException(error, attributes);
629
+ }
630
+ },
631
+ {
632
+ kind: "feedback",
633
+ captureUserFeedback: (form) => {
634
+ return new Promise((resolve, reject) => {
635
+ posthog.getSurveys((surveys) => {
636
+ (async () => {
637
+ try {
638
+ const survey = surveys.find((survey) => survey.id === feedbackSurveyId);
639
+ if (!survey || survey.questions.length === 0) {
640
+ log.error("Missing feedback survey or survey has no questions", { feedbackSurveyId }, {
641
+ "~LogMeta": "~LogMeta",
642
+ F: __dxlog_file$2,
643
+ L: 167,
644
+ S: this
645
+ });
646
+ resolve(void 0);
647
+ return;
648
+ }
649
+ let debugLogDumpKey = null;
650
+ if (form.includeLogs !== false && logStore !== void 0) {
651
+ const ndjson = await logStore.export({ maxSize: feedbackLogMaxSize });
652
+ if (ndjson.length > 0) debugLogDumpKey = await uploadLogs(ndjson) ?? "failed";
653
+ }
654
+ const question = survey.questions[0];
655
+ resolve(posthog.capture("survey sent", {
656
+ $survey_id: survey.id,
657
+ $survey_questions: [{
658
+ id: question.id,
659
+ question: question.question
660
+ }],
661
+ [`$survey_response_${question.id}`]: form.message,
662
+ $survey_completed: true,
663
+ debug_log_dump_key: debugLogDumpKey
664
+ })?.uuid);
665
+ } catch (err) {
666
+ log.error("Failed to capture user feedback", { err }, {
667
+ "~LogMeta": "~LogMeta",
668
+ F: __dxlog_file$2,
669
+ L: 192,
670
+ S: this
671
+ });
672
+ reject(err);
673
+ }
674
+ })();
675
+ });
676
+ });
677
+ },
678
+ isAvailable: checkFeedbackSurveyAvailable
679
+ }
680
+ ]
681
+ };
682
+ });
683
+ //#endregion
684
+ //#region src/extensions/posthog/index.ts
685
+ var posthog_exports = /* @__PURE__ */ __exportAll({ extensions: () => extensions });
686
+ //#endregion
687
+ //#region src/observability-extension.ts
688
+ var observability_extension_exports = /* @__PURE__ */ __exportAll({
689
+ Otel: () => otel_exports,
690
+ PostHog: () => posthog_exports
691
+ });
692
+ //#endregion
693
+ //#region src/providers/client-observability.ts
694
+ var client_observability_exports = /* @__PURE__ */ __exportAll({
695
+ identityProvider: () => identityProvider,
696
+ networkMetricsProvider: () => networkMetricsProvider,
697
+ runtimeMetricsProvider: () => runtimeMetricsProvider,
698
+ spacesMetricsProvider: () => spacesMetricsProvider
699
+ });
700
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/sdk/observability/src/providers/client-observability.ts";
701
+ var SPACE_METRICS_MIN_INTERVAL = 1e3 * 60 * 10;
702
+ var NETWORK_METRICS_MIN_INTERVAL = 1e3 * 60 * 10;
703
+ var RUNTIME_METRICS_MIN_INTERVAL = 1e3 * 60 * 10;
704
+ /** Subscribes to identity and device changes and sets observability tags accordingly. */
705
+ var identityProvider = (clientServices) => Effect.fn(function* (observability) {
706
+ clientServices.IdentityService.queryIdentity().subscribe((idqr) => {
707
+ if (!idqr?.identity?.did) return;
708
+ observability.identify(idqr.identity.did);
709
+ observability.setTags({ did: idqr.identity.did });
710
+ });
711
+ clientServices.DevicesService.queryDevices().subscribe((dqr) => {
712
+ if (!dqr?.devices || dqr.devices.length === 0) return;
713
+ const thisDevice = dqr.devices.find((device) => device.kind === DeviceKind.CURRENT);
714
+ if (!thisDevice) return;
715
+ observability.setTags({ deviceKey: thisDevice.deviceKey.truncate() });
716
+ if (thisDevice.profile?.label) observability.setTags({ deviceProfile: thisDevice.profile.label });
717
+ });
718
+ });
719
+ /** Periodically publishes network connection and buffer metrics. */
720
+ var networkMetricsProvider = (clientServices) => Effect.fn(function* (observability) {
721
+ const ctx = new Context(void 0, {
722
+ "~LogMeta": "~LogMeta",
723
+ F: __dxlog_file$1,
724
+ L: 61
725
+ });
726
+ let lastNetworkStatus;
727
+ const updateSignalMetrics = new Event().debounce(NETWORK_METRICS_MIN_INTERVAL);
728
+ updateSignalMetrics.on(ctx, async () => {
729
+ log("send signal metrics", void 0, {
730
+ "~LogMeta": "~LogMeta",
731
+ F: __dxlog_file$1,
732
+ L: 67,
733
+ S: this
734
+ });
735
+ (lastNetworkStatus?.signaling)?.forEach(({ server, state }) => {
736
+ observability.metrics.gauge("dxos.client.network.signal.connectionState", state, { server });
737
+ });
738
+ let swarmCount = 0;
739
+ const connectionStates = /* @__PURE__ */ new Map();
740
+ for (const state in ConnectionState) connectionStates.set(state, 0);
741
+ let totalReadBufferSize = 0;
742
+ let totalWriteBufferSize = 0;
743
+ let totalChannelBufferSize = 0;
744
+ lastNetworkStatus?.connectionInfo?.forEach((connectionInfo) => {
745
+ swarmCount++;
746
+ for (const conn of connectionInfo.connections ?? []) {
747
+ connectionStates.set(conn.state, (connectionStates.get(conn.state) ?? 0) + 1);
748
+ totalReadBufferSize += conn.readBufferSize ?? 0;
749
+ totalWriteBufferSize += conn.writeBufferSize ?? 0;
750
+ for (const stream of conn.streams ?? []) totalChannelBufferSize += stream.writeBufferSize ?? 0;
751
+ }
752
+ observability.metrics.gauge("dxos.client.network.swarm.count", swarmCount);
753
+ for (const state in ConnectionState) observability.metrics.gauge("dxos.client.network.connection.count", connectionStates.get(state) ?? 0, { state });
754
+ observability.metrics.gauge("dxos.client.network.totalReadBufferSize", totalReadBufferSize);
755
+ observability.metrics.gauge("dxos.client.network.totalWriteBufferSize", totalWriteBufferSize);
756
+ observability.metrics.gauge("dxos.client.network.totalChannelBufferSize", totalChannelBufferSize);
757
+ });
758
+ });
759
+ clientServices.NetworkService.queryStatus().subscribe((networkStatus) => {
760
+ lastNetworkStatus = networkStatus;
761
+ updateSignalMetrics.emit();
762
+ });
763
+ scheduleTaskInterval(ctx, async () => updateSignalMetrics.emit(), NETWORK_METRICS_MIN_INTERVAL);
764
+ return async () => {
765
+ await ctx.dispose();
766
+ };
767
+ });
768
+ /** Periodically publishes platform and heap memory metrics. */
769
+ var runtimeMetricsProvider = (clientServices) => Effect.fn(function* (observability) {
770
+ const ctx = new Context(void 0, {
771
+ "~LogMeta": "~LogMeta",
772
+ F: __dxlog_file$1,
773
+ L: 121
774
+ });
775
+ log("runtimeMetricsProvider: requesting platform from SystemService", void 0, {
776
+ "~LogMeta": "~LogMeta",
777
+ F: __dxlog_file$1,
778
+ L: 122,
779
+ S: this
780
+ });
781
+ const platform = yield* Effect.promise(() => clientServices.SystemService.getPlatform());
782
+ log("runtimeMetricsProvider: platform received", void 0, {
783
+ "~LogMeta": "~LogMeta",
784
+ F: __dxlog_file$1,
785
+ L: 124,
786
+ S: this
787
+ });
788
+ invariant(platform, "platform is required", {
789
+ "~LogMeta": "~LogMeta",
790
+ F: __dxlog_file$1,
791
+ L: 125,
792
+ S: this,
793
+ A: ["platform", "'platform is required'"]
794
+ });
795
+ observability.setTags({
796
+ platformType: Platform.PLATFORM_TYPE[platform.type].toLowerCase(),
797
+ platform: platform.platform,
798
+ arch: platform.arch,
799
+ runtime: platform.runtime
800
+ });
801
+ scheduleTaskInterval(ctx, async () => {
802
+ if (clientServices.constructor.name === "WorkerClientServices") {
803
+ const memory = window.performance.memory;
804
+ if (memory) {
805
+ observability.metrics.gauge("dxos.client.runtime.heapTotal", memory.totalJSHeapSize);
806
+ observability.metrics.gauge("dxos.client.runtime.heapUsed", memory.usedJSHeapSize);
807
+ observability.metrics.gauge("dxos.client.runtime.heapSizeLimit", memory.jsHeapSizeLimit);
808
+ }
809
+ }
810
+ clientServices.SystemService?.getPlatform().then((platform) => {
811
+ if (platform.memory) {
812
+ observability.metrics.gauge("dxos.client.services.runtime.rss", platform.memory.rss);
813
+ observability.metrics.gauge("dxos.client.services.runtime.heapTotal", platform.memory.heapTotal);
814
+ observability.metrics.gauge("dxos.client.services.runtime.heapUsed", platform.memory.heapUsed);
815
+ }
816
+ }).catch((error) => log("platform error", { error }, {
817
+ "~LogMeta": "~LogMeta",
818
+ F: __dxlog_file$1,
819
+ L: 154,
820
+ S: this
821
+ }));
822
+ }, RUNTIME_METRICS_MIN_INTERVAL);
823
+ return async () => {
824
+ await ctx.dispose();
825
+ };
826
+ });
827
+ /** Periodically publishes space membership, object count, and pipeline progress metrics. */
828
+ var spacesMetricsProvider = (client) => Effect.fn(function* (observability) {
829
+ const ctx = new Context(void 0, {
830
+ "~LogMeta": "~LogMeta",
831
+ F: __dxlog_file$1,
832
+ L: 167
833
+ });
834
+ const spaces = client.spaces.get();
835
+ const subscriptions = /* @__PURE__ */ new Map();
836
+ ctx.onDispose(() => subscriptions.forEach((subscription) => subscription.unsubscribe()));
837
+ const updateSpaceMetrics = new Event().debounce(SPACE_METRICS_MIN_INTERVAL);
838
+ updateSpaceMetrics.on(ctx, async () => {
839
+ log("send space metrics", void 0, {
840
+ "~LogMeta": "~LogMeta",
841
+ F: __dxlog_file$1,
842
+ L: 175,
843
+ S: this
844
+ });
845
+ for (const data of mapSpaces(spaces, { truncateKeys: true })) {
846
+ observability.metrics.gauge("dxos.client.space.members", data.members, { key: data.key });
847
+ observability.metrics.gauge("dxos.client.space.objects", data.objects, { key: data.key });
848
+ observability.metrics.gauge("dxos.client.space.epoch", data.epoch, { key: data.key });
849
+ observability.metrics.gauge("dxos.client.space.currentDataMutations", data.currentDataMutations, { key: data.key });
850
+ }
851
+ });
852
+ const subscribeToSpaceUpdate = (space) => space.pipeline.subscribe({ next: () => {
853
+ updateSpaceMetrics.emit();
854
+ } });
855
+ spaces.forEach((space) => {
856
+ subscriptions.set(space.id, subscribeToSpaceUpdate(space));
857
+ });
858
+ client.spaces.subscribe({ next: async (spaces) => {
859
+ spaces.filter((space) => !subscriptions.has(space.id)).forEach((space) => {
860
+ subscriptions.set(space.id, subscribeToSpaceUpdate(space));
861
+ });
862
+ } });
863
+ scheduleTaskInterval(ctx, async () => updateSpaceMetrics.emit(), SPACE_METRICS_MIN_INTERVAL);
864
+ return async () => {
865
+ await ctx.dispose();
866
+ };
867
+ });
868
+ var mapSpaces = (spaces, options = {
869
+ verbose: false,
870
+ truncateKeys: false
871
+ }) => {
872
+ return spaces.map((space) => {
873
+ const { open, ready } = space.internal.data.metrics ?? {};
874
+ const startup = open && ready && ready.getTime() - open.getTime();
875
+ const pipeline = space.internal.data.pipeline;
876
+ const startDataMutations = pipeline?.currentEpoch?.subject.assertion.timeframe.totalMessages() ?? 0;
877
+ const epoch = pipeline?.currentEpoch?.subject.assertion.number;
878
+ const currentDataMutations = pipeline?.currentDataTimeframe?.totalMessages() ?? 0;
879
+ const totalDataMutations = pipeline?.targetDataTimeframe?.totalMessages() ?? 0;
880
+ return {
881
+ key: space.key.truncate(),
882
+ open: space.isOpen,
883
+ members: space.members.get().length,
884
+ objects: space.internal.db.getAllObjectIds().length,
885
+ startup,
886
+ epoch,
887
+ startDataMutations,
888
+ currentDataMutations,
889
+ totalDataMutations,
890
+ progress: (Math.min(Math.abs((currentDataMutations - startDataMutations) / (totalDataMutations - startDataMutations)), 1) * 100).toFixed(0)
891
+ };
892
+ });
893
+ };
894
+ //#endregion
895
+ //#region src/providers/ip-data.ts
896
+ var ip_data_exports = /* @__PURE__ */ __exportAll({ provider: () => provider$1 });
897
+ var __dxlog_file = "/__w/dxos/dxos/packages/sdk/observability/src/providers/ip-data.ts";
898
+ var IP_DATA_CACHE_TIMEOUT = 360 * 60 * 1e3;
899
+ var IPData = Schema.Struct({
900
+ city: Schema.NullOr(Schema.String),
901
+ region: Schema.NullOr(Schema.String),
902
+ country_name: Schema.String,
903
+ latitude: Schema.NullOr(Schema.Number),
904
+ longitude: Schema.NullOr(Schema.Number)
905
+ });
906
+ var getIPData = Effect.fn(function* (config) {
907
+ const httpClientNoTrace = (yield* HttpClient.HttpClient).pipe(HttpClient.withTracerDisabledWhen(() => true));
908
+ const cachedData = yield* Effect.promise(() => localForage.getItem("dxos:observability:ipdata:v2"));
909
+ if (cachedData && cachedData.timestamp > Date.now() - IP_DATA_CACHE_TIMEOUT) return cachedData.data;
910
+ const IPDATA_API_KEY = config.get("runtime.app.env.DX_IPDATA_API_KEY");
911
+ if (!IPDATA_API_KEY) {
912
+ log.warn("DX_IPDATA_API_KEY is not configured; IP geolocation tags will be absent from telemetry", void 0, {
913
+ "~LogMeta": "~LogMeta",
914
+ F: __dxlog_file,
915
+ L: 52,
916
+ S: this
917
+ });
918
+ return cachedData?.data;
919
+ }
920
+ const data = yield* HttpClientRequest.get(`https://api.ipdata.co?api-key=${IPDATA_API_KEY}`).pipe(httpClientNoTrace.execute, Effect.flatMap((res) => res.json), Effect.flatMap(Schema.decodeUnknown(IPData)), Effect.catchAll((err) => Effect.sync(() => {
921
+ log.warn("ipdata fetch failed; IP geolocation tags will be absent or stale", { err }, {
922
+ "~LogMeta": "~LogMeta",
923
+ F: __dxlog_file,
924
+ L: 63,
925
+ S: this
926
+ });
927
+ return cachedData?.data;
928
+ })));
929
+ if (data) yield* Effect.promise(() => localForage.setItem("dxos:observability:ipdata:v2", {
930
+ data,
931
+ timestamp: Date.now()
932
+ }));
933
+ return data;
934
+ });
935
+ /** Fetches IP geolocation data and sets city/region/country tags on the observability instance. */
936
+ var provider$1 = (config) => (observability) => Effect.gen(function* () {
937
+ const ipData = yield* getIPData(config);
938
+ if (!ipData) return;
939
+ observability.setTags({
940
+ ...ipData.city != null && { city: ipData.city },
941
+ ...ipData.region != null && { region: ipData.region },
942
+ country: ipData.country_name,
943
+ ...ipData.latitude != null && { latitude: ipData.latitude },
944
+ ...ipData.longitude != null && { longitude: ipData.longitude }
945
+ });
946
+ }).pipe(Effect.provide(FetchHttpClient.layer), Effect.catchAll((err) => Effect.sync(() => {
947
+ log.warn("ipdata provider failed", { err }, {
948
+ "~LogMeta": "~LogMeta",
949
+ F: __dxlog_file,
950
+ L: 97,
951
+ S: void 0
952
+ });
953
+ })));
954
+ //#endregion
955
+ //#region src/providers/storage.ts
956
+ var storage_exports = /* @__PURE__ */ __exportAll({ provider: () => provider });
957
+ var provider = Effect.fn(function* (observability) {
958
+ if (typeof navigator !== "undefined" && navigator.storage?.estimate) {
959
+ const fiber = Effect.gen(function* () {
960
+ const storageEstimate = yield* Effect.tryPromise(() => navigator.storage.estimate());
961
+ storageEstimate.usage && observability.metrics.gauge("storageUsage", storageEstimate.usage);
962
+ storageEstimate.quota && observability.metrics.gauge("storageQuota", storageEstimate.quota);
963
+ }).pipe(Effect.repeat(Schedule.fixed(Duration.hours(1))), Effect.runFork);
964
+ return () => Effect.runSync(Fiber.interrupt(fiber));
965
+ }
966
+ });
967
+ //#endregion
968
+ //#region src/providers/index.ts
969
+ var providers_exports = /* @__PURE__ */ __exportAll({
970
+ Client: () => client_observability_exports,
971
+ IPData: () => ip_data_exports,
972
+ Storage: () => storage_exports
973
+ });
974
+ //#endregion
975
+ export { observability_exports as Observability, observability_extension_exports as ObservabilityExtension, providers_exports as ObservabilityProvider };
976
+
977
+ //# sourceMappingURL=index.mjs.map