@dxos/observability 0.10.0 → 0.11.0
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/chunk-log-processor.mjs +28 -0
- package/dist/lib/chunk-log-processor.mjs.map +1 -0
- package/dist/lib/chunk-logs.mjs +89 -0
- package/dist/lib/chunk-logs.mjs.map +1 -0
- package/dist/lib/chunk-metrics.mjs +118 -0
- package/dist/lib/chunk-metrics.mjs.map +1 -0
- package/dist/lib/chunk-otel.mjs +20 -0
- package/dist/lib/chunk-otel.mjs.map +1 -0
- package/dist/lib/chunk-traces-browser.mjs +145 -0
- package/dist/lib/chunk-traces-browser.mjs.map +1 -0
- package/dist/lib/index.mjs +977 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/types/src/cli-observability-secrets.json +2 -2
- package/dist/types/src/extensions/otel/logs.d.ts +1 -0
- package/dist/types/src/extensions/otel/logs.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/logs.js +11 -4
- package/dist/types/src/extensions/otel/logs.js.map +1 -1
- package/dist/types/src/extensions/otel/logs.test.d.ts +2 -0
- package/dist/types/src/extensions/otel/logs.test.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/logs.test.js +33 -0
- package/dist/types/src/extensions/otel/logs.test.js.map +1 -0
- package/dist/types/src/extensions/posthog/extension.d.ts.map +1 -1
- package/dist/types/src/extensions/posthog/extension.js +4 -0
- package/dist/types/src/extensions/posthog/extension.js.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -31
- package/src/cli-observability-secrets.json +2 -2
- package/src/extensions/otel/logs.test.ts +39 -0
- package/src/extensions/otel/logs.ts +11 -4
- package/src/extensions/posthog/extension.ts +4 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/browser/chunk-PSFTIG54.mjs +0 -20
- package/dist/lib/browser/chunk-PSFTIG54.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -1015
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/log-processor-MZCTEVJC.mjs +0 -46
- package/dist/lib/browser/log-processor-MZCTEVJC.mjs.map +0 -7
- package/dist/lib/browser/logs-UTNIFYHF.mjs +0 -116
- package/dist/lib/browser/logs-UTNIFYHF.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/metrics-PRGSYAZJ.mjs +0 -116
- package/dist/lib/browser/metrics-PRGSYAZJ.mjs.map +0 -7
- package/dist/lib/browser/traces-browser-H4NCYCVZ.mjs +0 -154
- package/dist/lib/browser/traces-browser-H4NCYCVZ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-EDDZWPYV.mjs +0 -22
- package/dist/lib/node-esm/chunk-EDDZWPYV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1021
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/log-processor-GA24XTJF.mjs +0 -47
- package/dist/lib/node-esm/log-processor-GA24XTJF.mjs.map +0 -7
- package/dist/lib/node-esm/logs-ARJUVN3T.mjs +0 -117
- package/dist/lib/node-esm/logs-ARJUVN3T.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/metrics-DREJOOAC.mjs +0 -117
- package/dist/lib/node-esm/metrics-DREJOOAC.mjs.map +0 -7
- package/dist/lib/node-esm/traces-74F7JUKF.mjs +0 -125
- 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
|