From bef464817338df4af733a453cc6db45c7231df6f Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 8 Aug 2025 18:47:08 -0400 Subject: [PATCH 1/7] first shot --- Sentry.sln | 45 +++++++++++ samples/Sentry.Samples.AI.Console/Program.cs | 58 ++++++++++++++ .../Sentry.Samples.AI.Console.csproj | 36 +++++++++ src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 1 + src/Sentry.Extensions.AI/Class1.cs | 79 +++++++++++++++++++ .../Extensions/SentryAIBuilderExtensions.cs | 33 ++++++++ .../Sentry.Extensions.AI.csproj | 21 +++++ .../Sentry.Extensions.AI.Tests.csproj | 18 +++++ .../SentryChatClientTests.cs | 38 +++++++++ 9 files changed, 329 insertions(+) create mode 100644 samples/Sentry.Samples.AI.Console/Program.cs create mode 100644 samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj create mode 100644 src/Sentry.Extensions.AI/Class1.cs create mode 100644 src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs create mode 100644 src/Sentry.Extensions.AI/Sentry.Extensions.AI.csproj create mode 100644 test/Sentry.Extensions.AI.Tests/Sentry.Extensions.AI.Tests.csproj create mode 100644 test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs diff --git a/Sentry.sln b/Sentry.sln index 3730377a46..6f1f3cf0a1 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -209,6 +209,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.SourceGenerators.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.CommunityToolkit.Mvvm.Tests", "test\Sentry.Maui.CommunityToolkit.Mvvm.Tests\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "{ADC91A84-6054-42EC-8241-0D717E4C7194}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI", "src\Sentry.Extensions.AI\Sentry.Extensions.AI.csproj", "{7FBB62AE-ED72-4746-A437-77F9718F7E2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests", "test\Sentry.Extensions.AI.Tests\Sentry.Extensions.AI.Tests.csproj", "{0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.AI.Console", "samples\Sentry.Samples.AI.Console\Sentry.Samples.AI.Console.csproj", "{211A757E-4870-4500-966F-ADEA76684971}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1252,6 +1258,42 @@ Global {ADC91A84-6054-42EC-8241-0D717E4C7194}.Release|x64.Build.0 = Release|Any CPU {ADC91A84-6054-42EC-8241-0D717E4C7194}.Release|x86.ActiveCfg = Release|Any CPU {ADC91A84-6054-42EC-8241-0D717E4C7194}.Release|x86.Build.0 = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x64.Build.0 = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x86.Build.0 = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|Any CPU.Build.0 = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x64.ActiveCfg = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x64.Build.0 = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x86.ActiveCfg = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x86.Build.0 = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x64.ActiveCfg = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x64.Build.0 = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x86.Build.0 = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|Any CPU.Build.0 = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x64.ActiveCfg = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x64.Build.0 = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x86.ActiveCfg = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x86.Build.0 = Release|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Debug|Any CPU.Build.0 = Debug|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Debug|x64.ActiveCfg = Debug|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Debug|x64.Build.0 = Debug|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Debug|x86.ActiveCfg = Debug|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Debug|x86.Build.0 = Debug|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Release|Any CPU.ActiveCfg = Release|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Release|Any CPU.Build.0 = Release|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Release|x64.ActiveCfg = Release|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Release|x64.Build.0 = Release|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Release|x86.ActiveCfg = Release|Any CPU + {211A757E-4870-4500-966F-ADEA76684971}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1343,5 +1385,8 @@ Global {C3CDF61C-3E28-441C-A9CE-011C89D11719} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} {3A76FF7D-2F32-4EA5-8999-2FFE3C7CB893} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {ADC91A84-6054-42EC-8241-0D717E4C7194} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {7FBB62AE-ED72-4746-A437-77F9718F7E2A} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} + {211A757E-4870-4500-966F-ADEA76684971} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} EndGlobalSection EndGlobal diff --git a/samples/Sentry.Samples.AI.Console/Program.cs b/samples/Sentry.Samples.AI.Console/Program.cs new file mode 100644 index 0000000000..50c246d64c --- /dev/null +++ b/samples/Sentry.Samples.AI.Console/Program.cs @@ -0,0 +1,58 @@ +using Sentry.Extensions.AI; +using Sentry.Extensibility; + +var services = new ServiceCollection(); + +// Initialize Sentry - get DSN from environment variable (set SENTRY_DSN env var with your DSN) +var sentryDsn = Environment.GetEnvironmentVariable("SENTRY_DSN") ?? "https://test@localhost/1"; // placeholder +Console.WriteLine($"Using Sentry DSN: {sentryDsn}"); + +using var sentryInstance = SentrySdk.Init(options => +{ + options.Dsn = sentryDsn; + options.Debug = true; // Enable for demo + options.TracesSampleRate = 1.0; +}); + +// Register Sentry's IHub for DI +services.AddSingleton(_ => HubAdapter.Instance); + +var sp = services.BuildServiceProvider(); + +// Create a simple echo client and wrap it with Sentry instrumentation +var echoClient = new EchoChatClient(); +var hub = sp.GetRequiredService(); +var chat = echoClient.WithSentry(hub, agentName: "Sample Agent", model: "gpt-4o-mini", system: "openai"); + +Console.WriteLine("Making AI call with Sentry instrumentation..."); + +var response = await chat.CompleteAsync(new[] +{ + new ChatMessage(ChatRole.User, "Say hello from Sentry sample") +}); + +Console.WriteLine($"Response: {response.Message.Text}"); +Console.WriteLine("Sample completed! Check your Sentry dashboard for the trace data."); + +// Simple echo client for demonstration +public class EchoChatClient : IChatClient +{ + public ChatClientMetadata Metadata => new("echo-client"); + + public Task CompleteAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) + { + var lastMessage = messages.LastOrDefault()?.Text ?? "Hello from echo client!"; + var responseMessage = new ChatMessage(ChatRole.Assistant, $"Echo: {lastMessage}"); + return Task.FromResult(new ChatCompletion(responseMessage)); + } + + public IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public TService GetService(object key = null) where TService : class => null; + public void Dispose() { } +} + + diff --git a/samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj b/samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj new file mode 100644 index 0000000000..8d637993a9 --- /dev/null +++ b/samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj @@ -0,0 +1,36 @@ + + + + Exe + net8.0 + + disable + + + + + $(DefineConstants);SENTRY_DSN_DEFINED_IN_ENV + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 77ad1d5797..3bbfc298dc 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -27,6 +27,7 @@ namespace Sentry.CocoaSdk; // typedef SentryEvent * _Nullable (^SentryBeforeSendEventCallback)(SentryEvent * _Nonnull); [Internal] +[return: NullAllowed] delegate SentryEvent SentryBeforeSendEventCallback (SentryEvent @event); // typedef id _Nullable (^SentryBeforeSendSpanCallback)(id _Nonnull); diff --git a/src/Sentry.Extensions.AI/Class1.cs b/src/Sentry.Extensions.AI/Class1.cs new file mode 100644 index 0000000000..d3bb55cb61 --- /dev/null +++ b/src/Sentry.Extensions.AI/Class1.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Sentry; + +namespace Sentry.Extensions.AI; + +internal sealed class SentryChatClient : IChatClient +{ + private readonly IChatClient _innerClient; + private readonly IHub _hub; + private readonly string? _agentName; + private readonly string? _model; + private readonly string? _system; + + public SentryChatClient(IChatClient innerClient, IHub hub, string? agentName = null, string? model = null, string? system = null) + { + _innerClient = innerClient; + _hub = hub; + _agentName = agentName; + _model = model; + _system = system; + } + + public ChatClientMetadata Metadata => _innerClient.Metadata; + + public async Task CompleteAsync(IList messages, ChatOptions? options = null, CancellationToken cancellationToken = default) + { + var operation = "gen_ai.invoke_agent"; + var spanName = _agentName is { Length: > 0 } ? $"invoke_agent {_agentName}" : "invoke_agent"; + var transaction = _hub.GetSpan()?.StartChild(operation, spanName) ?? _hub.StartTransaction(spanName, operation); + + if (_system is { Length: > 0 }) + { + transaction.SetTag("gen_ai.system", _system); + } + + if (_model is { Length: > 0 }) + { + transaction.SetTag("gen_ai.request.model", _model); + } + + transaction.SetTag("gen_ai.operation.name", "invoke_agent"); + if (_agentName is { Length: > 0 }) + { + transaction.SetTag("gen_ai.agent.name", _agentName); + } + + try + { + var response = await _innerClient.CompleteAsync(messages, options, cancellationToken).ConfigureAwait(false); + transaction.Finish(SpanStatus.Ok); + return response; + } + catch (Exception ex) + { + transaction.Finish(SpanStatus.InternalError); + _hub.CaptureException(ex); + throw; + } + } + + public IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions? options = null, CancellationToken cancellationToken = default) + { + // For streaming, we wrap the enumerable but don't create spans for each chunk + return _innerClient.CompleteStreamingAsync(messages, options, cancellationToken); + } + + public TService? GetService(object? key = null) where TService : class + { + return _innerClient.GetService(key); + } + + public void Dispose() + { + _innerClient?.Dispose(); + } +} + + diff --git a/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs b/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs new file mode 100644 index 0000000000..1e11d67fb1 --- /dev/null +++ b/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; + +namespace Sentry.Extensions.AI; + +/// +/// Extensions to instrument Microsoft.Extensions.AI builders with Sentry +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static class SentryAIExtensions +{ + /// + /// Adds Sentry instrumentation to the ChatClientBuilder pipeline. + /// + public static ChatClientBuilder UseSentry(this ChatClientBuilder builder, string? agentName = null, string? model = null, string? system = null) + { + return builder.Use((serviceProvider, inner) => + { + var hub = serviceProvider.GetRequiredService(); + return new SentryChatClient(inner, hub, agentName, model, system); + }); + } + + /// + /// Wraps an IChatClient with Sentry instrumentation. + /// + public static IChatClient WithSentry(this IChatClient client, IHub hub, string? agentName = null, string? model = null, string? system = null) + { + return new SentryChatClient(client, hub, agentName, model, system); + } +} + + diff --git a/src/Sentry.Extensions.AI/Sentry.Extensions.AI.csproj b/src/Sentry.Extensions.AI/Sentry.Extensions.AI.csproj new file mode 100644 index 0000000000..dbd26b9d7d --- /dev/null +++ b/src/Sentry.Extensions.AI/Sentry.Extensions.AI.csproj @@ -0,0 +1,21 @@ + + + + net9.0;net8.0 + $(PackageTags);Microsoft.Extensions.AI;AI;LLM + Microsoft.Extensions.AI integration for Sentry - captures AI Agent telemetry spans per Sentry AI Agents module. + + + + + + + + + + + + + + + diff --git a/test/Sentry.Extensions.AI.Tests/Sentry.Extensions.AI.Tests.csproj b/test/Sentry.Extensions.AI.Tests/Sentry.Extensions.AI.Tests.csproj new file mode 100644 index 0000000000..d6bec87995 --- /dev/null +++ b/test/Sentry.Extensions.AI.Tests/Sentry.Extensions.AI.Tests.csproj @@ -0,0 +1,18 @@ + + + + net9.0;net8.0 + + + + + + + + + + + + + + diff --git a/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs b/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs new file mode 100644 index 0000000000..6860d6dbc0 --- /dev/null +++ b/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs @@ -0,0 +1,38 @@ +using Sentry.Extensions.AI; + +namespace Sentry.Extensions.AI.Tests; + +public class SentryChatClientTests +{ + [Fact] + public async Task CompleteAsync_CallsInnerClient() + { + var inner = Substitute.For(); + var chatCompletion = new ChatCompletion(new ChatMessage(ChatRole.Assistant, "ok")); + inner.CompleteAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(chatCompletion)); + + var hub = Substitute.For(); + var client = new SentryChatClient(inner, hub, agentName: "Agent", model: "gpt-4o-mini", system: "openai"); + + var res = await client.CompleteAsync(new[] { new ChatMessage(ChatRole.User, "hi") }, null); + + Assert.Equal("ok", res.Message.Text); + await inner.Received(1).CompleteAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + } + + [Fact] + public void Metadata_ReturnsInnerClientMetadata() + { + var inner = Substitute.For(); + var metadata = new ChatClientMetadata("test-client"); + inner.Metadata.Returns(metadata); + + var hub = Substitute.For(); + var client = new SentryChatClient(inner, hub); + + Assert.Equal(metadata, client.Metadata); + } +} + + From 924b8681b01ec2f13572cbf5dcbb7d3ea26d5df3 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 8 Aug 2025 23:08:52 +0000 Subject: [PATCH 2/7] Format code --- samples/Sentry.Samples.AI.Console/Program.cs | 2 +- src/Sentry.Extensions.AI/Class1.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/Sentry.Samples.AI.Console/Program.cs b/samples/Sentry.Samples.AI.Console/Program.cs index 50c246d64c..59c91bfbf3 100644 --- a/samples/Sentry.Samples.AI.Console/Program.cs +++ b/samples/Sentry.Samples.AI.Console/Program.cs @@ -1,5 +1,5 @@ -using Sentry.Extensions.AI; using Sentry.Extensibility; +using Sentry.Extensions.AI; var services = new ServiceCollection(); diff --git a/src/Sentry.Extensions.AI/Class1.cs b/src/Sentry.Extensions.AI/Class1.cs index d3bb55cb61..d23965c609 100644 --- a/src/Sentry.Extensions.AI/Class1.cs +++ b/src/Sentry.Extensions.AI/Class1.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Sentry; From 3073dcfaa5b2a65e2a372a5a2f2df3c44b77b8b0 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 8 Aug 2025 20:09:53 -0400 Subject: [PATCH 3/7] more stuff --- .generated.NoMobile.sln | 45 +++++++++++++ Sentry-CI-Build-Linux-NoMobile.slnf | 4 ++ Sentry-CI-Build-Linux.slnf | 4 ++ Sentry-CI-Build-Windows-arm64.slnf | 4 ++ Sentry-CI-Build-Windows.slnf | 4 ++ Sentry-CI-Build-macOS.slnf | 4 ++ Sentry-CI-CodeQL.slnf | 1 + Sentry.sln | 28 ++++---- SentryNoMobile.slnf | 4 ++ SentryNoSamples.slnf | 2 + samples/Sentry.Samples.AI.Console/Program.cs | 58 ----------------- .../Sentry.Samples.AI.Console.csproj | 36 ----------- .../Sentry.Samples.ME.AI.Console/Program.cs | 64 +++++++++++++++++++ .../Sentry.Samples.ME.AI.Console.csproj | 26 ++++++++ .../Extensions/SentryAIBuilderExtensions.cs | 8 ++- 15 files changed, 181 insertions(+), 111 deletions(-) delete mode 100644 samples/Sentry.Samples.AI.Console/Program.cs delete mode 100644 samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj create mode 100644 samples/Sentry.Samples.ME.AI.Console/Program.cs create mode 100644 samples/Sentry.Samples.ME.AI.Console/Sentry.Samples.ME.AI.Console.csproj diff --git a/.generated.NoMobile.sln b/.generated.NoMobile.sln index 3730377a46..d06cfc4124 100644 --- a/.generated.NoMobile.sln +++ b/.generated.NoMobile.sln @@ -209,6 +209,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.SourceGenerators.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.CommunityToolkit.Mvvm.Tests", "test\Sentry.Maui.CommunityToolkit.Mvvm.Tests\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "{ADC91A84-6054-42EC-8241-0D717E4C7194}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI", "src\Sentry.Extensions.AI\Sentry.Extensions.AI.csproj", "{7FBB62AE-ED72-4746-A437-77F9718F7E2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests", "test\Sentry.Extensions.AI.Tests\Sentry.Extensions.AI.Tests.csproj", "{0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.ME.AI.Console", "samples\Sentry.Samples.ME.AI.Console\Sentry.Samples.ME.AI.Console.csproj", "{421B0105-CA96-43AA-A7EC-B8C4A4366B9B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1252,6 +1258,42 @@ Global {ADC91A84-6054-42EC-8241-0D717E4C7194}.Release|x64.Build.0 = Release|Any CPU {ADC91A84-6054-42EC-8241-0D717E4C7194}.Release|x86.ActiveCfg = Release|Any CPU {ADC91A84-6054-42EC-8241-0D717E4C7194}.Release|x86.Build.0 = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x64.Build.0 = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Debug|x86.Build.0 = Debug|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|Any CPU.Build.0 = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x64.ActiveCfg = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x64.Build.0 = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x86.ActiveCfg = Release|Any CPU + {7FBB62AE-ED72-4746-A437-77F9718F7E2A}.Release|x86.Build.0 = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x64.ActiveCfg = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x64.Build.0 = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Debug|x86.Build.0 = Debug|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|Any CPU.Build.0 = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x64.ActiveCfg = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x64.Build.0 = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x86.ActiveCfg = Release|Any CPU + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x86.Build.0 = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x64.Build.0 = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x86.Build.0 = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|Any CPU.Build.0 = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x64.ActiveCfg = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x64.Build.0 = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x86.ActiveCfg = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1343,5 +1385,8 @@ Global {C3CDF61C-3E28-441C-A9CE-011C89D11719} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} {3A76FF7D-2F32-4EA5-8999-2FFE3C7CB893} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {ADC91A84-6054-42EC-8241-0D717E4C7194} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {7FBB62AE-ED72-4746-A437-77F9718F7E2A} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} + {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B} = {21B42F60-5802-404E-90F0-AEBCC56760C0} EndGlobalSection EndGlobal diff --git a/Sentry-CI-Build-Linux-NoMobile.slnf b/Sentry-CI-Build-Linux-NoMobile.slnf index f29fd0a74e..97fc0e498e 100644 --- a/Sentry-CI-Build-Linux-NoMobile.slnf +++ b/Sentry-CI-Build-Linux-NoMobile.slnf @@ -24,6 +24,8 @@ "samples\\Sentry.Samples.GraphQL.Server\\Sentry.Samples.GraphQL.Server.csproj", "samples\\Sentry.Samples.Hangfire\\Sentry.Samples.Hangfire.csproj", "samples\\Sentry.Samples.Log4Net\\Sentry.Samples.Log4Net.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.AI.Console.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.ME.AI.Console.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", @@ -36,6 +38,7 @@ "src\\Sentry.Azure.Functions.Worker\\Sentry.Azure.Functions.Worker.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -54,6 +57,7 @@ "test\\Sentry.DiagnosticSource.IntegrationTests\\Sentry.DiagnosticSource.IntegrationTests.csproj", "test\\Sentry.DiagnosticSource.Tests\\Sentry.DiagnosticSource.Tests.csproj", "test\\Sentry.EntityFramework.Tests\\Sentry.EntityFramework.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/Sentry-CI-Build-Linux.slnf b/Sentry-CI-Build-Linux.slnf index b31c819bc8..4752159c73 100644 --- a/Sentry-CI-Build-Linux.slnf +++ b/Sentry-CI-Build-Linux.slnf @@ -26,6 +26,8 @@ "samples\\Sentry.Samples.Hangfire\\Sentry.Samples.Hangfire.csproj", "samples\\Sentry.Samples.Log4Net\\Sentry.Samples.Log4Net.csproj", "samples\\Sentry.Samples.Maui\\Sentry.Samples.Maui.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.AI.Console.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.ME.AI.Console.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", @@ -40,6 +42,7 @@ "src\\Sentry.Bindings.Android\\Sentry.Bindings.Android.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -61,6 +64,7 @@ "test\\Sentry.DiagnosticSource.IntegrationTests\\Sentry.DiagnosticSource.IntegrationTests.csproj", "test\\Sentry.DiagnosticSource.Tests\\Sentry.DiagnosticSource.Tests.csproj", "test\\Sentry.EntityFramework.Tests\\Sentry.EntityFramework.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/Sentry-CI-Build-Windows-arm64.slnf b/Sentry-CI-Build-Windows-arm64.slnf index c1f6bd735d..e7408b5427 100644 --- a/Sentry-CI-Build-Windows-arm64.slnf +++ b/Sentry-CI-Build-Windows-arm64.slnf @@ -27,6 +27,8 @@ "samples\\Sentry.Samples.Hangfire\\Sentry.Samples.Hangfire.csproj", "samples\\Sentry.Samples.Log4Net\\Sentry.Samples.Log4Net.csproj", "samples\\Sentry.Samples.Maui\\Sentry.Samples.Maui.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.AI.Console.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.ME.AI.Console.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", @@ -42,6 +44,7 @@ "src\\Sentry.Bindings.Android\\Sentry.Bindings.Android.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -61,6 +64,7 @@ "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", "test\\Sentry.Azure.Functions.Worker.Tests\\Sentry.Azure.Functions.Worker.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/Sentry-CI-Build-Windows.slnf b/Sentry-CI-Build-Windows.slnf index d5960f2212..f090004d30 100644 --- a/Sentry-CI-Build-Windows.slnf +++ b/Sentry-CI-Build-Windows.slnf @@ -27,6 +27,8 @@ "samples\\Sentry.Samples.Hangfire\\Sentry.Samples.Hangfire.csproj", "samples\\Sentry.Samples.Log4Net\\Sentry.Samples.Log4Net.csproj", "samples\\Sentry.Samples.Maui\\Sentry.Samples.Maui.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.AI.Console.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.ME.AI.Console.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", @@ -42,6 +44,7 @@ "src\\Sentry.Bindings.Android\\Sentry.Bindings.Android.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -64,6 +67,7 @@ "test\\Sentry.DiagnosticSource.IntegrationTests\\Sentry.DiagnosticSource.IntegrationTests.csproj", "test\\Sentry.DiagnosticSource.Tests\\Sentry.DiagnosticSource.Tests.csproj", "test\\Sentry.EntityFramework.Tests\\Sentry.EntityFramework.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf index 778a9a13db..d9899b2780 100644 --- a/Sentry-CI-Build-macOS.slnf +++ b/Sentry-CI-Build-macOS.slnf @@ -31,6 +31,8 @@ "samples\\Sentry.Samples.MacCatalyst\\Sentry.Samples.MacCatalyst.csproj", "samples\\Sentry.Samples.MacOS\\Sentry.Samples.MacOS.csproj", "samples\\Sentry.Samples.Maui\\Sentry.Samples.Maui.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.AI.Console.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.ME.AI.Console.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", @@ -47,6 +49,7 @@ "src\\Sentry.Bindings.Cocoa\\Sentry.Bindings.Cocoa.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -68,6 +71,7 @@ "test\\Sentry.DiagnosticSource.IntegrationTests\\Sentry.DiagnosticSource.IntegrationTests.csproj", "test\\Sentry.DiagnosticSource.Tests\\Sentry.DiagnosticSource.Tests.csproj", "test\\Sentry.EntityFramework.Tests\\Sentry.EntityFramework.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/Sentry-CI-CodeQL.slnf b/Sentry-CI-CodeQL.slnf index 0dbf4453dd..d843499f8f 100644 --- a/Sentry-CI-CodeQL.slnf +++ b/Sentry-CI-CodeQL.slnf @@ -11,6 +11,7 @@ "src\\Sentry.Azure.Functions.Worker\\Sentry.Azure.Functions.Worker.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", diff --git a/Sentry.sln b/Sentry.sln index 6f1f3cf0a1..d06cfc4124 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -213,7 +213,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests", "test\Sentry.Extensions.AI.Tests\Sentry.Extensions.AI.Tests.csproj", "{0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.AI.Console", "samples\Sentry.Samples.AI.Console\Sentry.Samples.AI.Console.csproj", "{211A757E-4870-4500-966F-ADEA76684971}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.ME.AI.Console", "samples\Sentry.Samples.ME.AI.Console\Sentry.Samples.ME.AI.Console.csproj", "{421B0105-CA96-43AA-A7EC-B8C4A4366B9B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1282,18 +1282,18 @@ Global {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x64.Build.0 = Release|Any CPU {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x86.ActiveCfg = Release|Any CPU {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7}.Release|x86.Build.0 = Release|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Debug|Any CPU.Build.0 = Debug|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Debug|x64.ActiveCfg = Debug|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Debug|x64.Build.0 = Debug|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Debug|x86.ActiveCfg = Debug|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Debug|x86.Build.0 = Debug|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Release|Any CPU.ActiveCfg = Release|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Release|Any CPU.Build.0 = Release|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Release|x64.ActiveCfg = Release|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Release|x64.Build.0 = Release|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Release|x86.ActiveCfg = Release|Any CPU - {211A757E-4870-4500-966F-ADEA76684971}.Release|x86.Build.0 = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x64.Build.0 = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Debug|x86.Build.0 = Debug|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|Any CPU.Build.0 = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x64.ActiveCfg = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x64.Build.0 = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x86.ActiveCfg = Release|Any CPU + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1387,6 +1387,6 @@ Global {ADC91A84-6054-42EC-8241-0D717E4C7194} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {7FBB62AE-ED72-4746-A437-77F9718F7E2A} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} {0013A6AD-D37E-489D-AFBA-53BB3AB8A8C7} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} - {211A757E-4870-4500-966F-ADEA76684971} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} + {421B0105-CA96-43AA-A7EC-B8C4A4366B9B} = {21B42F60-5802-404E-90F0-AEBCC56760C0} EndGlobalSection EndGlobal diff --git a/SentryNoMobile.slnf b/SentryNoMobile.slnf index 1592f77697..fda0eb06bf 100644 --- a/SentryNoMobile.slnf +++ b/SentryNoMobile.slnf @@ -26,6 +26,8 @@ "samples\\Sentry.Samples.Log4Net\\Sentry.Samples.Log4Net.csproj", "samples\\Sentry.Samples.MacCatalyst\\Sentry.Samples.MacCatalyst.csproj", "samples\\Sentry.Samples.MacOS\\Sentry.Samples.MacOS.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.AI.Console.csproj", + "samples\\Sentry.Samples.ME.AI.Console\\Sentry.Samples.ME.AI.Console.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", @@ -39,6 +41,7 @@ "src\\Sentry.Azure.Functions.Worker\\Sentry.Azure.Functions.Worker.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -58,6 +61,7 @@ "test\\Sentry.DiagnosticSource.IntegrationTests\\Sentry.DiagnosticSource.IntegrationTests.csproj", "test\\Sentry.DiagnosticSource.Tests\\Sentry.DiagnosticSource.Tests.csproj", "test\\Sentry.EntityFramework.Tests\\Sentry.EntityFramework.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/SentryNoSamples.slnf b/SentryNoSamples.slnf index d3b4e187ea..f442fa2534 100644 --- a/SentryNoSamples.slnf +++ b/SentryNoSamples.slnf @@ -12,6 +12,7 @@ "src\\Sentry.Azure.Functions.Worker\\Sentry.Azure.Functions.Worker.csproj", "src\\Sentry.DiagnosticSource\\Sentry.DiagnosticSource.csproj", "src\\Sentry.EntityFramework\\Sentry.EntityFramework.csproj", + "src\\Sentry.Extensions.AI\\Sentry.Extensions.AI.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", "src\\Sentry.Google.Cloud.Functions\\Sentry.Google.Cloud.Functions.csproj", "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", @@ -34,6 +35,7 @@ "test\\Sentry.DiagnosticSource.IntegrationTests\\Sentry.DiagnosticSource.IntegrationTests.csproj", "test\\Sentry.DiagnosticSource.Tests\\Sentry.DiagnosticSource.Tests.csproj", "test\\Sentry.EntityFramework.Tests\\Sentry.EntityFramework.Tests.csproj", + "test\\Sentry.Extensions.AI.Tests\\Sentry.Extensions.AI.Tests.csproj", "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", "test\\Sentry.Google.Cloud.Functions.Tests\\Sentry.Google.Cloud.Functions.Tests.csproj", "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", diff --git a/samples/Sentry.Samples.AI.Console/Program.cs b/samples/Sentry.Samples.AI.Console/Program.cs deleted file mode 100644 index 59c91bfbf3..0000000000 --- a/samples/Sentry.Samples.AI.Console/Program.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Sentry.Extensibility; -using Sentry.Extensions.AI; - -var services = new ServiceCollection(); - -// Initialize Sentry - get DSN from environment variable (set SENTRY_DSN env var with your DSN) -var sentryDsn = Environment.GetEnvironmentVariable("SENTRY_DSN") ?? "https://test@localhost/1"; // placeholder -Console.WriteLine($"Using Sentry DSN: {sentryDsn}"); - -using var sentryInstance = SentrySdk.Init(options => -{ - options.Dsn = sentryDsn; - options.Debug = true; // Enable for demo - options.TracesSampleRate = 1.0; -}); - -// Register Sentry's IHub for DI -services.AddSingleton(_ => HubAdapter.Instance); - -var sp = services.BuildServiceProvider(); - -// Create a simple echo client and wrap it with Sentry instrumentation -var echoClient = new EchoChatClient(); -var hub = sp.GetRequiredService(); -var chat = echoClient.WithSentry(hub, agentName: "Sample Agent", model: "gpt-4o-mini", system: "openai"); - -Console.WriteLine("Making AI call with Sentry instrumentation..."); - -var response = await chat.CompleteAsync(new[] -{ - new ChatMessage(ChatRole.User, "Say hello from Sentry sample") -}); - -Console.WriteLine($"Response: {response.Message.Text}"); -Console.WriteLine("Sample completed! Check your Sentry dashboard for the trace data."); - -// Simple echo client for demonstration -public class EchoChatClient : IChatClient -{ - public ChatClientMetadata Metadata => new("echo-client"); - - public Task CompleteAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) - { - var lastMessage = messages.LastOrDefault()?.Text ?? "Hello from echo client!"; - var responseMessage = new ChatMessage(ChatRole.Assistant, $"Echo: {lastMessage}"); - return Task.FromResult(new ChatCompletion(responseMessage)); - } - - public IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public TService GetService(object key = null) where TService : class => null; - public void Dispose() { } -} - - diff --git a/samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj b/samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj deleted file mode 100644 index 8d637993a9..0000000000 --- a/samples/Sentry.Samples.AI.Console/Sentry.Samples.AI.Console.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - Exe - net8.0 - - disable - - - - - $(DefineConstants);SENTRY_DSN_DEFINED_IN_ENV - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/Sentry.Samples.ME.AI.Console/Program.cs b/samples/Sentry.Samples.ME.AI.Console/Program.cs new file mode 100644 index 0000000000..44f47437af --- /dev/null +++ b/samples/Sentry.Samples.ME.AI.Console/Program.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Sentry.Extensions.AI; + +using var loggerFactory = LoggerFactory.Create(builder => +{ + builder.AddConsole(); + builder.AddSentry(options => + { +#if !SENTRY_DSN_DEFINED_IN_ENV + // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. + // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ + options.Dsn = SamplesShared.Dsn; +#endif + + // Set to true to SDK debugging to see the internal messages through the logging library. + options.Debug = false; + + // Enable performance monitoring for AI traces, 1.0 = 100% + options.TracesSampleRate = 1.0; + }); +}); +var logger = loggerFactory.CreateLogger(); + +logger.LogInformation("Starting Microsoft.Extensions.AI sample with Sentry instrumentation"); + +// Create a simple echo client and wrap it with Sentry instrumentation +var echoClient = new EchoChatClient(); +var chat = echoClient.WithSentry(agentName: "ME.AI Sample Agent", model: "gpt-4o-mini", system: "openai"); + +logger.LogInformation("Making AI call with Sentry instrumentation..."); + +var response = await chat.CompleteAsync(new[] +{ + new ChatMessage(ChatRole.User, "Say hello from Sentry sample") +}); + +logger.LogInformation("Response: {ResponseText}", response.Message.Text); + +logger.LogInformation("Microsoft.Extensions.AI sample completed! Check your Sentry dashboard for the trace data."); + +// Simple echo client for demonstration +public class EchoChatClient : IChatClient +{ + public ChatClientMetadata Metadata => new("echo-client"); + + public Task CompleteAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) + { + var lastMessage = messages.LastOrDefault()?.Text ?? "Hello from echo client!"; + var responseMessage = new ChatMessage(ChatRole.Assistant, $"Echo: {lastMessage}"); + return Task.FromResult(new ChatCompletion(responseMessage)); + } + + public IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public TService GetService(object key = null) where TService : class => null; + public void Dispose() { } +} + + diff --git a/samples/Sentry.Samples.ME.AI.Console/Sentry.Samples.ME.AI.Console.csproj b/samples/Sentry.Samples.ME.AI.Console/Sentry.Samples.ME.AI.Console.csproj new file mode 100644 index 0000000000..dba7d7fbff --- /dev/null +++ b/samples/Sentry.Samples.ME.AI.Console/Sentry.Samples.ME.AI.Console.csproj @@ -0,0 +1,26 @@ + + + + Exe + net8.0 + + + + + + + + + sentry-sdks + sentry-dotnet + true + true + + + + + + + + + diff --git a/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs b/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs index 1e11d67fb1..dcde9ed07c 100644 --- a/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs +++ b/src/Sentry.Extensions.AI/Extensions/SentryAIBuilderExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; +using Sentry.Extensibility; namespace Sentry.Extensions.AI; @@ -16,7 +17,8 @@ public static ChatClientBuilder UseSentry(this ChatClientBuilder builder, string { return builder.Use((serviceProvider, inner) => { - var hub = serviceProvider.GetRequiredService(); + // Try to get IHub from DI first, fallback to HubAdapter.Instance + var hub = serviceProvider.GetService() ?? HubAdapter.Instance; return new SentryChatClient(inner, hub, agentName, model, system); }); } @@ -24,9 +26,9 @@ public static ChatClientBuilder UseSentry(this ChatClientBuilder builder, string /// /// Wraps an IChatClient with Sentry instrumentation. /// - public static IChatClient WithSentry(this IChatClient client, IHub hub, string? agentName = null, string? model = null, string? system = null) + public static IChatClient WithSentry(this IChatClient client, string? agentName = null, string? model = null, string? system = null) { - return new SentryChatClient(client, hub, agentName, model, system); + return new SentryChatClient(client, HubAdapter.Instance, agentName, model, system); } } From c07e4c8bdef95fa5f6f7ad1f1171f73e6ac414a1 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 8 Aug 2025 20:30:59 -0400 Subject: [PATCH 4/7] rename class --- src/Sentry.Extensions.AI/{Class1.cs => SentryChatClient.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Sentry.Extensions.AI/{Class1.cs => SentryChatClient.cs} (100%) diff --git a/src/Sentry.Extensions.AI/Class1.cs b/src/Sentry.Extensions.AI/SentryChatClient.cs similarity index 100% rename from src/Sentry.Extensions.AI/Class1.cs rename to src/Sentry.Extensions.AI/SentryChatClient.cs From b606d4d5685950c9eea0170a48cd423f321b164f Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 8 Aug 2025 20:50:29 -0400 Subject: [PATCH 5/7] streaming support --- .../Sentry.Samples.ME.AI.Console/Program.cs | 26 +++- src/Sentry.Extensions.AI/SentryChatClient.cs | 122 +++++++++++++++++- .../SentryChatClientTests.cs | 31 +++++ 3 files changed, 174 insertions(+), 5 deletions(-) diff --git a/samples/Sentry.Samples.ME.AI.Console/Program.cs b/samples/Sentry.Samples.ME.AI.Console/Program.cs index 44f47437af..83d15e0e0f 100644 --- a/samples/Sentry.Samples.ME.AI.Console/Program.cs +++ b/samples/Sentry.Samples.ME.AI.Console/Program.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Sentry.Extensions.AI; +using System.Runtime.CompilerServices; using var loggerFactory = LoggerFactory.Create(builder => { @@ -38,6 +39,20 @@ logger.LogInformation("Response: {ResponseText}", response.Message.Text); +// Demonstrate streaming with Sentry instrumentation +logger.LogInformation("Making streaming AI call with Sentry instrumentation..."); + +var streamingResponse = new List(); +await foreach (var update in chat.CompleteStreamingAsync(new[] +{ + new ChatMessage(ChatRole.User, "Say hello and goodbye with streaming") +})) +{ + streamingResponse.Add(update.Text ?? ""); +} + +logger.LogInformation("Streaming Response: {StreamingText}", string.Join("", streamingResponse)); + logger.LogInformation("Microsoft.Extensions.AI sample completed! Check your Sentry dashboard for the trace data."); // Simple echo client for demonstration @@ -52,9 +67,16 @@ public Task CompleteAsync(IList messages, ChatOptio return Task.FromResult(new ChatCompletion(responseMessage)); } - public IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions options = null, CancellationToken cancellationToken = default) + public async IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + var lastMessage = messages.LastOrDefault()?.Text ?? "Hello from echo client!"; + var parts = new[] { "Echo: ", lastMessage.Substring(0, Math.Min(10, lastMessage.Length)), "...", " (streaming)" }; + + foreach (var part in parts) + { + await Task.Delay(100, cancellationToken); // Simulate streaming delay + yield return new StreamingChatCompletionUpdate { Text = part }; + } } public TService GetService(object key = null) where TService : class => null; diff --git a/src/Sentry.Extensions.AI/SentryChatClient.cs b/src/Sentry.Extensions.AI/SentryChatClient.cs index d23965c609..b574a8f240 100644 --- a/src/Sentry.Extensions.AI/SentryChatClient.cs +++ b/src/Sentry.Extensions.AI/SentryChatClient.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Sentry; +using System.Runtime.CompilerServices; namespace Sentry.Extensions.AI; @@ -53,7 +54,7 @@ public async Task CompleteAsync(IList messages, Cha } catch (Exception ex) { - transaction.Finish(SpanStatus.InternalError); + transaction.Finish(ex); _hub.CaptureException(ex); throw; } @@ -61,8 +62,7 @@ public async Task CompleteAsync(IList messages, Cha public IAsyncEnumerable CompleteStreamingAsync(IList messages, ChatOptions? options = null, CancellationToken cancellationToken = default) { - // For streaming, we wrap the enumerable but don't create spans for each chunk - return _innerClient.CompleteStreamingAsync(messages, options, cancellationToken); + return new SentryStreamingChatEnumerable(_innerClient.CompleteStreamingAsync(messages, options, cancellationToken), _hub, _agentName, _model, _system); } public TService? GetService(object? key = null) where TService : class @@ -76,4 +76,120 @@ public void Dispose() } } +internal sealed class SentryStreamingChatEnumerable : IAsyncEnumerable +{ + private readonly IAsyncEnumerable _innerEnumerable; + private readonly IHub _hub; + private readonly string? _agentName; + private readonly string? _model; + private readonly string? _system; + + public SentryStreamingChatEnumerable( + IAsyncEnumerable innerEnumerable, + IHub hub, + string? agentName, + string? model, + string? system) + { + _innerEnumerable = innerEnumerable; + _hub = hub; + _agentName = agentName; + _model = model; + _system = system; + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new SentryStreamingChatEnumerator(_innerEnumerable.GetAsyncEnumerator(cancellationToken), _hub, _agentName, _model, _system); + } +} + +internal sealed class SentryStreamingChatEnumerator : IAsyncEnumerator +{ + private readonly IAsyncEnumerator _innerEnumerator; + private readonly ISpan _transaction; + private readonly IHub _hub; + private bool _finished; + + public SentryStreamingChatEnumerator( + IAsyncEnumerator innerEnumerator, + IHub hub, + string? agentName, + string? model, + string? system) + { + _innerEnumerator = innerEnumerator; + _hub = hub; + + // Create the span/transaction + var operation = "gen_ai.invoke_agent"; + var spanName = agentName is { Length: > 0 } ? $"invoke_agent {agentName}" : "invoke_agent"; + _transaction = hub.GetSpan()?.StartChild(operation, spanName) ?? hub.StartTransaction(spanName, operation); + + // Set the same tags as CompleteAsync + if (system is { Length: > 0 }) + { + _transaction.SetTag("gen_ai.system", system); + } + + if (model is { Length: > 0 }) + { + _transaction.SetTag("gen_ai.request.model", model); + } + + _transaction.SetTag("gen_ai.operation.name", "invoke_agent"); + if (agentName is { Length: > 0 }) + { + _transaction.SetTag("gen_ai.agent.name", agentName); + } + + // Add streaming-specific tag + _transaction.SetTag("gen_ai.streaming", "true"); + } + + public StreamingChatCompletionUpdate Current => _innerEnumerator.Current; + + public async ValueTask MoveNextAsync() + { + try + { + var hasNext = await _innerEnumerator.MoveNextAsync().ConfigureAwait(false); + + if (!hasNext && !_finished) + { + _transaction.Finish(SpanStatus.Ok); + _finished = true; + } + + return hasNext; + } + catch (Exception ex) + { + if (!_finished) + { + _transaction.Finish(ex); + _hub.CaptureException(ex); + _finished = true; + } + throw; + } + } + + public async ValueTask DisposeAsync() + { + try + { + await _innerEnumerator.DisposeAsync().ConfigureAwait(false); + } + finally + { + if (!_finished) + { + _transaction.Finish(SpanStatus.Ok); + _finished = true; + } + } + } +} + diff --git a/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs b/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs index 6860d6dbc0..fa62e2fb20 100644 --- a/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs +++ b/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs @@ -33,6 +33,37 @@ public void Metadata_ReturnsInnerClientMetadata() Assert.Equal(metadata, client.Metadata); } + + [Fact] + public async Task CompleteStreamingAsync_CallsInnerClient() + { + var inner = Substitute.For(); + + inner.CompleteStreamingAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(CreateTestStreamingUpdates()); + + var hub = Substitute.For(); + var client = new SentryChatClient(inner, hub, agentName: "Agent", model: "gpt-4o-mini", system: "openai"); + + var results = new List(); + await foreach (var update in client.CompleteStreamingAsync(new[] { new ChatMessage(ChatRole.User, "hi") }, null)) + { + results.Add(update); + } + + Assert.Equal(2, results.Count); + Assert.Equal("Hello", results[0].Text); + Assert.Equal(" World", results[1].Text); + + inner.Received(1).CompleteStreamingAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + } + + private static async IAsyncEnumerable CreateTestStreamingUpdates() + { + yield return new StreamingChatCompletionUpdate { Text = "Hello" }; + await Task.Yield(); // Make it actually async + yield return new StreamingChatCompletionUpdate { Text = " World" }; + } } From f9d8323116a5950801b1189b0fc854bd6a9147ec Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 8 Aug 2025 21:01:33 -0400 Subject: [PATCH 6/7] add cursor guidance --- .cursor/sdk_development.mdc | 218 ++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 .cursor/sdk_development.mdc diff --git a/.cursor/sdk_development.mdc b/.cursor/sdk_development.mdc new file mode 100644 index 0000000000..38796d8c51 --- /dev/null +++ b/.cursor/sdk_development.mdc @@ -0,0 +1,218 @@ +# Sentry .NET SDK Development Guidelines + +This document contains important patterns, conventions, and guidelines learned during SDK development to ensure consistency and avoid common pitfalls. + +## Project Structure & Conventions + +### Project Organization +- **Source packages**: `src/Sentry.Extensions.*` +- **Tests**: `test/Sentry.Extensions.*.Tests` +- **Samples**: `samples/Sentry.Samples.ME.*` (ME = Microsoft.Extensions) +- **Solution file**: `Sentry.sln` in root + +### Naming Conventions +- Extension packages for packages targeting Microsoft.Extensions.Technology: `Sentry.Extensions.{Technology}` +- Sample projects for packages targeting Microsoft.Extensions.Technology: `Sentry.Samples.ME.{Technology}.{ProjectType}` +- Test projects: `{SourceProject}.Tests` + +## Directory.Build.props Pattern + +### Central Package Management +- Some packages are managed centrally in `Directory.Build.props` files +- Check existing projects of the same type (src/test/samples) for reference + +### Hierarchical Structure +``` +/Directory.Build.props # Root level +/src/Directory.Build.props # Source projects +/test/Directory.Build.props # Test projects +/samples/Directory.Build.props # Sample projects +``` + +### Key Properties Set Centrally +- `TargetFrameworks` +- Common package references +- Build configurations +- Analyzer references + +## Sample Project Guidelines + +### DSN Configuration Pattern +**Always use conditional compilation for DSN in samples:** + +```csharp +builder.AddSentry(options => +{ +#if !SENTRY_DSN_DEFINED_IN_ENV + // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. + // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ + options.Dsn = SamplesShared.Dsn; +#endif + + // Other configuration... +}); +``` + +### Required Sample Files +- **Don't exclude** `SamplesShared.cs` or `EnvironmentVariables.g.cs` +- These provide proper DSN handling for CI/CD environments +- The build system automatically defines `SENTRY_DSN_DEFINED_IN_ENV` when environment variable is set + +### Building and Running Samples +```bash +# Build (requires SENTRY_DSN environment variable) +env SENTRY_DSN=your_dsn dotnet build samples/Project/Project.csproj + +# Run (requires SENTRY_DSN environment variable) +env SENTRY_DSN=your_dsn dotnet run --project samples/Project/Project.csproj +``` + +### Sample Project Structure +Base sample projects on existing ones like `Sentry.Samples.ME.Logging`: +- Target the same TFM (such as `net9.0`) as other projects, for consistency +- Follow the same logging and configuration patterns + +## Extension Package Guidelines + +### Target Frameworks +- Use `net9.0;net8.0` for new extension packages (or whatever is being used on other projects) +- Follow existing patterns in similar extensions + +### Package References +- Check `Directory.Build.props` for centrally managed versions +- Avoid duplicating dependencies already brought in transitively +- Use exact versions for preview packages (e.g., `Microsoft.Extensions.AI`) + +### Internal Visibility +```xml + + + +``` + +### Hub Access Pattern +**Always use automatic hub detection:** +```csharp +// ✅ Good - Automatic hub detection +public static IChatClient WithSentry(this IChatClient client, string? agentName = null) +{ + return new SentryChatClient(client, HubAdapter.Instance, agentName); +} + +// ✅ Good - DI fallback pattern +public static ChatClientBuilder UseSentry(this ChatClientBuilder builder, string? agentName = null) +{ + return builder.Use((serviceProvider, inner) => + { + var hub = serviceProvider.GetService() ?? HubAdapter.Instance; + return new SentryChatClient(inner, hub, agentName); + }); +} + +// ❌ Avoid - Requiring manual hub passing, unless it's for testing so it can be injected. Then use a `internal` overload ctor. +public static IChatClient WithSentry(this IChatClient client, IHub hub, string? agentName = null) +``` + +## Span and Transaction Guidelines + +### Exception Handling in Spans +**Always pass exception reference to Finish():** +```csharp +try +{ + var result = await operation(); + transaction.Finish(SpanStatus.Ok); + return result; +} +catch (Exception ex) +{ + transaction.Finish(ex); // ✅ Pass exception reference + _hub.CaptureException(ex); + throw; +} +``` + + +## Testing Guidelines + +### Test Project Setup +- Target same frameworks as source project +- Reference source project, core Sentry project, and `Sentry.Testing` +- Stay consist with other test projects, for example use `NSubstitute` for mocking + +### Test Structure +```csharp +[Fact] +public async Task Operation_CallsInnerClient() +{ + // Arrange + var inner = Substitute.For(); + var hub = Substitute.For(); + var client = new YourClient(inner, hub); + + // Act & Assert + await client.Method(); + inner.Received(1).Method(); +} +``` + +### Async Enumerable Testing +```csharp +private static async IAsyncEnumerable CreateTestEnumerable() +{ + yield return item1; + await Task.Yield(); // Make it actually async + yield return item2; +} +``` + +## Build and Test Commands + +### Standard Build Commands +```bash +# Build entire solution +dotnet build Sentry.sln + +# Build specific project +dotnet build src/ProjectName/ProjectName.csproj + +# Run tests +dotnet test test/ProjectName.Tests/ProjectName.Tests.csproj + +# Build samples (with environment variable) +env SENTRY_DSN=placeholder dotnet build samples/SampleProject/SampleProject.csproj +``` + +### Target Framework Testing +- Always test on both target frameworks (net8.0, net9.0) +- CI builds will validate against all supported frameworks + +## Common Pitfalls to Avoid + +1. **Don't exclude shared sample files** - They provide proper DSN handling +3. **Don't require manual hub passing** - Use automatic detection +4. **Don't use `SpanStatus.InternalError`** - Pass exception reference +5. **Don't forget `[EnumeratorCancellation]`** - For async enumerable parameters +6. **Don't use `yield return` in try-catch** - Create wrapper classes instead + +## File Organization + +### Extension Structure +``` +src/Sentry.Extensions.Technology/ +├── Sentry.Extensions.Technology.csproj +├── Extensions/ +│ └── TechnologyExtensions.cs +└── Internal/ + ├── SentryWrapper.cs + └── SentryEnumerable.cs (if needed) +``` + +### Test Structure +``` +test/Sentry.Extensions.Technology.Tests/ +├── Sentry.Extensions.Technology.Tests.csproj +└── WrapperTests.cs +``` + +This document should be updated as new patterns emerge and conventions evolve. \ No newline at end of file From 206f35d68f4ec91d526016f3d71becfa514d1a78 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Sat, 9 Aug 2025 01:13:04 +0000 Subject: [PATCH 7/7] Format code --- samples/Sentry.Samples.ME.AI.Console/Program.cs | 4 ++-- src/Sentry.Extensions.AI/SentryChatClient.cs | 6 +++--- test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/Sentry.Samples.ME.AI.Console/Program.cs b/samples/Sentry.Samples.ME.AI.Console/Program.cs index 83d15e0e0f..3a6614ab61 100644 --- a/samples/Sentry.Samples.ME.AI.Console/Program.cs +++ b/samples/Sentry.Samples.ME.AI.Console/Program.cs @@ -1,8 +1,8 @@ +using System.Runtime.CompilerServices; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Sentry.Extensions.AI; -using System.Runtime.CompilerServices; using var loggerFactory = LoggerFactory.Create(builder => { @@ -71,7 +71,7 @@ public async IAsyncEnumerable CompleteStreamingAs { var lastMessage = messages.LastOrDefault()?.Text ?? "Hello from echo client!"; var parts = new[] { "Echo: ", lastMessage.Substring(0, Math.Min(10, lastMessage.Length)), "...", " (streaming)" }; - + foreach (var part in parts) { await Task.Delay(100, cancellationToken); // Simulate streaming delay diff --git a/src/Sentry.Extensions.AI/SentryChatClient.cs b/src/Sentry.Extensions.AI/SentryChatClient.cs index b574a8f240..57e5685478 100644 --- a/src/Sentry.Extensions.AI/SentryChatClient.cs +++ b/src/Sentry.Extensions.AI/SentryChatClient.cs @@ -1,7 +1,7 @@ +using System.Runtime.CompilerServices; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Sentry; -using System.Runtime.CompilerServices; namespace Sentry.Extensions.AI; @@ -154,13 +154,13 @@ public async ValueTask MoveNextAsync() try { var hasNext = await _innerEnumerator.MoveNextAsync().ConfigureAwait(false); - + if (!hasNext && !_finished) { _transaction.Finish(SpanStatus.Ok); _finished = true; } - + return hasNext; } catch (Exception ex) diff --git a/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs b/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs index fa62e2fb20..a608c52bf1 100644 --- a/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs +++ b/test/Sentry.Extensions.AI.Tests/SentryChatClientTests.cs @@ -38,7 +38,7 @@ public void Metadata_ReturnsInnerClientMetadata() public async Task CompleteStreamingAsync_CallsInnerClient() { var inner = Substitute.For(); - + inner.CompleteStreamingAsync(Arg.Any>(), Arg.Any(), Arg.Any()) .Returns(CreateTestStreamingUpdates()); @@ -54,7 +54,7 @@ public async Task CompleteStreamingAsync_CallsInnerClient() Assert.Equal(2, results.Count); Assert.Equal("Hello", results[0].Text); Assert.Equal(" World", results[1].Text); - + inner.Received(1).CompleteStreamingAsync(Arg.Any>(), Arg.Any(), Arg.Any()); }