diff --git a/docs/platforms/dotnet/common/tracing/instrumentation/custom-instrumentation/index.mdx b/docs/platforms/dotnet/common/tracing/instrumentation/custom-instrumentation/index.mdx index 1ccb9b0eb02be4..73bcf1eb92f001 100644 --- a/docs/platforms/dotnet/common/tracing/instrumentation/custom-instrumentation/index.mdx +++ b/docs/platforms/dotnet/common/tracing/instrumentation/custom-instrumentation/index.mdx @@ -15,3 +15,5 @@ To capture transactions and spans customized to your organization's needs, you m + + diff --git a/platform-includes/performance/record-completed-transactions/dotnet.mdx b/platform-includes/performance/record-completed-transactions/dotnet.mdx new file mode 100644 index 00000000000000..633a9e0c066291 --- /dev/null +++ b/platform-includes/performance/record-completed-transactions/dotnet.mdx @@ -0,0 +1,40 @@ +## Record Already-Completed Transactions + + + +Requires Sentry SDK version `6.8.0` or higher. + + + +`StartTransaction` measures work as it happens, using a live stopwatch. Sometimes, though, the work you want to report has already finished somewhere else — for example, spans measured on another machine or in another process and relayed to your application through a proxy. For those cases, use `SentrySdk.RecordTransaction` to record a transaction whose timing you supply explicitly: + +```csharp +SentrySdk.RecordTransaction( + "checkout", // name + "http.server", // operation + originalStartTimestamp, // when the work started (DateTimeOffset) + originalDuration, // how long it ran (TimeSpan, must not be negative) + traceId: originTraceId, // optional: preserve the trace id from the originating system + spanId: originRootSpanId, // optional: preserve the root span id + parentSpanId: originParentId, // optional: continue a trace from another service + configure: transaction => + { + transaction.Release = "1.2.3"; + transaction.SetTag("origin", "proxy"); + + // Child spans also take their timing up front and can be nested arbitrarily + transaction.RecordSpan("db.query", queryStart, queryDuration, configure: span => + { + span.Description = "SELECT * FROM orders"; + span.RecordSpan("db.connection", connectStart, connectDuration); + }); + }); +``` + +The transaction is captured and sent to Sentry once the `configure` callback returns — there is no `Finish()` to call. + +Because a recorded transaction represents work that happened elsewhere, it behaves differently from one started with `StartTransaction`: + +- **Timing is explicit.** Each transaction and span takes a start timestamp and a duration up front, rather than being measured live. +- **Sampling is skipped.** Recorded transactions are always sent, regardless of or . Your callback still runs, so you can filter or scrub there. +- **The current scope doesn't apply.** The transaction is captured against a clean scope, so breadcrumbs, user data, and tags from the current process won't leak onto work that happened elsewhere. To attach scope data that belongs with the original trace, use `transaction.ConfigureScope` inside the callback, or the `Release` and `Environment` setters.