diff --git a/CHANGELOG.md b/CHANGELOG.md index c875240588..49e5d0b10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ without native/platform specific bindings and SDKs. See [this ticket for more de API Changes: - If `null` has been supplied as DSN when initializing Sentry, and ArgumentNullException is now thrown ([#2655](https://github.com/getsentry/sentry-dotnet/pull/2655)) +- IHasBreadcrumbs was removed. Use IEventLike instead. ([#2670](https://github.com/getsentry/sentry-dotnet/pull/2670)) +- ISpanContext was removed. Use ITraceContext instead. ([#2668](https://github.com/getsentry/sentry-dotnet/pull/2668)) +- Removed IHasTransactionNameSource. Use ITransactionContext instead. ([#2654](https://github.com/getsentry/sentry-dotnet/pull/2654)) +- Adding `Distribution` to `IEventLike` ([#2660](https://github.com/getsentry/sentry-dotnet/pull/2660)) ### Features diff --git a/src/Sentry/IEventLike.cs b/src/Sentry/IEventLike.cs index 9992a99fc6..112d017566 100644 --- a/src/Sentry/IEventLike.cs +++ b/src/Sentry/IEventLike.cs @@ -3,8 +3,24 @@ namespace Sentry; /// /// Models members common between types that represent event-like data. /// -public interface IEventLike : IHasBreadcrumbs, IHasTags, IHasExtra +public interface IEventLike : IHasTags, IHasExtra { + /// + /// A trail of events which happened prior to an issue. + /// + /// + IReadOnlyCollection Breadcrumbs { get; } + + /// + /// Adds a breadcrumb. + /// + void AddBreadcrumb(Breadcrumb breadcrumb); + + /// + /// The release distribution of the application. + /// + public string? Distribution { get; set; } + /// /// Sentry level. /// @@ -91,6 +107,102 @@ public interface IEventLike : IHasBreadcrumbs, IHasTags, IHasExtra [EditorBrowsable(EditorBrowsableState.Never)] public static class EventLikeExtensions { +#if !NETFRAMEWORK + /// + /// Adds a breadcrumb to the object. + /// + /// The object. + /// The message. + /// The category. + /// The type. + /// The data key-value pair. + /// The level. + public static void AddBreadcrumb( + this IEventLike eventLike, + string message, + string? category, + string? type, + (string, string)? dataPair = null, + BreadcrumbLevel level = default) + { + Dictionary? data = null; + + if (dataPair != null) + { + data = new Dictionary + { + {dataPair.Value.Item1, dataPair.Value.Item2} + }; + } + + eventLike.AddBreadcrumb( + null, + message, + category, + type, + data, + level); + } +#endif + + /// + /// Adds a breadcrumb to the object. + /// + /// The object. + /// The message. + /// The category. + /// The type. + /// The data. + /// The level. + public static void AddBreadcrumb( + this IEventLike eventLike, + string message, + string? category = null, + string? type = null, + IReadOnlyDictionary? data = null, + BreadcrumbLevel level = default) + { + eventLike.AddBreadcrumb( + null, + message, + category, + type, + data, + level); + } + + /// + /// Adds a breadcrumb to the object. + /// + /// + /// This overload is used for testing. + /// + /// The object. + /// The timestamp + /// The message. + /// The category. + /// The type. + /// The data + /// The level. + [EditorBrowsable(EditorBrowsableState.Never)] + public static void AddBreadcrumb( + this IEventLike eventLike, + DateTimeOffset? timestamp, + string message, + string? category = null, + string? type = null, + IReadOnlyDictionary? data = null, + BreadcrumbLevel level = default) + { + eventLike.AddBreadcrumb(new Breadcrumb( + timestamp, + message, + type, + data, + category, + level)); + } + /// /// Whether a has been set to the object with any of its fields non null. /// diff --git a/src/Sentry/IHasBreadcrumbs.cs b/src/Sentry/IHasBreadcrumbs.cs deleted file mode 100644 index 0146bf12d7..0000000000 --- a/src/Sentry/IHasBreadcrumbs.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Sentry.Internal.Extensions; - -namespace Sentry; - -/// -/// Implemented by objects that contain breadcrumbs. -/// -public interface IHasBreadcrumbs -{ - /// - /// A trail of events which happened prior to an issue. - /// - /// - IReadOnlyCollection Breadcrumbs { get; } - - /// - /// Adds a breadcrumb. - /// - void AddBreadcrumb(Breadcrumb breadcrumb); -} - -/// -/// Extensions for . -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public static class HasBreadcrumbsExtensions -{ -#if !NETFRAMEWORK - /// - /// Adds a breadcrumb to the object. - /// - /// The object. - /// The message. - /// The category. - /// The type. - /// The data key-value pair. - /// The level. - public static void AddBreadcrumb( - this IHasBreadcrumbs hasBreadcrumbs, - string message, - string? category, - string? type, - (string, string)? dataPair = null, - BreadcrumbLevel level = default) - { - // Not to throw on code that ignores nullability warnings. - if (hasBreadcrumbs.IsNull()) - { - return; - } - - Dictionary? data = null; - - if (dataPair != null) - { - data = new Dictionary - { - {dataPair.Value.Item1, dataPair.Value.Item2} - }; - } - - hasBreadcrumbs.AddBreadcrumb( - null, - message, - category, - type, - data, - level); - } -#endif - - /// - /// Adds a breadcrumb to the object. - /// - /// The object. - /// The message. - /// The category. - /// The type. - /// The data. - /// The level. - public static void AddBreadcrumb( - this IHasBreadcrumbs hasBreadcrumbs, - string message, - string? category = null, - string? type = null, - IReadOnlyDictionary? data = null, - BreadcrumbLevel level = default) - { - // Not to throw on code that ignores nullability warnings. - if (hasBreadcrumbs.IsNull()) - { - return; - } - - hasBreadcrumbs.AddBreadcrumb( - null, - message, - category, - type, - data, - level); - } - - /// - /// Adds a breadcrumb to the object. - /// - /// - /// This overload is used for testing. - /// - /// The object. - /// The timestamp - /// The message. - /// The category. - /// The type. - /// The data - /// The level. - [EditorBrowsable(EditorBrowsableState.Never)] - public static void AddBreadcrumb( - this IHasBreadcrumbs hasBreadcrumbs, - DateTimeOffset? timestamp, - string message, - string? category = null, - string? type = null, - IReadOnlyDictionary? data = null, - BreadcrumbLevel level = default) - { - // Not to throw on code that ignores nullability warnings. - if (hasBreadcrumbs.IsNull()) - { - return; - } - - hasBreadcrumbs.AddBreadcrumb(new Breadcrumb( - timestamp, - message, - type, - data, - category, - level)); - } -} diff --git a/src/Sentry/IHasTransactionNameSource.cs b/src/Sentry/IHasTransactionNameSource.cs deleted file mode 100644 index 1362cd4dff..0000000000 --- a/src/Sentry/IHasTransactionNameSource.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Sentry; - -/// -/// Interface for transactions that implement a transaction name source. -/// -/// -/// Ideally, this would just be implemented as part of and . -/// However, adding a property to a public interface is a breaking change. We can do that in a future major version. -/// -public interface IHasTransactionNameSource -{ - /// - /// The source of the transaction name. - /// - TransactionNameSource NameSource { get; } -} diff --git a/src/Sentry/ISpanContext.cs b/src/Sentry/ISpanContext.cs deleted file mode 100644 index 9ba75a0669..0000000000 --- a/src/Sentry/ISpanContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Sentry.Protocol; - -namespace Sentry; - -/// -/// Span metadata. -/// -public interface ISpanContext : ITraceContext -{ -} \ No newline at end of file diff --git a/src/Sentry/ISpanData.cs b/src/Sentry/ISpanData.cs index 2187513f83..0f4f8a526c 100644 --- a/src/Sentry/ISpanData.cs +++ b/src/Sentry/ISpanData.cs @@ -1,9 +1,11 @@ +using Sentry.Protocol; + namespace Sentry; /// /// Immutable data belonging to a span. /// -public interface ISpanData : ISpanContext, IHasTags, IHasExtra +public interface ISpanData : ITraceContext, IHasTags, IHasExtra { /// /// Start timestamp. diff --git a/src/Sentry/ITransactionContext.cs b/src/Sentry/ITransactionContext.cs index 43291ac6d8..d1cb0dc849 100644 --- a/src/Sentry/ITransactionContext.cs +++ b/src/Sentry/ITransactionContext.cs @@ -1,9 +1,11 @@ +using Sentry.Protocol; + namespace Sentry; /// /// Transaction metadata. /// -public interface ITransactionContext : ISpanContext +public interface ITransactionContext : ITraceContext { /// /// Transaction name. @@ -14,4 +16,9 @@ public interface ITransactionContext : ISpanContext /// Whether the parent transaction of this transaction has been sampled. /// bool? IsParentSampled { get; } + + /// + /// The source of the transaction name. + /// + TransactionNameSource NameSource { get; } } diff --git a/src/Sentry/Internal/Enricher.cs b/src/Sentry/Internal/Enricher.cs index 8e59579a54..fd945e2eff 100644 --- a/src/Sentry/Internal/Enricher.cs +++ b/src/Sentry/Internal/Enricher.cs @@ -61,7 +61,7 @@ public void Apply(IEventLike eventLike) eventLike.Release ??= _options.SettingLocator.GetRelease(); // Distribution - eventLike.WithDistribution(_ => _.Distribution ??= _options.Distribution); + eventLike.Distribution ??= _options.Distribution; // Environment eventLike.Environment ??= _options.SettingLocator.GetEnvironment(); diff --git a/src/Sentry/Internal/IHasDistribution.cs b/src/Sentry/Internal/IHasDistribution.cs deleted file mode 100644 index 36c6309cea..0000000000 --- a/src/Sentry/Internal/IHasDistribution.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Sentry.Internal; -// NOTE: We only need this interface because IEventLike is public and thus we can't -// add more properties without introducing a potentially breaking change. -// TODO: Move the Distribution property to IEventLike in the next major release. - -internal interface IHasDistribution -{ - /// - /// The release distribution of the application. - /// - public string? Distribution { get; set; } -} - -internal static class HasDistributionExtensions -{ - internal static string? GetDistribution(this IEventLike obj) => - (obj as IHasDistribution)?.Distribution; - - internal static void WithDistribution(this IEventLike obj, Action action) - { - if (obj is IHasDistribution hasDistribution) - { - action.Invoke(hasDistribution); - } - } -} diff --git a/src/Sentry/Internal/NoOpTransaction.cs b/src/Sentry/Internal/NoOpTransaction.cs index 92535847d2..0bc6232cab 100644 --- a/src/Sentry/Internal/NoOpTransaction.cs +++ b/src/Sentry/Internal/NoOpTransaction.cs @@ -25,6 +25,14 @@ public bool? IsParentSampled set { } } + public TransactionNameSource NameSource => TransactionNameSource.Custom; + + public string? Distribution + { + get => string.Empty; + set { } + } + public SentryLevel? Level { get => default; diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index f89b9db5f5..85a9cab8d9 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -11,7 +11,7 @@ namespace Sentry; /// Scope data is sent together with any event captured /// during the lifetime of the scope. /// -public class Scope : IEventLike, IHasDistribution +public class Scope : IEventLike { internal SentryOptions Options { get; } @@ -435,7 +435,7 @@ public void Apply(IEventLike other) other.Platform ??= Platform; other.Release ??= Release; - other.WithDistribution(_ => _.Distribution ??= Distribution); + other.Distribution ??= Distribution; other.Environment ??= Environment; other.TransactionName ??= TransactionName; other.Level ??= Level; diff --git a/src/Sentry/SentryEvent.cs b/src/Sentry/SentryEvent.cs index 31c8de2ea1..9742d6f030 100644 --- a/src/Sentry/SentryEvent.cs +++ b/src/Sentry/SentryEvent.cs @@ -11,7 +11,7 @@ namespace Sentry; /// /// [DebuggerDisplay("{GetType().Name,nq}: {" + nameof(EventId) + ",nq}")] -public sealed class SentryEvent : IEventLike, IJsonSerializable, IHasDistribution +public sealed class SentryEvent : IEventLike, IJsonSerializable { private IDictionary? _modules; diff --git a/src/Sentry/SpanContext.cs b/src/Sentry/SpanContext.cs index f55bc1521a..176dab1923 100644 --- a/src/Sentry/SpanContext.cs +++ b/src/Sentry/SpanContext.cs @@ -1,9 +1,11 @@ +using Sentry.Protocol; + namespace Sentry; /// /// Span metadata used for sampling. /// -public class SpanContext : ISpanContext +public class SpanContext : ITraceContext { /// public SpanId SpanId { get; } diff --git a/src/Sentry/Transaction.cs b/src/Sentry/Transaction.cs index 4ca09f3cdb..582abfedbe 100644 --- a/src/Sentry/Transaction.cs +++ b/src/Sentry/Transaction.cs @@ -9,7 +9,7 @@ namespace Sentry; /// /// Sentry performance transaction. /// -public class Transaction : ITransactionData, IJsonSerializable, IHasDistribution, IHasTransactionNameSource, IHasMeasurements +public class Transaction : ITransactionData, IJsonSerializable, IHasMeasurements { /// /// Transaction's event ID. @@ -233,7 +233,7 @@ public Transaction(string name, string operation, TransactionNameSource nameSour /// Initializes an instance of . /// public Transaction(ITransaction tracer) - : this(tracer.Name, tracer is IHasTransactionNameSource t ? t.NameSource : TransactionNameSource.Custom) + : this(tracer.Name, tracer.NameSource) { // Contexts have to be set first because other fields use that Contexts = tracer.Contexts; @@ -244,7 +244,7 @@ public Transaction(ITransaction tracer) Operation = tracer.Operation; Platform = tracer.Platform; Release = tracer.Release; - Distribution = tracer.GetDistribution(); + Distribution = tracer.Distribution; StartTimestamp = tracer.StartTimestamp; EndTimestamp = tracer.EndTimestamp; Description = tracer.Description; diff --git a/src/Sentry/TransactionContext.cs b/src/Sentry/TransactionContext.cs index e3aee07680..f397984725 100644 --- a/src/Sentry/TransactionContext.cs +++ b/src/Sentry/TransactionContext.cs @@ -3,7 +3,7 @@ namespace Sentry; /// /// Transaction metadata used for sampling. /// -public class TransactionContext : SpanContext, ITransactionContext, IHasTransactionNameSource +public class TransactionContext : SpanContext, ITransactionContext { /// public string Name { get; set; } diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index 56f041949c..3b1425d03a 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -7,7 +7,7 @@ namespace Sentry; /// /// Transaction tracer. /// -public class TransactionTracer : ITransaction, IHasDistribution, IHasTransactionNameSource, IHasMeasurements +public class TransactionTracer : ITransaction, IHasMeasurements { private readonly IHub _hub; private readonly SentryOptions? _options; @@ -43,7 +43,7 @@ public SentryId TraceId /// public string Name { get; set; } - /// + /// public TransactionNameSource NameSource { get; set; } /// @@ -229,7 +229,7 @@ internal TransactionTracer(IHub hub, ITransactionContext context, TimeSpan? idle _hub = hub; _options = _hub.GetSentryOptions(); Name = context.Name; - NameSource = context is IHasTransactionNameSource c ? c.NameSource : TransactionNameSource.Custom; + NameSource = context.NameSource; Operation = context.Operation; SpanId = context.SpanId; ParentSpanId = context.ParentSpanId; diff --git a/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs b/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs index ef87155717..17b1bc0ddf 100644 --- a/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs +++ b/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs @@ -16,7 +16,7 @@ public void StartSentryTransaction_CreatesValidTransaction() // Assert transaction.Name.Should().Be("GET /the/path"); transaction.Operation.Should().Be("http.server"); - ((IHasTransactionNameSource)transaction).NameSource.Should().Be(TransactionNameSource.Url); + transaction.NameSource.Should().Be(TransactionNameSource.Url); } [Fact] diff --git a/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs index f547c5538c..0d604c7dba 100644 --- a/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs @@ -101,7 +101,7 @@ public async Task Transaction_is_bound_on_the_scope_automatically() // Assert transaction.Should().NotBeNull(); transaction.Name.Should().Be("GET /person/{id}"); - ((IHasTransactionNameSource)transaction).NameSource.Should().Be(TransactionNameSource.Route); + transaction.NameSource.Should().Be(TransactionNameSource.Route); } [Fact] diff --git a/test/Sentry.Tests/ApiApprovalTests.verify.cs b/test/Sentry.Tests/ApiApprovalTests.verify.cs index 952e4e6edd..4f8479b716 100644 --- a/test/Sentry.Tests/ApiApprovalTests.verify.cs +++ b/test/Sentry.Tests/ApiApprovalTests.verify.cs @@ -3,9 +3,15 @@ namespace Sentry.Tests; [UsesVerify] public class ApiApprovalTests { - [Fact] + [SkippableFact()] public Task Run() { + // Skip this test in the feat/v4.0.0 branch + var assembly = AppDomain.CurrentDomain.GetAssemblies(). + SingleOrDefault(assembly => assembly.GetName().Name == "Sentry"); + var version = assembly.GetVersion(); + Skip.If(version.StartsWith("3")); + return typeof(SentrySdk).Assembly.CheckApproval(); } }