diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index 49ef0becde5..6b715ca6d20 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -127,3 +127,25 @@ deno task start Open `http://localhost:16686` to browse traces. You'll see each request broken down into its middleware, handler, and rendering spans. + +## Client-side trace correlation + +When an OpenTelemetry exporter is active, Fresh automatically injects a +[W3C Trace Context](https://www.w3.org/TR/trace-context/) `` tag into the +`` of every rendered page: + +```html + + + + +``` + +This allows client-side OpenTelemetry instrumentation (such as +[`@opentelemetry/instrumentation-document-load`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/c9d62be989802534c01373e8ab41e13747d7ee3e/packages/instrumentation-document-load)) +to link browser performance traces back to the server-side span that rendered +the page, giving you end-to-end visibility from server rendering through page +load. diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index 320439bb97e..535a8945a87 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -35,6 +35,7 @@ import { getCodeFrame } from "../../dev/middlewares/error_overlay/code_frame.ts" import { escapeScript } from "../../utils.ts"; import { HeadContext } from "../head.ts"; import { useContext } from "preact/hooks"; +import { isSpanContextValid, trace } from "@opentelemetry/api"; interface InternalPreactOptions extends PreactOptions { [OptionsType.ATTR](name: string, value: unknown): string | void; @@ -282,6 +283,22 @@ options[OptionsType.DIFF] = (vnode) => { } } + // Inject W3C traceparent meta tag when OpenTelemetry is active, + // enabling client-side tracing to connect to the server span. + const activeSpan = trace.getActiveSpan(); + if (activeSpan) { + const spanCtx = activeSpan.spanContext(); + if (isSpanContextValid(spanCtx)) { + const flags = (spanCtx.traceFlags & 1) ? "01" : "00"; + const traceparent = + `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`; + items.push( + // deno-lint-ignore no-explicit-any + h("meta", { name: "traceparent", content: traceparent }) as any, + ); + } + } + // deno-lint-ignore no-explicit-any items.push(h(RemainingHead, null) as VNode);