Skip to content

Commit 9b440a3

Browse files
committed
Change SentryAIActivityListener to non-static class to a singleton pattern
1 parent 5745c0a commit 9b440a3

File tree

3 files changed

+50
-33
lines changed

3 files changed

+50
-33
lines changed

src/Sentry.Extensions.AI/Extensions/SentryAIExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public static ChatOptions AddSentryToolInstrumentation(this ChatOptions options)
5353
[Experimental(DiagnosticId.ExperimentalFeature)]
5454
public static IChatClient AddSentry(this IChatClient client, Action<SentryAIOptions>? configure = null)
5555
{
56-
SentryAIActivityListener.Init();
56+
// The constructor automatically adds the listener, so we can discard the instance
57+
_ = new SentryAiActivityListener();
5758
return new SentryChatClient(client, configure);
5859
}
5960
}

src/Sentry.Extensions.AI/SentryAIActivityListener.cs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,51 @@ namespace Sentry.Extensions.AI;
55
/// <summary>
66
/// Listens to FunctionInvokingChatClient's Activity
77
/// </summary>
8-
internal static class SentryAIActivityListener
8+
internal class SentryAiActivityListener
99
{
10-
private static volatile IHub Hub = HubAdapter.Instance;
10+
private static ActivityListener? Instance;
1111

1212
/// <summary>
13-
/// Sentry's <see cref="ActivityListener"/> to tap into function invocation's Activity
13+
/// Initializes Sentry's <see cref="ActivityListener"/> to tap into FunctionInvokingChatClient's Activity
1414
/// </summary>
15-
private static readonly ActivityListener FICCListener = new()
15+
/// <param name="hub">Optional IHub instance to use. If not provided, HubAdapter.Instance will be used.</param>
16+
public SentryAiActivityListener(IHub? hub = null)
1617
{
17-
ShouldListenTo = source => source.Name.StartsWith(SentryAIConstants.SentryActivitySourceName),
18-
Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
19-
SentryAIConstants.FICCActivityNames.Contains(options.Name) ?
20-
ActivitySamplingResult.AllDataAndRecorded : ActivitySamplingResult.None,
21-
ActivityStarted = activity =>
18+
if (Instance != null)
2219
{
23-
var agentSpan = Hub.StartSpan(SentryAIConstants.SpanAttributes.InvokeAgentOperation, SentryAIConstants.SpanAttributes.InvokeAgentDescription);
24-
activity.SetFused(SentryAIConstants.SentryFICCSpanAttributeName, agentSpan);
25-
},
26-
ActivityStopped = activity =>
27-
{
28-
var agentSpan = activity.GetFused<ISpan>(SentryAIConstants.SentryFICCSpanAttributeName);
29-
// Don't pass in OK status in case there was an exception
30-
agentSpan?.Finish();
20+
return;
3121
}
32-
};
22+
23+
var currHub = hub ?? HubAdapter.Instance;
24+
Instance = new ActivityListener
25+
{
26+
ShouldListenTo = source => source.Name.StartsWith(SentryAIConstants.SentryActivitySourceName),
27+
Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
28+
SentryAIConstants.FICCActivityNames.Contains(options.Name)
29+
? ActivitySamplingResult.AllDataAndRecorded
30+
: ActivitySamplingResult.None,
31+
ActivityStarted = activity =>
32+
{
33+
var agentSpan = currHub.StartSpan(SentryAIConstants.SpanAttributes.InvokeAgentOperation,
34+
SentryAIConstants.SpanAttributes.InvokeAgentDescription);
35+
activity.SetFused(SentryAIConstants.SentryFICCSpanAttributeName, agentSpan);
36+
},
37+
ActivityStopped = activity =>
38+
{
39+
var agentSpan = activity.GetFused<ISpan>(SentryAIConstants.SentryFICCSpanAttributeName);
40+
// Don't pass in OK status in case there was an exception
41+
agentSpan?.Finish();
42+
}
43+
};
44+
ActivitySource.AddActivityListener(Instance);
45+
}
3346

