diff --git a/CHANGELOG.md b/CHANGELOG.md
index 55cdefbcea..d60b26aa33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
- Add support for dynamic transaction sampling. (#753) @Tyrrrz
- Integrate trace headers. (#758) @Tyrrrz
- Renamed Option `DiagnosticsLevel` to `DiagnosticLevel` (#759) @bruno-garcia
+- Add additional data to transactions (#763) @Tyrrrz
## 3.0.0-beta.0
diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs
index 18b86057c9..a195bff47d 100644
--- a/src/Sentry/Internal/Hub.cs
+++ b/src/Sentry/Internal/Hub.cs
@@ -111,6 +111,12 @@ public ITransaction StartTransaction(
{
var transaction = new Transaction(this, context);
+ // Transactions are not handled by event processors, so some things need to be added manually
+
+ // Apply scope
+ ScopeManager.GetCurrent().Key.Apply(transaction);
+
+ // SDK information
var nameAndVersion = MainSentryEventProcessor.NameAndVersion;
var protocolPackageName = MainSentryEventProcessor.ProtocolPackageName;
@@ -125,6 +131,17 @@ public ITransaction StartTransaction(
transaction.Sdk.AddPackage(protocolPackageName, nameAndVersion.Version);
}
+ // Release information
+ transaction.Release ??= _options.Release ?? ReleaseLocator.GetCurrent();
+
+ // Environment information
+ var foundEnvironment = EnvironmentLocator.Locate();
+ transaction.Environment ??= (string.IsNullOrWhiteSpace(foundEnvironment)
+ ? string.IsNullOrWhiteSpace(_options.Environment)
+ ? Constants.ProductionEnvironmentSetting
+ : _options.Environment
+ : foundEnvironment);
+
// Make a sampling decision
var samplingContext = new TransactionSamplingContext(
context,
diff --git a/src/Sentry/Protocol/ITransaction.cs b/src/Sentry/Protocol/ITransaction.cs
index 65e34d0c3d..83a3e77fdd 100644
--- a/src/Sentry/Protocol/ITransaction.cs
+++ b/src/Sentry/Protocol/ITransaction.cs
@@ -18,6 +18,12 @@ public interface ITransaction : ISpan, ITransactionContext, IEventLike
// 'new' because it adds a setter
new string Name { get; set; }
+ // Should this be on IEventLike?
+ ///
+ /// The release version of the application.
+ ///
+ string? Release { get; set; }
+
///
/// Flat list of spans within this transaction.
///
diff --git a/src/Sentry/Protocol/Transaction.cs b/src/Sentry/Protocol/Transaction.cs
index 5f8fb4e2af..6dd3eedd60 100644
--- a/src/Sentry/Protocol/Transaction.cs
+++ b/src/Sentry/Protocol/Transaction.cs
@@ -64,6 +64,9 @@ public SentryId TraceId
///
public string Name { get; set; }
+ ///
+ public string? Release { get; set; }
+
///
public DateTimeOffset StartTimestamp { get; internal set; } = DateTimeOffset.UtcNow;
@@ -277,6 +280,11 @@ public void WriteTo(Utf8JsonWriter writer)
writer.WriteString("level", level.ToString().ToLowerInvariant());
}
+ if (!string.IsNullOrWhiteSpace(Release))
+ {
+ writer.WriteString("release", Release);
+ }
+
if (!string.IsNullOrWhiteSpace(Name))
{
writer.WriteString("transaction", Name);
@@ -392,6 +400,7 @@ public static Transaction FromJson(JsonElement json)
var startTimestamp = json.GetProperty("start_timestamp").GetDateTimeOffset();
var endTimestamp = json.GetPropertyOrNull("timestamp")?.GetDateTimeOffset();
var level = json.GetPropertyOrNull("level")?.GetString()?.Pipe(s => s.ParseEnum());
+ var release = json.GetPropertyOrNull("release")?.GetString();
var request = json.GetPropertyOrNull("request")?.Pipe(Request.FromJson);
var contexts = json.GetPropertyOrNull("contexts")?.Pipe(Contexts.FromJson);
var user = json.GetPropertyOrNull("user")?.Pipe(User.FromJson);
@@ -409,6 +418,7 @@ public static Transaction FromJson(JsonElement json)
StartTimestamp = startTimestamp,
EndTimestamp = endTimestamp,
Level = level,
+ Release = release,
_request = request,
_contexts = contexts,
_user = user,
diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs
index 2d5f822ff3..51d7460d85 100644
--- a/test/Sentry.Tests/HubTests.cs
+++ b/test/Sentry.Tests/HubTests.cs
@@ -330,6 +330,76 @@ public void StartTransaction_DynamicSampling_FallbackToStatic_SampledOut()
transaction.IsSampled.Should().BeFalse();
}
+ [Fact]
+ public void StartTransaction_ContainsSdk()
+ {
+ // Arrange
+ var hub = new Hub(new SentryOptions
+ {
+ Dsn = DsnSamples.ValidDsnWithSecret
+ });
+
+ // Act
+ var transaction = hub.StartTransaction("name", "operation");
+
+ // Assert
+ transaction.Sdk.Name.Should().NotBeNullOrWhiteSpace();
+ transaction.Sdk.Version.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact]
+ public void StartTransaction_ContainsRelease()
+ {
+ // Arrange
+ var hub = new Hub(new SentryOptions
+ {
+ Dsn = DsnSamples.ValidDsnWithSecret
+ });
+
+ // Act
+ var transaction = hub.StartTransaction("name", "operation");
+
+ // Assert
+ transaction.Release.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact]
+ public void StartTransaction_ContainsEnvironment()
+ {
+ // Arrange
+ var hub = new Hub(new SentryOptions
+ {
+ Dsn = DsnSamples.ValidDsnWithSecret
+ });
+
+ // Act
+ var transaction = hub.StartTransaction("name", "operation");
+
+ // Assert
+ transaction.Environment.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact]
+ public void StartTransaction_ContainsTagsFromScope()
+ {
+ // Arrange
+ var hub = new Hub(new SentryOptions
+ {
+ Dsn = DsnSamples.ValidDsnWithSecret
+ });
+
+ hub.ConfigureScope(scope =>
+ {
+ scope.SetTag("foo", "bar");
+
+ // Act
+ var transaction = hub.StartTransaction("name", "operation");
+
+ // Assert
+ transaction.Tags.Should().Contain(tag => tag.Key == "foo" && tag.Value == "bar");
+ });
+ }
+
[Fact]
public void GetTraceHeader_ReturnsHeaderForActiveSpan()
{