Correlation Ids
Cross-service correlation is handled by automatic W3C
traceparentpropagation (useW3CTraceContext(app), in@benzene/diagnostics), which continues a distributed trace from the incomingtraceparentheader on every transport. See Common Middleware for that surface. This page covers the smaller, still-usefulICorrelationId— a per-invocation marker you can put in your logs and forward to downstream calls.
Overview
ICorrelationId (@benzene/abstractions) tracks a single correlation id for the current
invocation. It is a scoped service — one instance per request — with two methods:
export interface ICorrelationId {
set(correlationId: string): void;
get(): string;
}
The default implementation, CorrelationId (@benzene/diagnostics), self-generates a value with
crypto.randomUUID() when it is constructed, so get() always returns a non-empty id even if
nothing ever calls set(). That makes it a zero-configuration per-invocation marker for logs.
set() only overwrites the id with a non-empty value — passing '' or undefined leaves the
generated id in place.
This is the TypeScript equivalent of .NET's
Guid.NewGuid().ToString()seed:crypto.randomUUID()(from the built-innode:crypto) produces an RFC 4122 v4 UUID in the same canonical string form.
Installation
npm install @benzene/diagnostics
ICorrelationId itself lives in @benzene/abstractions (already a transitive dependency of the
core packages); @benzene/diagnostics supplies the CorrelationId implementation and the
registration/log-scope helpers.
Adding the correlation id to your logs
The correlation extensions are exported under the CorrelationExtensions namespace (TypeScript has
no extension methods, so the C# Extensions static class becomes a namespaced set of free
functions). withCorrelationId attaches the current correlationId to the request's logging scope,
registering the CorrelationId service for the pipeline if it is not already:
import { CorrelationExtensions } from '@benzene/diagnostics';
app.useLogResult((x) => CorrelationExtensions.withCorrelationId(x));
useLogResult is a pipeline-builder member; the action it passes you is an
ILogContextBuilder<TContext>, and withCorrelationId adds a correlationId field to every log
scope created for the rest of the pipeline. With nothing populating it, the id is the
self-generated UUID — a stable marker that ties together all the log lines of one invocation.
Populating it from a custom source
To seed the id from your own source (for example, a partner's proprietary request-id header),
register the service with addCorrelationId at startup and call ICorrelationId.set(...) from your
own middleware:
import { addBenzene } from '@benzene/core-message-handlers';
import { InlineAwsLambdaStartUp } from '@benzene/aws-lambda-core';
import { CorrelationExtensions } from '@benzene/diagnostics';
import { ICorrelationId } from '@benzene/abstractions';
import { IMessageHeadersGetter } from '@benzene/abstractions-messages';
// startup
const entryPoint = new InlineAwsLambdaStartUp()
.configureServices((services) => {
addBenzene(services);
CorrelationExtensions.addCorrelationId(services);
})
.configure((app) => {
app.useFn('PartnerCorrelation', async (context, next, resolver) => {
const headers = resolver.getService(IMessageHeadersGetter) as unknown as IMessageHeadersGetter<typeof context>;
const partnerId = CorrelationExtensions.getHeader(headers, context, 'x-partner-request-id');
resolver.getService(ICorrelationId).set(partnerId);
await next();
});
// ... the rest of your pipeline
})
.build();
getHeader(headersGetter, context, keys) is the ported header lookup: it matches header keys
case-insensitively, skips empty values, and — when given a list — returns the first key that is
present, in the order you give them. It returns '' when none match, which set() then ignores,
so the self-generated id survives a missing header:
// try a partner header first, then fall back to a standard one
const id = CorrelationExtensions.getHeader(headers, context, ['x-partner-request-id', 'x-correlation-id']);
Because addCorrelationId registers the service as scoped, each invocation gets its own
CorrelationId, so setting it in one request never bleeds into another.
Forwarding it to outbound calls
An outbound message client can stamp the current invocation's correlation id onto every request it
sends. withCorrelationId on a ClientBuilder (@benzene/clients) wraps the client in a decorator
that copies ICorrelationId.get() into the outgoing headers before delegating:
import { withCorrelationId } from '@benzene/clients';
// on your outbound client builder
withCorrelationId(clientBuilder);
By default the value is written under the correlationId header key; the underlying
CorrelationIdBenzeneMessageClient takes an optional third constructor argument to use a different
key. Existing headers on the request are preserved — only the correlation header is added.
The default key is
correlationId(not thex-correlation-idstring the .NET docs mention); pass a custom key to theCorrelationIdBenzeneMessageClientconstructor if you need a specific wire header.
See Also
- Common Middleware — W3C
traceparentpropagation (useW3CTraceContext) and the other ready-made middleware. - Middleware — writing the custom
useFnmiddleware used above. - Cookbooks — end-to-end recipes, including request correlation.
- Porting conventions — how C#
Guid/AsyncLocal/extension methods map to the TypeScript shapes shown here.