PATTERN Cited by 1 source
Framework-exposed tracing API for renderer developers¶
Problem¶
Custom client-side operations (button clicks, filter applies, lazy-load AJAX calls, form submits) are the most interesting things to trace on the web — they represent actual user intent and are the load-bearing events for CBOs at browser altitude. But:
- Frontend developers are often not OTel experts; asking them to import OpenTelemetry, create tracers, manage spans, and remember to close them is friction that leads to skipped instrumentation.
- The browser has no async-context propagation primitive
the way Node.js has
AsyncLocalStorage, so manual span passing through function parameters is the default (see concepts/async-context-propagation). - Fleet-wide conventions (span naming, attribute tagging, error-tag semantics) drift if each frontend team rolls their own.
Solution¶
The web framework itself exposes a tracing API as a
first-class developer tool, scoped to the unit of composition
the framework already has (a renderer, component, view,
handler — whatever the framework's atomic unit is). The API
returns a span object with addTags() / finish() methods
that developers can use to annotate their own operations
without knowing OTel.
Zalando's Rendering Engine instantiation (Source: sources/2024-07-28-zalando-opentelemetry-for-javascript-observability-at-zalando):
export default view()
.withQueries(/* ... */)
.withProcessDependencies(/* ... */)
.withRender(props => {
const traceAs = props.tools.observability.traceAs;
// props.tools.observability has tools related to client-
// side observability
const fetchFilteredProducts = (filter: string) => {
const span = traceAs("fetch_filtered_products");
span.addTags({ href: window.href });
serviceClient.get(`/search?q=${filter}`)
.then(res => {
// process response
})
.catch(err => {
span.addTags({ error: true });
})
.finally(() => {
span.finish();
});
};
return (
<div>
<button onClick={() => fetchFilteredProducts("shoes")}>
Fetch Shoes
</button>
</div>
);
})
Key properties:
traceAs("op_name")is a one-function entry point; the developer doesn't import OTel or know what a tracer is.- The returned
spanobject has a minimal surface:addTags()andfinish()(and error-tagging implicitly via.catchblock) — a subset of OTel's API chosen for frontend ergonomics. - The span is created in the framework's context so it gets automatic parent linkage to the page-load span.
- The framework controls the exporter config, the consent gate (concepts/gdpr-consent-gated-telemetry), the propagation format — all invisible to the developer.
When to use this pattern¶
- You own a web framework (internal or public) and want to drive adoption of client-side tracing without training every frontend engineer.
- You have fleet-wide conventions (span naming, attributes, error semantics) you want to enforce centrally.
- You already have a composition unit (renderer, component, handler, resolver) the API can hang off of.
When not to use this pattern¶
- You don't own a framework — there's no natural hook point to expose the API.
- Your application's tracing is already set up end-to-end via OTel directly and developers are comfortable with it — the wrapper adds a layer for no gain.
- Your scope is a small number of handcrafted pages where direct OTel use is simpler than building an abstraction.
Failure modes¶
- Span leaks from forgotten
finish()calls — developers usetraceAsbut forget to callfinish()in some error paths. Unbounded span accumulation can memory-leak the page. Framework should ideally wire inAbortController/ cleanup on component unmount. - Attribute naming drift — without an enforced
vocabulary the
addTags({ foo: bar })calls produce undifferentiated attribute soup across the fleet. Pair this pattern with a published list of standard attribute names. - Over-instrumentation — because the API is easy, developers may instrument trivial operations that add noise without signal. A naming review during code review is the only forcing function.
Sibling patterns¶
- patterns/observability-sdk-as-zalando-specific-wrapper — the SDK-level wrapper this framework-level API sits on top of.
- patterns/manual-span-passing-over-async-context — the span-passing convention the framework API inherits by returning a span object instead of relying on async context.
Seen in¶
- sources/2024-07-28-zalando-opentelemetry-for-javascript-observability-at-zalando
— canonical Zalando Rendering Engine instantiation,
props.tools.observability.traceAs("op_name")exposed per renderer.