@decocms/start 4.6.0 → 5.0.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/.github/workflows/release.yml +26 -2
- package/CHANGELOG.md +113 -0
- package/bun.lock +0 -67
- package/package.json +5 -10
- package/scripts/migrate-to-cf-observability.test.ts +63 -17
- package/scripts/migrate-to-cf-observability.ts +175 -87
- package/src/sdk/observability.ts +26 -18
- package/src/sdk/otel.test.ts +50 -71
- package/src/sdk/otel.ts +70 -295
- package/src/sdk/otelAdapters/clickhouseCollector.ts +65 -0
- package/src/sdk/otelAdapters.test.ts +11 -194
- package/src/sdk/otelAdapters.ts +18 -353
- package/src/sdk/sampler.test.ts +0 -165
- package/src/sdk/sampler.ts +0 -213
package/src/sdk/otel.ts
CHANGED
|
@@ -4,21 +4,30 @@
|
|
|
4
4
|
* `instrumentWorker(handler, options)` wraps a Worker handler with:
|
|
5
5
|
* - structured JSON logger (stdout → Cloudflare Workers Logs) — always
|
|
6
6
|
* - Workers Analytics Engine metrics — when `env.DECO_METRICS` binding exists
|
|
7
|
-
* - OTLP/HTTP metrics exporter (HyperDX) — when `OTEL_EXPORTER_OTLP_ENDPOINT`
|
|
8
|
-
* is set (CF doesn't support OTLP metrics export yet, so this stays app-side)
|
|
9
|
-
* - Per-request `ctx.waitUntil(forceFlush)` for any registered OTel batch
|
|
10
|
-
* processors so log/metric batches don't die with the isolate
|
|
11
7
|
* - Bridges framework-internal `withTracing()` calls onto the global
|
|
12
8
|
* `@opentelemetry/api` tracer, stamping `deco.*` attributes on every span
|
|
13
9
|
* so they survive Cloudflare's platform-managed trace export
|
|
14
10
|
*
|
|
15
|
-
* **
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
11
|
+
* **All export goes through Cloudflare.** Logs reach the dashboard via
|
|
12
|
+
* `console.*` capture; traces reach the dashboard via CF auto-instrumentation
|
|
13
|
+
* plus the global-tracer spans this module forwards. There is no in-Worker
|
|
14
|
+
* OTLP exporter and no third-party destination — the CF dashboard is the
|
|
15
|
+
* destination.
|
|
16
|
+
*
|
|
17
|
+
* Required `wrangler.jsonc` block (run `scripts/migrate-to-cf-observability.ts`
|
|
18
|
+
* to inject this automatically):
|
|
19
|
+
* ```jsonc
|
|
20
|
+
* "observability": {
|
|
21
|
+
* "enabled": true,
|
|
22
|
+
* "logs": { "enabled": true, "invocation_logs": true,
|
|
23
|
+
* "head_sampling_rate": 1, "persist": true },
|
|
24
|
+
* "traces": { "enabled": true,
|
|
25
|
+
* "head_sampling_rate": 0.1, "persist": true }
|
|
26
|
+
* },
|
|
27
|
+
* "version_metadata": { "binding": "CF_VERSION_METADATA" },
|
|
28
|
+
* "analytics_engine_datasets": [{ "binding": "DECO_METRICS",
|
|
29
|
+
* "dataset": "deco_metrics_my_site" }]
|
|
30
|
+
* ```
|
|
22
31
|
*
|
|
23
32
|
* @example
|
|
24
33
|
* ```ts
|
|
@@ -30,54 +39,18 @@
|
|
|
30
39
|
* export default instrumentWorker(handler, { serviceName: "my-store" });
|
|
31
40
|
* ```
|
|
32
41
|
*
|
|
33
|
-
*
|
|
34
|
-
* to
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* "head_sampling_rate": 1.0, "persist": false },
|
|
39
|
-
* "traces": { "enabled": true, "destinations": ["hyperdx-traces"],
|
|
40
|
-
* "head_sampling_rate": 0.1, "persist": false }
|
|
41
|
-
* },
|
|
42
|
-
* "version_metadata": { "binding": "CF_VERSION_METADATA" },
|
|
43
|
-
* "analytics_engine_datasets": [{ "binding": "DECO_METRICS",
|
|
44
|
-
* "dataset": "deco_metrics_my_site" }]
|
|
45
|
-
* ```
|
|
46
|
-
*
|
|
47
|
-
* **Back-compat seam.** Sites that need to keep app-side OTLP log export
|
|
48
|
-
* (custom destination not covered by CF, custom batching, etc.) can opt back
|
|
49
|
-
* in with `enableAppSideOtlpLogs: true` and the existing `OTEL_EXPORTER_OTLP_*`
|
|
50
|
-
* secrets. Slated for removal in 5.0.0.
|
|
42
|
+
* **Future ClickHouse path.** When a co-deployed OTel collector lands, an
|
|
43
|
+
* exporter that pushes spans + logs + metrics to that collector will live in
|
|
44
|
+
* `./otelAdapters/clickhouseCollector.ts` (today: documented stub that throws).
|
|
45
|
+
* The `withTracing` / `recordRequestMetric` / `logger` instrumentation surface
|
|
46
|
+
* does not change — only the transport layer wires up.
|
|
51
47
|
*/
|
|
52
48
|
|
|
53
49
|
import { trace } from "@opentelemetry/api";
|
|
54
|
-
import { type Resource, resourceFromAttributes } from "@opentelemetry/resources";
|
|
55
50
|
|
|
56
51
|
import { configureMeter, configureTracer } from "../middleware/observability";
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
configureLogger,
|
|
60
|
-
defaultLoggerAdapter,
|
|
61
|
-
type LogLevel,
|
|
62
|
-
logger,
|
|
63
|
-
setLoggerAttributeFloor,
|
|
64
|
-
} from "./logger";
|
|
65
|
-
import {
|
|
66
|
-
createAnalyticsEngineMeterAdapter,
|
|
67
|
-
createOtelLoggerAdapter,
|
|
68
|
-
createOtelMeterAdapter,
|
|
69
|
-
flushOtelProviders,
|
|
70
|
-
setRuntimeEnv,
|
|
71
|
-
} from "./otelAdapters";
|
|
72
|
-
import { RequestContext } from "./requestContext";
|
|
73
|
-
|
|
74
|
-
const VALID_LOG_LEVELS: readonly LogLevel[] = ["debug", "info", "warn", "error"];
|
|
75
|
-
|
|
76
|
-
function parseLogLevel(value: unknown): LogLevel | undefined {
|
|
77
|
-
if (typeof value !== "string") return undefined;
|
|
78
|
-
const lc = value.toLowerCase();
|
|
79
|
-
return VALID_LOG_LEVELS.find((l) => l === lc);
|
|
80
|
-
}
|
|
52
|
+
import { configureLogger, defaultLoggerAdapter, setLoggerAttributeFloor } from "./logger";
|
|
53
|
+
import { createAnalyticsEngineMeterAdapter } from "./otelAdapters";
|
|
81
54
|
|
|
82
55
|
// ---------------------------------------------------------------------------
|
|
83
56
|
// Types
|
|
@@ -86,49 +59,17 @@ function parseLogLevel(value: unknown): LogLevel | undefined {
|
|
|
86
59
|
export interface OtelOptions {
|
|
87
60
|
/** Logical service name. Falls back to `env.DECO_SITE_NAME`, then "deco-site". */
|
|
88
61
|
serviceName?: string;
|
|
89
|
-
/** Override OTLP endpoint. Defaults to `env.OTEL_EXPORTER_OTLP_ENDPOINT`. */
|
|
90
|
-
endpoint?: string;
|
|
91
|
-
/** Override OTLP auth headers. Defaults to parsed `env.OTEL_EXPORTER_OTLP_HEADERS`. */
|
|
92
|
-
headers?: Record<string, string>;
|
|
93
62
|
/** Env var name holding the AE binding. Defaults to `"DECO_METRICS"`. */
|
|
94
63
|
analyticsEngineBindingName?: string;
|
|
95
64
|
/** Set to `false` to disable AE even when the binding is present. */
|
|
96
65
|
analyticsEngineEnabled?: boolean;
|
|
97
|
-
/** Push interval for OTLP metrics, in ms. Defaults to env.OTEL_EXPORT_INTERVAL or 60_000. */
|
|
98
|
-
metricsExportIntervalMillis?: number;
|
|
99
|
-
/**
|
|
100
|
-
* Minimum severity to forward to the **app-side OTLP** logger (only
|
|
101
|
-
* relevant when `enableAppSideOtlpLogs: true`). The default `console.*`
|
|
102
|
-
* adapter is unaffected and continues to capture every level for
|
|
103
|
-
* Cloudflare Workers Logs / CF-side OTLP export.
|
|
104
|
-
*
|
|
105
|
-
* Defaults to `"warn"`. Falls back to env `OTEL_LOG_MIN_SEVERITY` when
|
|
106
|
-
* unset. Set to `"debug"` to forward everything.
|
|
107
|
-
*/
|
|
108
|
-
otlpMinSeverity?: LogLevel;
|
|
109
|
-
/**
|
|
110
|
-
* Opt-in: also wire an in-Worker OTLP logger that pushes log records to
|
|
111
|
-
* `OTEL_EXPORTER_OTLP_ENDPOINT`. Defaults to `false` — sites should
|
|
112
|
-
* prefer the platform-managed CF-side path
|
|
113
|
-
* (`observability.logs.destinations` in `wrangler.jsonc`), which is
|
|
114
|
-
* cheaper, has no flush-bug class, and consumes zero subrequest budget.
|
|
115
|
-
*
|
|
116
|
-
* Use this only when CF's OTLP logs export doesn't meet a specific need
|
|
117
|
-
* (e.g. shipping to a destination CF doesn't support, custom batching,
|
|
118
|
-
* staging-only debugging). Requires `OTEL_EXPORTER_OTLP_ENDPOINT` and
|
|
119
|
-
* `OTEL_EXPORTER_OTLP_HEADERS` to be set.
|
|
120
|
-
*
|
|
121
|
-
* Slated for removal in 5.0.0.
|
|
122
|
-
*/
|
|
123
|
-
enableAppSideOtlpLogs?: boolean;
|
|
124
66
|
/**
|
|
125
67
|
* Version of `@decocms/start` to advertise as `deco.runtime.version`
|
|
126
|
-
* on every span
|
|
127
|
-
*
|
|
128
|
-
* Falls back to a build-time constant; override only for tests.
|
|
68
|
+
* on every span and every log line. Falls back to a build-time constant;
|
|
69
|
+
* override only for tests.
|
|
129
70
|
*/
|
|
130
71
|
decoRuntimeVersion?: string;
|
|
131
|
-
/** Optional `@decocms/apps` version, stamped as `deco.apps.version
|
|
72
|
+
/** Optional `@decocms/apps` version, stamped as `deco.apps.version`. */
|
|
132
73
|
decoAppsVersion?: string;
|
|
133
74
|
}
|
|
134
75
|
|
|
@@ -151,24 +92,12 @@ interface WorkerHandler {
|
|
|
151
92
|
|
|
152
93
|
let booted = false;
|
|
153
94
|
|
|
154
|
-
interface BootState {
|
|
155
|
-
serviceName: string;
|
|
156
|
-
otlpEndpoint: string | null;
|
|
157
|
-
otlpHeaders: Record<string, string>;
|
|
158
|
-
resource: Resource;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let bootState: BootState | null = null;
|
|
162
|
-
|
|
163
95
|
/**
|
|
164
96
|
* Per-span attribute floor — stamped on every span we create via
|
|
165
|
-
* `configureTracer().startSpan(...)`.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* `deployment.environment` keep working with CF-managed export, which only
|
|
170
|
-
* preserves CF's own resource attribute set (`service.name`, `faas.name`,
|
|
171
|
-
* `cloudflare.script_version.id`, etc.).
|
|
97
|
+
* `configureTracer().startSpan(...)`. CF's trace export emits its own
|
|
98
|
+
* resource attribute set (service.name=Worker name, faas.name,
|
|
99
|
+
* cloudflare.script_version.id, etc.) so framework-level dimensions like
|
|
100
|
+
* `deco.runtime.version` only survive when stamped per-span.
|
|
172
101
|
*
|
|
173
102
|
* Populated by `bootObservability` before any span is created. Stays an
|
|
174
103
|
* empty object until then so early span creation is a no-op stamp.
|
|
@@ -182,19 +111,15 @@ let spanAttributeFloor: Record<string, string> = {};
|
|
|
182
111
|
/**
|
|
183
112
|
* Wraps a Cloudflare Worker handler with the @decocms/start observability
|
|
184
113
|
* stack:
|
|
185
|
-
* - structured JSON logger (
|
|
114
|
+
* - structured JSON logger to console.* (CF captures via observability.logs)
|
|
186
115
|
* - AE meter (when `DECO_METRICS` binding present)
|
|
187
|
-
* - optional app-side OTLP meter (when `OTEL_EXPORTER_OTLP_ENDPOINT` set)
|
|
188
|
-
* - optional app-side OTLP logger (when `enableAppSideOtlpLogs: true`)
|
|
189
|
-
* - per-request `ctx.waitUntil(forceFlush)` for any registered batch processors
|
|
190
116
|
* - bridge from framework-internal `withTracing()` to `@opentelemetry/api`
|
|
191
|
-
* global tracer
|
|
117
|
+
* global tracer (CF observability.traces ingests via auto-instrumentation
|
|
118
|
+
* + the global-tracer hook)
|
|
192
119
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* `instrument()` — CF's platform-managed export captures `console.*` output
|
|
197
|
-
* and global-tracer spans out-of-band.
|
|
120
|
+
* No external destinations, no OTLP transport. Forwarding to a future
|
|
121
|
+
* OTel collector for ClickHouse will live behind a separate adapter
|
|
122
|
+
* (see `./otelAdapters/clickhouseCollector.ts`).
|
|
198
123
|
*/
|
|
199
124
|
export function instrumentWorker(
|
|
200
125
|
handler: WorkerHandler,
|
|
@@ -206,9 +131,9 @@ export function instrumentWorker(
|
|
|
206
131
|
//
|
|
207
132
|
// CF Workers Tracing (when `observability.traces.enabled = true` in
|
|
208
133
|
// wrangler) installs its own TracerProvider into the @opentelemetry/api
|
|
209
|
-
// global, so these spans flow through to
|
|
210
|
-
//
|
|
211
|
-
//
|
|
134
|
+
// global, so these spans flow through to the CF dashboard. Without CF
|
|
135
|
+
// tracing the global tracer is a no-op proxy and the spans simply drop
|
|
136
|
+
// — same outcome as before, no error.
|
|
212
137
|
configureTracer({
|
|
213
138
|
startSpan: (name, attrs) => {
|
|
214
139
|
const merged = { ...spanAttributeFloor, ...(attrs ?? {}) };
|
|
@@ -228,34 +153,10 @@ export function instrumentWorker(
|
|
|
228
153
|
const opts =
|
|
229
154
|
typeof options === "function" ? options(env as Record<string, unknown>) : options;
|
|
230
155
|
bootObservability(opts, env as Record<string, unknown>);
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
// over a handler that doesn't go through `createDecoWorkerEntry`.
|
|
236
|
-
const wrap = async () => {
|
|
237
|
-
setRuntimeEnv(env);
|
|
238
|
-
return handler.fetch(request, env, ctx);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
if (RequestContext.current) {
|
|
243
|
-
return await wrap();
|
|
244
|
-
}
|
|
245
|
-
return await RequestContext.run(request, wrap);
|
|
246
|
-
} finally {
|
|
247
|
-
// Drain OTLP meter (and OTLP logger, if `enableAppSideOtlpLogs`)
|
|
248
|
-
// batches inside the post-response window `waitUntil` guarantees.
|
|
249
|
-
// Without this hook, `PeriodicExportingMetricReader` (60s flush)
|
|
250
|
-
// batches usually die with the isolate before the timer fires.
|
|
251
|
-
// No-op when no batch processors are registered.
|
|
252
|
-
try {
|
|
253
|
-
ctx.waitUntil(flushOtelProviders());
|
|
254
|
-
} catch {
|
|
255
|
-
// `waitUntil` only throws if `ctx` isn't a real ExecutionContext
|
|
256
|
-
// (e.g. test stubs). Telemetry flush failures are not request-fatal.
|
|
257
|
-
}
|
|
258
|
-
}
|
|
156
|
+
// RequestContext.run + setRuntimeEnv(env) is handled inside
|
|
157
|
+
// workerEntry.ts on the inner handler — instrumentWorker does
|
|
158
|
+
// NOT re-wrap so we don't double-enter AsyncLocalStorage.
|
|
159
|
+
return handler.fetch(request, env, ctx);
|
|
259
160
|
},
|
|
260
161
|
};
|
|
261
162
|
}
|
|
@@ -265,119 +166,47 @@ export function instrumentWorker(
|
|
|
265
166
|
// ---------------------------------------------------------------------------
|
|
266
167
|
|
|
267
168
|
function bootObservability(opts: OtelOptions, env: Record<string, unknown>): void {
|
|
268
|
-
if (booted
|
|
169
|
+
if (booted) return;
|
|
269
170
|
|
|
270
171
|
const serviceName = opts.serviceName ?? (env.DECO_SITE_NAME as string | undefined) ?? "deco-site";
|
|
271
|
-
|
|
272
|
-
const otlpEndpoint =
|
|
273
|
-
opts.endpoint ?? (env.OTEL_EXPORTER_OTLP_ENDPOINT as string | undefined) ?? null;
|
|
274
|
-
const otlpHeaders =
|
|
275
|
-
opts.headers ?? parseHeaders(env.OTEL_EXPORTER_OTLP_HEADERS as string | undefined);
|
|
276
|
-
|
|
277
172
|
const decoRuntimeVersion = opts.decoRuntimeVersion ?? DECO_RUNTIME_VERSION;
|
|
278
173
|
const deploymentEnvironment = (env.DECO_ENV_NAME as string | undefined) ?? "production";
|
|
279
174
|
|
|
280
|
-
const
|
|
281
|
-
serviceName,
|
|
282
|
-
serviceVersion: (env.CF_VERSION_METADATA as { id?: string } | undefined)?.id,
|
|
283
|
-
serviceInstanceId: cryptoRandomId(),
|
|
284
|
-
deploymentEnvironment,
|
|
285
|
-
decoRuntimeVersion,
|
|
286
|
-
decoAppsVersion: opts.decoAppsVersion,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// Stamp deco.* attributes on every span we create. CF-managed trace
|
|
290
|
-
// export emits its own resource attribute set (service.name=Worker name,
|
|
291
|
-
// faas.name, cloudflare.script_version.id, faas.version, etc.) so the
|
|
292
|
-
// legacy resource attrs from `buildResource` don't survive on the
|
|
293
|
-
// CF-side path. Stamping them per-span preserves the dimensions
|
|
294
|
-
// existing HyperDX dashboards filter on.
|
|
295
|
-
spanAttributeFloor = {
|
|
175
|
+
const floor: Record<string, string> = {
|
|
296
176
|
"deco.runtime.version": decoRuntimeVersion,
|
|
297
177
|
"deployment.environment": deploymentEnvironment,
|
|
298
|
-
...(opts.decoAppsVersion ? { "deco.apps.version": opts.decoAppsVersion } : {}),
|
|
299
178
|
};
|
|
179
|
+
if (opts.decoAppsVersion) floor["deco.apps.version"] = opts.decoAppsVersion;
|
|
300
180
|
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
setLoggerAttributeFloor({
|
|
307
|
-
"deco.runtime.version": decoRuntimeVersion,
|
|
308
|
-
"deployment.environment": deploymentEnvironment,
|
|
309
|
-
...(opts.decoAppsVersion ? { "deco.apps.version": opts.decoAppsVersion } : {}),
|
|
310
|
-
});
|
|
181
|
+
// Stamp on every span we create. CF-managed trace export emits its own
|
|
182
|
+
// resource attribute set, so legacy resource attrs don't survive.
|
|
183
|
+
// Stamping per-span preserves the dimensions dashboards / saved searches
|
|
184
|
+
// filter on.
|
|
185
|
+
spanAttributeFloor = floor;
|
|
311
186
|
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
// cost per emit.
|
|
318
|
-
//
|
|
319
|
-
// Opt-in mode (`enableAppSideOtlpLogs: true`): also wire the OTLP logger
|
|
320
|
-
// adapter for sites with destinations CF doesn't support. Requires
|
|
321
|
-
// `OTEL_EXPORTER_OTLP_ENDPOINT` to be set.
|
|
322
|
-
const otlpMinSeverity =
|
|
323
|
-
opts.otlpMinSeverity ?? parseLogLevel(env.OTEL_LOG_MIN_SEVERITY) ?? "warn";
|
|
324
|
-
|
|
325
|
-
const wantAppSideLogs = opts.enableAppSideOtlpLogs === true;
|
|
326
|
-
const otelLogger =
|
|
327
|
-
wantAppSideLogs && otlpEndpoint != null
|
|
328
|
-
? createOtelLoggerAdapter({
|
|
329
|
-
endpoint: otlpEndpoint,
|
|
330
|
-
headers: otlpHeaders,
|
|
331
|
-
resource,
|
|
332
|
-
name: serviceName,
|
|
333
|
-
minSeverity: otlpMinSeverity,
|
|
334
|
-
})
|
|
335
|
-
: null;
|
|
187
|
+
// Stamp on every log record. CF Workers Logs ships the JSON body
|
|
188
|
+
// verbatim — without this floor, panels grouping logs by these
|
|
189
|
+
// dimensions return empty. Caller-supplied `attrs` still win on
|
|
190
|
+
// key collision (see logger.ts).
|
|
191
|
+
setLoggerAttributeFloor(floor);
|
|
336
192
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// ---- Meter -----------------------------------------------------------
|
|
340
|
-
// OTLP meter stays default-on when an endpoint is configured: CF doesn't
|
|
341
|
-
// support OTLP metrics export yet, so this is the only path to
|
|
342
|
-
// HyperDX-compatible metrics. Drop this branch when CF ships metrics.
|
|
343
|
-
const otelMeter =
|
|
344
|
-
otlpEndpoint != null
|
|
345
|
-
? createOtelMeterAdapter({
|
|
346
|
-
endpoint: otlpEndpoint,
|
|
347
|
-
headers: otlpHeaders,
|
|
348
|
-
resource,
|
|
349
|
-
exportIntervalMillis:
|
|
350
|
-
opts.metricsExportIntervalMillis ?? numericEnv(env.OTEL_EXPORT_INTERVAL, 60_000),
|
|
351
|
-
name: serviceName,
|
|
352
|
-
})
|
|
353
|
-
: null;
|
|
193
|
+
// Logger: structured JSON to console.*, captured by CF observability.logs.
|
|
194
|
+
configureLogger(defaultLoggerAdapter);
|
|
354
195
|
|
|
196
|
+
// Meter: AE only. OTLP metrics path was removed in 5.0.0; will return
|
|
197
|
+
// via the ClickHouse collector adapter when that lands.
|
|
355
198
|
const aeBindingName = opts.analyticsEngineBindingName ?? "DECO_METRICS";
|
|
356
199
|
const aeEnabled = opts.analyticsEngineEnabled !== false && Boolean(env[aeBindingName]);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
configureMeter(createCompositeMeter([aeMeter, otelMeter]));
|
|
200
|
+
if (aeEnabled) {
|
|
201
|
+
configureMeter(createAnalyticsEngineMeterAdapter({ bindingName: aeBindingName }));
|
|
202
|
+
}
|
|
362
203
|
|
|
363
|
-
bootState = {
|
|
364
|
-
serviceName,
|
|
365
|
-
otlpEndpoint,
|
|
366
|
-
otlpHeaders,
|
|
367
|
-
resource,
|
|
368
|
-
};
|
|
369
204
|
booted = true;
|
|
370
205
|
|
|
371
206
|
// Single boot-time breadcrumb so operators can confirm the wiring at a
|
|
372
|
-
// glance from CF Logs without enabling debug.
|
|
373
|
-
|
|
374
|
-
// obvious from the first request.
|
|
375
|
-
logger.info("observability booted", {
|
|
207
|
+
// glance from CF Logs without enabling debug.
|
|
208
|
+
defaultLoggerAdapter.log("info", "observability booted", {
|
|
376
209
|
service: serviceName,
|
|
377
|
-
mode: wantAppSideLogs ? "hybrid (app-side OTLP logs + CF traces)" : "cf-native",
|
|
378
|
-
otlpMeter: Boolean(otlpEndpoint),
|
|
379
|
-
otlpLogger: wantAppSideLogs && otlpEndpoint != null,
|
|
380
|
-
otlpMinSeverity: wantAppSideLogs && otlpEndpoint != null ? otlpMinSeverity : null,
|
|
381
210
|
analyticsEngine: aeEnabled,
|
|
382
211
|
runtimeVersion: decoRuntimeVersion,
|
|
383
212
|
deploymentEnvironment,
|
|
@@ -390,37 +219,10 @@ function bootObservability(opts: OtelOptions, env: Record<string, unknown>): voi
|
|
|
390
219
|
*/
|
|
391
220
|
export function _resetBootStateForTests(): void {
|
|
392
221
|
booted = false;
|
|
393
|
-
bootState = null;
|
|
394
222
|
spanAttributeFloor = {};
|
|
395
223
|
setLoggerAttributeFloor({});
|
|
396
224
|
}
|
|
397
225
|
|
|
398
|
-
// ---------------------------------------------------------------------------
|
|
399
|
-
// Resource attributes
|
|
400
|
-
// ---------------------------------------------------------------------------
|
|
401
|
-
|
|
402
|
-
interface ResourceInput {
|
|
403
|
-
serviceName: string;
|
|
404
|
-
serviceVersion?: string;
|
|
405
|
-
serviceInstanceId: string;
|
|
406
|
-
deploymentEnvironment: string;
|
|
407
|
-
decoRuntimeVersion: string;
|
|
408
|
-
decoAppsVersion?: string;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function buildResource(input: ResourceInput): Resource {
|
|
412
|
-
const attrs: Record<string, string> = {
|
|
413
|
-
"service.name": input.serviceName,
|
|
414
|
-
"service.version": input.serviceVersion ?? "unknown",
|
|
415
|
-
"service.instance.id": input.serviceInstanceId,
|
|
416
|
-
"cloud.provider": "cloudflare",
|
|
417
|
-
"deployment.environment": input.deploymentEnvironment,
|
|
418
|
-
"deco.runtime.version": input.decoRuntimeVersion,
|
|
419
|
-
};
|
|
420
|
-
if (input.decoAppsVersion) attrs["deco.apps.version"] = input.decoAppsVersion;
|
|
421
|
-
return resourceFromAttributes(attrs);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
226
|
// ---------------------------------------------------------------------------
|
|
425
227
|
// Helpers
|
|
426
228
|
// ---------------------------------------------------------------------------
|
|
@@ -434,31 +236,4 @@ function buildResource(input: ResourceInput): Resource {
|
|
|
434
236
|
* Drift is acceptable — this attribute is for operator triage, not for
|
|
435
237
|
* billing / SLOs.
|
|
436
238
|
*/
|
|
437
|
-
const DECO_RUNTIME_VERSION = "
|
|
438
|
-
|
|
439
|
-
function parseHeaders(str?: string): Record<string, string> {
|
|
440
|
-
if (!str) return {};
|
|
441
|
-
return Object.fromEntries(
|
|
442
|
-
str
|
|
443
|
-
.split(",")
|
|
444
|
-
.map((kv) => {
|
|
445
|
-
const [k, ...v] = kv.split("=");
|
|
446
|
-
return [k.trim(), v.join("=").trim()] as const;
|
|
447
|
-
})
|
|
448
|
-
.filter(([k]) => k.length > 0),
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function numericEnv(value: unknown, fallback: number): number {
|
|
453
|
-
const n = Number(value);
|
|
454
|
-
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function cryptoRandomId(): string {
|
|
458
|
-
// crypto.randomUUID is universally available in CF Workers + Node 19+.
|
|
459
|
-
try {
|
|
460
|
-
return crypto.randomUUID();
|
|
461
|
-
} catch {
|
|
462
|
-
return `inst-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
239
|
+
const DECO_RUNTIME_VERSION = "5.0.0";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder for a future OTel-collector based exporter that ships
|
|
3
|
+
* logs / metrics / traces from this Worker to a co-deployed OTel Collector
|
|
4
|
+
* gateway, which in turn batches into ClickHouse.
|
|
5
|
+
*
|
|
6
|
+
* **NOT IMPLEMENTED in 5.0.0.** The framework today defaults to CF-native
|
|
7
|
+
* observability: logs and traces appear in the Cloudflare dashboard
|
|
8
|
+
* (`observability.{logs,traces}` in `wrangler.jsonc`), and metrics go to
|
|
9
|
+
* Workers Analytics Engine. When the platform-side ClickHouse + collector
|
|
10
|
+
* deployment lands, this file gets the real OTLP/HTTP exporter wiring back
|
|
11
|
+
* — mirror the implementation that lived in `src/sdk/otelAdapters.ts` and
|
|
12
|
+
* `src/sdk/sampler.ts` at the pre-5.0.0 git tag.
|
|
13
|
+
*
|
|
14
|
+
* The expected shape (`endpoint`, optional `headers`, optional `resource`,
|
|
15
|
+
* `exportIntervalMillis`) is recorded here so site-side bootstrapping code
|
|
16
|
+
* can compile against the future surface today and we don't churn the
|
|
17
|
+
* `OtelOptions` type when the implementation lands.
|
|
18
|
+
*
|
|
19
|
+
* Reference: https://clickhouse.com/docs/observability/integrating-opentelemetry
|
|
20
|
+
*
|
|
21
|
+
* Expected wiring once implemented:
|
|
22
|
+
* - Worker → OTLP/HTTP → OTel Collector (gateway, per region)
|
|
23
|
+
* - Collector pipelines:
|
|
24
|
+
* receivers[otlp] → processors[batch, memory_limiter] → exporters[clickhouse]
|
|
25
|
+
* - The Worker uses the collector URL as `endpoint`, NOT a direct
|
|
26
|
+
* ClickHouse URL. The collector owns the credentials, retry policy,
|
|
27
|
+
* and schema mapping.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export interface ClickhouseCollectorOptions {
|
|
31
|
+
/**
|
|
32
|
+
* OTel Collector OTLP/HTTP endpoint, e.g. `https://otel-collector.internal`.
|
|
33
|
+
* The Worker never talks to ClickHouse directly — it talks to the collector.
|
|
34
|
+
*/
|
|
35
|
+
endpoint: string;
|
|
36
|
+
/** Optional bearer token / mTLS identity headers — collector-defined. */
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Resource attributes to stamp on every emitted record. Keep narrow
|
|
40
|
+
* (`service.name`, `deco.runtime.version`, `deployment.environment`) —
|
|
41
|
+
* higher-cardinality attributes belong on individual spans / log records.
|
|
42
|
+
*/
|
|
43
|
+
resource?: Record<string, string>;
|
|
44
|
+
/** Periodic metric reader push interval in ms. Defaults to 60_000 once wired. */
|
|
45
|
+
exportIntervalMillis?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns nothing today — throws to make accidental usage loud and obvious.
|
|
50
|
+
*
|
|
51
|
+
* The intended return shape is `{ logger: LoggerAdapter, meter: MeterAdapter,
|
|
52
|
+
* tracer: TracerAdapter, flush: () => Promise<void> }` so the wiring inside
|
|
53
|
+
* `instrumentWorker` can compose this with the AE meter (dual-emit) and the
|
|
54
|
+
* default console logger. None of that ships in 5.0.0.
|
|
55
|
+
*/
|
|
56
|
+
export function createClickhouseCollectorAdapter(_options: ClickhouseCollectorOptions): never {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"createClickhouseCollectorAdapter is not implemented in @decocms/start@5.x. " +
|
|
59
|
+
"ClickHouse + OTel-collector integration is on the roadmap. " +
|
|
60
|
+
"Until then, use Cloudflare-native observability " +
|
|
61
|
+
"(observability.{logs,traces} in wrangler.jsonc) plus the Workers " +
|
|
62
|
+
"Analytics Engine meter wired by instrumentWorker(). Track progress " +
|
|
63
|
+
"at https://github.com/decocms/deco-start/issues.",
|
|
64
|
+
);
|
|
65
|
+
}
|