Skip to content

Map ParentId to MT-Activity-Id in MassTransit interop mode#2373

Merged
jeremydmiller merged 3 commits intoJasperFx:mainfrom
outofrange-consulting:fix/masstransit-interop-trace-propagation
Mar 30, 2026
Merged

Map ParentId to MT-Activity-Id in MassTransit interop mode#2373
jeremydmiller merged 3 commits intoJasperFx:mainfrom
outofrange-consulting:fix/masstransit-interop-trace-propagation

Conversation

@outofrange-consulting
Copy link
Copy Markdown

@outofrange-consulting outofrange-consulting commented Mar 30, 2026

Summary

When UseMassTransitInterop() is enabled on an endpoint, distributed traces break across Wolverine ↔ MassTransit because the two frameworks use different header keys for W3C trace context propagation:

Framework Header key
Wolverine parent-id
MassTransit MT-Activity-Id

InteropWithMassTransit() already remaps MessageTypeMT-MessageType and ReplyUriMT-Response-Address, but it does not remap ParentIdMT-Activity-Id. Both directions start a new root trace instead of continuing the caller's trace.

Fix

ASB / RabbitMQ (via EnvelopeMapper)

Add one line to InteropWithMassTransit() in EnvelopeMapper.cs:

MapPropertyToHeader(x => x.ParentId!, MassTransitHeaders.ActivityId);

This replaces the default parent-id mapping with MT-Activity-Id when interop mode is active — the same pattern already used for MessageType.

SQS / SNS (standalone MassTransitMapper)

The SQS and SNS transports use standalone MassTransitMapper classes that bypass EnvelopeMapper entirely. Trace context is propagated via MessageAttributes instead:

  • Outgoing: Write Envelope.ParentId as an MT-Activity-Id MessageAttribute
  • Incoming: Read the MT-Activity-Id MessageAttribute into Envelope.ParentId

Also added InternalsVisibleTo("Wolverine.AmazonSns") so the SNS transport can access MassTransitHeaders.

How it works

  • Outgoing (Wolverine → MassTransit): Envelope.ParentId is written as MT-Activity-Id in transport headers/attributes. MassTransit's GetParentActivityContext() reads it and continues the trace.
  • Incoming (MassTransit → Wolverine): MT-Activity-Id is read from transport headers/attributes into Envelope.ParentId. Wolverine's WolverineTracing.StartEnvelopeActivity() uses ParentId to create the Activity with the correct parent span.
  • Outbox-safe: Envelope.ParentId is already persisted through the PostgreSQL outbox, so the mapped value survives the outbox round-trip.

Files changed

File Change
MassTransitHeaders.cs Add ActivityId = "MT-Activity-Id" constant
EnvelopeMapper.cs Map ParentId to MT-Activity-Id in InteropWithMassTransit()
AssemblyAttributes.cs Add InternalsVisibleTo("Wolverine.AmazonSns")
Wolverine.AmazonSqs/Internal/MassTransitMapper.cs Propagate MT-Activity-Id via SQS MessageAttributes
Wolverine.AmazonSns/Internal/MassTransitMapper.cs Propagate MT-Activity-Id via SNS MessageAttributes

Verified

Tested end-to-end with a .NET 10 Aspire PoC using both frameworks over Azure Service Bus (emulator). Before the fix, each side started independent traces. After the fix, all four use cases (event publish, command send, in both directions) produce a single connected trace visible in the Aspire dashboard.

Test plan

  • Existing MassTransitEnvelopeTests pass (7/7 on net9.0 and net10.0)
  • Wolverine.csproj, Wolverine.AzureServiceBus.csproj, Wolverine.AmazonSqs.csproj, Wolverine.AmazonSns.csproj all build cleanly
  • End-to-end verification over ASB: Wolverine → MassTransit and MassTransit → Wolverine both share the same traceId
  • RabbitMQ transport interop (same EnvelopeMapper.InteropWithMassTransit code path)
  • SQS/SNS transport interop (separate MassTransitMapper, now with attribute propagation)

Geoffrey MARC added 3 commits March 30, 2026 11:47
When UseMassTransitInterop() is enabled, Wolverine writes trace context
as `parent-id` in transport headers but MassTransit expects `MT-Activity-Id`.
This breaks distributed trace propagation across the two frameworks.

Remap the ParentId property to use MassTransit's `MT-Activity-Id` header
key when interop mode is active, matching what InteropWithMassTransit
already does for MessageType and ResponseAddress.
The SQS/SNS transports use standalone MassTransitMapper classes that
bypass EnvelopeMapper entirely, so the ParentId → MT-Activity-Id
header remapping does not apply to them.

- SQS/SNS MassTransitMapper: write ParentId as MT-Activity-Id in
  outgoing MessageAttributes and read it back on incoming messages
- MassTransitEnvelope: include ParentId in envelope body headers
  (outgoing) and restore it from body headers (incoming) as a
  fallback for any transport
- AssemblyAttributes: add InternalsVisibleTo for Wolverine.AmazonSns
  so it can reference MassTransitHeaders
The trace context is already propagated at the transport level:
- ASB/RabbitMQ via EnvelopeMapper (ApplicationProperties/AMQP headers)
- SQS/SNS via MassTransitMapper (MessageAttributes)

The envelope body changes were redundant with those, and the incoming
side ran too late (after Activity creation) to be effective anyway.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants