diff --git a/apps/web/src/features/block-md/definition.ts b/apps/web/src/features/block-md/definition.ts index 5656d60edcc..f36dbc2cab1 100644 --- a/apps/web/src/features/block-md/definition.ts +++ b/apps/web/src/features/block-md/definition.ts @@ -53,6 +53,9 @@ export const definition = defineBlock({ registerDocumentSpan(documentId, rootSpan); } return rootSpan.span('doc.load', async (loadSpan) => { + // The long-lived root may not be retained, so keep completed loads + // independently searchable by document. + loadSpan.setAttr('document.id', documentId); const loadBundle = () => loadSpan.span('doc.load.bundle', async (bundleSpan) => { const result = await fetchDocumentLoadBundle(documentId); diff --git a/packages/observability/src/span.ts b/packages/observability/src/span.ts index 366e37ab7a5..7a0acc8c3a0 100644 --- a/packages/observability/src/span.ts +++ b/packages/observability/src/span.ts @@ -99,10 +99,26 @@ export class SpanImpl implements Span { } private static normalizeError(error: unknown): Error | string { - return error instanceof Error || - typeof error === "string" || - (typeof error === "object" && error !== null && "message" in error) - ? (error as Error | string) - : String(error); + if (error instanceof Error || typeof error === "string") return error; + + if ( + typeof error === "object" && + error !== null && + "message" in error && + typeof error.message === "string" + ) { + return new Error(error.message); + } + + if (typeof error === "object" && error !== null) { + try { + const message = JSON.stringify(error); + if (message !== undefined) return new Error(message); + } catch { + return new Error("Unserializable thrown object"); + } + } + + return new Error(String(error)); } } diff --git a/packages/observability/src/telemetry.test.ts b/packages/observability/src/telemetry.test.ts index 65820b54f3c..055b45bbbf4 100644 --- a/packages/observability/src/telemetry.test.ts +++ b/packages/observability/src/telemetry.test.ts @@ -172,6 +172,20 @@ describe("Telemetry", () => { expect(exported?.events[0]?.name).toBe("exception"); }); + test("records structured non-Error values", () => { + const span = Telemetry.span("edit.apply"); + span.error([{ code: "ImportFailed", detail: "missing dependency" }]); + span.end(); + + const [exported] = spanExporter.getFinishedSpans(); + expect(exported?.status.message).toBe( + '[{"code":"ImportFailed","detail":"missing dependency"}]', + ); + expect(exported?.events[0]?.attributes?.["exception.message"]).toBe( + '[{"code":"ImportFailed","detail":"missing dependency"}]', + ); + }); + test("emits structured logs", () => { Telemetry.warn("WAL flush not acked", { "document.id": "doc-1", diff --git a/services/sync-service/wrangler.docker.toml b/services/sync-service/wrangler.docker.toml index 68c13429603..845cb99b4a4 100644 --- a/services/sync-service/wrangler.docker.toml +++ b/services/sync-service/wrangler.docker.toml @@ -95,7 +95,7 @@ name = "sync-service-prod2" [env.prod.vars] INTERNAL_API_SECRET_KEY = "SYNC_SERVICE_KEY_PROD" -ENVIRONMENT = "production" +ENVIRONMENT = "prod" SPS_URL = "https://search-processing.macro.com" [env.prod.durable_objects] diff --git a/services/sync-service/wrangler.toml b/services/sync-service/wrangler.toml index f80ba658cd5..ff7c8a1894c 100644 --- a/services/sync-service/wrangler.toml +++ b/services/sync-service/wrangler.toml @@ -3,7 +3,7 @@ main = "build/worker/shim.mjs" compatibility_date = "2025-03-05" [build] -command = "cargo install -q worker-build@0.8.4 --locked && worker-build --profile sync-service-release" +command = "cargo install -q worker-build@0.8.4 --locked --force && worker-build --profile sync-service-release" [observability.logs] enabled = true @@ -138,7 +138,7 @@ name = "sync-service-prod2" [env.prod.vars] INTERNAL_API_SECRET_KEY = "SYNC_SERVICE_KEY_PROD" -ENVIRONMENT = "production" +ENVIRONMENT = "prod" # The proxy injects the Datadog key; the exporter appends the signal path. OTEL_EXPORTER_OTLP_ENDPOINT = "https://macro-prox-prod.macroverse.workers.dev/i/otlp" SPS_URL = "https://search-processing.macro.com"