@dxos/observability 0.8.4-main.ef1bc66f44 → 0.8.4-main.f466a3d56e
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/LICENSE +102 -5
- package/README.md +14 -11
- package/dist/lib/browser/{chunk-K4VFBKST.mjs → chunk-PSFTIG54.mjs} +9 -2
- package/dist/lib/browser/chunk-PSFTIG54.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +181 -283
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/{log-processor-FDLTDQEM.mjs → log-processor-HPHWNBOK.mjs} +11 -18
- package/dist/lib/browser/log-processor-HPHWNBOK.mjs.map +7 -0
- package/dist/lib/browser/{logs-ATTRIUTL.mjs → logs-UTNIFYHF.mjs} +7 -4
- package/dist/lib/browser/logs-UTNIFYHF.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{metrics-PKTV6IGF.mjs → metrics-PRGSYAZJ.mjs} +7 -21
- package/dist/lib/browser/metrics-PRGSYAZJ.mjs.map +7 -0
- package/dist/lib/browser/traces-browser-XRINKQUA.mjs +154 -0
- package/dist/lib/browser/traces-browser-XRINKQUA.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-FEVP3MK4.mjs → chunk-EDDZWPYV.mjs} +9 -2
- package/dist/lib/node-esm/chunk-EDDZWPYV.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +166 -250
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/{log-processor-TKJVJJSJ.mjs → log-processor-ZODHERST.mjs} +11 -18
- package/dist/lib/node-esm/log-processor-ZODHERST.mjs.map +7 -0
- package/dist/lib/node-esm/{logs-7J45KLM7.mjs → logs-ARJUVN3T.mjs} +7 -4
- package/dist/lib/node-esm/logs-ARJUVN3T.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{metrics-H7DDLYSG.mjs → metrics-DREJOOAC.mjs} +7 -21
- package/dist/lib/node-esm/metrics-DREJOOAC.mjs.map +7 -0
- package/dist/lib/node-esm/traces-74F7JUKF.mjs +125 -0
- package/dist/lib/node-esm/traces-74F7JUKF.mjs.map +7 -0
- package/dist/types/src/cli-observability-secrets.json +2 -2
- package/dist/types/src/extensions/otel/extension.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/extension.js +112 -16
- package/dist/types/src/extensions/otel/extension.js.map +1 -1
- package/dist/types/src/extensions/otel/logs.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/logs.js +6 -3
- package/dist/types/src/extensions/otel/logs.js.map +1 -1
- package/dist/types/src/extensions/otel/metrics.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/metrics.js +2 -2
- package/dist/types/src/extensions/otel/metrics.js.map +1 -1
- package/dist/types/src/extensions/otel/otel.d.ts +7 -0
- package/dist/types/src/extensions/otel/otel.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/otel.js +12 -0
- package/dist/types/src/extensions/otel/otel.js.map +1 -1
- package/dist/types/src/extensions/otel/span-processors.d.ts +25 -0
- package/dist/types/src/extensions/otel/span-processors.d.ts.map +1 -0
- package/dist/types/src/extensions/otel/span-processors.js +41 -0
- package/dist/types/src/extensions/otel/span-processors.js.map +1 -0
- package/dist/types/src/extensions/otel/traces-browser.d.ts +17 -0
- package/dist/types/src/extensions/otel/traces-browser.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/traces-browser.js +73 -12
- package/dist/types/src/extensions/otel/traces-browser.js.map +1 -1
- package/dist/types/src/extensions/otel/traces.d.ts +17 -0
- package/dist/types/src/extensions/otel/traces.d.ts.map +1 -1
- package/dist/types/src/extensions/otel/traces.js +59 -9
- package/dist/types/src/extensions/otel/traces.js.map +1 -1
- package/dist/types/src/extensions/posthog/extension.d.ts +13 -0
- package/dist/types/src/extensions/posthog/extension.d.ts.map +1 -1
- package/dist/types/src/extensions/posthog/extension.js +41 -28
- package/dist/types/src/extensions/posthog/extension.js.map +1 -1
- package/dist/types/src/extensions/posthog/log-processor.d.ts.map +1 -1
- package/dist/types/src/extensions/posthog/log-processor.js +10 -19
- package/dist/types/src/extensions/posthog/log-processor.js.map +1 -1
- package/dist/types/src/extensions/posthog/log-processor.test.js +2 -2
- package/dist/types/src/extensions/posthog/log-processor.test.js.map +1 -1
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/index.js +0 -1
- package/dist/types/src/index.js.map +1 -1
- package/dist/types/src/observability-extension.d.ts +1 -1
- package/dist/types/src/observability-extension.d.ts.map +1 -1
- package/dist/types/src/observability.d.ts.map +1 -1
- package/dist/types/src/observability.js +4 -2
- package/dist/types/src/observability.js.map +1 -1
- package/dist/types/src/observability.test.js +1 -1
- package/dist/types/src/observability.test.js.map +1 -1
- package/dist/types/src/providers/client-observability.d.ts.map +1 -1
- package/dist/types/src/providers/client-observability.js +2 -0
- package/dist/types/src/providers/client-observability.js.map +1 -1
- package/dist/types/src/providers/ip-data.d.ts.map +1 -1
- package/dist/types/src/providers/ip-data.js +4 -2
- package/dist/types/src/providers/ip-data.js.map +1 -1
- package/dist/types/src/storage/browser.d.ts.map +1 -1
- package/dist/types/src/storage/browser.js +15 -4
- package/dist/types/src/storage/browser.js.map +1 -1
- package/dist/types/src/storage/node.d.ts.map +1 -1
- package/dist/types/src/storage/node.js +1 -1
- package/dist/types/src/storage/node.js.map +1 -1
- package/dist/types/src/storage/node.test.js +1 -1
- package/dist/types/src/storage/node.test.js.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +36 -32
- package/src/cli-observability-secrets.json +2 -2
- package/src/extensions/otel/extension.ts +118 -17
- package/src/extensions/otel/logs.ts +6 -3
- package/src/extensions/otel/metrics.ts +2 -2
- package/src/extensions/otel/otel.ts +13 -0
- package/src/extensions/otel/span-processors.ts +45 -0
- package/src/extensions/otel/traces-browser.ts +95 -13
- package/src/extensions/otel/traces.ts +76 -12
- package/src/extensions/posthog/extension.ts +54 -27
- package/src/extensions/posthog/log-processor.test.ts +7 -6
- package/src/extensions/posthog/log-processor.ts +10 -21
- package/src/index.ts +0 -1
- package/src/observability-extension.ts +1 -1
- package/src/observability.test.ts +1 -1
- package/src/observability.ts +4 -2
- package/src/providers/client-observability.ts +2 -0
- package/src/providers/ip-data.ts +5 -2
- package/src/storage/browser.ts +14 -4
- package/src/storage/node.test.ts +1 -2
- package/src/storage/node.ts +1 -2
- package/src/vite-import-meta.d.ts +14 -0
- package/dist/lib/browser/chunk-K4VFBKST.mjs.map +0 -7
- package/dist/lib/browser/log-processor-FDLTDQEM.mjs.map +0 -7
- package/dist/lib/browser/logs-ATTRIUTL.mjs.map +0 -7
- package/dist/lib/browser/metrics-PKTV6IGF.mjs.map +0 -7
- package/dist/lib/browser/traces-browser-XYXBF5ZX.mjs +0 -62
- package/dist/lib/browser/traces-browser-XYXBF5ZX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FEVP3MK4.mjs.map +0 -7
- package/dist/lib/node-esm/log-processor-TKJVJJSJ.mjs.map +0 -7
- package/dist/lib/node-esm/logs-7J45KLM7.mjs.map +0 -7
- package/dist/lib/node-esm/metrics-H7DDLYSG.mjs.map +0 -7
- package/dist/lib/node-esm/traces-KMTHMYFX.mjs +0 -44
- package/dist/lib/node-esm/traces-KMTHMYFX.mjs.map +0 -7
- package/dist/types/src/log-buffer.d.ts +0 -34
- package/dist/types/src/log-buffer.d.ts.map +0 -1
- package/dist/types/src/log-buffer.js +0 -70
- package/dist/types/src/log-buffer.js.map +0 -1
- package/dist/types/src/log-buffer.test.d.ts +0 -2
- package/dist/types/src/log-buffer.test.d.ts.map +0 -1
- package/dist/types/src/log-buffer.test.js +0 -107
- package/dist/types/src/log-buffer.test.js.map +0 -1
- package/src/log-buffer.test.ts +0 -134
- package/src/log-buffer.ts +0 -101
|
@@ -2,48 +2,112 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ROOT_CONTEXT,
|
|
7
|
+
SpanStatusCode,
|
|
8
|
+
type Tracer,
|
|
9
|
+
context as otelContext,
|
|
10
|
+
propagation,
|
|
11
|
+
trace,
|
|
12
|
+
} from '@opentelemetry/api';
|
|
13
|
+
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
6
14
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
7
15
|
import { BasicTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
8
16
|
import { ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
9
|
-
import { log } from 'debug';
|
|
10
17
|
|
|
11
|
-
import {
|
|
18
|
+
import { log } from '@dxos/log';
|
|
19
|
+
import { type RemoteSpan, type StartSpanOptions, TRACE_PROCESSOR } from '@dxos/tracing';
|
|
12
20
|
|
|
13
|
-
import { type OtelOptions } from './otel';
|
|
21
|
+
import { type OtelOptions, resolveOtlpUrl } from './otel';
|
|
22
|
+
import { TagInjectorSpanProcessor } from './span-processors';
|
|
14
23
|
|
|
15
24
|
export class OtelTraces {
|
|
16
25
|
private _tracer: Tracer;
|
|
26
|
+
private readonly _tracerProvider: BasicTracerProvider;
|
|
17
27
|
|
|
18
28
|
constructor(private readonly options: OtelOptions) {
|
|
19
|
-
|
|
29
|
+
propagation.setGlobalPropagator(new W3CTraceContextPropagator());
|
|
30
|
+
|
|
31
|
+
this._tracerProvider = new BasicTracerProvider({
|
|
20
32
|
resource: this.options.resource,
|
|
21
33
|
spanProcessors: [
|
|
34
|
+
new TagInjectorSpanProcessor(this.options.getTags),
|
|
22
35
|
new BatchSpanProcessor(
|
|
23
36
|
new OTLPTraceExporter({
|
|
24
|
-
url: this.options.endpoint + '/v1/traces',
|
|
37
|
+
url: resolveOtlpUrl(this.options.endpoint + '/v1/traces'),
|
|
25
38
|
headers: this.options.headers,
|
|
26
|
-
concurrencyLimit: 10,
|
|
39
|
+
concurrencyLimit: 10,
|
|
27
40
|
}),
|
|
28
41
|
),
|
|
29
42
|
],
|
|
30
43
|
});
|
|
31
44
|
|
|
32
|
-
trace.setGlobalTracerProvider(
|
|
45
|
+
trace.setGlobalTracerProvider(this._tracerProvider);
|
|
33
46
|
this._tracer = trace.getTracer(
|
|
34
47
|
'dxos-observability',
|
|
35
48
|
this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString(),
|
|
36
49
|
);
|
|
37
50
|
}
|
|
38
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Forcibly flush the BatchSpanProcessor. Call before process exit to avoid
|
|
54
|
+
* losing queued spans (which manifests as "Missing Span" in SigNoz — their
|
|
55
|
+
* already-exported children reference a parent that never made it to OTLP).
|
|
56
|
+
*/
|
|
57
|
+
public async flush(): Promise<void> {
|
|
58
|
+
await this._tracerProvider.forceFlush();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Flush + shut down the tracer provider via `BasicTracerProvider.shutdown()`,
|
|
63
|
+
* which forces a final export then terminates all span processors.
|
|
64
|
+
*
|
|
65
|
+
* Terminal and effectively one-shot: safe to call after `flush()`, but
|
|
66
|
+
* `flush()` MUST NOT be called after `close()` — shutdown stops further
|
|
67
|
+
* exporting, so subsequent `close()`/`flush()` calls resolve without
|
|
68
|
+
* emitting new spans.
|
|
69
|
+
*/
|
|
70
|
+
public async close(): Promise<void> {
|
|
71
|
+
await this._tracerProvider.shutdown();
|
|
72
|
+
}
|
|
73
|
+
|
|
39
74
|
public start(): void {
|
|
40
75
|
log('trace processor registered');
|
|
41
76
|
|
|
42
|
-
|
|
43
|
-
|
|
77
|
+
const tracer = this._tracer;
|
|
78
|
+
|
|
79
|
+
TRACE_PROCESSOR.tracingBackend = {
|
|
80
|
+
startSpan: (options: StartSpanOptions): RemoteSpan => {
|
|
44
81
|
log('begin otel trace', { options });
|
|
45
|
-
|
|
82
|
+
const parentCtx = options.parentContext
|
|
83
|
+
? propagation.extract(ROOT_CONTEXT, {
|
|
84
|
+
traceparent: options.parentContext.traceparent,
|
|
85
|
+
tracestate: options.parentContext.tracestate ?? '',
|
|
86
|
+
})
|
|
87
|
+
: otelContext.active();
|
|
88
|
+
|
|
89
|
+
const span = tracer.startSpan(options.name, options, parentCtx);
|
|
90
|
+
|
|
91
|
+
const sc = span.spanContext();
|
|
92
|
+
const spanContext =
|
|
93
|
+
sc && sc.traceId && sc.spanId
|
|
94
|
+
? {
|
|
95
|
+
traceparent: `00-${sc.traceId}-${sc.spanId}-${(sc.traceFlags ?? 0).toString(16).padStart(2, '0')}`,
|
|
96
|
+
tracestate: sc.traceState?.serialize(),
|
|
97
|
+
}
|
|
98
|
+
: undefined;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
end: (endTime?: number) => span.end(endTime),
|
|
102
|
+
setError: (err: unknown) => {
|
|
103
|
+
if (err instanceof Error) {
|
|
104
|
+
span.recordException(err);
|
|
105
|
+
}
|
|
106
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });
|
|
107
|
+
},
|
|
108
|
+
spanContext,
|
|
109
|
+
};
|
|
46
110
|
},
|
|
47
|
-
}
|
|
111
|
+
};
|
|
48
112
|
}
|
|
49
113
|
}
|
|
@@ -7,8 +7,8 @@ import { type PostHogConfig } from 'posthog-js';
|
|
|
7
7
|
|
|
8
8
|
import { type Config } from '@dxos/config';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
|
+
import { type IdbLogStore } from '@dxos/log-store-idb';
|
|
10
11
|
|
|
11
|
-
import { LogBuffer } from '../../log-buffer';
|
|
12
12
|
import { type Extension } from '../../observability-extension';
|
|
13
13
|
import { stubExtension } from '../stub';
|
|
14
14
|
|
|
@@ -19,6 +19,18 @@ export type ExtensionsOptions = {
|
|
|
19
19
|
/** Deployment environment, e.g. `production` or `staging`. */
|
|
20
20
|
environment?: string;
|
|
21
21
|
posthog?: Partial<PostHogConfig>;
|
|
22
|
+
/**
|
|
23
|
+
* Shared persistent log store for debug log dumps.
|
|
24
|
+
* The owning app is expected to register `logStore.processor` with `log` itself —
|
|
25
|
+
* this extension only consumes the buffered logs (via `export()`).
|
|
26
|
+
*/
|
|
27
|
+
logStore?: IdbLogStore;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum byte size passed to `logStore.export()` when uploading feedback logs.
|
|
30
|
+
* Should match the upload limit enforced by the server receiving the logs.
|
|
31
|
+
* When omitted the full store is exported without trimming.
|
|
32
|
+
*/
|
|
33
|
+
feedbackLogMaxSize?: number;
|
|
22
34
|
};
|
|
23
35
|
|
|
24
36
|
/** Upload serialized logs to the feedback-logs endpoint. Returns the R2 key on success. */
|
|
@@ -47,6 +59,8 @@ export const extensions: (options: ExtensionsOptions) => Effect.Effect<Extension
|
|
|
47
59
|
release,
|
|
48
60
|
environment,
|
|
49
61
|
posthog: posthogConfig,
|
|
62
|
+
logStore,
|
|
63
|
+
feedbackLogMaxSize,
|
|
50
64
|
}) {
|
|
51
65
|
if (typeof window === 'undefined') {
|
|
52
66
|
log('PostHog is being stubbed because it is running in a worker.');
|
|
@@ -63,8 +77,8 @@ export const extensions: (options: ExtensionsOptions) => Effect.Effect<Extension
|
|
|
63
77
|
|
|
64
78
|
const { default: posthog } = yield* Effect.promise(() => import('posthog-js'));
|
|
65
79
|
const { logProcessor } = yield* Effect.promise(() => import('./log-processor'));
|
|
66
|
-
const logBuffer = new LogBuffer();
|
|
67
80
|
let feedbackSurveyAvailable: boolean | null = null;
|
|
81
|
+
let unregisterPosthogProcessors: (() => void) | undefined;
|
|
68
82
|
|
|
69
83
|
const checkFeedbackSurveyAvailable = (): Effect.Effect<boolean> =>
|
|
70
84
|
feedbackSurveyId
|
|
@@ -98,17 +112,16 @@ export const extensions: (options: ExtensionsOptions) => Effect.Effect<Extension
|
|
|
98
112
|
...(environment ? { environment } : {}),
|
|
99
113
|
});
|
|
100
114
|
}
|
|
101
|
-
|
|
102
|
-
log.
|
|
115
|
+
unregisterPosthogProcessors?.();
|
|
116
|
+
const removePosthogLog = log.addProcessor(logProcessor);
|
|
117
|
+
unregisterPosthogProcessors = () => {
|
|
118
|
+
removePosthogLog();
|
|
119
|
+
};
|
|
103
120
|
}),
|
|
104
121
|
close: () =>
|
|
105
122
|
Effect.sync(() => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (index !== -1) {
|
|
109
|
-
log.runtimeConfig.processors.splice(index, 1);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
123
|
+
unregisterPosthogProcessors?.();
|
|
124
|
+
unregisterPosthogProcessors = undefined;
|
|
112
125
|
}),
|
|
113
126
|
enable: () => Effect.sync(() => posthog.opt_in_capturing()),
|
|
114
127
|
disable: () => Effect.sync(() => posthog.opt_out_capturing()),
|
|
@@ -143,25 +156,39 @@ export const extensions: (options: ExtensionsOptions) => Effect.Effect<Extension
|
|
|
143
156
|
kind: 'feedback',
|
|
144
157
|
// TODO(wittjosiah): Support custom surveys.
|
|
145
158
|
captureUserFeedback: (form) => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
return new Promise<string | undefined>((resolve, reject) => {
|
|
160
|
+
posthog.getSurveys((surveys) => {
|
|
161
|
+
void (async () => {
|
|
162
|
+
try {
|
|
163
|
+
const survey = surveys.find((survey) => survey.id === feedbackSurveyId);
|
|
164
|
+
if (!survey || survey.questions.length === 0) {
|
|
165
|
+
log.error('Missing feedback survey or survey has no questions', { feedbackSurveyId });
|
|
166
|
+
resolve(undefined);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
let debugLogDumpKey: string | null = null;
|
|
171
|
+
if (form.includeLogs !== false && logStore !== undefined) {
|
|
172
|
+
const ndjson = await logStore.export({ maxSize: feedbackLogMaxSize });
|
|
173
|
+
if (ndjson.length > 0) {
|
|
174
|
+
debugLogDumpKey = (await uploadLogs(ndjson)) ?? 'failed';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
157
177
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
// https://posthog.com/docs/surveys/implementing-custom-surveys
|
|
179
|
+
const question = survey.questions[0];
|
|
180
|
+
const result = posthog.capture('survey sent', {
|
|
181
|
+
$survey_id: survey.id,
|
|
182
|
+
$survey_questions: [{ id: question.id, question: question.question }],
|
|
183
|
+
[`$survey_response_${question.id}`]: form.message,
|
|
184
|
+
debug_log_dump_key: debugLogDumpKey,
|
|
185
|
+
});
|
|
186
|
+
resolve(result?.uuid);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
log.error('Failed to capture user feedback', { err });
|
|
189
|
+
reject(err);
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
165
192
|
});
|
|
166
193
|
});
|
|
167
194
|
},
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { InvariantViolation } from '@dxos/invariant';
|
|
8
|
-
import { type LogConfig, type
|
|
8
|
+
import { type LogConfig, LogEntry, type LogEntryInit, LogLevel } from '@dxos/log';
|
|
9
9
|
|
|
10
10
|
// Replace the posthog-js module with a stub so log-processor's top-level import resolves to our mock.
|
|
11
11
|
// Dynamic imports are used so that both the mock and log-processor are loaded after vi.mock is hoisted.
|
|
@@ -23,11 +23,12 @@ const baseConfig: LogConfig = {
|
|
|
23
23
|
processors: [],
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const createEntry = (overrides: Partial<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const createEntry = (overrides: Partial<LogEntryInit> = {}): LogEntry =>
|
|
27
|
+
new LogEntry({
|
|
28
|
+
level: LogLevel.ERROR,
|
|
29
|
+
message: 'test error',
|
|
30
|
+
...overrides,
|
|
31
|
+
});
|
|
31
32
|
|
|
32
33
|
describe('logProcessor', () => {
|
|
33
34
|
beforeEach(() => {
|
|
@@ -23,15 +23,16 @@ export const logProcessor: LogProcessor = (config: LogConfig, entry: LogEntry) =
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const additionalProperties: Record<string, string | boolean | number> = {};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
const { filename, line } = entry.computedMeta;
|
|
27
|
+
if (filename !== undefined && line !== undefined) {
|
|
28
|
+
additionalProperties.transaction = `${filename}:${line}`;
|
|
29
|
+
}
|
|
30
|
+
if (entry.meta?.S?.hostSessionId) {
|
|
31
|
+
additionalProperties.service_host_issue = true;
|
|
32
|
+
additionalProperties.service_host_session = entry.meta.S.hostSessionId;
|
|
33
|
+
}
|
|
34
|
+
if (entry.meta?.S?.uptimeSeconds != null) {
|
|
35
|
+
additionalProperties.uptime_seconds = entry.meta.S.uptimeSeconds;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
if (capturedError instanceof InvariantViolation) {
|
|
@@ -40,15 +41,3 @@ export const logProcessor: LogProcessor = (config: LogConfig, entry: LogEntry) =
|
|
|
40
41
|
|
|
41
42
|
posthog.captureException(capturedError, additionalProperties);
|
|
42
43
|
};
|
|
43
|
-
|
|
44
|
-
const getRelativeFilename = (filename: string) => {
|
|
45
|
-
// TODO(burdon): Hack uses "packages" as an anchor (pre-parse NX?)
|
|
46
|
-
// Including `packages/` part of the path so that excluded paths (e.g. from dist) are clickable in vscode.
|
|
47
|
-
const match = filename.match(/.+\/(packages\/.+\/.+)/);
|
|
48
|
-
if (match) {
|
|
49
|
-
const [, filePath] = match;
|
|
50
|
-
return filePath;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return filename;
|
|
54
|
-
};
|
package/src/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ export type Events = {
|
|
|
53
53
|
* Feedback extension API (kind-specific methods only).
|
|
54
54
|
*/
|
|
55
55
|
export type Feedback = {
|
|
56
|
-
captureUserFeedback(form: FeedbackForm):
|
|
56
|
+
captureUserFeedback(form: FeedbackForm): Promise<string | undefined>;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
export type ExtensionApi =
|
|
@@ -363,7 +363,7 @@ describe('Observability', () => {
|
|
|
363
363
|
Observability.addExtension(Effect.succeed(feedbackExt)),
|
|
364
364
|
Observability.initialize,
|
|
365
365
|
);
|
|
366
|
-
obs.feedback.captureUserFeedback({ message: 'great app' });
|
|
366
|
+
void obs.feedback.captureUserFeedback({ message: 'great app' });
|
|
367
367
|
expect(captureUserFeedback).toHaveBeenCalledWith({ message: 'great app' });
|
|
368
368
|
}),
|
|
369
369
|
);
|
package/src/observability.ts
CHANGED
|
@@ -214,10 +214,12 @@ class ObservabilityImpl implements Observability {
|
|
|
214
214
|
|
|
215
215
|
get feedback(): Feedback {
|
|
216
216
|
return {
|
|
217
|
-
captureUserFeedback: (form) => {
|
|
217
|
+
captureUserFeedback: async (form) => {
|
|
218
|
+
let eventUuid: string | undefined;
|
|
218
219
|
for (const extension of this._getExtensions('feedback')) {
|
|
219
|
-
extension.captureUserFeedback(form);
|
|
220
|
+
eventUuid = (await extension.captureUserFeedback(form)) ?? eventUuid;
|
|
220
221
|
}
|
|
222
|
+
return eventUuid;
|
|
221
223
|
},
|
|
222
224
|
};
|
|
223
225
|
}
|
|
@@ -119,7 +119,9 @@ export const networkMetricsProvider = (clientServices: Partial<ClientServices>):
|
|
|
119
119
|
export const runtimeMetricsProvider = (clientServices: Partial<ClientServices>): DataProvider =>
|
|
120
120
|
Effect.fn(function* (observability) {
|
|
121
121
|
const ctx = new Context();
|
|
122
|
+
log('runtimeMetricsProvider: requesting platform from SystemService');
|
|
122
123
|
const platform = yield* Effect.promise(() => clientServices.SystemService!.getPlatform());
|
|
124
|
+
log('runtimeMetricsProvider: platform received');
|
|
123
125
|
invariant(platform, 'platform is required');
|
|
124
126
|
|
|
125
127
|
observability.setTags({
|
package/src/providers/ip-data.ts
CHANGED
|
@@ -34,6 +34,9 @@ type CachedIPData = {
|
|
|
34
34
|
const getIPData = Effect.fn(function* (config: Config) {
|
|
35
35
|
const httpClient = yield* HttpClient.HttpClient;
|
|
36
36
|
|
|
37
|
+
// Disable tracing to avoid CORS errors from traceparent header on cross-origin requests.
|
|
38
|
+
const httpClientNoTrace = httpClient.pipe(HttpClient.withTracerDisabledWhen(() => true));
|
|
39
|
+
|
|
37
40
|
// Check cache first.
|
|
38
41
|
const cachedData = yield* Effect.promise(() => localForage.getItem<CachedIPData>('dxos:observability:ipdata'));
|
|
39
42
|
if (cachedData && cachedData.timestamp > Date.now() - IP_DATA_CACHE_TIMEOUT) {
|
|
@@ -44,7 +47,7 @@ const getIPData = Effect.fn(function* (config: Config) {
|
|
|
44
47
|
const IPDATA_API_KEY = config.get('runtime.app.env.DX_IPDATA_API_KEY');
|
|
45
48
|
if (IPDATA_API_KEY) {
|
|
46
49
|
const data = yield* HttpClientRequest.get(`https://api.ipdata.co?api-key=${IPDATA_API_KEY}`).pipe(
|
|
47
|
-
|
|
50
|
+
httpClientNoTrace.execute,
|
|
48
51
|
Effect.flatMap((res) => res.json),
|
|
49
52
|
Effect.flatMap(Schema.decodeUnknown(IPData)),
|
|
50
53
|
);
|
|
@@ -82,7 +85,7 @@ export const provider =
|
|
|
82
85
|
Effect.provide(FetchHttpClient.layer),
|
|
83
86
|
Effect.catchAll((err) =>
|
|
84
87
|
Effect.sync(() => {
|
|
85
|
-
log.
|
|
88
|
+
log.verbose('ipdata fetch failed', { err });
|
|
86
89
|
}),
|
|
87
90
|
),
|
|
88
91
|
);
|
package/src/storage/browser.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import * as localForage from 'localforage';
|
|
7
7
|
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
|
+
import { compositeKey } from '@dxos/util';
|
|
9
10
|
|
|
10
11
|
const OBSERVABILITY_DISABLED_KEY = 'observability-disabled';
|
|
11
12
|
const OBSERVABILITY_GROUP_KEY = 'observability-group';
|
|
@@ -20,7 +21,7 @@ export const showObservabilityBanner = () => {
|
|
|
20
21
|
*/
|
|
21
22
|
export const isObservabilityDisabled = async (namespace: string): Promise<boolean> => {
|
|
22
23
|
try {
|
|
23
|
-
return (await localForage.getItem(
|
|
24
|
+
return (await localForage.getItem(compositeKey(namespace, OBSERVABILITY_DISABLED_KEY))) === 'true';
|
|
24
25
|
} catch (err) {
|
|
25
26
|
log.catch('Failed to check if observability is disabled, assuming it is', err);
|
|
26
27
|
return true;
|
|
@@ -32,10 +33,19 @@ export const isObservabilityDisabled = async (namespace: string): Promise<boolea
|
|
|
32
33
|
*/
|
|
33
34
|
export const storeObservabilityDisabled = async (namespace: string, value: boolean) => {
|
|
34
35
|
try {
|
|
35
|
-
await localForage.setItem(
|
|
36
|
+
await localForage.setItem(compositeKey(namespace, OBSERVABILITY_DISABLED_KEY), String(value));
|
|
36
37
|
} catch (err) {
|
|
37
38
|
log.catch('Failed to store observability disabled', err);
|
|
38
39
|
}
|
|
40
|
+
// Mirror to localStorage so the synchronous opt-out check in the OTEL extension picks it up
|
|
41
|
+
// without waiting for an async IndexedDB read.
|
|
42
|
+
try {
|
|
43
|
+
if (typeof localStorage !== 'undefined') {
|
|
44
|
+
localStorage.setItem(`${namespace}/${OBSERVABILITY_DISABLED_KEY}`, String(value));
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// localStorage not available (e.g., in workers).
|
|
48
|
+
}
|
|
39
49
|
};
|
|
40
50
|
|
|
41
51
|
/**
|
|
@@ -43,7 +53,7 @@ export const storeObservabilityDisabled = async (namespace: string, value: boole
|
|
|
43
53
|
*/
|
|
44
54
|
export const getObservabilityGroup = async (namespace: string): Promise<string | undefined> => {
|
|
45
55
|
try {
|
|
46
|
-
return (await localForage.getItem(
|
|
56
|
+
return (await localForage.getItem(compositeKey(namespace, OBSERVABILITY_GROUP_KEY))) ?? undefined;
|
|
47
57
|
} catch (err) {
|
|
48
58
|
log.catch('Failed to get observability group', err);
|
|
49
59
|
}
|
|
@@ -54,7 +64,7 @@ export const getObservabilityGroup = async (namespace: string): Promise<string |
|
|
|
54
64
|
*/
|
|
55
65
|
export const storeObservabilityGroup = async (namespace: string, value: string) => {
|
|
56
66
|
try {
|
|
57
|
-
await localForage.setItem(
|
|
67
|
+
await localForage.setItem(compositeKey(namespace, OBSERVABILITY_GROUP_KEY), value);
|
|
58
68
|
} catch (err) {
|
|
59
69
|
log.catch('Failed to store observability group', err);
|
|
60
70
|
}
|
package/src/storage/node.test.ts
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import yaml from 'js-yaml';
|
|
5
6
|
import { existsSync } from 'node:fs';
|
|
6
7
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
7
8
|
import { tmpdir } from 'node:os';
|
|
8
9
|
import { join } from 'node:path';
|
|
9
|
-
|
|
10
|
-
import yaml from 'js-yaml';
|
|
11
10
|
import { validate as validateUuid } from 'uuid';
|
|
12
11
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
13
12
|
|
package/src/storage/node.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import yaml from 'js-yaml';
|
|
1
2
|
//
|
|
2
3
|
// Copyright 2022 DXOS.org
|
|
3
4
|
//
|
|
4
5
|
import { existsSync, statSync } from 'node:fs';
|
|
5
6
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
6
7
|
import { join } from 'node:path';
|
|
7
|
-
|
|
8
|
-
import yaml from 'js-yaml';
|
|
9
8
|
import { v4 as uuid, validate as validateUuid } from 'uuid';
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vite replaces `import.meta.env` at build time. Aligns with `vite/client` without taking a Vite dependency.
|
|
7
|
+
* @see https://vitejs.dev/guide/env-and-mode.html
|
|
8
|
+
*/
|
|
9
|
+
interface ImportMetaEnv {
|
|
10
|
+
readonly DEV: boolean;
|
|
11
|
+
readonly PROD: boolean;
|
|
12
|
+
readonly MODE: string;
|
|
13
|
+
readonly SSR: boolean;
|
|
14
|
+
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/extensions/otel/otel.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport { type Resource } from '@opentelemetry/resources';\n\nexport type OtelOptions = {\n endpoint: string;\n headers: Record<string, string>;\n resource: Resource;\n getTags: () => { [key: string]: string };\n consoleDiagLogLevel?: string;\n};\n\nexport const setDiagLogger = (level?: string) => {\n const logLevel = DiagLogLevel[level as keyof typeof DiagLogLevel];\n if (logLevel) {\n diag.setLogger(new DiagConsoleLogger(), logLevel);\n }\n};\n"],
|
|
5
|
-
"mappings": ";AAIA,SAASA,mBAAmBC,cAAcC,YAAY;AAW/C,IAAMC,gBAAgB,CAACC,UAAAA;AAC5B,QAAMC,WAAWC,aAAaF,KAAAA;AAC9B,MAAIC,UAAU;AACZE,SAAKC,UAAU,IAAIC,kBAAAA,GAAqBJ,QAAAA;EAC1C;AACF;",
|
|
6
|
-
"names": ["DiagConsoleLogger", "DiagLogLevel", "diag", "setDiagLogger", "level", "logLevel", "DiagLogLevel", "diag", "setLogger", "DiagConsoleLogger"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/extensions/posthog/log-processor.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport posthog from 'posthog-js';\n\nimport { InvariantViolation } from '@dxos/invariant';\nimport { type LogConfig, type LogEntry, LogLevel, type LogProcessor, shouldLog } from '@dxos/log';\n\nexport const logProcessor: LogProcessor = (config: LogConfig, entry: LogEntry) => {\n // Don't forward logs from remote sessions.\n if (!shouldLog(entry, config.captureFilters) || entry.meta?.S?.remoteSessionId) {\n return;\n }\n\n let capturedError = entry.error;\n if (capturedError == null && entry.level === LogLevel.ERROR) {\n capturedError = Object.values(entry.context ?? {}).find((v): v is Error => v instanceof Error);\n }\n\n if (!capturedError) {\n return;\n }\n\n const additionalProperties: Record<string, string | boolean | number> = {};\n if (entry.meta) {\n additionalProperties.transaction = `${getRelativeFilename(entry.meta.F)}:${entry.meta.L}`;\n if (entry.meta.S?.hostSessionId) {\n additionalProperties.service_host_issue = true;\n additionalProperties.service_host_session = entry.meta.S?.hostSessionId;\n }\n if (entry.meta.S?.uptimeSeconds != null) {\n additionalProperties.uptime_seconds = entry.meta.S.uptimeSeconds;\n }\n }\n\n if (capturedError instanceof InvariantViolation) {\n additionalProperties.invariant_violation = true;\n }\n\n posthog.captureException(capturedError, additionalProperties);\n};\n\nconst getRelativeFilename = (filename: string) => {\n // TODO(burdon): Hack uses \"packages\" as an anchor (pre-parse NX?)\n // Including `packages/` part of the path so that excluded paths (e.g. from dist) are clickable in vscode.\n const match = filename.match(/.+\\/(packages\\/.+\\/.+)/);\n if (match) {\n const [, filePath] = match;\n return filePath;\n }\n\n return filename;\n};\n"],
|
|
5
|
-
"mappings": ";;;AAIA,OAAOA,aAAa;AAEpB,SAASC,0BAA0B;AACnC,SAAwCC,UAA6BC,iBAAiB;AAE/E,IAAMC,eAA6B,CAACC,QAAmBC,UAAAA;AAE5D,MAAI,CAACC,UAAUD,OAAOD,OAAOG,cAAc,KAAKF,MAAMG,MAAMC,GAAGC,iBAAiB;AAC9E;EACF;AAEA,MAAIC,gBAAgBN,MAAMO;AAC1B,MAAID,iBAAiB,QAAQN,MAAMQ,UAAUC,SAASC,OAAO;AAC3DJ,oBAAgBK,OAAOC,OAAOZ,MAAMa,WAAW,CAAC,CAAA,EAAGC,KAAK,CAACC,MAAkBA,aAAaC,KAAAA;EAC1F;AAEA,MAAI,CAACV,eAAe;AAClB;EACF;AAEA,QAAMW,uBAAkE,CAAC;AACzE,MAAIjB,MAAMG,MAAM;AACdc,yBAAqBC,cAAc,GAAGC,oBAAoBnB,MAAMG,KAAKiB,CAAC,CAAA,IAAKpB,MAAMG,KAAKkB,CAAC;AACvF,QAAIrB,MAAMG,KAAKC,GAAGkB,eAAe;AAC/BL,2BAAqBM,qBAAqB;AAC1CN,2BAAqBO,uBAAuBxB,MAAMG,KAAKC,GAAGkB;IAC5D;AACA,QAAItB,MAAMG,KAAKC,GAAGqB,iBAAiB,MAAM;AACvCR,2BAAqBS,iBAAiB1B,MAAMG,KAAKC,EAAEqB;IACrD;EACF;AAEA,MAAInB,yBAAyBqB,oBAAoB;AAC/CV,yBAAqBW,sBAAsB;EAC7C;AAEAC,UAAQC,iBAAiBxB,eAAeW,oBAAAA;AAC1C;AAEA,IAAME,sBAAsB,CAACY,aAAAA;AAG3B,QAAMC,QAAQD,SAASC,MAAM,wBAAA;AAC7B,MAAIA,OAAO;AACT,UAAM,CAAA,EAAGC,QAAAA,IAAYD;AACrB,WAAOC;EACT;AAEA,SAAOF;AACT;",
|
|
6
|
-
"names": ["posthog", "InvariantViolation", "LogLevel", "shouldLog", "logProcessor", "config", "entry", "shouldLog", "captureFilters", "meta", "S", "remoteSessionId", "capturedError", "error", "level", "LogLevel", "ERROR", "Object", "values", "context", "find", "v", "Error", "additionalProperties", "transaction", "getRelativeFilename", "F", "L", "hostSessionId", "service_host_issue", "service_host_session", "uptimeSeconds", "uptime_seconds", "InvariantViolation", "invariant_violation", "posthog", "captureException", "filename", "match", "filePath"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/extensions/otel/logs.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';\nimport { ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\n\nimport {\n type LogConfig,\n type LogEntry,\n LogLevel,\n type LogProcessor,\n getContextFromEntry,\n getRelativeFilename,\n} from '@dxos/log';\n\nimport { type OtelOptions, setDiagLogger } from './otel';\n\nconst FLATTEN_DEPTH = 1;\n\nexport type OtelLogOptions = OtelOptions & {\n logLevel: LogLevel;\n /**\n * Include logs forwarded from the shared worker via LoggingService.\n *\n * @deprecated\n */\n includeSharedWorkerLogs?: boolean;\n};\n\nexport class OtelLogs {\n private _loggerProvider: LoggerProvider;\n constructor(private readonly options: OtelLogOptions) {\n setDiagLogger(options.consoleDiagLogLevel);\n const logExporter = new OTLPLogExporter({\n url: this.options.endpoint + '/v1/logs',\n headers: this.options.headers,\n concurrencyLimit: 10, // an optional limit on pending requests\n });\n this._loggerProvider = new LoggerProvider({\n resource: this.options.resource,\n processors: [new BatchLogRecordProcessor(logExporter)],\n });\n }\n\n public readonly logProcessor: LogProcessor = (_config: LogConfig, entry: LogEntry) => {\n const logger = this._loggerProvider.getLogger(\n 'dxos-observability',\n this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString(),\n );\n\n if (\n entry.level < this.options.logLevel ||\n (!this.options.includeSharedWorkerLogs && entry.meta?.S?.remoteSessionId)\n ) {\n return;\n }\n\n const attributes = {\n ...this.options.getTags(),\n ...(entry.meta ? { meta: { file: getRelativeFilename(entry.meta.F), line: entry.meta.L } } : {}),\n ...(entry.error ? { error: entry.error.stack } : {}),\n ...stringifyValues(getContextFromEntry(entry), 'ctx_'),\n };\n\n logger.emit({\n severityNumber: convertLevel(entry.level),\n body: entry.message,\n attributes,\n });\n };\n\n flush(): Promise<void> {\n return this._loggerProvider.forceFlush();\n }\n\n close(): Promise<void> {\n return this._loggerProvider.shutdown();\n }\n}\n\nconst convertLevel = (level: LogLevel): SeverityNumber => {\n switch (level) {\n case LogLevel.DEBUG:\n return SeverityNumber.DEBUG;\n case LogLevel.VERBOSE:\n return SeverityNumber.INFO;\n case LogLevel.INFO:\n return SeverityNumber.INFO;\n case LogLevel.WARN:\n return SeverityNumber.WARN;\n case LogLevel.ERROR:\n return SeverityNumber.ERROR;\n default:\n return SeverityNumber.ERROR;\n }\n};\n\nconst safeStringify = (value: unknown): string => {\n try {\n return JSON.stringify(value);\n } catch {\n return '[Circular]';\n }\n};\n\n// TODO(wittjosiah): Reconcile logging utils w/ EDGE.\nconst stringifyValues = (object: object | undefined, keyPrefix?: string, depth: number = 1) => {\n if (!object) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(object)) {\n if (value === undefined) {\n continue;\n }\n const newKey = keyPrefix ? `${keyPrefix}${key}` : key;\n if (typeof value === 'object') {\n if (!value || Array.isArray(value) || depth > FLATTEN_DEPTH) {\n result[newKey] = safeStringify(value);\n } else {\n const flattened = stringifyValues(value, `${newKey}_`, depth + 1);\n for (const [flattenedKey, flattenedValue] of Object.entries(flattened)) {\n result[flattenedKey] = flattenedValue;\n }\n }\n } else {\n result[newKey] = String(value);\n }\n }\n return result;\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["SeverityNumber", "OTLPLogExporter", "BatchLogRecordProcessor", "LoggerProvider", "ATTR_SERVICE_VERSION", "LogLevel", "getContextFromEntry", "getRelativeFilename", "FLATTEN_DEPTH", "OtelLogs", "_loggerProvider", "options", "setDiagLogger", "consoleDiagLogLevel", "logExporter", "OTLPLogExporter", "url", "endpoint", "headers", "concurrencyLimit", "LoggerProvider", "resource", "processors", "BatchLogRecordProcessor", "logProcessor", "_config", "entry", "logger", "getLogger", "attributes", "ATTR_SERVICE_VERSION", "toString", "level", "logLevel", "includeSharedWorkerLogs", "meta", "S", "remoteSessionId", "getTags", "file", "getRelativeFilename", "F", "line", "L", "error", "stack", "stringifyValues", "getContextFromEntry", "emit", "severityNumber", "convertLevel", "body", "message", "flush", "forceFlush", "close", "shutdown", "LogLevel", "DEBUG", "SeverityNumber", "VERBOSE", "INFO", "WARN", "ERROR", "safeStringify", "value", "JSON", "stringify", "object", "keyPrefix", "depth", "result", "key", "Object", "entries", "undefined", "newKey", "Array", "isArray", "flattened", "flattenedKey", "flattenedValue", "String"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/extensions/otel/metrics.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Meter } from '@opentelemetry/api';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\n\nimport { log } from '@dxos/log';\nimport { type MetricData, TRACE_PROCESSOR } from '@dxos/tracing';\n\nimport { type OtelOptions, setDiagLogger } from './otel';\n\nconst EXPORT_INTERVAL = 60 * 1000;\n\nexport class OtelMetrics {\n private _meterProvider: MeterProvider;\n private _meter: Meter;\n\n constructor(private readonly options: OtelOptions) {\n // TODO: improve error handling/logging\n // https://github.com/open-telemetry/opentelemetry-js/issues/4823\n setDiagLogger(options.consoleDiagLogLevel);\n\n const grafanaMetricReader = new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: this.options.endpoint + '/v1/metrics',\n headers: this.options.headers,\n }),\n exportIntervalMillis: EXPORT_INTERVAL,\n });\n\n this._meterProvider = new MeterProvider({\n resource: this.options.resource,\n readers: [grafanaMetricReader],\n });\n this._meter = this._meterProvider.getMeter('dxos-observability');\n\n const metrics = {\n // TODO: update metrics names and remove prefix?\n increment: (name: string, value?: number, data?: MetricData) => {\n this.increment(name, value, convertTags(data));\n },\n distribution: (name: string, value: number, data?: MetricData) => {\n this.distribution(name, value, convertTags(data));\n },\n set: (name: string, value: number | string, data?: MetricData) => {\n // Not implemented, not part of Otel spec.\n },\n gauge: (name: string, value: number, data?: MetricData) => {\n this.gauge(name, value, convertTags(data));\n },\n };\n\n TRACE_PROCESSOR.remoteMetrics.registerProcessor(metrics);\n }\n\n gauge(name: string, value: number, tags?: any): void {\n const gauge = this._meter.createGauge(name);\n log('otel gauge', { name, value, tags: { ...this.options.getTags(), ...tags } });\n gauge.record(value, { ...this.options.getTags(), ...tags });\n }\n\n increment(name: string, value?: number, tags?: any): void {\n const counter = this._meter.createCounter(name);\n log('otel counter', { name, value, tags: { ...this.options.getTags(), ...tags } });\n counter.add(value ?? 1, { ...this.options.getTags(), ...tags });\n }\n\n distribution(name: string, value: number, tags?: any): void {\n const distribution = this._meter.createHistogram(name);\n log('otel distribution', { name, value, tags: { ...this.options.getTags(), ...tags } });\n distribution.record(value, { ...this.options.getTags(), ...tags });\n }\n\n flush(): Promise<void> {\n return this._meterProvider.forceFlush();\n }\n\n close(): Promise<void> {\n return this._meterProvider.shutdown();\n }\n}\n\nconst convertTags = (data?: MetricData) => {\n if (data && data.tags) {\n return Object.entries(data.tags).reduce<{ [key: string]: any }>((obj, [key, value]) => {\n obj[key] = value;\n return obj;\n }, {});\n } else {\n return {};\n }\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;AAKA,SAASA,0BAA0B;AACnC,SAASC,eAAeC,qCAAqC;AAE7D,SAASC,WAAW;AACpB,SAA0BC,uBAAuB;;AAIjD,IAAMC,kBAAkB,KAAK;AAEtB,IAAMC,cAAN,MAAMA;;EACHC;EACAC;EAER,YAA6BC,SAAsB;SAAtBA,UAAAA;AAG3BC,kBAAcD,QAAQE,mBAAmB;AAEzC,UAAMC,sBAAsB,IAAIC,8BAA8B;MAC5DC,UAAU,IAAIC,mBAAmB;QAC/BC,KAAK,KAAKP,QAAQQ,WAAW;QAC7BC,SAAS,KAAKT,QAAQS;MACxB,CAAA;MACAC,sBAAsBd;IACxB,CAAA;AAEA,SAAKE,iBAAiB,IAAIa,cAAc;MACtCC,UAAU,KAAKZ,QAAQY;MACvBC,SAAS;QAACV;;IACZ,CAAA;AACA,SAAKJ,SAAS,KAAKD,eAAegB,SAAS,oBAAA;AAE3C,UAAMC,UAAU;;MAEdC,WAAW,CAACC,MAAcC,OAAgBC,SAAAA;AACxC,aAAKH,UAAUC,MAAMC,OAAOE,YAAYD,IAAAA,CAAAA;MAC1C;MACAE,cAAc,CAACJ,MAAcC,OAAeC,SAAAA;AAC1C,aAAKE,aAAaJ,MAAMC,OAAOE,YAAYD,IAAAA,CAAAA;MAC7C;MACAG,KAAK,CAACL,MAAcC,OAAwBC,SAAAA;MAE5C;MACAI,OAAO,CAACN,MAAcC,OAAeC,SAAAA;AACnC,aAAKI,MAAMN,MAAMC,OAAOE,YAAYD,IAAAA,CAAAA;MACtC;IACF;AAEAK,oBAAgBC,cAAcC,kBAAkBX,OAAAA;EAClD;EAEAQ,MAAMN,MAAcC,OAAeS,MAAkB;AACnD,UAAMJ,QAAQ,KAAKxB,OAAO6B,YAAYX,IAAAA;AACtCY,QAAI,cAAc;MAAEZ;MAAMC;MAAOS,MAAM;QAAE,GAAG,KAAK3B,QAAQ8B,QAAO;QAAI,GAAGH;MAAK;IAAE,GAAA;;;;;;AAC9EJ,UAAMQ,OAAOb,OAAO;MAAE,GAAG,KAAKlB,QAAQ8B,QAAO;MAAI,GAAGH;IAAK,CAAA;EAC3D;EAEAX,UAAUC,MAAcC,OAAgBS,MAAkB;AACxD,UAAMK,UAAU,KAAKjC,OAAOkC,cAAchB,IAAAA;AAC1CY,QAAI,gBAAgB;MAAEZ;MAAMC;MAAOS,MAAM;QAAE,GAAG,KAAK3B,QAAQ8B,QAAO;QAAI,GAAGH;MAAK;IAAE,GAAA;;;;;;AAChFK,YAAQE,IAAIhB,SAAS,GAAG;MAAE,GAAG,KAAKlB,QAAQ8B,QAAO;MAAI,GAAGH;IAAK,CAAA;EAC/D;EAEAN,aAAaJ,MAAcC,OAAeS,MAAkB;AAC1D,UAAMN,eAAe,KAAKtB,OAAOoC,gBAAgBlB,IAAAA;AACjDY,QAAI,qBAAqB;MAAEZ;MAAMC;MAAOS,MAAM;QAAE,GAAG,KAAK3B,QAAQ8B,QAAO;QAAI,GAAGH;MAAK;IAAE,GAAA;;;;;;AACrFN,iBAAaU,OAAOb,OAAO;MAAE,GAAG,KAAKlB,QAAQ8B,QAAO;MAAI,GAAGH;IAAK,CAAA;EAClE;EAEAS,QAAuB;AACrB,WAAO,KAAKtC,eAAeuC,WAAU;EACvC;EAEAC,QAAuB;AACrB,WAAO,KAAKxC,eAAeyC,SAAQ;EACrC;AACF;AAEA,IAAMnB,cAAc,CAACD,SAAAA;AACnB,MAAIA,QAAQA,KAAKQ,MAAM;AACrB,WAAOa,OAAOC,QAAQtB,KAAKQ,IAAI,EAAEe,OAA+B,CAACC,KAAK,CAACC,KAAK1B,KAAAA,MAAM;AAChFyB,UAAIC,GAAAA,IAAO1B;AACX,aAAOyB;IACT,GAAG,CAAC,CAAA;EACN,OAAO;AACL,WAAO,CAAC;EACV;AACF;",
|
|
6
|
-
"names": ["OTLPMetricExporter", "MeterProvider", "PeriodicExportingMetricReader", "log", "TRACE_PROCESSOR", "EXPORT_INTERVAL", "OtelMetrics", "_meterProvider", "_meter", "options", "setDiagLogger", "consoleDiagLogLevel", "grafanaMetricReader", "PeriodicExportingMetricReader", "exporter", "OTLPMetricExporter", "url", "endpoint", "headers", "exportIntervalMillis", "MeterProvider", "resource", "readers", "getMeter", "metrics", "increment", "name", "value", "data", "convertTags", "distribution", "set", "gauge", "TRACE_PROCESSOR", "remoteMetrics", "registerProcessor", "tags", "createGauge", "log", "getTags", "record", "counter", "createCounter", "add", "createHistogram", "flush", "forceFlush", "close", "shutdown", "Object", "entries", "reduce", "obj", "key"]
|
|
7
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
-
|
|
3
|
-
// src/extensions/otel/traces-browser.ts
|
|
4
|
-
import { trace } from "@opentelemetry/api";
|
|
5
|
-
import { getWebAutoInstrumentations } from "@opentelemetry/auto-instrumentations-web";
|
|
6
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
7
|
-
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
|
8
|
-
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
9
|
-
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
10
|
-
import { ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
11
|
-
import { log } from "@dxos/log";
|
|
12
|
-
import { TRACE_PROCESSOR } from "@dxos/tracing";
|
|
13
|
-
var __dxlog_file = "/__w/dxos/dxos/packages/sdk/observability/src/extensions/otel/traces-browser.ts";
|
|
14
|
-
var OtelTraces = class {
|
|
15
|
-
options;
|
|
16
|
-
_tracer;
|
|
17
|
-
constructor(options) {
|
|
18
|
-
this.options = options;
|
|
19
|
-
const tracerProvider = new WebTracerProvider({
|
|
20
|
-
resource: this.options.resource,
|
|
21
|
-
spanProcessors: [
|
|
22
|
-
new BatchSpanProcessor(new OTLPTraceExporter({
|
|
23
|
-
url: this.options.endpoint + "/v1/traces",
|
|
24
|
-
headers: this.options.headers,
|
|
25
|
-
concurrencyLimit: 10
|
|
26
|
-
}))
|
|
27
|
-
]
|
|
28
|
-
});
|
|
29
|
-
trace.setGlobalTracerProvider(tracerProvider);
|
|
30
|
-
this._tracer = trace.getTracer("dxos-observability", this.options.resource.attributes[ATTR_SERVICE_VERSION]?.toString());
|
|
31
|
-
}
|
|
32
|
-
start() {
|
|
33
|
-
log("trace processor registered", void 0, {
|
|
34
|
-
F: __dxlog_file,
|
|
35
|
-
L: 44,
|
|
36
|
-
S: this,
|
|
37
|
-
C: (f, a) => f(...a)
|
|
38
|
-
});
|
|
39
|
-
registerInstrumentations({
|
|
40
|
-
instrumentations: [
|
|
41
|
-
getWebAutoInstrumentations()
|
|
42
|
-
]
|
|
43
|
-
});
|
|
44
|
-
TRACE_PROCESSOR.remoteTracing.registerProcessor({
|
|
45
|
-
startSpan: (options) => {
|
|
46
|
-
log("begin otel trace", {
|
|
47
|
-
options
|
|
48
|
-
}, {
|
|
49
|
-
F: __dxlog_file,
|
|
50
|
-
L: 52,
|
|
51
|
-
S: this,
|
|
52
|
-
C: (f, a) => f(...a)
|
|
53
|
-
});
|
|
54
|
-
return this._tracer.startSpan(options.name, options);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
export {
|
|
60
|
-
OtelTraces
|
|
61
|
-
};
|
|
62
|
-
//# sourceMappingURL=traces-browser-XYXBF5ZX.mjs.map
|