Skip to content

Otel improvements#7798

Draft
ramonsmits wants to merge 9 commits into
masterfrom
otel
Draft

Otel improvements#7798
ramonsmits wants to merge 9 commits into
masterfrom
otel

Conversation

@ramonsmits

Copy link
Copy Markdown
Member

No description provided.

/// <summary>
/// Controls which message payloads are promoted to span attributes.
/// </summary>
public enum MessagePayloadAsTag

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we want an option to only log the payload on error

/// as <c>nservicebus.message.{PropertyName}</c>.
/// Defaults to <see cref="MessagePayloadAsTag.None"/>. May expose sensitive data and incurs reflection cost.
/// </summary>
public MessagePayloadAsTag MessagePayloadAsTag { get; set; }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to split this in 2 properties one for incoming and one for outgoing.

/// as <c>nservicebus.message.{PropertyName}</c>.
/// Defaults to <see cref="MessagePayloadAsTag.None"/>. May expose sensitive data and incurs reflection cost.
/// </summary>
public MessagePayloadAsTag MessagePayloadAsTag { get; set; }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to have a similar option to log the payload in our regular logs?

using System.Threading.Tasks;
using Pipeline;

class IncomingMessagePayloadToTagsBehavior : IBehavior<IIncomingLogicalMessageContext, IIncomingLogicalMessageContext>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a behavior? Aren't we already settings tags already in core? If so, that likely can be extended instead.

{
var json = JsonSerializer.Serialize(instance, instance.GetType());
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
activity.SetTag("nservicebus.message.body", base64);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it should always use base64. If the payload if text, then it should just be utf8.

Second, we should NOT do extra (de)serialization. We should do this where we already have access to byte[] and then write this either as utf8 string or base64 string. To decide that we have probe for \0, force if via config (log payload as base64 or utf8), or assume utf8, catch exception on GetBytes and fallback to base64 encoding, maybe even another option.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI As part of the assembly scanning efforts we have tried to sort as much as possible the paths that require dynamically referenced code. The JSonSerializer usage here would regress on that because it introduces another method usage that is not trimming friendly

[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
public static string Serialize(object? value, Type inputType, System.Text.Json.JsonSerializerOptions? options = default);

we are planning to add some kind of "regression" approval soon that should capture things like that in the future

While I'm here, I would like to add something else. I’m concerned about adding the full message body as an activity tag.

The OTel messaging semantic conventions define messaging.message.body.size, but that is only the size of the body in bytes, not the body content itself. I don’t see a standard semantic convention for attaching the full message payload to a span attribute.

Adding the full body as nservicebus.message.body, even base64 encoded, seems risky:

  • high-cardinality span attributes
  • potentially large telemetry payloads
  • possible PII/secrets leakage
  • increased indexing/storage cost in observability backends
  • base64 does not provide sanitization

But I'm not in the weeds as you are so I might miss important context.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI #7800

Comment thread src/NServiceBus.Core/OpenTelemetry/MessagePayloadToTagsBehaviors.cs Outdated
Comment on lines +71 to +80
// Append destination to span name per OTel messaging spec: {operation} {destination}
// Only for send/reply (unicast); publish destination is already set at span creation.
if (activityFactory.Options.UseMessageDestinationInSpanNames
&& operations.Length > 0
&& operations[0].AddressTag is UnicastAddressTag unicastTag
&& outgoingMessage.Headers.TryGetValue(Headers.MessageIntent, out var intentStr)
&& intentStr is "Send" or "Reply")
{
activity.DisplayName = $"{activity.DisplayName} {unicastTag.Destination}";
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This likely should be moved to the ActivityDecorator

&& operations.Length > 0
&& operations[0].AddressTag is UnicastAddressTag unicastTag
&& outgoingMessage.Headers.TryGetValue(Headers.MessageIntent, out var intentStr)
&& intentStr is "Send" or "Reply")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THis should not use magic strings

MergeDispatchProperties(publishContext, options.DispatchProperties);

using var activity = activityFactory.StartOutgoingPipelineActivity(ActivityNames.OutgoingEventActivityName, ActivityDisplayNames.PublishEvent, publishContext);
var publishDisplayName = activityFactory.Options.UseMessageDestinationInSpanNames

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This likely should be in ActivityDecorator


activity.DisplayName = ActivityDisplayNames.ProcessMessage;
activity.DisplayName = Options.UseMessageDestinationInSpanNames
? $"{ActivityDisplayNames.ProcessOperation} {context.ReceiveAddress}"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we append the incoming message type? Like the 1st type from EnclodedMessageTypes?

irinascurtu and others added 5 commits June 10, 2026 14:15
…opagator (#7820)

* initial migration to the DistributedContextPropagator for W3C compatibility

* new propagator with backwards compatiblity tests

* fixing the startnewtrace header progagation

* refactor ContextPropagation.cs

* small tweak

* warning fixes

* att tests reflect supported baggage and tracestate formats
Activity baggage with a null value used to throw ArgumentNullException
during context propagation (Uri.EscapeDataString(null)). The switch to
DistributedContextPropagator on this branch fixes the root cause; this
test pins the behavior so it cannot regress.
…ards compatible until v11) (#7825)

* ✨ Make DistributedContextPropagator opt-in via AppContext switch

The switch to System.Diagnostics.DistributedContextPropagator changes the
OpenTelemetry baggage wire format (W3C OWS encoding + whitespace trimming),
which is breaking on rolling upgrades. Keep the legacy percent-encoded
propagator as the default and gate the new propagator behind the
NServiceBus.Core.OpenTelemetry.UseDistributedContextPropagator AppContext
switch (default in v11).

All temporary code (switch plumbing + legacy propagator) lives in
obsolete_v11.cs so v11 cleanup is a single file deletion plus removing the two
delegation blocks in ContextPropagation.cs. Follows the existing
AppContextSwitches.UseV2DeterministicGuid / PreObsolete pattern.

- ContextPropagation: delegate to the legacy propagator unless the switch is on
- obsolete_v11.cs: switch + byte-for-byte revert of the pre-10.3 propagator
- Tests asserting the new W3C format enable the switch per-fixture
- New ContextPropagationDefaultBehaviorTests locks in legacy default behavior
- Acceptance baggage assertion reverted to the legacy default wire format

Span naming (UseMessageDestinationInSpanNames) is already opt-in and unchanged.

* Refactor: Replace `ObsoleteV11` with `LegacyContextPropagation`

* Add test to verify null baggage value does not throw in legacy propagator

* Added comments about preserve legacy baggage handling behavior when escaping and trimming values

* Add test to verify that we are preserving whitespace in legacy propagator baggage values.

* add comments to clarify intent behind legacy propagator handling

* update ContextPropagationCompatibilityTests to use correct LegacyContextPropagation delegates

* rename ContextPropagationTests to LegacyContextPropagationTests and remove outdated baggage handling tests

* allow changing the propagator implementation at runtime

---------

Co-authored-by: Tomasz Masternak <tomasz.masternak@gmail.com>
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.

4 participants