Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ OpenTelemetry.Instrumentation.ServiceFabricRemoting.ServiceRemotingMessageDispat
OpenTelemetry.Instrumentation.ServiceFabricRemoting.ServiceRemotingMessageDispatcherAdapter.HandleRequestResponseAsync(Microsoft.ServiceFabric.Services.Remoting.V2.Runtime.IServiceRemotingRequestContext! requestContext, Microsoft.ServiceFabric.Services.Remoting.V2.IServiceRemotingRequestMessage! requestMessage) -> System.Threading.Tasks.Task<Microsoft.ServiceFabric.Services.Remoting.V2.IServiceRemotingResponseMessage!>!
OpenTelemetry.Instrumentation.ServiceFabricRemoting.ServiceRemotingMessageDispatcherAdapter.ServiceRemotingMessageDispatcherAdapter(Microsoft.ServiceFabric.Services.Remoting.V2.Runtime.IServiceRemotingMessageHandler! dispatcher) -> void
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute.RemotingExceptionDepth.get -> int
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute.RemotingExceptionDepth.set -> void
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute.TraceContextEnrichedActorRemotingProviderAttribute() -> void
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingClientFactoryAdapter
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingClientFactoryAdapter.ClientConnected -> System.EventHandler<Microsoft.ServiceFabric.Services.Communication.Client.CommunicationClientEventArgs<Microsoft.ServiceFabric.Services.Remoting.V2.Client.IServiceRemotingClient!>!>!
Expand All @@ -27,6 +29,8 @@ OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceR
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingClientFactoryAdapter.ReportOperationExceptionAsync(Microsoft.ServiceFabric.Services.Remoting.V2.Client.IServiceRemotingClient! client, Microsoft.ServiceFabric.Services.Communication.Client.ExceptionInformation! exceptionInformation, Microsoft.ServiceFabric.Services.Communication.Client.OperationRetrySettings! retrySettings, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.ServiceFabric.Services.Communication.Client.OperationRetryControl!>!
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingClientFactoryAdapter.TraceContextEnrichedServiceRemotingClientFactoryAdapter(Microsoft.ServiceFabric.Services.Remoting.V2.Client.IServiceRemotingClientFactory! serviceRemotingClientFactory) -> void
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute.RemotingExceptionDepth.get -> int
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute.RemotingExceptionDepth.set -> void
OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute.TraceContextEnrichedServiceRemotingProviderAttribute() -> void
OpenTelemetry.Trace.TracerProviderBuilderExtensions
override OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute.CreateServiceRemotingClientFactory(Microsoft.ServiceFabric.Services.Remoting.V2.Client.IServiceRemotingCallbackMessageHandler? callbackMessageHandler) -> Microsoft.ServiceFabric.Services.Remoting.V2.Client.IServiceRemotingClientFactory!
Expand All @@ -35,3 +39,7 @@ override OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnriche
override OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute.CreateServiceRemotingListeners() -> System.Collections.Generic.Dictionary<string!, System.Func<System.Fabric.ServiceContext!, Microsoft.ServiceFabric.Services.Remoting.IService!, Microsoft.ServiceFabric.Services.Remoting.Runtime.IServiceRemotingListener!>!>!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddServiceFabricRemotingInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddServiceFabricRemotingInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder, System.Action<OpenTelemetry.Instrumentation.ServiceFabricRemoting.ServiceFabricRemotingInstrumentationOptions!>? configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
virtual OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute.GetClientExceptionConvertors() -> System.Collections.Generic.IEnumerable<Microsoft.ServiceFabric.Services.Remoting.V2.Client.IExceptionConvertor!>?
virtual OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedActorRemotingProviderAttribute.GetServiceExceptionConvertors() -> System.Collections.Generic.IEnumerable<Microsoft.ServiceFabric.Services.Remoting.V2.Runtime.IExceptionConvertor!>?
virtual OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute.GetClientExceptionConvertors() -> System.Collections.Generic.IEnumerable<Microsoft.ServiceFabric.Services.Remoting.V2.Client.IExceptionConvertor!>?
virtual OpenTelemetry.Instrumentation.ServiceFabricRemoting.TraceContextEnrichedServiceRemotingProviderAttribute.GetServiceExceptionConvertors() -> System.Collections.Generic.IEnumerable<Microsoft.ServiceFabric.Services.Remoting.V2.Runtime.IExceptionConvertor!>?
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.CodeAnalysis;
using Microsoft.ServiceFabric.Actors.Generator;
using Microsoft.ServiceFabric.Actors.Remoting.FabricTransport;
using Microsoft.ServiceFabric.Actors.Remoting.V2.FabricTransport.Client;
Expand All @@ -11,14 +12,21 @@
using Microsoft.ServiceFabric.Services.Remoting.FabricTransport.Runtime;
using Microsoft.ServiceFabric.Services.Remoting.Runtime;
using Microsoft.ServiceFabric.Services.Remoting.V2.Client;
using ClientExceptionConvertor = Microsoft.ServiceFabric.Services.Remoting.V2.Client.IExceptionConvertor;
using RuntimeExceptionConvertor = Microsoft.ServiceFabric.Services.Remoting.V2.Runtime.IExceptionConvertor;

namespace OpenTelemetry.Instrumentation.ServiceFabricRemoting;

/// <summary>
/// Sets fabric TCP transport as the default remoting provider for the actors.
/// </summary>
/// <remarks>
/// This type is not sealed so that applications can derive from it to register custom exception convertors by
/// overriding <see cref="GetServiceExceptionConvertors"/> and <see cref="GetClientExceptionConvertors"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class TraceContextEnrichedActorRemotingProviderAttribute : FabricTransportActorRemotingProviderAttribute
[SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "The attribute is designed to be derived from, so that applications can register custom exception convertors.")]
public class TraceContextEnrichedActorRemotingProviderAttribute : FabricTransportActorRemotingProviderAttribute
{
private const string DefaultV2listenerName = "V2Listener";

Expand All @@ -31,6 +39,12 @@ public TraceContextEnrichedActorRemotingProviderAttribute()
this.RemotingListenerVersion = Microsoft.ServiceFabric.Services.Remoting.RemotingListenerVersion.V2;
}

