diff --git a/projects/Directory.Packages.props b/projects/Directory.Packages.props
index cf53e6134..26c03fde2 100644
--- a/projects/Directory.Packages.props
+++ b/projects/Directory.Packages.props
@@ -7,9 +7,10 @@
-
+
+
-
@@ -44,4 +44,4 @@
-
+
\ No newline at end of file
diff --git a/projects/RabbitMQ.Client.OpenTelemetry/RabbitMQ.Client.OpenTelemetry.csproj b/projects/RabbitMQ.Client.OpenTelemetry/RabbitMQ.Client.OpenTelemetry.csproj
index fe1af035f..31ddffbee 100644
--- a/projects/RabbitMQ.Client.OpenTelemetry/RabbitMQ.Client.OpenTelemetry.csproj
+++ b/projects/RabbitMQ.Client.OpenTelemetry/RabbitMQ.Client.OpenTelemetry.csproj
@@ -50,6 +50,7 @@
+
diff --git a/projects/RabbitMQ.Client/ConnectionFactory.cs b/projects/RabbitMQ.Client/ConnectionFactory.cs
index 06ed2b23e..97522a4fb 100644
--- a/projects/RabbitMQ.Client/ConnectionFactory.cs
+++ b/projects/RabbitMQ.Client/ConnectionFactory.cs
@@ -31,6 +31,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Security;
@@ -544,17 +545,22 @@ public async Task CreateConnectionAsync(IEndpointResolver endpointR
CancellationToken cancellationToken = default)
{
ConnectionConfig config = CreateConfig(clientProvidedName);
+ using Activity? connectionActivity = RabbitMQActivitySource.OpenConnection(false);
try
{
if (AutomaticRecoveryEnabled)
{
- return await AutorecoveringConnection.CreateAsync(config, endpointResolver, cancellationToken)
+ connectionActivity?.SetTag("messaging.rabbitmq.connection.automatic_recovery", true);
+ return await AutorecoveringConnection.CreateAsync(config, endpointResolver, connectionActivity, cancellationToken)
.ConfigureAwait(false);
}
else
{
+
+ connectionActivity?.SetTag("messaging.rabbitmq.connection.automatic_recovery", false);
IFrameHandler frameHandler = await endpointResolver.SelectOneAsync(CreateFrameHandlerAsync, cancellationToken)
.ConfigureAwait(false);
+ connectionActivity.SetNetworkTags(frameHandler);
var c = new Connection(config, frameHandler);
return await c.OpenAsync(cancellationToken)
.ConfigureAwait(false);
@@ -562,6 +568,8 @@ public async Task CreateConnectionAsync(IEndpointResolver endpointR
}
catch (OperationCanceledException ex)
{
+ connectionActivity?.SetStatus(ActivityStatusCode.Error);
+ connectionActivity?.AddException(ex);
if (cancellationToken.IsCancellationRequested)
{
throw;
@@ -573,7 +581,10 @@ public async Task CreateConnectionAsync(IEndpointResolver endpointR
}
catch (Exception ex)
{
- throw new BrokerUnreachableException(ex);
+ var brokerUnreachableException = new BrokerUnreachableException(ex);
+ connectionActivity?.SetStatus(ActivityStatusCode.Error);
+ connectionActivity?.AddException(brokerUnreachableException);
+ throw brokerUnreachableException;
}
}
diff --git a/projects/RabbitMQ.Client/IEndpointResolverExtensions.cs b/projects/RabbitMQ.Client/IEndpointResolverExtensions.cs
index 74378f893..7ec12edd7 100644
--- a/projects/RabbitMQ.Client/IEndpointResolverExtensions.cs
+++ b/projects/RabbitMQ.Client/IEndpointResolverExtensions.cs
@@ -41,49 +41,39 @@ public static class EndpointResolverExtensions
public static async Task SelectOneAsync(this IEndpointResolver resolver,
Func> selector, CancellationToken cancellationToken)
{
- var t = default(T);
var exceptions = new List();
foreach (AmqpTcpEndpoint ep in resolver.All())
{
cancellationToken.ThrowIfCancellationRequested();
+ using var tcpConnection = RabbitMQActivitySource.OpenTcpConnection();
+ tcpConnection?.SetServerTags(ep);
try
{
- t = await selector(ep, cancellationToken).ConfigureAwait(false);
- if (t!.Equals(default(T)) == false)
- {
- return t;
- }
+ return await selector(ep, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
+ tcpConnection?.AddException(ex);
if (cancellationToken.IsCancellationRequested)
{
throw;
}
- else
- {
- exceptions.Add(ex);
- }
+
+ exceptions.Add(ex);
}
catch (Exception e)
{
+ tcpConnection?.AddException(e);
exceptions.Add(e);
}
}
- if (EqualityComparer.Default.Equals(t!, default!))
+ if (exceptions.Count > 0)
{
- if (exceptions.Count > 0)
- {
- throw new AggregateException(exceptions);
- }
- else
- {
- throw new InvalidOperationException(InternalConstants.BugFound);
- }
+ throw new AggregateException(exceptions);
}
- return t!;
+ throw new InvalidOperationException(InternalConstants.BugFound);
}
}
}
diff --git a/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.Recovery.cs b/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.Recovery.cs
index 7f3439fa6..bb78f374c 100644
--- a/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.Recovery.cs
+++ b/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.Recovery.cs
@@ -31,6 +31,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -243,13 +244,13 @@ await _innerConnection.AbortAsync(Constants.InternalError, "FailedAutoRecovery",
private async ValueTask TryRecoverConnectionDelegateAsync(CancellationToken cancellationToken)
{
Connection? maybeNewInnerConnection = null;
+ using Activity? connectionActivity = RabbitMQActivitySource.OpenConnection(true);
try
{
Connection defunctConnection = _innerConnection;
-
IFrameHandler fh = await _endpoints.SelectOneAsync(_config.FrameHandlerFactoryAsync, cancellationToken)
.ConfigureAwait(false);
-
+ connectionActivity?.SetNetworkTags(fh);
maybeNewInnerConnection = new Connection(_config, fh);
await maybeNewInnerConnection.OpenAsync(cancellationToken)
@@ -267,6 +268,8 @@ await maybeNewInnerConnection.OpenAsync(cancellationToken)
}
catch (Exception e)
{
+ connectionActivity?.AddException(e);
+ connectionActivity?.SetStatus(ActivityStatusCode.Error);
ESLog.Error("Connection recovery exception.", e);
// Trigger recovery error events
if (!_connectionRecoveryErrorAsyncWrapper.IsEmpty)
diff --git a/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.cs b/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.cs
index 817bf0101..af22c0a12 100644
--- a/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.cs
+++ b/projects/RabbitMQ.Client/Impl/AutorecoveringConnection.cs
@@ -31,6 +31,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -89,11 +90,12 @@ Task onExceptionAsync(Exception exception, string context, CancellationToken can
_innerConnection.OnCallbackExceptionAsync(CallbackExceptionEventArgs.Build(exception, context, cancellationToken));
}
- internal static async ValueTask CreateAsync(ConnectionConfig config, IEndpointResolver endpoints,
+ internal static async ValueTask CreateAsync(ConnectionConfig config, IEndpointResolver endpoints, Activity? connectionActivity,
CancellationToken cancellationToken)
{
IFrameHandler fh = await endpoints.SelectOneAsync(config.FrameHandlerFactoryAsync, cancellationToken)
.ConfigureAwait(false);
+ connectionActivity.SetNetworkTags(fh);
Connection innerConnection = new(config, fh);
AutorecoveringConnection connection = new(config, endpoints, innerConnection);
await innerConnection.OpenAsync(cancellationToken)
diff --git a/projects/RabbitMQ.Client/Impl/Channel.BasicPublish.cs b/projects/RabbitMQ.Client/Impl/Channel.BasicPublish.cs
index 9ed4e0de1..c27628290 100644
--- a/projects/RabbitMQ.Client/Impl/Channel.BasicPublish.cs
+++ b/projects/RabbitMQ.Client/Impl/Channel.BasicPublish.cs
@@ -60,9 +60,7 @@ await MaybeEnforceFlowControlAsync(cancellationToken)
var cmd = new BasicPublish(exchange, routingKey, mandatory, default);
- using Activity? sendActivity = RabbitMQActivitySource.PublisherHasListeners
- ? RabbitMQActivitySource.BasicPublish(routingKey, exchange, body.Length)
- : default;
+ using Activity? sendActivity = RabbitMQActivitySource.BasicPublish(routingKey, exchange, body.Length);
ulong publishSequenceNumber = 0;
if (publisherConfirmationInfo is not null)
@@ -115,9 +113,7 @@ await MaybeEnforceFlowControlAsync(cancellationToken)
var cmd = new BasicPublishMemory(exchange.Bytes, routingKey.Bytes, mandatory, default);
- using Activity? sendActivity = RabbitMQActivitySource.PublisherHasListeners
- ? RabbitMQActivitySource.BasicPublish(routingKey.Value, exchange.Value, body.Length)
- : default;
+ using Activity? sendActivity = RabbitMQActivitySource.BasicPublish(routingKey.Value, exchange.Value, body.Length);
ulong publishSequenceNumber = 0;
if (publisherConfirmationInfo is not null)
diff --git a/projects/RabbitMQ.Client/Impl/Connection.cs b/projects/RabbitMQ.Client/Impl/Connection.cs
index f661669af..eb9c12a84 100644
--- a/projects/RabbitMQ.Client/Impl/Connection.cs
+++ b/projects/RabbitMQ.Client/Impl/Connection.cs
@@ -228,11 +228,9 @@ internal void TakeOver(Connection other)
internal async ValueTask OpenAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
-
try
{
RabbitMqClientEventSource.Log.ConnectionOpened();
-
cancellationToken.ThrowIfCancellationRequested();
// Note: this must happen *after* the frame handler is started
@@ -250,7 +248,7 @@ await _channel0.ConnectionOpenAsync(_config.VirtualHost, cancellationToken)
return this;
}
- catch
+ catch (Exception)
{
try
{
diff --git a/projects/RabbitMQ.Client/Impl/RabbitMQActivitySource.cs b/projects/RabbitMQ.Client/Impl/RabbitMQActivitySource.cs
index 31d076cbd..7fbbe6d65 100644
--- a/projects/RabbitMQ.Client/Impl/RabbitMQActivitySource.cs
+++ b/projects/RabbitMQ.Client/Impl/RabbitMQActivitySource.cs
@@ -43,16 +43,20 @@ public static class RabbitMQActivitySource
private static readonly ActivitySource s_subscriberSource =
new ActivitySource(SubscriberSourceName, AssemblyVersion);
+ private static readonly ActivitySource s_connectionSource =
+ new ActivitySource(ConnectionSourceName, AssemblyVersion);
+
public const string PublisherSourceName = "RabbitMQ.Client.Publisher";
public const string SubscriberSourceName = "RabbitMQ.Client.Subscriber";
+ public const string ConnectionSourceName = "RabbitMQ.Client.Connection";
- public static Action> ContextInjector { get; set; } = DefaultContextInjector;
+ public static Action> ContextInjector { get; set; } =
+ DefaultContextInjector;
public static Func ContextExtractor { get; set; } =
DefaultContextExtractor;
public static bool UseRoutingKeyAsOperationName { get; set; } = true;
- internal static bool PublisherHasListeners => s_publisherSource.HasListeners();
internal static readonly IEnumerable> CreationTags = new[]
{
@@ -61,14 +65,24 @@ public static class RabbitMQActivitySource
new KeyValuePair(ProtocolVersion, "0.9.1")
};
+ internal static Activity? OpenConnection(bool isReconnection)
+ {
+ Activity? connectionActivity =
+ s_connectionSource.StartRabbitMQActivity("connection attempt", ActivityKind.Client);
+ connectionActivity?.SetTag("messaging.rabbitmq.connection.is_reconnection", isReconnection);
+ return connectionActivity;
+ }
+
+ internal static Activity? OpenTcpConnection()
+ {
+ Activity? connectionActivity =
+ s_connectionSource.StartRabbitMQActivity("tcp connection attempt", ActivityKind.Client);
+ return connectionActivity;
+ }
+
internal static Activity? BasicPublish(string routingKey, string exchange, int bodySize,
ActivityContext linkedContext = default)
{
- if (!s_publisherSource.HasListeners())
- {
- return null;
- }
-
Activity? activity = linkedContext == default
? s_publisherSource.StartRabbitMQActivity(
UseRoutingKeyAsOperationName ? $"{MessagingOperationNameBasicPublish} {routingKey}" : MessagingOperationNameBasicPublish,
@@ -82,16 +96,10 @@ public static class RabbitMQActivitySource
}
return activity;
-
}
internal static Activity? BasicGetEmpty(string queue)
{
- if (!s_subscriberSource.HasListeners())
- {
- return null;
- }
-
Activity? activity = s_subscriberSource.StartRabbitMQActivity(
UseRoutingKeyAsOperationName ? $"{MessagingOperationNameBasicGetEmpty} {queue}" : MessagingOperationNameBasicGetEmpty,
ActivityKind.Consumer);
@@ -109,11 +117,6 @@ public static class RabbitMQActivitySource
internal static Activity? BasicGet(string routingKey, string exchange, ulong deliveryTag,
IReadOnlyBasicProperties readOnlyBasicProperties, int bodySize)
{
- if (!s_subscriberSource.HasListeners())
- {
- return null;
- }
-
// Extract the PropagationContext of the upstream parent from the message headers.
Activity? activity = s_subscriberSource.StartLinkedRabbitMQActivity(
UseRoutingKeyAsOperationName ? $"{MessagingOperationNameBasicGet} {routingKey}" : MessagingOperationNameBasicGet, ActivityKind.Consumer,
@@ -130,11 +133,6 @@ public static class RabbitMQActivitySource
internal static Activity? Deliver(string routingKey, string exchange, ulong deliveryTag,
IReadOnlyBasicProperties basicProperties, int bodySize)
{
- if (!s_subscriberSource.HasListeners())
- {
- return null;
- }
-
// Extract the PropagationContext of the upstream parent from the message headers.
Activity? activity = s_subscriberSource.StartLinkedRabbitMQActivity(
UseRoutingKeyAsOperationName ? $"{MessagingOperationNameBasicDeliver} {routingKey}" : MessagingOperationNameBasicDeliver,
@@ -197,7 +195,7 @@ private static void PopulateMessagingTags(string operationType, string operation
internal static void PopulateMessageEnvelopeSize(Activity? activity, int size)
{
- if (activity != null && activity.IsAllDataRequested && PublisherHasListeners)
+ if (activity?.IsAllDataRequested ?? false)
{
activity.SetTag(MessagingEnvelopeSize, size);
}
@@ -205,7 +203,7 @@ internal static void PopulateMessageEnvelopeSize(Activity? activity, int size)
internal static void SetNetworkTags(this Activity? activity, IFrameHandler frameHandler)
{
- if (PublisherHasListeners && activity != null && activity.IsAllDataRequested)
+ if (activity?.IsAllDataRequested ?? false)
{
switch (frameHandler.RemoteEndPoint.AddressFamily)
{
@@ -216,15 +214,7 @@ internal static void SetNetworkTags(this Activity? activity, IFrameHandler frame
activity.SetTag("network.type", "ipv4");
break;
}
-
- if (!string.IsNullOrEmpty(frameHandler.Endpoint.HostName))
- {
- activity
- .SetTag("server.address", frameHandler.Endpoint.HostName);
- }
-
- activity
- .SetTag("server.port", frameHandler.Endpoint.Port);
+ activity.SetServerTags(frameHandler.Endpoint);
if (frameHandler.RemoteEndPoint is IPEndPoint ipEndpoint)
{
@@ -252,6 +242,18 @@ internal static void SetNetworkTags(this Activity? activity, IFrameHandler frame
}
}
+ internal static void SetServerTags(this Activity activity, AmqpTcpEndpoint endpoint)
+ {
+ if (!string.IsNullOrEmpty(endpoint.HostName))
+ {
+ activity
+ .SetTag("server.address", endpoint.HostName);
+ }
+
+ activity
+ .SetTag("server.port", endpoint.Port);
+ }
+
private static void DefaultContextInjector(Activity sendActivity, IDictionary props)
{
DistributedContextPropagator.Current.Inject(sendActivity, props, DefaultContextSetter);
diff --git a/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt b/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt
index 9a4615009..770bebcc2 100644
--- a/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt
+++ b/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt
@@ -4,4 +4,5 @@ RabbitMQ.Client.Exceptions.PublishReturnException.Exchange.get -> string!
RabbitMQ.Client.Exceptions.PublishReturnException.PublishReturnException(ulong publishSequenceNumber, string! message, string? exchange = null, string? routingKey = null, ushort? replyCode = null, string? replyText = null) -> void
RabbitMQ.Client.Exceptions.PublishReturnException.ReplyCode.get -> ushort
RabbitMQ.Client.Exceptions.PublishReturnException.ReplyText.get -> string!
-RabbitMQ.Client.Exceptions.PublishReturnException.RoutingKey.get -> string!
\ No newline at end of file
+RabbitMQ.Client.Exceptions.PublishReturnException.RoutingKey.get -> string!
+const RabbitMQ.Client.RabbitMQActivitySource.ConnectionSourceName = "RabbitMQ.Client.Connection" -> string!
diff --git a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
index d552e6324..765704f0d 100644
--- a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
+++ b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
@@ -67,6 +67,7 @@
+
@@ -76,7 +77,6 @@
* https://github.com/rabbitmq/rabbitmq-dotnet-client/pull/1481#pullrequestreview-1847905299
* https://github.com/rabbitmq/rabbitmq-dotnet-client/pull/1594
-->
-
diff --git a/projects/Test/Common/ActivityRecorder.cs b/projects/Test/Common/ActivityRecorder.cs
new file mode 100644
index 000000000..b1ddaa2ac
--- /dev/null
+++ b/projects/Test/Common/ActivityRecorder.cs
@@ -0,0 +1,192 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2025 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2025 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Xunit;
+
+namespace Test
+{
+ public class ActivityRecorder : IDisposable
+ {
+ private string _activitySourceName;
+ private string _activityName;
+
+ private readonly ActivityListener _listener;
+ private List _finishedActivities = new();
+
+ private int _started;
+ private int _stopped;
+
+ public int Started => _started;
+ public int Stopped => _stopped;
+
+ public Predicate Filter { get; set; } = _ => true;
+ public bool VerifyParent { get; set; } = true;
+ public Activity ExpectedParent { get; set; }
+
+ public Activity LastStartedActivity { get; private set; }
+ public Activity LastFinishedActivity { get; private set; }
+ public IEnumerable FinishedActivities => _finishedActivities;
+
+ public ActivityRecorder(string activitySourceName, string activityName)
+ {
+ _activitySourceName = activitySourceName;
+ _activityName = activityName;
+ _listener = new ActivityListener
+ {
+ ShouldListenTo = (activitySource) => activitySource.Name == _activitySourceName,
+ Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData,
+ ActivityStarted = (activity) =>
+ {
+ if (activity.OperationName == _activityName && Filter(activity))
+ {
+ if (VerifyParent)
+ {
+ Assert.Same(ExpectedParent, activity.Parent);
+ }
+
+ Interlocked.Increment(ref _started);
+
+ LastStartedActivity = activity;
+ }
+ },
+ ActivityStopped = (activity) =>
+ {
+ if (activity.OperationName == _activityName && Filter(activity))
+ {
+ if (VerifyParent)
+ {
+ Assert.Same(ExpectedParent, activity.Parent);
+ }
+
+ Interlocked.Increment(ref _stopped);
+
+ lock (_finishedActivities)
+ {
+ LastFinishedActivity = activity;
+ _finishedActivities.Add(activity);
+ }
+ }
+ }
+ };
+
+ ActivitySource.AddActivityListener(_listener);
+ }
+
+ public void Dispose() => _listener.Dispose();
+
+ public void VerifyActivityRecorded(int times)
+ {
+ Assert.Equal(times, Started);
+ Assert.Equal(times, Stopped);
+ }
+
+ public Activity VerifyActivityRecordedOnce()
+ {
+ VerifyActivityRecorded(1);
+ return LastFinishedActivity;
+ }
+ }
+
+ public static class ActivityAssert
+ {
+ public static KeyValuePair HasTag(this Activity activity, string name)
+ {
+ KeyValuePair tag = activity.TagObjects.SingleOrDefault(t => t.Key == name);
+ if (tag.Key is null)
+ {
+ Assert.Fail($"The Activity tags should contain {name}.");
+ }
+
+ return tag;
+ }
+
+ public static void HasTag(this Activity activity, string name, T expectedValue)
+ {
+ KeyValuePair tag = HasTag(activity, name);
+ Assert.Equal(expectedValue, (T)tag.Value);
+ }
+
+ public static void HasRecordedException(this Activity activity, Exception exception)
+ {
+ activity.HasRecordedException(exception.GetType().ToString());
+ }
+
+ public static void HasRecordedException(this Activity activity, string exceptionTypeName)
+ {
+ var exceptionEvent = activity.Events.First();
+ Assert.Equal("exception", exceptionEvent.Name);
+ Assert.Equal(exceptionTypeName,
+ exceptionEvent.Tags.SingleOrDefault(t => t.Key == "exception.type").Value);
+ }
+
+ public static void IsInError(this Activity activity)
+ {
+ Assert.Equal(ActivityStatusCode.Error, activity.Status);
+ }
+
+ public static void HasNoTag(this Activity activity, string name)
+ {
+ bool contains = activity.TagObjects.Any(t => t.Key == name);
+ Assert.False(contains, $"The Activity tags should not contain {name}.");
+ }
+
+ public static void FinishedInOrder(this Activity first, Activity second)
+ {
+ Assert.True(first.StartTimeUtc + first.Duration < second.StartTimeUtc + second.Duration,
+ $"{first.OperationName} should stop before {second.OperationName}");
+ }
+
+ public static string CamelToSnake(string camel)
+ {
+ if (string.IsNullOrEmpty(camel)) return camel;
+ StringBuilder bld = new();
+ bld.Append(char.ToLower(camel[0]));
+ for (int i = 1; i < camel.Length; i++)
+ {
+ char c = camel[i];
+ if (char.IsUpper(c))
+ {
+ bld.Append('_');
+ }
+
+ bld.Append(char.ToLower(c));
+ }
+
+ return bld.ToString();
+ }
+ }
+}
diff --git a/projects/Test/Integration/TestConnectionFactory.cs b/projects/Test/Integration/TestConnectionFactory.cs
index 3a17efcd9..2c30a0c2f 100644
--- a/projects/Test/Integration/TestConnectionFactory.cs
+++ b/projects/Test/Integration/TestConnectionFactory.cs
@@ -31,6 +31,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -434,5 +435,54 @@ public async Task TestCreateConnectionAsync_TruncatesWhenClientNameIsLong_GH980(
Assert.Contains(conn.ClientProvidedName, cpn);
}
}
+
+ [Fact]
+ public async Task TestCreateConnectionRegisterAnActivity()
+ {
+ using ActivityRecorder connectionRecorder =
+ new(RabbitMQActivitySource.ConnectionSourceName, "connection attempt");
+ using ActivityRecorder tcpConnectionRecorder =
+ new(RabbitMQActivitySource.ConnectionSourceName, "tcp connection attempt");
+ tcpConnectionRecorder.VerifyParent = false;
+ ConnectionFactory cf = CreateConnectionFactory();
+ await using IConnection conn = await cf.CreateConnectionAsync();
+ var connectionActivity = connectionRecorder.VerifyActivityRecordedOnce();
+ connectionActivity.HasTag("network.peer.address");
+ connectionActivity.HasTag("network.local.address");
+ connectionActivity.HasTag("server.address");
+ connectionActivity.HasTag("client.address");
+ connectionActivity.HasTag("network.peer.port");
+ connectionActivity.HasTag("network.local.port");
+ connectionActivity.HasTag("server.port");
+ connectionActivity.HasTag("client.port");
+ connectionActivity.HasTag("network.type");
+ var tcpConnectionActivity = tcpConnectionRecorder.VerifyActivityRecordedOnce();
+ tcpConnectionActivity.HasTag("server.port");
+ tcpConnectionActivity.HasTag("server.address");
+ Assert.Equal(connectionActivity, tcpConnectionActivity.Parent);
+ await conn.CloseAsync();
+ }
+
+ [Fact]
+ public async Task TestCreateConnectionWithFailureRecordException()
+ {
+ using ActivityRecorder recorder =
+ new(RabbitMQActivitySource.ConnectionSourceName, "connection attempt");
+ using ActivityRecorder tcpConnectionRecorder =
+ new(RabbitMQActivitySource.ConnectionSourceName, "tcp connection attempt");
+ tcpConnectionRecorder.VerifyParent = false;
+ ConnectionFactory cf = CreateConnectionFactory();
+ var unreachablePort = 1234;
+ var ep = new AmqpTcpEndpoint("localhost", unreachablePort);
+ var exception = await Assert.ThrowsAsync(() =>
+ {
+ return cf.CreateConnectionAsync(new List { ep });
+ });
+ Activity connectionActivity = recorder.VerifyActivityRecordedOnce();
+ connectionActivity.HasRecordedException(exception);
+ connectionActivity.IsInError();
+ Activity tcpConnectionActivity = tcpConnectionRecorder.VerifyActivityRecordedOnce();
+ tcpConnectionActivity.HasRecordedException("RabbitMQ.Client.Exceptions.ConnectFailureException");
+ }
}
}