From cc22c0fde2cf085227af919b28e842b87c3d6298 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 12 Jun 2025 11:46:00 +1200 Subject: [PATCH 1/2] Experimental SqlLite integration Relates to #1837: - https://github.com/getsentry/sentry-dotnet/issues/1837 This is just mucking around for a couple of hours to see how such an integration might be pieced together. If we want to proceed, there is some complexity around the `SqliteDataReader`. This only has an internal constructor, so we'd have to wrap it via a proxy class rather than inheritance.... which would doubtless involve a bunch of boilerplate grunt work. We'd also have to make decisions about what the hang the spans off. Ideally these DB spans would be children of any active transaction (e.g. the transactions created by the Mvvm integration)... which means storing/retrieving those transaction on the Scope - and as @bruno-garcia recently discovered in the SymbolCollector project, that's fiddly business in GlobalMode (particularly for DB stuff that is likely to be running on a background thread). --- Sentry-CI-Build-macOS.slnf | 6 +- Sentry.sln | 30 +++++++ samples/Sentry.Samples.Sqlite/Program.cs | 89 +++++++++++++++++++ .../Sentry.Samples.Sqlite.csproj | 19 ++++ src/Sentry.Sqlite/Sentry.Sqlite.csproj | 20 +++++ src/Sentry.Sqlite/SentrySqliteCommand.cs | 37 ++++++++ src/Sentry.Sqlite/SentrySqliteConnection.cs | 52 +++++++++++ 7 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 samples/Sentry.Samples.Sqlite/Program.cs create mode 100644 samples/Sentry.Samples.Sqlite/Sentry.Samples.Sqlite.csproj create mode 100644 src/Sentry.Sqlite/Sentry.Sqlite.csproj create mode 100644 src/Sentry.Sqlite/SentrySqliteCommand.cs create mode 100644 src/Sentry.Sqlite/SentrySqliteConnection.cs diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf index e654c81e63..ae938b72d0 100644 --- a/Sentry-CI-Build-macOS.slnf +++ b/Sentry-CI-Build-macOS.slnf @@ -36,12 +36,13 @@ "samples\\Sentry.Samples.OpenTelemetry.AspNetCore\\Sentry.Samples.OpenTelemetry.AspNetCore.csproj", "samples\\Sentry.Samples.OpenTelemetry.Console\\Sentry.Samples.OpenTelemetry.Console.csproj", "samples\\Sentry.Samples.Serilog\\Sentry.Samples.Serilog.csproj", + "samples\\Sentry.Samples.Sqlite\\Sentry.Samples.Sqlite.csproj", "src\\Sentry.Analyzers\\Sentry.Analyzers.csproj", "src\\Sentry.Android.AssemblyReader\\Sentry.Android.AssemblyReader.csproj", - "src\\Sentry.AspNet\\Sentry.AspNet.csproj", "src\\Sentry.AspNetCore.Blazor.WebAssembly\\Sentry.AspNetCore.Blazor.WebAssembly.csproj", "src\\Sentry.AspNetCore.Grpc\\Sentry.AspNetCore.Grpc.csproj", "src\\Sentry.AspNetCore\\Sentry.AspNetCore.csproj", + "src\\Sentry.AspNet\\Sentry.AspNet.csproj", "src\\Sentry.Azure.Functions.Worker\\Sentry.Azure.Functions.Worker.csproj", "src\\Sentry.Bindings.Android\\Sentry.Bindings.Android.csproj", "src\\Sentry.Bindings.Cocoa\\Sentry.Bindings.Cocoa.csproj", @@ -58,6 +59,7 @@ "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", "src\\Sentry.SourceGenerators\\Sentry.SourceGenerators.csproj", + "src\\Sentry.Sqlite\\Sentry.Sqlite.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", @@ -87,4 +89,4 @@ "test\\SingleFileTestApp\\SingleFileTestApp.csproj" ] } -} +} \ No newline at end of file diff --git a/Sentry.sln b/Sentry.sln index 3730377a46..fa8e6cac7d 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -209,6 +209,10 @@ 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.Sqlite", "src\Sentry.Sqlite\Sentry.Sqlite.csproj", "{CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.Sqlite", "samples\Sentry.Samples.Sqlite\Sentry.Samples.Sqlite.csproj", "{BB4FF3AE-C835-41AB-8F75-1611977BCF71}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1252,6 +1256,30 @@ 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 + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Debug|x64.ActiveCfg = Debug|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Debug|x64.Build.0 = Debug|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Debug|x86.ActiveCfg = Debug|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Debug|x86.Build.0 = Debug|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Release|Any CPU.Build.0 = Release|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Release|x64.ActiveCfg = Release|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Release|x64.Build.0 = Release|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Release|x86.ActiveCfg = Release|Any CPU + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912}.Release|x86.Build.0 = Release|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Debug|x64.Build.0 = Debug|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Debug|x86.Build.0 = Debug|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Release|Any CPU.Build.0 = Release|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Release|x64.ActiveCfg = Release|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Release|x64.Build.0 = Release|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Release|x86.ActiveCfg = Release|Any CPU + {BB4FF3AE-C835-41AB-8F75-1611977BCF71}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1343,5 +1371,7 @@ 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} + {CDCCB2EC-9DA8-4CBE-94D6-00EAB990D912} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} + {BB4FF3AE-C835-41AB-8F75-1611977BCF71} = {21B42F60-5802-404E-90F0-AEBCC56760C0} EndGlobalSection EndGlobal diff --git a/samples/Sentry.Samples.Sqlite/Program.cs b/samples/Sentry.Samples.Sqlite/Program.cs new file mode 100644 index 0000000000..e0887366f2 --- /dev/null +++ b/samples/Sentry.Samples.Sqlite/Program.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using Microsoft.Data.Sqlite; +using Sentry.Sqlite; + +SentrySdk.Init(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 + options.Debug = false; + options.TracesSampleRate = 1.0; +}); + +using (var connection = new SentrySqliteConnection("Data Source=hello.db")) +{ + connection.Open(); + + var command = connection.CreateCommand(); + command.CommandText = + @" + CREATE TABLE user ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL + ); + + INSERT INTO user + VALUES (1, 'Brice'), + (2, 'Alexander'), + (3, 'Nate'); + "; + command.ExecuteNonQuery(); + + Console.Write("Name: "); + var name = Console.ReadLine(); + + #region snippet_Parameter + command.CommandText = + @" + INSERT INTO user (name) + VALUES ($name) + "; + command.Parameters.AddWithValue("$name", name); + #endregion + command.ExecuteNonQuery(); + + command.CommandText = + @" + SELECT last_insert_rowid() + "; + var newId = (long)command.ExecuteScalar()!; + + Console.WriteLine($"Your new user ID is {newId}."); +} + +Console.Write("User ID: "); +var id = int.Parse(Console.ReadLine()!); + +#region snippet_HelloWorld +using (var connection = new SqliteConnection("Data Source=hello.db")) +{ + connection.Open(); + + var command = connection.CreateCommand(); + command.CommandText = + @" + SELECT name + FROM user + WHERE id = $id + "; + command.Parameters.AddWithValue("$id", id); + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var name = reader.GetString(0); + + Console.WriteLine($"Hello, {name}!"); + } + } +} +#endregion + +// Clean up +SqliteConnection.ClearAllPools(); +File.Delete("hello.db"); diff --git a/samples/Sentry.Samples.Sqlite/Sentry.Samples.Sqlite.csproj b/samples/Sentry.Samples.Sqlite/Sentry.Samples.Sqlite.csproj new file mode 100644 index 0000000000..89a257e5cd --- /dev/null +++ b/samples/Sentry.Samples.Sqlite/Sentry.Samples.Sqlite.csproj @@ -0,0 +1,19 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Sentry.Sqlite/Sentry.Sqlite.csproj b/src/Sentry.Sqlite/Sentry.Sqlite.csproj new file mode 100644 index 0000000000..abebe72060 --- /dev/null +++ b/src/Sentry.Sqlite/Sentry.Sqlite.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + enable + enable + Experimental Microsoft.Data.Sqlite integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. + $(PackageTags);Sqlite + + + + + + + + + + + + diff --git a/src/Sentry.Sqlite/SentrySqliteCommand.cs b/src/Sentry.Sqlite/SentrySqliteCommand.cs new file mode 100644 index 0000000000..d1d13ecd84 --- /dev/null +++ b/src/Sentry.Sqlite/SentrySqliteCommand.cs @@ -0,0 +1,37 @@ +using Microsoft.Data.Sqlite; + +namespace Sentry.Sqlite; + +/// +public class SentrySqliteCommand : SqliteCommand +{ + internal ITransactionTracer? TransactionTracer { get; set; } = null; + + /// + public override int ExecuteNonQuery() + { + var span = TransactionTracer?.StartChild("db.query", this.CommandText); + try + { + return base.ExecuteNonQuery(); + } + finally + { + span?.Finish(); + } + } + + /// + public override object? ExecuteScalar() + { + var span = TransactionTracer?.StartChild("db.query", this.CommandText); + try + { + return base.ExecuteScalar(); + } + finally + { + span?.Finish(); + } + } +} diff --git a/src/Sentry.Sqlite/SentrySqliteConnection.cs b/src/Sentry.Sqlite/SentrySqliteConnection.cs new file mode 100644 index 0000000000..19d0904eab --- /dev/null +++ b/src/Sentry.Sqlite/SentrySqliteConnection.cs @@ -0,0 +1,52 @@ +using System.Data.Common; +using Microsoft.Data.Sqlite; + +namespace Sentry.Sqlite; + +/// +public class SentrySqliteConnection : SqliteConnection +{ + private ITransactionTracer? TransactionTracer { get; set; } = null; + + /// + /// Initializes a new instance of the class. + /// + /// The string used to open the connection. + /// Connection Strings + /// + public SentrySqliteConnection(string? connectionString) : base(connectionString) + { + } + + /// + public override void Open() + { + TransactionTracer = SentrySdk.StartTransaction( + "Open SQLite Connection", + "db.sqlite.open" + ); + base.Open(); + } + + /// + public new virtual SentrySqliteCommand CreateCommand() + => new() + { + Connection = this, + CommandTimeout = DefaultTimeout, + Transaction = Transaction, + TransactionTracer = TransactionTracer + }; + + /// + protected override DbCommand CreateDbCommand() + => CreateCommand(); + + /// + public override void Close() + { + base.Close(); + TransactionTracer?.Finish(); + TransactionTracer = null; + } +} From 1352e1419faf6c3af4cc0e22053644057ff69952 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 12 Jun 2025 00:01:47 +0000 Subject: [PATCH 2/2] Format code --- samples/Sentry.Samples.Sqlite/Program.cs | 2 +- src/Sentry.Sqlite/SentrySqliteCommand.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/Sentry.Samples.Sqlite/Program.cs b/samples/Sentry.Samples.Sqlite/Program.cs index e0887366f2..d28bf95db6 100644 --- a/samples/Sentry.Samples.Sqlite/Program.cs +++ b/samples/Sentry.Samples.Sqlite/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.Data.Sqlite; using Sentry.Sqlite; diff --git a/src/Sentry.Sqlite/SentrySqliteCommand.cs b/src/Sentry.Sqlite/SentrySqliteCommand.cs index d1d13ecd84..dc2b03f521 100644 --- a/src/Sentry.Sqlite/SentrySqliteCommand.cs +++ b/src/Sentry.Sqlite/SentrySqliteCommand.cs @@ -10,7 +10,7 @@ public class SentrySqliteCommand : SqliteCommand /// public override int ExecuteNonQuery() { - var span = TransactionTracer?.StartChild("db.query", this.CommandText); + var span = TransactionTracer?.StartChild("db.query", CommandText); try { return base.ExecuteNonQuery(); @@ -24,7 +24,7 @@ public override int ExecuteNonQuery() /// public override object? ExecuteScalar() { - var span = TransactionTracer?.StartChild("db.query", this.CommandText); + var span = TransactionTracer?.StartChild("db.query", CommandText); try { return base.ExecuteScalar();