3447
/// <summary>
35-
/// Initializes Sentry's <see cref="ActivityListener"/> to tap into FunctionInvokingChatClient's Activity
48+
/// Dispose the singleton instance (for testing purposes mostly)
3649
/// </summary>
37-
/// <param name="hub">Optional IHub instance to use. If not provided, HubAdapter.Instance will be used.</param>
38-
internal static void Init(IHub? hub = null)
50+
internal static void Dispose()
3951
{
40-
Hub = hub ?? HubAdapter.Instance;
41-
ActivitySource.AddActivityListener(FICCListener);
52+
Instance?.Dispose();
53+
Instance = null;
4254
}
4355
}

test/Sentry.Extensions.AI.Tests/SentryAIActivityListenerTests.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ public Fixture()
1616

1717
private readonly Fixture _fixture = new();
1818

19+
public SentryAIActivityListenerTests()
20+
{
21+
// Dispose ActivityListener before each test, otherwise the singleton instance will persist between tests
22+
SentryAiActivityListener.Dispose();
23+
}
24+
1925
[Fact]
2026
public void Init_AddsActivityListenerToActivitySource()
2127
{
2228
// Act
23-
SentryAIActivityListener.Init();
29+
_ = new SentryAiActivityListener();
2430

2531
// Assert
2632
Assert.True(SentryAIActivitySource.Instance.HasListeners());
@@ -31,7 +37,7 @@ public void Init_AddsActivityListenerToActivitySource()
3137
public void ShouldListenTo_ReturnsTrueForSentryActivitySource(string sourceName)
3238
{
3339
// Arrange
34-
SentryAIActivityListener.Init(_fixture.Hub);
40+
_ = new SentryAiActivityListener(_fixture.Hub);
3541
var activitySource = new ActivitySource(sourceName);
3642

3743
// Act
@@ -51,7 +57,7 @@ public void ShouldListenTo_ReturnsTrueForSentryActivitySource(string sourceName)
5157
public void ShouldListenTo_ReturnsFalseForNonSentryActivitySource()
5258
{
5359
// Arrange
54-
SentryAIActivityListener.Init(_fixture.Hub);
60+
_ = new SentryAiActivityListener(_fixture.Hub);
5561
var activitySource = new ActivitySource("Other.ActivitySource");
5662

5763
// Act & Assert
@@ -65,12 +71,10 @@ public void ShouldListenTo_ReturnsFalseForNonSentryActivitySource()
6571

6672
[Theory]
6773
[InlineData("orchestrate_tools")]
68-
[InlineData("FunctionInvokingChatClient.GetResponseAsync")]
69-
[InlineData("FunctionInvokingChatClient")]
7074
public void Sample_ReturnsAllDataAndRecordedForFICCActivityNames(string activityName)
7175
{
7276
// Arrange
73-
SentryAIActivityListener.Init(_fixture.Hub);
77+
_ = new SentryAiActivityListener(_fixture.Hub);
7478

7579
// Act
7680
using var activity = SentryAIActivitySource.Instance.StartActivity(activityName);
@@ -90,9 +94,9 @@ public void Sample_ReturnsAllDataAndRecordedForFICCActivityNames(string activity
9094
public void Init_MultipleCalls_NoDuplicateListener_StartsOnlyOneTransaction()
9195
{
9296
// Arrange
93-
SentryAIActivityListener.Init(_fixture.Hub);
94-
SentryAIActivityListener.Init(_fixture.Hub);
95-
SentryAIActivityListener.Init(_fixture.Hub);
97+
_ = new SentryAiActivityListener(_fixture.Hub);
98+
_ = new SentryAiActivityListener(_fixture.Hub);
99+
_ = new SentryAiActivityListener(_fixture.Hub);
96100

97101
// Act
98102
using var activity = SentryAIActivitySource.Instance.StartActivity(SentryAIConstants.FICCActivityNames[0]);

0 commit comments

Comments
 (0)