diff --git a/src/Tingle.Extensions.MongoDB/Diagnostics/MongoDbDiagnosticEvents.cs b/src/Tingle.Extensions.MongoDB/Diagnostics/MongoDbDiagnosticEvents.cs
deleted file mode 100644
index 7da6149..0000000
--- a/src/Tingle.Extensions.MongoDB/Diagnostics/MongoDbDiagnosticEvents.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using MongoDB.Driver.Core.Events;
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Net;
-using System.Reflection;
-
-namespace Tingle.Extensions.MongoDB.Diagnostics;
-
-///
-/// A subscriber to events that writes to a
-///
-///
-/// This class is highly borrowed from https://github.com/jbogard/MongoDB.Driver.Core.Extensions.DiagnosticSources
-///
-public class MongoDbDiagnosticEvents : IEventSubscriber
-{
- internal static readonly AssemblyName AssemblyName = typeof(MongoDbDiagnosticEvents).Assembly.GetName();
- internal static readonly string ActivitySourceName = AssemblyName.Name!;
- internal static readonly Version Version = AssemblyName.Version!;
- internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString());
-
- private const string ActivityName = "MongoDB";
-
- private readonly bool captureCommandText = false; // Ideally should be provided via constructor
- private readonly Func? shouldStartActivity;
- private readonly ReflectionEventSubscriber subscriber;
- private readonly ConcurrentDictionary activityMap = new();
-
- ///
- /// Creates an instance of .
- ///
- /// indicates if the command text should be captured
- /// optional delegate to check if an activity should be started
- public MongoDbDiagnosticEvents(bool captureCommandText = false, Func? shouldStartActivity = null)
- {
- this.captureCommandText = captureCommandText;
- this.shouldStartActivity = shouldStartActivity;
-
- // the reflection-based subscriber accepts any objects, for this case, we take non public ones
- subscriber = new ReflectionEventSubscriber(this, bindingFlags: BindingFlags.Instance | BindingFlags.NonPublic);
- }
-
- ///
- /// Tries to get an event handler for an event of type TEvent.
- ///
- /// The type of the event.
- /// The handler.
- /// true if this subscriber has provided an event handler; otherwise false.
- public bool TryGetEventHandler(out Action handler) => subscriber.TryGetEventHandler(out handler);
-
-#pragma warning disable IDE0051 // Remove unused private members
-
- private void Handle(CommandStartedEvent @event)
- {
- if (shouldStartActivity != null && !shouldStartActivity(@event))
- {
- return;
- }
-
- var activity = ActivitySource.StartActivity(ActivityName, ActivityKind.Client);
-
- // if the activity is null, there is no one listening so just return
- if (activity == null) return;
-
- var collectionName = @event.GetCollectionName();
-
- // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md
- activity.DisplayName = collectionName == null ? $"mongodb.{@event.CommandName}" : $"{collectionName}.{@event.CommandName}";
-
- // add tags known by open telemetry
- activity.AddTag("db.system", "mongodb");
- activity.AddTag("db.name", @event.DatabaseNamespace?.DatabaseName);
- activity.AddTag("db.mongodb.collection", collectionName);
- activity.AddTag("db.operation", @event.CommandName);
- var endPoint = @event.ConnectionId?.ServerId?.EndPoint;
- switch (endPoint)
- {
- case IPEndPoint ipe:
- activity.AddTag("db.user", $"mongodb://{ipe.Address}:{ipe.Port}");
- activity.AddTag("net.peer.ip", ipe.Address.ToString());
- activity.AddTag("net.peer.port", ipe.Port.ToString());
- break;
- case DnsEndPoint dnse:
- activity.AddTag("db.user", $"mongodb://{dnse.Host}:{dnse.Port}");
- activity.AddTag("net.peer.name", dnse.Host);
- activity.AddTag("net.peer.port", dnse.Port.ToString());
- break;
- }
-
- if (activity.IsAllDataRequested && captureCommandText)
- {
- activity.AddTag("db.statement", @event.Command.ToString());
- }
-
- activityMap.TryAdd(@event.RequestId, activity);
- }
-
- private void Handle(CommandSucceededEvent @event)
- {
- if (activityMap.TryRemove(@event.RequestId, out var activity))
- {
- WithReplacedActivityCurrent(activity, () =>
- {
- activity.AddTag("otel.status_code", "Ok");
- activity.Stop();
- });
- }
- }
-
- private void Handle(CommandFailedEvent @event)
- {
- if (activityMap.TryRemove(@event.RequestId, out var activity))
- {
- WithReplacedActivityCurrent(activity, () =>
- {
- if (activity.IsAllDataRequested)
- {
- activity.AddTag("otel.status_code", "Error");
- activity.AddTag("otel.status_description", @event.Failure.Message);
- activity.AddTag("error.type", @event.Failure.GetType().FullName);
- activity.AddTag("error.msg", @event.Failure.Message);
- activity.AddTag("error.stack", @event.Failure.StackTrace);
- }
-
- activity.Stop();
- });
- }
- }
-
- private static void WithReplacedActivityCurrent(Activity activity, Action action)
- {
- var current = Activity.Current;
- try
- {
- Activity.Current = activity;
- action();
- }
- finally
- {
- Activity.Current = current;
- }
- }
-
-#pragma warning restore IDE0051 // Remove unused private members
-}
diff --git a/src/Tingle.Extensions.MongoDB/MongoDbContextOptions.cs b/src/Tingle.Extensions.MongoDB/MongoDbContextOptions.cs
index 771a228..26fb71f 100644
--- a/src/Tingle.Extensions.MongoDB/MongoDbContextOptions.cs
+++ b/src/Tingle.Extensions.MongoDB/MongoDbContextOptions.cs
@@ -1,7 +1,6 @@
using MongoDB.Driver;
-using MongoDB.Driver.Core.Events;
+using MongoDB.Driver.Core.Extensions.DiagnosticSources;
using System.Diagnostics.CodeAnalysis;
-using Tingle.Extensions.MongoDB.Diagnostics;
namespace Microsoft.Extensions.DependencyInjection;
@@ -122,16 +121,9 @@ public virtual MongoDbContextOptionsBuilder UseApplicationServiceProvider(IServi
/// Sets the to use when configuring the context.
///
/// The to be used
- ///
- /// Whether the command text should be captured in instrumentation.
- ///
- ///
- /// Delegate for determining if a should be instrumented.
- ///
+ /// The options to use for instrumentation.
///
- public virtual MongoDbContextOptionsBuilder UseMongoUrl(MongoUrl url,
- bool instrumentCommandText = true,
- Func? shouldInstrument = null)
+ public virtual MongoDbContextOptionsBuilder UseMongoUrl(MongoUrl url, InstrumentationOptions? instrumentationOptions = null)
{
ArgumentNullException.ThrowIfNull(url);
if (string.IsNullOrWhiteSpace(url.DatabaseName))
@@ -146,10 +138,7 @@ public virtual MongoDbContextOptionsBuilder UseMongoUrl(MongoUrl url,
{
settings.ClusterConfigurator = builder =>
{
- builder.Subscribe(
- new MongoDbDiagnosticEvents(
- captureCommandText: instrumentCommandText,
- shouldStartActivity: shouldInstrument));
+ builder.Subscribe(new DiagnosticsActivityEventSubscriber(instrumentationOptions ?? new() { CaptureCommandText = true }));
};
});
@@ -165,19 +154,12 @@ public virtual MongoDbContextOptionsBuilder UseMongoUrl(MongoUrl url,
/// mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
/// e.g. mongodb://localhost:27017/myDatabase
///
- ///
- /// Whether the command text should be captured in instrumentation.
- ///
- ///
- /// Delegate for determining if a should be instrumented.
- ///
+ /// The options to use for instrumentation.
///
- public virtual MongoDbContextOptionsBuilder UseMongoConnectionString(string connectionString,
- bool instrumentCommandText = true,
- Func? shouldInstrument = null)
+ public virtual MongoDbContextOptionsBuilder UseMongoConnectionString(string connectionString, InstrumentationOptions? instrumentationOptions = null)
{
ArgumentNullException.ThrowIfNull(connectionString);
- return UseMongoUrl(new MongoUrl(connectionString), instrumentCommandText, shouldInstrument);
+ return UseMongoUrl(new MongoUrl(connectionString), instrumentationOptions);
}
///
@@ -246,17 +228,10 @@ public MongoDbContextOptionsBuilder() : this(new MongoDbContextOptions
/// Sets the to use when configuring the context.
///
/// The to be used
- ///
- /// Whether the command text should be captured in instrumentation.
- ///
- ///
- /// Delegate for determining if a should be instrumented.
- ///
+ /// The options to use for instrumentation.
///
- public new virtual MongoDbContextOptionsBuilder UseMongoUrl(MongoUrl url,
- bool instrumentCommandText = true,
- Func? shouldInstrument = null)
- => (MongoDbContextOptionsBuilder)base.UseMongoUrl(url, instrumentCommandText, shouldInstrument);
+ public new virtual MongoDbContextOptionsBuilder UseMongoUrl(MongoUrl url, InstrumentationOptions? instrumentationOptions = null)
+ => (MongoDbContextOptionsBuilder)base.UseMongoUrl(url, instrumentationOptions);
///
/// Sets the to use when configuring the context by
@@ -267,17 +242,10 @@ public MongoDbContextOptionsBuilder() : this(new MongoDbContextOptions
/// mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
/// e.g. mongodb://localhost:27017/myDatabase
///
- ///
- /// Whether the command text should be captured in instrumentation.
- ///
- ///
- /// Delegate for determining if a should be instrumented.
- ///
+ /// The options to use for instrumentation.
///
- public new virtual MongoDbContextOptionsBuilder UseMongoConnectionString(string connectionString,
- bool instrumentCommandText = true,
- Func? shouldInstrument = null)
- => (MongoDbContextOptionsBuilder)base.UseMongoConnectionString(connectionString, instrumentCommandText, shouldInstrument);
+ public new virtual MongoDbContextOptionsBuilder UseMongoConnectionString(string connectionString, InstrumentationOptions? instrumentationOptions=null)
+ => (MongoDbContextOptionsBuilder)base.UseMongoConnectionString(connectionString, instrumentationOptions);
///
/// Further configure the existing instance of .
diff --git a/src/Tingle.Extensions.MongoDB/README.md b/src/Tingle.Extensions.MongoDB/README.md
index eb2a5e8..356773a 100644
--- a/src/Tingle.Extensions.MongoDB/README.md
+++ b/src/Tingle.Extensions.MongoDB/README.md
@@ -49,7 +49,7 @@ If MongoDB client is configured with a connection string, add the `ConnectionStr
## Diagnostics
-Events for Mongo are produced on an `ActivitySource` named `MongoDB`. This is done by registering and instance of `IEventSubscriber` named `MongoDbDiagnosticEvents` to the `ClusterConfigurator`. However, this is done automatically when using `MongoDbContext`.
+Events for Mongo are produced on an `ActivitySource` named `MongoDB.Driver.Core.Extensions.DiagnosticSources`, automatically when using `MongoDbContext`.
## HealthChecks
diff --git a/src/Tingle.Extensions.MongoDB/Tingle.Extensions.MongoDB.csproj b/src/Tingle.Extensions.MongoDB/Tingle.Extensions.MongoDB.csproj
index 172b05f..45298ab 100644
--- a/src/Tingle.Extensions.MongoDB/Tingle.Extensions.MongoDB.csproj
+++ b/src/Tingle.Extensions.MongoDB/Tingle.Extensions.MongoDB.csproj
@@ -16,6 +16,7 @@
+
diff --git a/tests/Tingle.Extensions.MongoDB.Tests/MongoDbDiagnosticEventsTests.cs b/tests/Tingle.Extensions.MongoDB.Tests/MongoDbDiagnosticEventsTests.cs
deleted file mode 100644
index 30b89d9..0000000
--- a/tests/Tingle.Extensions.MongoDB.Tests/MongoDbDiagnosticEventsTests.cs
+++ /dev/null
@@ -1,329 +0,0 @@
-using MongoDB.Bson;
-using MongoDB.Driver;
-using MongoDB.Driver.Core.Clusters;
-using MongoDB.Driver.Core.Connections;
-using MongoDB.Driver.Core.Events;
-using MongoDB.Driver.Core.Servers;
-using System.Diagnostics;
-using System.Net;
-using Tingle.Extensions.MongoDB.Diagnostics;
-
-namespace Tingle.Extensions.MongoDB.Tests;
-
-public class MongoDbDiagnosticEventsTests
-{
- static MongoDbDiagnosticEventsTests()
- {
- Activity.DefaultIdFormat = ActivityIdFormat.W3C;
- Activity.ForceDefaultIdFormat = true;
- }
-
- [Fact]
- public void NoActivityCreatedWhenNoListenerIsAttached()
- {
- var startFired = false;
- var stopFired = false;
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Nonsense",
- ActivityStarted = _ => startFired = true,
- ActivityStopped = _ => stopFired = true
- };
-
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents();
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- startEvent(new CommandStartedEvent());
- stopEvent(new CommandSucceededEvent());
-
- Assert.False(startFired);
- Assert.False(stopFired);
- }
-
- [Fact]
- public void ActivityStartedAndStoppedWhenSampling()
- {
- var startFired = false;
- var stopFired = false;
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.PropagationData,
- ActivityStarted = _ => startFired = true,
- ActivityStopped = _ => stopFired = true
- };
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents();
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- startEvent(new CommandStartedEvent());
- stopEvent(new CommandSucceededEvent());
-
- Assert.True(startFired);
- Assert.True(stopFired);
- Assert.Null(Activity.Current);
- }
-
- [Fact]
- public void StartsAndLogsSuccessfulActivity()
- {
- var stopFired = false;
- var startFired = false;
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.PropagationData,
- ActivityStarted = activity =>
- {
- startFired = true;
- Assert.NotNull(activity);
- Assert.Equal("MongoDB", Activity.Current?.OperationName);
- },
- ActivityStopped = activity =>
- {
- stopFired = true;
- Assert.NotNull(activity);
- Assert.Equal("MongoDB", Activity.Current?.OperationName);
- }
- };
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents();
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- startEvent(new CommandStartedEvent());
- stopEvent(new CommandSucceededEvent());
-
- Assert.True(startFired);
- Assert.True(stopFired);
- Assert.Null(Activity.Current);
- }
-
- [Fact]
- public void StartsAndLogsFailedActivity()
- {
- var exceptionFired = false;
- var startFired = false;
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData,
- ActivityStarted = activity =>
- {
- startFired = true;
- Assert.NotNull(activity);
- Assert.Equal("MongoDB", Activity.Current?.OperationName);
- },
- ActivityStopped = activity =>
- {
- exceptionFired = true;
- Assert.NotNull(activity);
- Assert.Equal("MongoDB", Activity.Current?.OperationName);
- var statusTag = activity.Tags.SingleOrDefault(t => t.Key == "otel.status_code");
- Assert.NotEqual(default, statusTag);
- Assert.Equal("Error", statusTag.Value);
- }
- };
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents();
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- var connectionId = new ConnectionId(new ServerId(new ClusterId(), new DnsEndPoint("localhost", 8000)));
- var databaseNamespace = new DatabaseNamespace("test");
- var command = new BsonDocument(new Dictionary
- {
- {"update", "my_collection"}
- });
- startEvent(new CommandStartedEvent("update", command, databaseNamespace, null, 1, connectionId));
- stopEvent(new CommandFailedEvent("update", databaseNamespace, new Exception("Failed"), null, 1, connectionId, TimeSpan.Zero));
-
- Assert.True(startFired);
- Assert.True(exceptionFired);
- Assert.Null(Activity.Current);
- }
-
- [Fact]
- public void RecordsAllData()
- {
- var stopFired = false;
- var startFired = false;
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData,
- ActivityStarted = activity =>
- {
- startFired = true;
- Assert.NotNull(activity);
- },
- ActivityStopped = activity =>
- {
- stopFired = true;
- Assert.NotNull(activity);
- Assert.Equal("MongoDB", Activity.Current?.OperationName);
- var instanceTag = activity.Tags.SingleOrDefault(t => t.Key == "db.name");
- Assert.NotEqual(default, instanceTag);
- Assert.Equal("test", instanceTag.Value);
-
- Assert.Equal("mongodb", activity.Tags.SingleOrDefault(t => t.Key == "db.system").Value);
- Assert.Equal("update", activity.Tags.SingleOrDefault(t => t.Key == "db.operation").Value);
- Assert.Equal(default, activity.Tags.SingleOrDefault(t => t.Key == "db.statement").Value);
- }
- };
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents(captureCommandText: false);
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- var connectionId = new ConnectionId(new ServerId(new ClusterId(), new DnsEndPoint("localhost", 8000)));
- var databaseNamespace = new DatabaseNamespace("test");
- var command = new BsonDocument(new Dictionary
- {
- {"update", "my_collection"}
- });
- startEvent(new CommandStartedEvent("update", command, databaseNamespace, null, 1, connectionId));
- stopEvent(new CommandSucceededEvent("update", command, databaseNamespace, null, 1, connectionId, TimeSpan.Zero));
-
- Assert.True(startFired);
- Assert.True(stopFired);
- }
-
- [Fact]
- public void RecordsCommandTextWhenOptionIsSet()
- {
- var stopFired = false;
- var startFired = false;
-
- var command = new BsonDocument(new Dictionary
- {
- {"update", "my_collection"}
- });
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData,
- ActivityStarted = activity =>
- {
- startFired = true;
- Assert.NotNull(activity);
- },
- ActivityStopped = activity =>
- {
- Assert.NotNull(activity);
- Assert.Equal("MongoDB", Activity.Current?.OperationName);
- var statementTag = activity.Tags.SingleOrDefault(t => t.Key == "db.statement");
- Assert.NotEqual(default, statementTag);
- Assert.Equal(command.ToString(), statementTag.Value);
-
- stopFired = true;
- }
- };
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents(captureCommandText: true);
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- var connectionId = new ConnectionId(new ServerId(new ClusterId(), new DnsEndPoint("localhost", 8000)));
- var databaseNamespace = new DatabaseNamespace("test");
- startEvent(new CommandStartedEvent("update", command, databaseNamespace, null, 1, connectionId));
- stopEvent(new CommandSucceededEvent("update", command, databaseNamespace, null, 1, connectionId, TimeSpan.Zero));
-
- Assert.True(startFired);
- Assert.True(stopFired);
- }
-
- [Fact]
- public void WorksWithParallelActivities()
- {
- var activities = new List();
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded,
- ActivityStarted = _ => activities.Add(Activity.Current),
- ActivityStopped = _ => activities.Add(Activity.Current)
- };
- ActivitySource.AddActivityListener(listener);
-
- var behavior = new MongoDbDiagnosticEvents();
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- var outerActivity = new Activity("Outer");
- outerActivity.Start();
-
- var connectionId = new ConnectionId(new ServerId(new ClusterId(), new DnsEndPoint("localhost", 8000)));
- var databaseNamespace = new DatabaseNamespace("test");
- var updateCommand = new BsonDocument(new Dictionary
- {
- {"update", "my_collection"}
- });
- var insertCommand = new BsonDocument(new Dictionary
- {
- {"insert", "my_collection"}
- });
- startEvent(new CommandStartedEvent("update", updateCommand, databaseNamespace, null, 1, connectionId));
- startEvent(new CommandStartedEvent("insert", insertCommand, databaseNamespace, null, 2, connectionId));
- stopEvent(new CommandSucceededEvent("update", updateCommand, databaseNamespace, null, 1, connectionId, TimeSpan.Zero));
- stopEvent(new CommandSucceededEvent("insert", insertCommand, databaseNamespace, null, 2, connectionId, TimeSpan.Zero));
-
- outerActivity.Stop();
-
- Assert.Equal(4, activities.Count);
- Assert.Equal(4, activities.Count(a => a != null && a.OperationName == "MongoDB"));
- Assert.Null(Activity.Current);
- }
-
- [Theory]
- [InlineData(null, true)]
- [InlineData(true, true)]
- [InlineData(false, false)]
- public void ShouldStartActivityIsRespected(bool? filterResult, bool shouldFireActivity)
- {
- var activities = new List();
-
- using var listener = new ActivityListener
- {
- ShouldListenTo = source => source.Name == "Tingle.Extensions.MongoDB",
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.PropagationData,
- ActivityStarted = _ => activities.Add(Activity.Current)
- };
- ActivitySource.AddActivityListener(listener);
-
- Func? filter = filterResult == null ? null : x => filterResult.Value;
-
- var behavior = new MongoDbDiagnosticEvents(shouldStartActivity: filter);
-
- Assert.True(behavior.TryGetEventHandler(out var startEvent));
- Assert.True(behavior.TryGetEventHandler(out var stopEvent));
-
- startEvent(new CommandStartedEvent());
- stopEvent(new CommandSucceededEvent());
-
- Assert.Equal(shouldFireActivity ? 1 : 0, activities.Count);
- }
-}