Skip to content

Commit

Permalink
Add Filter public API to enable filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
akoken committed May 24, 2023
1 parent efda793 commit 1746af5
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions.EntityFrameworkInstrumentationOptions() -> void
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions.Filter.get -> System.Func<System.Data.IDbCommand, bool>
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions.Filter.set -> void
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions.SetDbStatementForStoredProcedure.get -> bool
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions.SetDbStatementForStoredProcedure.set -> void
OpenTelemetry.Instrumentation.EntityFrameworkCore.EntityFrameworkInstrumentationOptions.SetDbStatementForText.get -> bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Added `Filter` public API on `EntityFrameworkInstrumentationOptions` to enable filtering
of instrumentation based on the db command being executed.

## 1.0.0-beta.6

Released 2023-Mar-13
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,24 @@ public class EntityFrameworkInstrumentationOptions
/// <para><see cref="IDbCommand"/>: db command to allow access to command.</para>
/// </remarks>
public Action<Activity, IDbCommand> EnrichWithIDbCommand { get; set; }

/// <summary>
/// Gets or sets a filter function that determines whether or not to
/// collect telemetry about a command.
/// </summary>
/// <remarks>
/// <b>Notes:</b>
/// <list type="bullet">
/// <item>The first parameter passed to the filter function is <see cref="IDbCommand"/> from which additional
/// information can be extracted.</item>
/// <item>The return value for the filter:
/// <list type="number">
/// <item>If filter returns <see langword="true" />, the command is
/// collected.</item>
/// <item>If filter returns <see langword="false" /> or throws an
/// exception, the command is <b>NOT</b> collected.</item>
/// </list></item>
/// </list>
/// </remarks>
public Func<IDbCommand, bool> Filter { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,24 @@ public override void OnCustom(string name, Activity activity, object payload)
{
var command = this.commandFetcher.Fetch(payload);

try
{
if (command is IDbCommand typedCommand && this.options.Filter?.Invoke(typedCommand) == false)
{
EntityFrameworkInstrumentationEventSource.Log.CommandIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
return;
}
}
catch (Exception ex)
{
EntityFrameworkInstrumentationEventSource.Log.CommandFilterException(ex);
activity.IsAllDataRequested = false;
activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
return;
}

if (this.commandTypeFetcher.Fetch(command) is CommandType commandType)
{
var commandText = this.commandTextFetcher.Fetch(command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ public void EnrichmentException(string eventName, Exception ex)
}
}

[NonEvent]
public void CommandFilterException(Exception ex)
{
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
{
this.CommandFilterException(ex.ToInvariantString());
}
}

[Event(1, Message = "Unknown error processing event '{1}' from handler '{0}', Exception: {2}", Level = EventLevel.Error)]
public void UnknownErrorProcessingEvent(string handlerName, string eventName, string ex)
{
Expand Down Expand Up @@ -75,4 +84,16 @@ public void EnrichmentException(string eventName, string exception)
this.WriteEvent(5, eventName, exception);
}
}

[Event(6, Message = "Command is filtered out. Activity {0}", Level = EventLevel.Verbose)]
public void CommandIsFilteredOut(string activityName)
{
this.WriteEvent(6, activityName);
}

[Event(7, Message = "Command filter threw exception. Command will not be collected. Exception {0}.", Level = EventLevel.Error)]
public void CommandFilterException(string exception)
{
this.WriteEvent(7, exception);
}
}
22 changes: 22 additions & 0 deletions src/OpenTelemetry.Instrumentation.EntityFrameworkCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,28 @@ services.AddOpenTelemetry()
.AddConsoleExporter());
```

### Filter

This option can be used to filter out activities based on the properties of the
db command object being instrumented using a `Func<IDbCommand, bool>`. The
function receives an instance of the db command and should return `true`
if the telemetry is to be collected, and `false` if it should not.

The following code snippet shows how to use `Filter` to collect traces for stored procedures only.

```csharp
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.Filter = (command) =>
{
return command.CommandType == System.Data.CommandType.StoredProcedure;
};
})
.AddConsoleExporter());
```

## References

* [OpenTelemetry Project](https://opentelemetry.io/)
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,60 @@ public void EntityFrameworkContextExceptionEventsInstrumentedTest()
VerifyActivityData(activity, isError: true);
}

[Fact]
public void ShouldCollectTelemetryWhenFilterEvaluatesToTrue()
{
var activityProcessor = new Mock<BaseProcessor<Activity>>();
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
.AddProcessor(activityProcessor.Object)
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.Filter = (command) =>
{
return command.CommandText.Contains("Item", StringComparison.OrdinalIgnoreCase);
};
}).Build();

using (var context = new ItemsContext(this.contextOptions))
{
_ = context.Set<Item>().OrderBy(e => e.Name).ToList();
}

Assert.Equal(3, activityProcessor.Invocations.Count);

var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];

Assert.True(activity.IsAllDataRequested);
Assert.True(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));
}

[Fact]
public void ShouldCollectTelemetryWhenFilterEvaluatesToFalse()
{
var activityProcessor = new Mock<BaseProcessor<Activity>>();
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
.AddProcessor(activityProcessor.Object)
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.Filter = (command) =>
{
return !command.CommandText.Contains("Item", StringComparison.OrdinalIgnoreCase);
};
}).Build();

using (var context = new ItemsContext(this.contextOptions))
{
_ = context.Set<Item>().OrderBy(e => e.Name).ToList();
}

Assert.Equal(2, activityProcessor.Invocations.Count);

var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];

Assert.False(activity.IsAllDataRequested);
Assert.True(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.None));
}

public void Dispose() => this.connection.Dispose();

private static DbConnection CreateInMemoryDatabase()
Expand Down

0 comments on commit 1746af5

Please sign in to comment.