-
-
Notifications
You must be signed in to change notification settings - Fork 237
Added Sentry.Samples.OpenTelemetry.MongoDB #5335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||||||||||
| /* | ||||||||||||||
| * This sample demonstrates how MongoDB queries can be captured as Sentry spans using the | ||||||||||||||
| * MongoDB.Driver's built-in OpenTelemetry instrumentation (available since driver version 3.7.0). | ||||||||||||||
| * | ||||||||||||||
| * The sample requires a MongoDB server. See the README.md alongside this sample for instructions | ||||||||||||||
| * on how to start one using Docker. | ||||||||||||||
| */ | ||||||||||||||
|
|
||||||||||||||
| using System.Diagnostics; | ||||||||||||||
| using MongoDB.Bson; | ||||||||||||||
| using MongoDB.Driver; | ||||||||||||||
| using MongoDB.Driver.Core.Configuration; | ||||||||||||||
| using OpenTelemetry; | ||||||||||||||
| using OpenTelemetry.Trace; | ||||||||||||||
| using Sentry.OpenTelemetry; | ||||||||||||||
| using Sentry.OpenTelemetry.Exporter; | ||||||||||||||
|
|
||||||||||||||
| #if SENTRY_DSN_DEFINED_IN_ENV | ||||||||||||||
| var dsn = Environment.GetEnvironmentVariable("SENTRY_DSN") | ||||||||||||||
| ?? throw new InvalidOperationException("SENTRY_DSN environment variable is not set"); | ||||||||||||||
| #else | ||||||||||||||
| // 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/ | ||||||||||||||
| var dsn = SamplesShared.Dsn; | ||||||||||||||
| #endif | ||||||||||||||
|
|
||||||||||||||
| var activitySource = new ActivitySource("Sentry.Samples.OpenTelemetry.MongoDB"); | ||||||||||||||
|
|
||||||||||||||
| SentrySdk.Init(options => | ||||||||||||||
| { | ||||||||||||||
| options.Dsn = dsn; | ||||||||||||||
| options.Debug = true; | ||||||||||||||
| options.TracesSampleRate = 1.0; | ||||||||||||||
| options.UseOtlp(); // <-- Configure Sentry to use OpenTelemetry trace information | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| using var tracerProvider = Sdk.CreateTracerProviderBuilder() | ||||||||||||||
| .AddSource(activitySource.Name) | ||||||||||||||
| .AddSource(MongoTelemetry.ActivitySourceName) // <-- Subscribe to the MongoDB driver's built-in instrumentation | ||||||||||||||
| .AddProcessor(new Sentry.Samples.OpenTelemetry.MongoDB.RedactSensitiveMongoData()) // <-- Redact PII from query text BEFORE it's exported (see below) | ||||||||||||||
| .AddSentryOtlpExporter(dsn) // <-- Configure OpenTelemetry to send traces to Sentry over OTLP | ||||||||||||||
| .Build(); | ||||||||||||||
|
|
||||||||||||||
| // MongoDB.Driver 3.7.0+ creates OpenTelemetry activities for every command out of the box. | ||||||||||||||
| // TracingOptions is only needed to tweak that behaviour - here we opt in to capturing the query | ||||||||||||||
| // text on each span (off by default), which shows up in Sentry as the `db.query.text` span data. | ||||||||||||||
| // NOTE: The captured query text includes the actual field values sent to MongoDB, which may contain | ||||||||||||||
| // PII. See the RedactSensitiveMongoData span processor at the bottom of this file for how to scrub it. | ||||||||||||||
| var mongoUri = Environment.GetEnvironmentVariable("MONGODB_URI") ?? "mongodb://localhost:27017"; | ||||||||||||||
| var clientSettings = MongoClientSettings.FromConnectionString(mongoUri); | ||||||||||||||
| clientSettings.ServerSelectionTimeout = TimeSpan.FromSeconds(3); // <-- Fail fast if MongoDB isn't running | ||||||||||||||
| clientSettings.TracingOptions = new TracingOptions | ||||||||||||||
| { | ||||||||||||||
| QueryTextMaxLength = 4096 | ||||||||||||||
| }; | ||||||||||||||
| var mongoClient = new MongoClient(clientSettings); | ||||||||||||||
|
|
||||||||||||||
| var db = mongoClient.GetDatabase("sentry_mongo_sample"); | ||||||||||||||
| var fruit = db.GetCollection<BsonDocument>("fruit"); | ||||||||||||||
|
|
||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| // Everything within this activity gets sent to Sentry as a single transaction, containing one | ||||||||||||||
| // span for each MongoDB command. | ||||||||||||||
| using (activitySource.StartActivity("Fruit Salad")) | ||||||||||||||
| { | ||||||||||||||
| // Each recipe records the name of the person who contributed it. That's PII, and because we | ||||||||||||||
| // capture query text (above) it would otherwise be sent to Sentry verbatim in the insert | ||||||||||||||
| // command - the RedactSensitiveMongoData processor strips it out before export. | ||||||||||||||
| await fruit.InsertManyAsync([ | ||||||||||||||
| new BsonDocument { { "name", "Apple" }, { "color", "Red" }, { "contributor", "Alice Johnson" } }, | ||||||||||||||
| new BsonDocument { { "name", "Banana" }, { "color", "Yellow" }, { "contributor", "Bob Nguyen" } }, | ||||||||||||||
| new BsonDocument { { "name", "Cherry" }, { "color", "Red" }, { "contributor", "Carla Méndez" } }, | ||||||||||||||
| new BsonDocument { { "name", "Kiwi" }, { "color", "Green" }, { "contributor", "David O'Brien" } } | ||||||||||||||
| ]); | ||||||||||||||
|
|
||||||||||||||
| var redFruit = await fruit.Find(Builders<BsonDocument>.Filter.Eq("color", "Red")).ToListAsync(); | ||||||||||||||
| Console.WriteLine($"Found {redFruit.Count} red fruit: " + | ||||||||||||||
| string.Join(", ", redFruit.Select(f => f["name"].AsString))); | ||||||||||||||
|
|
||||||||||||||
| await fruit.UpdateOneAsync( | ||||||||||||||
| Builders<BsonDocument>.Filter.Eq("name", "Apple"), | ||||||||||||||
| Builders<BsonDocument>.Update.Set("color", "Green")); | ||||||||||||||
|
|
||||||||||||||
| var count = await fruit.CountDocumentsAsync(Builders<BsonDocument>.Filter.Eq("color", "Green")); | ||||||||||||||
| Console.WriteLine($"There are now {count} green fruit."); | ||||||||||||||
|
|
||||||||||||||
| // Drop the database so the sample can be run repeatedly | ||||||||||||||
| await mongoClient.DropDatabaseAsync(db.DatabaseNamespace.DatabaseName); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| catch (TimeoutException) | ||||||||||||||
| { | ||||||||||||||
| Console.WriteLine($""" | ||||||||||||||
| Could not connect to MongoDB at {mongoUri} | ||||||||||||||
| Is MongoDB running? See the README.md for this sample for instructions on starting MongoDB using Docker. | ||||||||||||||
| """); | ||||||||||||||
| } | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong exception type caughtMedium Severity When MongoDB is unreachable, the driver throws a Reviewed by Cursor Bugbot for commit 9403424. Configure here.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a false positive — When the server can't be selected within Source: Verified empirically with driver 3.9.0 and this sample's exact settings (
Broadening the catch would actually be a regression: the intent is to show the "Is MongoDB running?" hint only for the connection-timeout case. A genuine |
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| # Sentry.Samples.OpenTelemetry.MongoDB | ||
|
|
||
| This sample demonstrates how MongoDB queries can be captured as Sentry spans using OpenTelemetry. | ||
|
|
||
| [MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver) 3.7.0 and later has | ||
| [built-in OpenTelemetry instrumentation](https://www.mongodb.com/docs/drivers/csharp/current/logging-and-monitoring/) | ||
| — no additional instrumentation package is required. The driver emits one activity per MongoDB | ||
| command from an `ActivitySource` named `MongoDB.Driver`, which this sample subscribes to and | ||
| exports to Sentry: | ||
|
|
||
| ```csharp | ||
| using var tracerProvider = Sdk.CreateTracerProviderBuilder() | ||
| .AddSource(MongoTelemetry.ActivitySourceName) // "MongoDB.Driver" | ||
| .AddSentryOtlpExporter(dsn) | ||
| .Build(); | ||
| ``` | ||
|
|
||
| By default the driver does not record the query text on spans. This sample opts in via | ||
| `TracingOptions` on the client settings, so each span includes the (truncated) command as | ||
| `db.query.text`: | ||
|
|
||
| ```csharp | ||
| clientSettings.TracingOptions = new TracingOptions | ||
| { | ||
| QueryTextMaxLength = 4096 | ||
| }; | ||
| ``` | ||
|
|
||
|
jamescrosswell marked this conversation as resolved.
|
||
| ## A note on PII | ||
|
|
||
| When you capture query text, the MongoDB driver records the **actual field values** sent in each | ||
| command (for example `{ "name": "Apple", "contributor": "Alice Johnson" }`) in the `db.query.text` | ||
| span attribute. If those values can contain PII, you need to scrub them. | ||
|
|
||
| Importantly, the Sentry OTLP exporter used by this sample sends spans **straight to Sentry**, without | ||
| running them through the SDK's `BeforeSend`/`BeforeSendTransaction` hooks — so you can't rely on those | ||
| to redact data here. | ||
|
|
||
| Your options are: | ||
| - **Don't capture query text at all** — leave `QueryTextMaxLength` at its default of `0`. The Queries | ||
| module still works using the operation and collection name. | ||
| - **Redact client-side with an OpenTelemetry span processor** — registered *before* | ||
| `AddSentryOtlpExporter` so it runs before spans are exported. This sample includes an example | ||
| `RedactSensitiveMongoData`, which strips the `contributor` field out of `db.query.text` | ||
| before it leaves the process. Adapt the field names to your own data. | ||
| - **Configure [server-side data scrubbing](https://docs.sentry.io/security-legal-pii/scrubbing/server-side-scrubbing/)** | ||
| — note the default rules only catch known-sensitive patterns (passwords, tokens, card numbers), so | ||
| for arbitrary field values you'll need to add explicit scrubbing rules. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| ### MongoDB | ||
|
|
||
| The sample expects a MongoDB server at `mongodb://localhost:27017`. The easiest way to get one | ||
| running is with Docker: | ||
|
|
||
| ```shell | ||
| docker run --rm -d -p 27017:27017 --name sentry-mongo mongo:8 | ||
| ``` | ||
|
|
||
| To check that it's up: | ||
|
|
||
| ```shell | ||
| docker ps --filter name=sentry-mongo | ||
| ``` | ||
|
|
||
| And to stop it again once you're done (the `--rm` flag means the container is removed | ||
| automatically when stopped): | ||
|
|
||
| ```shell | ||
| docker stop sentry-mongo | ||
| ``` | ||
|
|
||
| If your MongoDB server is running somewhere else, set the `MONGODB_URI` environment variable to | ||
| the appropriate connection string before running the sample. | ||
|
|
||
| ### Sentry DSN | ||
|
|
||
| As with the other samples in this repository, you need a Sentry DSN. Either set the `SENTRY_DSN` | ||
| environment variable, or replace the placeholder in [samples/SamplesShared.cs](../SamplesShared.cs). | ||
| See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ for details. | ||
|
|
||
| ## Running the sample | ||
|
|
||
| ```shell | ||
| dotnet run --project samples/Sentry.Samples.OpenTelemetry.MongoDB | ||
| ``` | ||
|
|
||
| The sample performs a few operations against a `sentry_mongo_sample` database (insert, find, | ||
| update, count and drop) inside a single transaction. In Sentry, look for the `Fruit Salad` | ||
| transaction in **Performance** — each MongoDB command shows up as a child span, with the query | ||
| text attached as `db.query.text`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| using System.Diagnostics; | ||
| using System.Text.RegularExpressions; | ||
| using OpenTelemetry; | ||
|
|
||
| namespace Sentry.Samples.OpenTelemetry.MongoDB; | ||
|
|
||
| /// <summary> | ||
| /// An example OpenTelemetry span processor that redacts sensitive values from the MongoDB | ||
| /// <c>db.query.text</c> attribute before spans are exported to Sentry. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// When query-text capture is enabled (see <c>TracingOptions.QueryTextMaxLength</c> above), the | ||
| /// MongoDB driver records the full command - including field values - on the span. If those values | ||
| /// can contain PII, you should redact them before they leave your process. | ||
| /// <para> | ||
| /// A span processor is the right place to do this: the Sentry OTLP exporter sends spans straight to | ||
| /// Sentry and does NOT run them through the SDK's <c>BeforeSend</c>/<c>BeforeSendTransaction</c> | ||
| /// hooks, so the processor has to be registered before <c>AddSentryOtlpExporter</c> in the pipeline. | ||
| /// </para> | ||
| /// <para> | ||
| /// This example redacts the "contributor" field (a person's name). Adapt the field names and logic | ||
| /// to match the sensitive data in your own queries. | ||
| /// </para> | ||
| /// </remarks> | ||
| internal sealed partial class RedactSensitiveMongoData : BaseProcessor<Activity> | ||
| { | ||
| // Matches "contributor": "<value>" in MongoDB's extended JSON (spacing may vary). | ||
| [GeneratedRegex("(\"contributor\"\\s*:\\s*)\"[^\"]*\"")] | ||
| private static partial Regex SensitiveField(); | ||
|
|
||
| public override void OnEnd(Activity activity) | ||
| { | ||
| if (activity.GetTagItem("db.query.text") is string queryText) | ||
| { | ||
| activity.SetTag("db.query.text", SensitiveField().Replace(queryText, "$1\"[Filtered]\"")); | ||
| } | ||
|
Comment on lines
+31
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The Suggested FixMove the redaction logic to a point in the OpenTelemetry pipeline before the Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not true - the redacted spans are hitting Sentry just fine. See this trace, for example: |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <!-- MongoDB.Driver 3.7.0 and later has built-in OpenTelemetry instrumentation --> | ||
| <PackageReference Include="MongoDB.Driver" Version="3.9.0" /> | ||
| <PackageReference Include="OpenTelemetry" Version="1.15.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <PropertyGroup Condition="'$(Configuration)' == 'Release'"> | ||
| <SentryOrg>sentry-sdks</SentryOrg> | ||
| <SentryProject>sentry-dotnet</SentryProject> | ||
| <SentryUploadSymbols>true</SentryUploadSymbols> | ||
| <SentryUploadSources>true</SentryUploadSources> | ||
| </PropertyGroup> | ||
|
|
||
| <!-- In your own project, this would be PackageReferences to the latest versions of these Sentry packages. --> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\Sentry\Sentry.csproj" /> | ||
| <ProjectReference Include="..\..\src\Sentry.OpenTelemetry.Exporter\Sentry.OpenTelemetry.Exporter.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |



Uh oh!
There was an error while loading. Please reload this page.