@dxos/observability 0.3.11-main.e19f39d → 0.3.11-main.e28df4f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-ROSK6U3K.mjs +83 -0
- package/dist/lib/browser/chunk-ROSK6U3K.mjs.map +7 -0
- package/dist/lib/browser/chunk-STWCJK52.mjs +748 -0
- package/dist/lib/browser/chunk-STWCJK52.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +28 -553
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/observability-XIDFB6PJ.mjs +10 -0
- package/dist/lib/browser/observability-XIDFB6PJ.mjs.map +7 -0
- package/dist/lib/browser/segment/index.mjs +8 -0
- package/dist/lib/browser/segment/index.mjs.map +7 -0
- package/dist/lib/node/chunk-ARUBPK2W.cjs +170 -0
- package/dist/lib/node/chunk-ARUBPK2W.cjs.map +7 -0
- package/dist/lib/node/chunk-DE7OVD7T.cjs +769 -0
- package/dist/lib/node/chunk-DE7OVD7T.cjs.map +7 -0
- package/dist/lib/node/index.cjs +26 -602
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/observability-YMIQHLME.cjs +32 -0
- package/dist/lib/node/observability-YMIQHLME.cjs.map +7 -0
- package/dist/lib/node/segment/index.cjs +30 -0
- package/dist/lib/node/segment/index.cjs.map +7 -0
- package/dist/types/src/helpers/app-observability.d.ts +19 -0
- package/dist/types/src/helpers/app-observability.d.ts.map +1 -0
- package/dist/types/src/helpers/app-observability.js +195 -0
- package/dist/types/src/helpers/app-observability.js.map +1 -0
- package/dist/types/src/helpers/index.d.ts +4 -0
- package/dist/types/src/helpers/index.d.ts.map +1 -0
- package/dist/types/src/helpers/index.js +23 -0
- package/dist/types/src/helpers/index.js.map +1 -0
- package/dist/types/src/{util.d.ts → helpers/map-spaces.d.ts} +2 -3
- package/dist/types/src/helpers/map-spaces.d.ts.map +1 -0
- package/dist/types/src/{util.js → helpers/map-spaces.js} +1 -2
- package/dist/types/src/helpers/map-spaces.js.map +1 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.d.ts +4 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.d.ts.map +1 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.js +96 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.js.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/index.js +1 -0
- package/dist/types/src/index.js.map +1 -1
- package/dist/types/src/observability.d.ts +3 -4
- package/dist/types/src/observability.d.ts.map +1 -1
- package/dist/types/src/observability.js +26 -25
- package/dist/types/src/observability.js.map +1 -1
- package/package.json +15 -14
- package/src/helpers/app-observability.ts +205 -0
- package/src/helpers/index.ts +7 -0
- package/src/{util.ts → helpers/map-spaces.ts} +1 -2
- package/src/helpers/setup-telemetry-listeners.ts +115 -0
- package/src/index.ts +1 -0
- package/src/observability.ts +26 -24
- package/dist/types/src/util.d.ts.map +0 -1
- package/dist/types/src/util.js.map +0 -1
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatadogMetrics
|
|
3
|
+
} from "./chunk-2J5YS65Q.mjs";
|
|
4
|
+
import {
|
|
5
|
+
SegmentTelemetry
|
|
6
|
+
} from "./chunk-ROSK6U3K.mjs";
|
|
7
|
+
import {
|
|
8
|
+
captureException,
|
|
9
|
+
configureTracing,
|
|
10
|
+
enableSentryLogProcessor,
|
|
11
|
+
init,
|
|
12
|
+
setTag
|
|
13
|
+
} from "./chunk-6KUJPLEH.mjs";
|
|
14
|
+
|
|
15
|
+
// packages/sdk/observability/src/observability.ts
|
|
16
|
+
import { Event, scheduleTaskInterval } from "@dxos/async";
|
|
17
|
+
import { Context } from "@dxos/context";
|
|
18
|
+
import { invariant } from "@dxos/invariant";
|
|
19
|
+
import { log as log2 } from "@dxos/log";
|
|
20
|
+
import { ConnectionState } from "@dxos/network-manager";
|
|
21
|
+
import { DeviceKind, Platform } from "@dxos/protocols/proto/dxos/client/services";
|
|
22
|
+
import { isNode } from "@dxos/util";
|
|
23
|
+
|
|
24
|
+
// packages/sdk/observability/src/cli-observability-secrets.json
|
|
25
|
+
var cli_observability_secrets_default = {
|
|
26
|
+
SENTRY_DESTINATION: "https://2647916221e643869965e78469479aa4@o4504012000067584.ingest.sentry.io/4504012027265029",
|
|
27
|
+
TELEMETRY_API_KEY: "B00QG6PtJJrJ0VVFe0H5a6bcUUShKyZM",
|
|
28
|
+
IPDATA_API_KEY: "73dfdecdf979c18f07d50cf841bbdd9e589f237256326ac8cca23786",
|
|
29
|
+
DATADOG_API_KEY: null,
|
|
30
|
+
DATADOG_APP_KEY: null
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// packages/sdk/observability/src/helpers/app-observability.ts
|
|
34
|
+
import * as localForage from "localforage";
|
|
35
|
+
import { log } from "@dxos/log";
|
|
36
|
+
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/sdk/observability/src/helpers/app-observability.ts";
|
|
37
|
+
var BASE_TELEMETRY_PROPERTIES = {};
|
|
38
|
+
var OBSERVABILITY_DISABLED_KEY = "telemetry-disabled";
|
|
39
|
+
var OBSERVABILITY_GROUP_KEY = "telemetry-group";
|
|
40
|
+
if (typeof navigator !== "undefined" && navigator.storage?.estimate) {
|
|
41
|
+
setInterval(async () => {
|
|
42
|
+
try {
|
|
43
|
+
const storageEstimate = await navigator.storage.estimate();
|
|
44
|
+
BASE_TELEMETRY_PROPERTIES.storageUsage = storageEstimate.usage;
|
|
45
|
+
BASE_TELEMETRY_PROPERTIES.storageQuota = storageEstimate.quota;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
log.warn("Failed to run estimate()", error, {
|
|
48
|
+
F: __dxlog_file,
|
|
49
|
+
L: 27,
|
|
50
|
+
S: void 0,
|
|
51
|
+
C: (f, a) => f(...a)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}, 1e4);
|
|
55
|
+
}
|
|
56
|
+
var getTelemetryIdentifier = (client) => {
|
|
57
|
+
if (!client?.initialized) {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
const identity = client.halo.identity.get();
|
|
61
|
+
if (identity) {
|
|
62
|
+
return identity.identityKey.truncate();
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
65
|
+
};
|
|
66
|
+
var isObservabilityDisabled = async (namespace) => {
|
|
67
|
+
try {
|
|
68
|
+
return await localForage.getItem(`${namespace}:${OBSERVABILITY_DISABLED_KEY}`) === "true";
|
|
69
|
+
} catch (err) {
|
|
70
|
+
log.catch("Failed to check if observability is disabled, assuming it is", err, {
|
|
71
|
+
F: __dxlog_file,
|
|
72
|
+
L: 54,
|
|
73
|
+
S: void 0,
|
|
74
|
+
C: (f, a) => f(...a)
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var storeObservabilityDisabled = async (namespace, value) => {
|
|
80
|
+
try {
|
|
81
|
+
await localForage.setItem(`${namespace}:${OBSERVABILITY_DISABLED_KEY}`, String(value));
|
|
82
|
+
} catch (err) {
|
|
83
|
+
log.catch("Failed to store observability disabled", err, {
|
|
84
|
+
F: __dxlog_file,
|
|
85
|
+
L: 63,
|
|
86
|
+
S: void 0,
|
|
87
|
+
C: (f, a) => f(...a)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var getObservabilityGroup = async (namespace) => {
|
|
92
|
+
try {
|
|
93
|
+
return await localForage.getItem(`${namespace}:${OBSERVABILITY_GROUP_KEY}`) ?? void 0;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
log.catch("Failed to get observability group", err, {
|
|
96
|
+
F: __dxlog_file,
|
|
97
|
+
L: 71,
|
|
98
|
+
S: void 0,
|
|
99
|
+
C: (f, a) => f(...a)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var storeObservabilityGroup = async (namespace, value) => {
|
|
104
|
+
try {
|
|
105
|
+
await localForage.setItem(`${namespace}:${OBSERVABILITY_GROUP_KEY}`, value);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
log.catch("Failed to store observability group", err, {
|
|
108
|
+
F: __dxlog_file,
|
|
109
|
+
L: 79,
|
|
110
|
+
S: void 0,
|
|
111
|
+
C: (f, a) => f(...a)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var initializeAppObservability = async ({ namespace, config, mode = "basic", tracingEnable = true, replayEnable = true }) => {
|
|
116
|
+
log("initializeAppObservability", {
|
|
117
|
+
config
|
|
118
|
+
}, {
|
|
119
|
+
F: __dxlog_file,
|
|
120
|
+
L: 104,
|
|
121
|
+
S: void 0,
|
|
122
|
+
C: (f, a) => f(...a)
|
|
123
|
+
});
|
|
124
|
+
const group = await getObservabilityGroup(namespace) ?? void 0;
|
|
125
|
+
const release = `${namespace}@${config.get("runtime.app.build.version")}`;
|
|
126
|
+
const environment = config.get("runtime.app.env.DX_ENVIRONMENT");
|
|
127
|
+
BASE_TELEMETRY_PROPERTIES.group = group;
|
|
128
|
+
BASE_TELEMETRY_PROPERTIES.release = release;
|
|
129
|
+
BASE_TELEMETRY_PROPERTIES.environment = environment;
|
|
130
|
+
const observabilityDisabled = await isObservabilityDisabled(namespace);
|
|
131
|
+
const { Observability: Observability2 } = await import("./observability-XIDFB6PJ.mjs");
|
|
132
|
+
const observability = new Observability2({
|
|
133
|
+
namespace,
|
|
134
|
+
group,
|
|
135
|
+
mode,
|
|
136
|
+
config,
|
|
137
|
+
errorLog: {
|
|
138
|
+
sentryInitOptions: {
|
|
139
|
+
environment,
|
|
140
|
+
release,
|
|
141
|
+
tracing: tracingEnable,
|
|
142
|
+
replay: replayEnable,
|
|
143
|
+
// TODO(wittjosiah): Configure these.
|
|
144
|
+
sampleRate: 1,
|
|
145
|
+
replaySampleRate: 0.1,
|
|
146
|
+
replaySampleRateOnError: 1
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
if (observabilityDisabled) {
|
|
151
|
+
observability.setMode("disabled");
|
|
152
|
+
log.info("observability disabled", void 0, {
|
|
153
|
+
F: __dxlog_file,
|
|
154
|
+
L: 147,
|
|
155
|
+
S: void 0,
|
|
156
|
+
C: (f, a) => f(...a)
|
|
157
|
+
});
|
|
158
|
+
return observability;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const getIPData = async (config2) => {
|
|
162
|
+
const IP_DATA_CACHE_TIMEOUT = 6 * 60 * 60 * 1e3;
|
|
163
|
+
const cachedData = await localForage.getItem("dxos:telemetry:ipdata");
|
|
164
|
+
if (cachedData && cachedData.timestamp > Date.now() - IP_DATA_CACHE_TIMEOUT) {
|
|
165
|
+
return cachedData.data;
|
|
166
|
+
}
|
|
167
|
+
const IPDATA_API_KEY = config2.get("runtime.app.env.DX_IPDATA_API_KEY");
|
|
168
|
+
if (IPDATA_API_KEY) {
|
|
169
|
+
return fetch(`https://api.ipdata.co?api-key=${IPDATA_API_KEY}`).then((res) => res.json()).then((data) => {
|
|
170
|
+
localForage.setItem("dxos:telemetry:ipdata", {
|
|
171
|
+
data,
|
|
172
|
+
timestamp: Date.now()
|
|
173
|
+
}).catch((err) => observability.captureException(err));
|
|
174
|
+
return data;
|
|
175
|
+
}).catch((err) => observability.captureException(err));
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const ipData = await getIPData(config);
|
|
179
|
+
if (ipData && ipData.city) {
|
|
180
|
+
BASE_TELEMETRY_PROPERTIES.city = ipData.city;
|
|
181
|
+
BASE_TELEMETRY_PROPERTIES.region = ipData.region;
|
|
182
|
+
BASE_TELEMETRY_PROPERTIES.country = ipData.country;
|
|
183
|
+
BASE_TELEMETRY_PROPERTIES.latitude = ipData.latitude;
|
|
184
|
+
BASE_TELEMETRY_PROPERTIES.longitude = ipData.longitude;
|
|
185
|
+
}
|
|
186
|
+
observability.initialize();
|
|
187
|
+
} catch (err) {
|
|
188
|
+
log.error("Failed to initialize app observability", err, {
|
|
189
|
+
F: __dxlog_file,
|
|
190
|
+
L: 201,
|
|
191
|
+
S: void 0,
|
|
192
|
+
C: (f, a) => f(...a)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return observability;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// packages/sdk/observability/src/helpers/map-spaces.ts
|
|
199
|
+
var mapSpaces = (spaces, options = {
|
|
200
|
+
verbose: false,
|
|
201
|
+
truncateKeys: false
|
|
202
|
+
}) => {
|
|
203
|
+
return spaces.map((space) => {
|
|
204
|
+
const { open, ready } = space.internal.data.metrics ?? {};
|
|
205
|
+
const startup = open && ready && ready.getTime() - open.getTime();
|
|
206
|
+
const pipeline = space.internal.data.pipeline;
|
|
207
|
+
const startDataMutations = pipeline?.currentEpoch?.subject.assertion.timeframe.totalMessages() ?? 0;
|
|
208
|
+
const epoch = pipeline?.currentEpoch?.subject.assertion.number;
|
|
209
|
+
const currentDataMutations = pipeline?.currentDataTimeframe?.totalMessages() ?? 0;
|
|
210
|
+
const totalDataMutations = pipeline?.targetDataTimeframe?.totalMessages() ?? 0;
|
|
211
|
+
return {
|
|
212
|
+
// TODO(nf): truncate keys for DD?
|
|
213
|
+
key: space.key.truncate(),
|
|
214
|
+
open: space.isOpen,
|
|
215
|
+
members: space.members.get().length,
|
|
216
|
+
objects: space.db.query().objects.length,
|
|
217
|
+
startup,
|
|
218
|
+
epoch,
|
|
219
|
+
// appliedEpoch,
|
|
220
|
+
startDataMutations,
|
|
221
|
+
currentDataMutations,
|
|
222
|
+
totalDataMutations,
|
|
223
|
+
// TODO(burdon): Negative?
|
|
224
|
+
progress: (Math.min(Math.abs((currentDataMutations - startDataMutations) / (totalDataMutations - startDataMutations)), 1) * 100).toFixed(0)
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// packages/sdk/observability/src/helpers/setup-telemetry-listeners.ts
|
|
230
|
+
var lastFocusEvent = /* @__PURE__ */ new Date();
|
|
231
|
+
var totalTime = 0;
|
|
232
|
+
var setupTelemetryListeners = (namespace, client, observability) => {
|
|
233
|
+
const clickCallback = (event) => {
|
|
234
|
+
if (BASE_TELEMETRY_PROPERTIES.group === "dxos" && event.target && !event.target.id) {
|
|
235
|
+
console.warn("Click event on element without id:", event.target);
|
|
236
|
+
}
|
|
237
|
+
setTimeout(() => observability.event({
|
|
238
|
+
identityId: getTelemetryIdentifier(client),
|
|
239
|
+
name: `${namespace}.window.click`,
|
|
240
|
+
properties: {
|
|
241
|
+
...BASE_TELEMETRY_PROPERTIES,
|
|
242
|
+
href: window.location.href,
|
|
243
|
+
id: event.target?.id,
|
|
244
|
+
path: event.path?.filter((el) => Boolean(el.tagName)).map((el) => `${el.tagName.toLowerCase()}${el.id ? `#${el.id}` : ""}`).reverse().join(">")
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
};
|
|
248
|
+
const focusCallback = () => {
|
|
249
|
+
const now = /* @__PURE__ */ new Date();
|
|
250
|
+
setTimeout(() => observability.event({
|
|
251
|
+
identityId: getTelemetryIdentifier(client),
|
|
252
|
+
name: `${namespace}.window.focus`,
|
|
253
|
+
properties: {
|
|
254
|
+
...BASE_TELEMETRY_PROPERTIES,
|
|
255
|
+
href: window.location.href,
|
|
256
|
+
timeAway: now.getTime() - lastFocusEvent.getTime()
|
|
257
|
+
}
|
|
258
|
+
}));
|
|
259
|
+
lastFocusEvent = now;
|
|
260
|
+
};
|
|
261
|
+
const blurCallback = () => {
|
|
262
|
+
const now = /* @__PURE__ */ new Date();
|
|
263
|
+
const timeSpent = now.getTime() - lastFocusEvent.getTime();
|
|
264
|
+
setTimeout(() => observability.event({
|
|
265
|
+
identityId: getTelemetryIdentifier(client),
|
|
266
|
+
name: `${namespace}.window.blur`,
|
|
267
|
+
properties: {
|
|
268
|
+
...BASE_TELEMETRY_PROPERTIES,
|
|
269
|
+
href: window.location.href,
|
|
270
|
+
timeSpent
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
lastFocusEvent = now;
|
|
274
|
+
totalTime = totalTime + timeSpent;
|
|
275
|
+
};
|
|
276
|
+
const unloadCallback = () => {
|
|
277
|
+
setTimeout(() => observability.event({
|
|
278
|
+
identityId: getTelemetryIdentifier(client),
|
|
279
|
+
name: `${namespace}.page.unload`,
|
|
280
|
+
properties: {
|
|
281
|
+
...BASE_TELEMETRY_PROPERTIES,
|
|
282
|
+
href: window.location.href,
|
|
283
|
+
timeSpent: totalTime
|
|
284
|
+
}
|
|
285
|
+
}));
|
|
286
|
+
};
|
|
287
|
+
const errorCallback = (event) => {
|
|
288
|
+
setTimeout(() => observability.event({
|
|
289
|
+
identityId: getTelemetryIdentifier(client),
|
|
290
|
+
name: `${namespace}.window.error`,
|
|
291
|
+
properties: {
|
|
292
|
+
...BASE_TELEMETRY_PROPERTIES,
|
|
293
|
+
href: window.location.href,
|
|
294
|
+
message: event.message,
|
|
295
|
+
filename: event.filename,
|
|
296
|
+
stack: event.error.stack
|
|
297
|
+
}
|
|
298
|
+
}));
|
|
299
|
+
};
|
|
300
|
+
window.addEventListener("click", clickCallback, true);
|
|
301
|
+
window.addEventListener("focus", focusCallback);
|
|
302
|
+
window.addEventListener("blur", blurCallback);
|
|
303
|
+
window.addEventListener("beforeunload", unloadCallback);
|
|
304
|
+
window.addEventListener("error", errorCallback);
|
|
305
|
+
return () => {
|
|
306
|
+
window.removeEventListener("click", clickCallback, true);
|
|
307
|
+
window.removeEventListener("focus", focusCallback);
|
|
308
|
+
window.removeEventListener("blur", blurCallback);
|
|
309
|
+
window.removeEventListener("beforeunload", unloadCallback);
|
|
310
|
+
window.removeEventListener("error", errorCallback);
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// packages/sdk/observability/src/observability.ts
|
|
315
|
+
var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/sdk/observability/src/observability.ts";
|
|
316
|
+
var SPACE_METRICS_MIN_INTERVAL = 1e3 * 60;
|
|
317
|
+
var DATADOG_IDLE_INTERVAL = 1e3 * 60 * 1;
|
|
318
|
+
var ERROR_TAGS = [
|
|
319
|
+
"identityKey",
|
|
320
|
+
"username",
|
|
321
|
+
"deviceKey",
|
|
322
|
+
"group"
|
|
323
|
+
];
|
|
324
|
+
var Observability = class {
|
|
325
|
+
// TODO(nf): make platform a required extension?
|
|
326
|
+
constructor({ namespace, config, secrets, group, mode, telemetry, errorLog }) {
|
|
327
|
+
this._mode = "disabled";
|
|
328
|
+
// TODO(nf): accept upstream context?
|
|
329
|
+
this._ctx = new Context();
|
|
330
|
+
this._tags = /* @__PURE__ */ new Map();
|
|
331
|
+
this._namespace = namespace;
|
|
332
|
+
this._config = config;
|
|
333
|
+
this._mode = mode ?? "disabled";
|
|
334
|
+
this._group = group;
|
|
335
|
+
this._secrets = this._loadSecrets(config, secrets);
|
|
336
|
+
this._telemetryBatchSize = telemetry?.batchSize ?? 30;
|
|
337
|
+
this._errorReportingOptions = errorLog?.sentryInitOptions;
|
|
338
|
+
this._errorLogProcessor = errorLog?.logProcessor ?? false;
|
|
339
|
+
if (this._group) {
|
|
340
|
+
this.setTag("group", this._group);
|
|
341
|
+
}
|
|
342
|
+
this.setTag("namespace", this._namespace);
|
|
343
|
+
if (this._mode === "full") {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
_loadSecrets(config, secrets) {
|
|
347
|
+
if (isNode()) {
|
|
348
|
+
return cli_observability_secrets_default;
|
|
349
|
+
} else {
|
|
350
|
+
invariant(config, "runtime config is required", {
|
|
351
|
+
F: __dxlog_file2,
|
|
352
|
+
L: 117,
|
|
353
|
+
S: this,
|
|
354
|
+
A: [
|
|
355
|
+
"config",
|
|
356
|
+
"'runtime config is required'"
|
|
357
|
+
]
|
|
358
|
+
});
|
|
359
|
+
log2("config", {
|
|
360
|
+
rtc: this._secrets,
|
|
361
|
+
config
|
|
362
|
+
}, {
|
|
363
|
+
F: __dxlog_file2,
|
|
364
|
+
L: 118,
|
|
365
|
+
S: this,
|
|
366
|
+
C: (f, a) => f(...a)
|
|
367
|
+
});
|
|
368
|
+
return {
|
|
369
|
+
DX_ENVIRONMENT: config.get("runtime.app.env.DX_ENVIRONMENT"),
|
|
370
|
+
DX_RELEASE: config.get("runtime.app.env.DX_RELEASE"),
|
|
371
|
+
SENTRY_DESTINATION: config.get("runtime.app.env.DX_SENTRY_DESTINATION"),
|
|
372
|
+
TELEMETRY_API_KEY: config.get("runtime.app.env.DX_TELEMETRY_API_KEY"),
|
|
373
|
+
IPDATA_API_KEY: config.get("runtime.app.env.DX_IPDATA_API_KEY"),
|
|
374
|
+
DATADOG_API_KEY: config.get("runtime.app.env.DX_DATADOG_API_KEY"),
|
|
375
|
+
DATADOG_APP_KEY: config.get("runtime.app.env.DX_DATADOG_APP_KEY"),
|
|
376
|
+
...secrets
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
initialize() {
|
|
381
|
+
this._initMetrics();
|
|
382
|
+
this._initTelemetry();
|
|
383
|
+
this._initErrorLogs();
|
|
384
|
+
}
|
|
385
|
+
async close() {
|
|
386
|
+
await this._ctx.dispose();
|
|
387
|
+
}
|
|
388
|
+
setMode(mode) {
|
|
389
|
+
this._mode = mode;
|
|
390
|
+
}
|
|
391
|
+
get mode() {
|
|
392
|
+
return this._mode;
|
|
393
|
+
}
|
|
394
|
+
get enabled() {
|
|
395
|
+
return this._mode !== "disabled";
|
|
396
|
+
}
|
|
397
|
+
//
|
|
398
|
+
// Tags
|
|
399
|
+
//
|
|
400
|
+
/**
|
|
401
|
+
* Set a tag for all observability services.
|
|
402
|
+
*/
|
|
403
|
+
setTag(key, value) {
|
|
404
|
+
if (this.enabled && ERROR_TAGS.includes(key)) {
|
|
405
|
+
setTag(key, value);
|
|
406
|
+
}
|
|
407
|
+
this._tags.set(key, value);
|
|
408
|
+
}
|
|
409
|
+
// TODO(wittjosiah): Improve privacy of telemetry identifiers. See `getTelemetryIdentifier`.
|
|
410
|
+
async setIdentityTags(client) {
|
|
411
|
+
client.services.services.IdentityService.queryIdentity().subscribe((idqr) => {
|
|
412
|
+
if (!idqr?.identity?.identityKey) {
|
|
413
|
+
log2("empty response from identity service", {
|
|
414
|
+
idqr
|
|
415
|
+
}, {
|
|
416
|
+
F: __dxlog_file2,
|
|
417
|
+
L: 175,
|
|
418
|
+
S: this,
|
|
419
|
+
C: (f, a) => f(...a)
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
this.setTag("identityKey", idqr.identity.identityKey.truncate());
|
|
424
|
+
});
|
|
425
|
+
client.services.services.DevicesService.queryDevices().subscribe((dqr) => {
|
|
426
|
+
if (!dqr || !dqr.devices || dqr.devices.length === 0) {
|
|
427
|
+
log2("empty response from device service", {
|
|
428
|
+
device: dqr
|
|
429
|
+
}, {
|
|
430
|
+
F: __dxlog_file2,
|
|
431
|
+
L: 184,
|
|
432
|
+
S: this,
|
|
433
|
+
C: (f, a) => f(...a)
|
|
434
|
+
});
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
invariant(dqr, "empty response from device service", {
|
|
438
|
+
F: __dxlog_file2,
|
|
439
|
+
L: 187,
|
|
440
|
+
S: this,
|
|
441
|
+
A: [
|
|
442
|
+
"dqr",
|
|
443
|
+
"'empty response from device service'"
|
|
444
|
+
]
|
|
445
|
+
});
|
|
446
|
+
const thisDevice = dqr.devices.find((device) => device.kind === DeviceKind.CURRENT);
|
|
447
|
+
if (!thisDevice) {
|
|
448
|
+
log2("no current device", {
|
|
449
|
+
device: dqr
|
|
450
|
+
}, {
|
|
451
|
+
F: __dxlog_file2,
|
|
452
|
+
L: 191,
|
|
453
|
+
S: this,
|
|
454
|
+
C: (f, a) => f(...a)
|
|
455
|
+
});
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.setTag("deviceKey", thisDevice.deviceKey.truncate());
|
|
459
|
+
if (thisDevice.profile?.label) {
|
|
460
|
+
this.setTag("deviceProfile", thisDevice.profile.label);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
//
|
|
465
|
+
// Metrics
|
|
466
|
+
//
|
|
467
|
+
_initMetrics() {
|
|
468
|
+
if (this.enabled && this._secrets.DATADOG_API_KEY) {
|
|
469
|
+
this._metrics = new DatadogMetrics({
|
|
470
|
+
apiKey: this._secrets.DATADOG_API_KEY,
|
|
471
|
+
getTags: () => this._tags,
|
|
472
|
+
// TODO(nf): move/refactor from telementryContext, needed to read CORS proxy
|
|
473
|
+
config: this._config
|
|
474
|
+
});
|
|
475
|
+
} else {
|
|
476
|
+
log2("datadog disabled", void 0, {
|
|
477
|
+
F: __dxlog_file2,
|
|
478
|
+
L: 214,
|
|
479
|
+
S: this,
|
|
480
|
+
C: (f, a) => f(...a)
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Gauge metric.
|
|
486
|
+
*
|
|
487
|
+
* The default implementation uses Datadog.
|
|
488
|
+
*/
|
|
489
|
+
gauge(name, value, extraTags) {
|
|
490
|
+
this._metrics?.gauge(name, value, extraTags);
|
|
491
|
+
}
|
|
492
|
+
// TODO(nf): Refactor into ObservabilityExtensions.
|
|
493
|
+
startNetworkMetrics(client) {
|
|
494
|
+
const updateSignalMetrics = new Event();
|
|
495
|
+
updateSignalMetrics.on(this._ctx, async (networkStatus) => {
|
|
496
|
+
log2("send signal metrics", void 0, {
|
|
497
|
+
F: __dxlog_file2,
|
|
498
|
+
L: 234,
|
|
499
|
+
S: this,
|
|
500
|
+
C: (f, a) => f(...a)
|
|
501
|
+
});
|
|
502
|
+
networkStatus.signaling.forEach(({ server, state }) => {
|
|
503
|
+
this.gauge("dxos.client.network.signal.connectionState", state, {
|
|
504
|
+
server
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
let swarmCount = 0;
|
|
508
|
+
const connectionStates = /* @__PURE__ */ new Map();
|
|
509
|
+
for (const state in ConnectionState) {
|
|
510
|
+
connectionStates.set(state, 0);
|
|
511
|
+
}
|
|
512
|
+
let totalReadBufferSize = 0;
|
|
513
|
+
let totalWriteBufferSize = 0;
|
|
514
|
+
let totalChannelBufferSize = 0;
|
|
515
|
+
networkStatus.connectionInfo?.forEach((connectionInfo) => {
|
|
516
|
+
swarmCount++;
|
|
517
|
+
for (const conn of connectionInfo.connections ?? []) {
|
|
518
|
+
connectionStates.set(conn.state, (connectionStates.get(conn.state) ?? 0) + 1);
|
|
519
|
+
totalReadBufferSize += conn.readBufferSize ?? 0;
|
|
520
|
+
totalWriteBufferSize += conn.writeBufferSize ?? 0;
|
|
521
|
+
for (const stream of conn.streams ?? []) {
|
|
522
|
+
totalChannelBufferSize += stream.writeBufferSize ?? 0;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
this.gauge("dxos.client.network.swarm.count", swarmCount);
|
|
526
|
+
for (const state in ConnectionState) {
|
|
527
|
+
this.gauge("dxos.client.network.connection.count", connectionStates.get(state) ?? 0, {
|
|
528
|
+
state
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
this.gauge("dxox.client.network.totalReadBufferSize", totalReadBufferSize);
|
|
532
|
+
this.gauge("dxos.client.network.totalWriteBufferSize", totalWriteBufferSize);
|
|
533
|
+
this.gauge("dxos.client.network.totalChannelBufferSize", totalChannelBufferSize);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
client.services.services.NetworkService?.queryStatus().subscribe((networkStatus) => {
|
|
537
|
+
updateSignalMetrics.emit(networkStatus);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
startSpacesMetrics(client, namespace) {
|
|
541
|
+
const spaces = client.spaces.get();
|
|
542
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
543
|
+
this._ctx.onDispose(() => subscriptions.forEach((subscription) => subscription.unsubscribe()));
|
|
544
|
+
const updateSpaceMetrics = new Event().debounce(SPACE_METRICS_MIN_INTERVAL);
|
|
545
|
+
updateSpaceMetrics.on(this._ctx, async () => {
|
|
546
|
+
log2("send space update", void 0, {
|
|
547
|
+
F: __dxlog_file2,
|
|
548
|
+
L: 286,
|
|
549
|
+
S: this,
|
|
550
|
+
C: (f, a) => f(...a)
|
|
551
|
+
});
|
|
552
|
+
for (const data of mapSpaces(spaces, {
|
|
553
|
+
truncateKeys: true
|
|
554
|
+
})) {
|
|
555
|
+
this.gauge("dxos.client.space.members", data.members, {
|
|
556
|
+
key: data.key
|
|
557
|
+
});
|
|
558
|
+
this.gauge("dxos.client.space.objects", data.objects, {
|
|
559
|
+
key: data.key
|
|
560
|
+
});
|
|
561
|
+
this.gauge("dxos.client.space.epoch", data.epoch, {
|
|
562
|
+
key: data.key
|
|
563
|
+
});
|
|
564
|
+
this.gauge("dxos.client.space.currentDataMutations", data.currentDataMutations, {
|
|
565
|
+
key: data.key
|
|
566
|
+
});
|
|
567
|
+
this.event({
|
|
568
|
+
identityId: getTelemetryIdentifier(client),
|
|
569
|
+
name: `${namespace}.space.update`,
|
|
570
|
+
properties: {
|
|
571
|
+
...BASE_TELEMETRY_PROPERTIES,
|
|
572
|
+
...data
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
const subscribeToSpaceUpdate = (space) => space.pipeline.subscribe({
|
|
578
|
+
next: () => {
|
|
579
|
+
updateSpaceMetrics.emit();
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
spaces.forEach((space) => {
|
|
583
|
+
subscriptions.set(space.key.toHex(), subscribeToSpaceUpdate(space));
|
|
584
|
+
});
|
|
585
|
+
client.spaces.subscribe({
|
|
586
|
+
next: async (spaces2) => {
|
|
587
|
+
spaces2.filter((space) => !subscriptions.has(space.key.toHex())).forEach((space) => {
|
|
588
|
+
subscriptions.set(space.key.toHex(), subscribeToSpaceUpdate(space));
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
scheduleTaskInterval(this._ctx, async () => updateSpaceMetrics.emit(), DATADOG_IDLE_INTERVAL);
|
|
593
|
+
}
|
|
594
|
+
async startRuntimeMetrics(client, frequency = DATADOG_IDLE_INTERVAL) {
|
|
595
|
+
const platform = await client.services.services.SystemService?.getPlatform();
|
|
596
|
+
invariant(platform, "platform is required", {
|
|
597
|
+
F: __dxlog_file2,
|
|
598
|
+
L: 332,
|
|
599
|
+
S: this,
|
|
600
|
+
A: [
|
|
601
|
+
"platform",
|
|
602
|
+
"'platform is required'"
|
|
603
|
+
]
|
|
604
|
+
});
|
|
605
|
+
this.setTag("platform_type", Platform.PLATFORM_TYPE[platform.type].toLowerCase());
|
|
606
|
+
if (this._mode === "full") {
|
|
607
|
+
if (platform.platform) {
|
|
608
|
+
this.setTag("platform", platform.platform);
|
|
609
|
+
}
|
|
610
|
+
if (platform.arch) {
|
|
611
|
+
this.setTag("arch", platform.arch);
|
|
612
|
+
}
|
|
613
|
+
if (platform.runtime) {
|
|
614
|
+
this.setTag("runtime", platform.runtime);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
scheduleTaskInterval(this._ctx, async () => {
|
|
618
|
+
log2("platform", void 0, {
|
|
619
|
+
F: __dxlog_file2,
|
|
620
|
+
L: 350,
|
|
621
|
+
S: this,
|
|
622
|
+
C: (f, a) => f(...a)
|
|
623
|
+
});
|
|
624
|
+
client.services.services.SystemService?.getPlatform().then((platform2) => {
|
|
625
|
+
log2("platform", {
|
|
626
|
+
platform: platform2
|
|
627
|
+
}, {
|
|
628
|
+
F: __dxlog_file2,
|
|
629
|
+
L: 353,
|
|
630
|
+
S: this,
|
|
631
|
+
C: (f, a) => f(...a)
|
|
632
|
+
});
|
|
633
|
+
if (platform2.memory) {
|
|
634
|
+
this.gauge("dxos.client.runtime.rss", platform2.memory.rss);
|
|
635
|
+
this.gauge("dxos.client.runtime.heapTotal", platform2.memory.heapTotal);
|
|
636
|
+
this.gauge("dxos.client.runtime.heapUsed", platform2.memory.heapUsed);
|
|
637
|
+
}
|
|
638
|
+
}).catch((error) => log2("platform error", {
|
|
639
|
+
error
|
|
640
|
+
}, {
|
|
641
|
+
F: __dxlog_file2,
|
|
642
|
+
L: 360,
|
|
643
|
+
S: this,
|
|
644
|
+
C: (f, a) => f(...a)
|
|
645
|
+
}));
|
|
646
|
+
}, frequency);
|
|
647
|
+
}
|
|
648
|
+
//
|
|
649
|
+
// Telemetry
|
|
650
|
+
//
|
|
651
|
+
_initTelemetry() {
|
|
652
|
+
if (this._secrets.TELEMETRY_API_KEY && this._mode !== "disabled") {
|
|
653
|
+
this._telemetry = new SegmentTelemetry({
|
|
654
|
+
apiKey: this._secrets.TELEMETRY_API_KEY,
|
|
655
|
+
batchSize: this._telemetryBatchSize,
|
|
656
|
+
getTags: () => this._tags
|
|
657
|
+
});
|
|
658
|
+
} else {
|
|
659
|
+
log2("segment disabled", void 0, {
|
|
660
|
+
F: __dxlog_file2,
|
|
661
|
+
L: 378,
|
|
662
|
+
S: this,
|
|
663
|
+
C: (f, a) => f(...a)
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* A telemetry event.
|
|
669
|
+
*
|
|
670
|
+
* The default implementation uses Segment.
|
|
671
|
+
*/
|
|
672
|
+
event(options) {
|
|
673
|
+
this._telemetry?.event(options);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* A telemetry page view.
|
|
677
|
+
*
|
|
678
|
+
* The default implementation uses Segment.
|
|
679
|
+
*/
|
|
680
|
+
page(options) {
|
|
681
|
+
this._telemetry?.page(options);
|
|
682
|
+
}
|
|
683
|
+
//
|
|
684
|
+
// Error Logs
|
|
685
|
+
//
|
|
686
|
+
_initErrorLogs() {
|
|
687
|
+
if (this._secrets.SENTRY_DESTINATION && this._mode !== "disabled") {
|
|
688
|
+
log2.info("Initializing Sentry", {
|
|
689
|
+
dest: this._secrets.SENTRY_DESTINATION,
|
|
690
|
+
options: this._errorReportingOptions
|
|
691
|
+
}, {
|
|
692
|
+
F: __dxlog_file2,
|
|
693
|
+
L: 407,
|
|
694
|
+
S: this,
|
|
695
|
+
C: (f, a) => f(...a)
|
|
696
|
+
});
|
|
697
|
+
init({
|
|
698
|
+
...this._errorReportingOptions,
|
|
699
|
+
destination: this._secrets.SENTRY_DESTINATION,
|
|
700
|
+
scrubFilenames: this._mode !== "full"
|
|
701
|
+
});
|
|
702
|
+
if (this._errorReportingOptions?.tracing) {
|
|
703
|
+
configureTracing();
|
|
704
|
+
}
|
|
705
|
+
if (this._errorLogProcessor) {
|
|
706
|
+
enableSentryLogProcessor();
|
|
707
|
+
}
|
|
708
|
+
this._tags.forEach((v, k) => {
|
|
709
|
+
if (ERROR_TAGS.includes(k)) {
|
|
710
|
+
setTag(k, v);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
} else {
|
|
714
|
+
log2("sentry disabled", void 0, {
|
|
715
|
+
F: __dxlog_file2,
|
|
716
|
+
L: 430,
|
|
717
|
+
S: this,
|
|
718
|
+
C: (f, a) => f(...a)
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Manually capture an exception.
|
|
724
|
+
*
|
|
725
|
+
* The default implementation uses Sentry.
|
|
726
|
+
*/
|
|
727
|
+
captureException(err) {
|
|
728
|
+
if (this.enabled) {
|
|
729
|
+
captureException(err);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
export {
|
|
735
|
+
Observability,
|
|
736
|
+
BASE_TELEMETRY_PROPERTIES,
|
|
737
|
+
OBSERVABILITY_DISABLED_KEY,
|
|
738
|
+
OBSERVABILITY_GROUP_KEY,
|
|
739
|
+
getTelemetryIdentifier,
|
|
740
|
+
isObservabilityDisabled,
|
|
741
|
+
storeObservabilityDisabled,
|
|
742
|
+
getObservabilityGroup,
|
|
743
|
+
storeObservabilityGroup,
|
|
744
|
+
initializeAppObservability,
|
|
745
|
+
mapSpaces,
|
|
746
|
+
setupTelemetryListeners
|
|
747
|
+
};
|
|
748
|
+
//# sourceMappingURL=chunk-STWCJK52.mjs.map
|