/// <summary>
/// Gets or sets the maximum number of levels of inner exceptions that are serialized when a remoting call fails.
/// When set to a value greater than zero it overrides the Service Fabric default.
/// </summary>
public int RemotingExceptionDepth { get; set; }

/// <summary>
/// Creates a service remoting listener for remoting the actor interfaces.
/// </summary>.
Expand All @@ -47,7 +61,12 @@ public override Dictionary<string, Func<ActorService, IServiceRemotingListener>>
var dispatcherAdapter = new ServiceRemotingMessageDispatcherAdapter(actorServiceRemotingDispatcher);
var listenerSettings = this.InitializeListenerSettings(actorService);

return new FabricTransportActorServiceRemotingListener(actorService, dispatcherAdapter, listenerSettings);
return new FabricTransportActorServiceRemotingListener(
actorService,
dispatcherAdapter,
listenerSettings,
serializationProvider: null,
exceptionConvertors: this.GetServiceExceptionConvertors());
},
};

Expand All @@ -73,11 +92,33 @@ public override IServiceRemotingClientFactory CreateServiceRemotingClientFactory
callbackMessageHandler,
servicePartitionResolver: null,
exceptionHandlers: null,
traceId: null);
traceId: null,
serializationProvider: null,
exceptionConvertors: this.GetClientExceptionConvertors());

return new TraceContextEnrichedServiceRemotingClientFactoryAdapter(fabricTransportActorRemotingClientFactory);
}

/// <summary>
/// Gets the exception convertors that the remoting listener uses to convert the exceptions thrown by the actor
/// implementation into a serializable form. Override this method to support custom exception types.
/// </summary>
/// <remarks>
/// Service Fabric always appends its built-in convertors, so only convertors for custom exception types need to be returned here.
/// </remarks>
/// <returns>The exception convertors to register with the listener, or <see langword="null"/> to register none.</returns>
protected virtual IEnumerable<RuntimeExceptionConvertor>? GetServiceExceptionConvertors() => null;

/// <summary>
/// Gets the exception convertors that the remoting client uses to reconstruct the exceptions thrown by the actor
/// implementation. Override this method to support custom exception types.
/// </summary>
/// <remarks>
/// Service Fabric always appends its built-in convertors, so only convertors for custom exception types need to be returned here.
/// </remarks>
/// <returns>The exception convertors to register with the client factory, or <see langword="null"/> to register none.</returns>
protected virtual IEnumerable<ClientExceptionConvertor>? GetClientExceptionConvertors() => null;

private static FabricTransportRemotingListenerSettings GetActorListenerSettings(ActorService actorService)
{
var sectionName = ActorNameFormat.GetFabricServiceTransportSettingsSectionName(actorService.ActorTypeInformation.ImplementationType);
Expand All @@ -98,6 +139,7 @@ private FabricTransportRemotingListenerSettings InitializeListenerSettings(Actor
listenerSettings.MaxMessageSize = this.GetAndValidateMaxMessageSize(listenerSettings.MaxMessageSize);
listenerSettings.OperationTimeout = this.GetAndValidateOperationTimeout(listenerSettings.OperationTimeout);
listenerSettings.KeepAliveTimeout = this.GetAndValidateKeepAliveTimeout(listenerSettings.KeepAliveTimeout);
listenerSettings.RemotingExceptionDepth = this.GetRemotingExceptionDepth(listenerSettings.RemotingExceptionDepth);

return listenerSettings;
}
Expand All @@ -113,4 +155,7 @@ private TimeSpan GetAndValidateKeepAliveTimeout(TimeSpan keepAliveTimeoutDefault

private TimeSpan GetConnectTimeout(TimeSpan connectTimeoutDefault)
=> (this.ConnectTimeoutInMilliseconds > 0) ? TimeSpan.FromMilliseconds(this.ConnectTimeoutInMilliseconds) : connectTimeoutDefault;

private int GetRemotingExceptionDepth(int remotingExceptionDepthDefault)
=> (this.RemotingExceptionDepth > 0) ? this.RemotingExceptionDepth : remotingExceptionDepthDefault;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## Unreleased

* Added support for registering custom Service Fabric exception convertors, so
that applications can propagate their own exception types across remoting
calls while still being instrumented. Service Fabric SDK 8 (runtime 11) no
longer enables the `BinaryFormatter` fallback for exception serialization by
default, and SDK 9 removes it altogether, so an exception without a
registered convertor reaches the client as a `ServiceException`.
`TraceContextEnrichedServiceRemotingProviderAttribute` and
`TraceContextEnrichedActorRemotingProviderAttribute` are no longer sealed and
expose `GetServiceExceptionConvertors()` / `GetClientExceptionConvertors()`
for this purpose. A `RemotingExceptionDepth` property was also added to
control how many levels of inner exceptions are serialized.

## 1.18.0-beta.1

Released 2026-Aug-21
Expand Down
120 changes: 117 additions & 3 deletions src/OpenTelemetry.Instrumentation.ServiceFabricRemoting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
[![NuGet download count badge](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.ServiceFabricRemoting)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.ServiceFabricRemoting)
[![codecov.io](https://codecov.io/gh/open-telemetry/opentelemetry-dotnet-contrib/branch/main/graphs/badge.svg?flag=unittests-Instrumentation.ServiceFabricRemoting)](https://app.codecov.io/gh/open-telemetry/opentelemetry-dotnet-contrib?flags[0]=unittests-Instrumentation.ServiceFabricRemoting)

This is an [Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/glossary.md#instrumentation-library),
which instruments [Service Fabric Remoting](https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-remoting)
and collects telemetry about incoming requests.
This is an [Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library)
that instruments [Service Fabric Remoting](https://learn.microsoft.com/azure/service-fabric/service-fabric-reliable-services-communication-remoting),
emitting distributed traces for both client (outgoing) and
server (incoming) calls.

## Steps to enable OpenTelemetry.Instrumentation.ServiceFabricRemoting

Expand Down Expand Up @@ -189,6 +190,119 @@ the exception will be recorded at the server as an `ActivityEvent` or not
.Build();
```

## Advanced scenarios

### Propagating custom exception types

Service Fabric serializes the exceptions thrown by a service so that they can be
rethrown on the client. Exception types that Service Fabric does not recognize
are surfaced to the client as a `ServiceException` instead of the original type.
To preserve a custom exception type, register an
[exception convertor](https://learn.microsoft.com/azure/service-fabric/service-fabric-reliable-services-exception-serialization)
on both the service and the client.

This matters from Service Fabric SDK 8 (runtime 11) onwards. Earlier versions
enabled `BinaryFormatter` as a fallback for exception serialization, which
round-tripped any `[Serializable]` exception without extra configuration. As
described in the
[Remoting V1 deprecation strategy](https://github.com/microsoft/service-fabric/blob/master/release_notes/Deprecated/RemotingV1.md),
SDK 8 no longer enables that fallback by default, and SDK 9 (runtime 12) removes
it altogether. A custom exception type therefore arrives at the client as a
`ServiceException` unless a convertor is registered for it. Service Fabric still
handles its own exceptions and a fixed list of system exceptions.

Derive from the provider attribute and override the convertor methods. Service
Fabric appends its own built-in convertors, so only convertors for custom
exception types need to be returned.

```csharp
public sealed class MyRemotingProviderAttribute : TraceContextEnrichedServiceRemotingProviderAttribute
{
protected override IEnumerable<Microsoft.ServiceFabric.Services.Remoting.V2.Runtime.IExceptionConvertor> GetServiceExceptionConvertors()
{
return [new MyExceptionConvertorService()];
}

protected override IEnumerable<Microsoft.ServiceFabric.Services.Remoting.V2.Client.IExceptionConvertor> GetClientExceptionConvertors()
{
return [new MyExceptionConvertorClient()];
}
}
```

Then apply the derived attribute instead of the built-in one:

```csharp
[assembly: MyRemotingProvider]
```

The derived attribute continues to accept the Service Fabric settings inherited
from the base attribute, such as `MaxMessageSize` and the timeout properties.

The same pattern applies to
`TraceContextEnrichedActorRemotingProviderAttribute`.

If the original exception has several levels of inner exceptions, use
`RemotingExceptionDepth` to control how many of them are serialized:

```csharp
[assembly: MyRemotingProvider(RemotingExceptionDepth = 5)]
```

### Composing the adapters manually

The provider attributes are a convenience that build the Service Fabric listener
and client factory for you. Applications that need full control over that
construction, for example to supply a custom serialization provider, exception
handlers or a partition resolver, can skip the attributes and wrap their own
objects with the adapters directly.

On the service, wrap the message handler with
`ServiceRemotingMessageDispatcherAdapter`:

```csharp
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
return
[
new ServiceReplicaListener(serviceContext =>
{
var dispatcher = new ServiceRemotingMessageDispatcher(serviceContext, this);
var dispatcherAdapter = new ServiceRemotingMessageDispatcherAdapter(dispatcher);

return new FabricTransportServiceRemotingListener(
serviceContext,
dispatcherAdapter,
listenerSettings,
serializationProvider: null,
exceptionConvertors: [new MyExceptionConvertorService()]);
},
"ServiceEndpointV2")
];
}
```

On the client, wrap the client factory with
`TraceContextEnrichedServiceRemotingClientFactoryAdapter`:

```csharp
var serviceProxyFactory = new ServiceProxyFactory(callbackClient =>
{
var clientFactory = new FabricTransportServiceRemotingClientFactory(
remotingSettings,
callbackClient,
servicePartitionResolver: null,
exceptionHandlers: null,
traceId: null,
serializationProvider: null,
exceptionConvertors: [new MyExceptionConvertorClient()]);

return new TraceContextEnrichedServiceRemotingClientFactoryAdapter(clientFactory);
});
```

Both adapters provide the same instrumentation as the provider attributes.

## References

- [Azure Service Fabric documentation](https://learn.microsoft.com/en-us/azure/service-fabric/)
Expand Down
Loading