diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf8393806..d68aa61574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ If you have conflicts, you can opt-out by adding the following to your `csproj`: ``` +### Features + +- Timing metrics can now be captured with `SentrySdk.Metrics.StartTimer` ([#3075](https://github.com/getsentry/sentry-dotnet/pull/3075)) + ### Fixes - Moved the binding to MAUI events for breadcrumb creation from `WillFinishLaunching` to `FinishedLaunching`. This delays the initial instantiation of `app`. ([#3057](https://github.com/getsentry/sentry-dotnet/pull/3057)) diff --git a/samples/Sentry.Samples.Console.Metrics/Program.cs b/samples/Sentry.Samples.Console.Metrics/Program.cs index 09c7b3246e..7854b6e39a 100644 --- a/samples/Sentry.Samples.Console.Metrics/Program.cs +++ b/samples/Sentry.Samples.Console.Metrics/Program.cs @@ -15,6 +15,8 @@ private static void Main() options.Debug = true; options.StackTraceMode = StackTraceMode.Enhanced; + options.SampleRate = 1.0f; // Not recommended in production - may adversely impact quota + options.TracesSampleRate = 1.0f; // Not recommended in production - may adversely impact quota // Initialize some (non null) ExperimentalMetricsOptions to enable Sentry Metrics, options.ExperimentalMetrics = new ExperimentalMetricsOptions { @@ -24,22 +26,17 @@ private static void Main() })) { System.Console.WriteLine("Measure, Yeah, Measure!"); + Action[] actions = + [ + () => PlaySetBingo(10), + () => CreateRevenueGauge(100), + () => MeasureShrimp(30), + ]; while (true) { // Perform your task here - switch (Roll.Next(1,3)) - { - case 1: - PlaySetBingo(10); - break; - case 2: - CreateRevenueGauge(100); - break; - case 3: - MeasureShrimp(30); - break; - } - + var actionIdx = Roll.Next(0, actions.Length); + actions[actionIdx](); // Optional: Delay to prevent tight looping var sleepTime = Roll.Next(1, 10); @@ -60,9 +57,10 @@ private static void PlaySetBingo(int attempts) { var solution = new[] { 3, 5, 7, 11, 13, 17 }; - // The Timing class creates a distribution that is designed to measure the amount of time it takes to run code + // StartTimer creates a distribution that is designed to measure the amount of time it takes to run code // blocks. By default it will use a unit of Seconds - we're configuring it to use milliseconds here though. - using (new Timing("bingo", MeasurementUnit.Duration.Millisecond)) + // The return value is an IDisposable and the timer will stop when the timer is disposed of. + using (SentrySdk.Metrics.StartTimer("bingo", MeasurementUnit.Duration.Millisecond)) { for (var i = 0; i < attempts; i++) { @@ -78,7 +76,7 @@ private static void PlaySetBingo(int attempts) private static void CreateRevenueGauge(int sampleCount) { - using (new Timing(nameof(CreateRevenueGauge), MeasurementUnit.Duration.Millisecond)) + using (SentrySdk.Metrics.StartTimer(nameof(CreateRevenueGauge), MeasurementUnit.Duration.Millisecond)) { for (var i = 0; i < sampleCount; i++) { @@ -92,7 +90,7 @@ private static void CreateRevenueGauge(int sampleCount) private static void MeasureShrimp(int sampleCount) { - using (new Timing(nameof(MeasureShrimp), MeasurementUnit.Duration.Millisecond)) + using (SentrySdk.Metrics.StartTimer(nameof(MeasureShrimp), MeasurementUnit.Duration.Millisecond)) { for (var i = 0; i < sampleCount; i++) { diff --git a/src/Sentry/DisabledMetricAggregator.cs b/src/Sentry/DisabledMetricAggregator.cs index 61304898cf..eadecd07b8 100644 --- a/src/Sentry/DisabledMetricAggregator.cs +++ b/src/Sentry/DisabledMetricAggregator.cs @@ -37,6 +37,14 @@ public void Timing(string key, double value, MeasurementUnit.Duration unit = Mea // No Op } + public IDisposable StartTimer(string key, MeasurementUnit.Duration unit = MeasurementUnit.Duration.Second, + IDictionary? tags = null, + int stackLevel = 1) + { + // No Op + return NoOpDisposable.Instance; + } + public Task FlushAsync(bool force = true, CancellationToken cancellationToken = default) { // No Op @@ -48,3 +56,14 @@ public void Dispose() // No Op } } + +internal class NoOpDisposable : IDisposable +{ + private static readonly Lazy LazyInstance = new(); + internal static NoOpDisposable Instance => LazyInstance.Value; + + public void Dispose() + { + // No Op + } +} diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs index bd12084c72..239d860942 100644 --- a/src/Sentry/Extensibility/DisabledHub.cs +++ b/src/Sentry/Extensibility/DisabledHub.cs @@ -1,3 +1,6 @@ +using Sentry.Protocol.Envelopes; +using Sentry.Protocol.Metrics; + namespace Sentry.Extensibility; /// @@ -133,6 +136,14 @@ public void BindClient(ISentryClient client) { } + /// + /// No-Op. + /// + public bool CaptureEnvelope(Envelope envelope) + { + return false; + } + /// /// No-Op. /// diff --git a/src/Sentry/Extensibility/HubAdapter.cs b/src/Sentry/Extensibility/HubAdapter.cs index 1935ede45b..239b072fd5 100644 --- a/src/Sentry/Extensibility/HubAdapter.cs +++ b/src/Sentry/Extensibility/HubAdapter.cs @@ -1,4 +1,6 @@ using Sentry.Infrastructure; +using Sentry.Protocol.Envelopes; +using Sentry.Protocol.Metrics; namespace Sentry.Extensibility; @@ -209,6 +211,9 @@ public SentryId CaptureEvent(SentryEvent evt) public SentryId CaptureEvent(SentryEvent evt, Scope? scope) => SentrySdk.CaptureEvent(evt, scope, null); + /// + public bool CaptureEnvelope(Envelope envelope) => SentrySdk.CurrentHub.CaptureEnvelope(envelope); + /// /// Forwards the call to . /// diff --git a/src/Sentry/IHub.cs b/src/Sentry/IHub.cs index 726d548a7a..5a55a70a49 100644 --- a/src/Sentry/IHub.cs +++ b/src/Sentry/IHub.cs @@ -1,3 +1,5 @@ +using Sentry.Protocol.Metrics; + namespace Sentry; /// @@ -19,6 +21,11 @@ public interface IHub : /// SentryId LastEventId { get; } + /// + /// + /// + IMetricAggregator Metrics { get; } + /// /// Starts a transaction. /// diff --git a/src/Sentry/IMetricAggregator.cs b/src/Sentry/IMetricAggregator.cs index 04bc946b97..25c25905bf 100644 --- a/src/Sentry/IMetricAggregator.cs +++ b/src/Sentry/IMetricAggregator.cs @@ -96,6 +96,18 @@ void Timing(string key, DateTimeOffset? timestamp = null, int stackLevel = 1); + /// + /// Measures the time it takes to run a given code block and emits this as a metric. + /// + /// + /// using (SentrySdk.Metrics.StartTimer("my-operation")) + /// { + /// ... + /// } + /// + IDisposable StartTimer(string key, MeasurementUnit.Duration unit = MeasurementUnit.Duration.Second, + IDictionary? tags = null, int stackLevel = 1); + /// /// Flushes any flushable metrics and/or code locations. /// If is true then the cutoff is ignored and all metrics are flushed. diff --git a/src/Sentry/IMetricHub.cs b/src/Sentry/IMetricHub.cs new file mode 100644 index 0000000000..861793b086 --- /dev/null +++ b/src/Sentry/IMetricHub.cs @@ -0,0 +1,21 @@ +using Sentry.Protocol.Metrics; + +namespace Sentry; + +internal interface IMetricHub +{ + /// + /// Captures one or more metrics to be sent to Sentry. + /// + void CaptureMetrics(IEnumerable metrics); + + /// + /// Captures one or more to be sent to Sentry. + /// + void CaptureCodeLocations(CodeLocations codeLocations); + + /// + /// Starts a child span for the current transaction or, if there is no active transaction, starts a new transaction. + /// + ISpan StartSpan(string operation, string description); +} diff --git a/src/Sentry/ISentryClient.cs b/src/Sentry/ISentryClient.cs index d676448a1a..56d41b565b 100644 --- a/src/Sentry/ISentryClient.cs +++ b/src/Sentry/ISentryClient.cs @@ -1,3 +1,6 @@ +using Sentry.Protocol.Envelopes; +using Sentry.Protocol.Metrics; + namespace Sentry; /// @@ -10,6 +13,13 @@ public interface ISentryClient /// bool IsEnabled { get; } + /// + /// Capture an envelope and queue it. + /// + /// The envelope. + /// true if the enveloped was queued, false otherwise. + bool CaptureEnvelope(Envelope envelope); + /// /// Capture the event /// @@ -68,9 +78,4 @@ public interface ISentryClient /// The amount of time allowed for flushing. /// A task to await for the flush operation. Task FlushAsync(TimeSpan timeout); - - /// - /// - /// - IMetricAggregator Metrics { get; } } diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index a047c0eaaf..a1ce81b56c 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -1,9 +1,11 @@ using Sentry.Extensibility; using Sentry.Infrastructure; +using Sentry.Protocol.Envelopes; +using Sentry.Protocol.Metrics; namespace Sentry.Internal; -internal class Hub : IHub, IDisposable +internal class Hub : IHub, IMetricHub, IDisposable { private readonly object _sessionPauseLock = new(); @@ -59,7 +61,14 @@ internal Hub( PushScope(); } - Metrics = _ownedClient.Metrics; + if (options.ExperimentalMetrics is not null) + { + Metrics = new MetricAggregator(options, this); + } + else + { + Metrics = new DisabledMetricAggregator(); + } foreach (var integration in options.Integrations) { @@ -394,6 +403,8 @@ public SentryId CaptureEvent(SentryEvent evt, Hint? hint, Action configur } } + public bool CaptureEnvelope(Envelope envelope) => _ownedClient.CaptureEnvelope(envelope); + public SentryId CaptureEvent(SentryEvent evt, Scope? scope = null, Hint? hint = null) { if (!IsEnabled) @@ -486,6 +497,57 @@ public void CaptureTransaction(SentryTransaction transaction, Scope? scope, Hint } } + /// + public void CaptureMetrics(IEnumerable metrics) + { + if (!IsEnabled) + { + return; + } + + Metric[]? enumerable = null; + try + { + enumerable = metrics as Metric[] ?? metrics.ToArray(); + _options.LogDebug("Capturing metrics."); + _ownedClient.CaptureEnvelope(Envelope.FromMetrics(metrics)); + } + catch (Exception e) + { + var metricEventIds = enumerable?.Select(m => m.EventId).ToArray() ?? []; + _options.LogError(e, "Failure to capture metrics: {0}", string.Join(",", metricEventIds)); + } + } + + /// + public void CaptureCodeLocations(CodeLocations codeLocations) + { + if (!IsEnabled) + { + return; + } + + try + { + _options.LogDebug("Capturing code locations for period: {0}", codeLocations.Timestamp); + _ownedClient.CaptureEnvelope(Envelope.FromCodeLocations(codeLocations)); + } + catch (Exception e) + { + _options.LogError(e, "Failure to capture code locations"); + } + } + + /// + public ISpan StartSpan(string operation, string description) + { + ITransactionTracer? currentTransaction = null; + ConfigureScope(s => currentTransaction = s.Transaction); + return currentTransaction is {} transaction + ? transaction.StartChild(operation, description) + : this.StartTransaction(operation, description); + } + public void CaptureSession(SessionUpdate sessionUpdate) { if (!IsEnabled) @@ -527,7 +589,7 @@ public void Dispose() try { - _ownedClient.Metrics.FlushAsync().ContinueWith(_ => + Metrics.FlushAsync().ContinueWith(_ => _ownedClient.FlushAsync(_options.ShutdownTimeout).Wait() ).ConfigureAwait(false).GetAwaiter().GetResult(); } diff --git a/src/Sentry/MetricAggregator.cs b/src/Sentry/MetricAggregator.cs index 26dce5e234..16f9e8416f 100644 --- a/src/Sentry/MetricAggregator.cs +++ b/src/Sentry/MetricAggregator.cs @@ -8,8 +8,7 @@ namespace Sentry; internal class MetricAggregator : IMetricAggregator { private readonly SentryOptions _options; - private readonly Action> _captureMetrics; - private readonly Action _captureCodeLocations; + private readonly IMetricHub _metricHub; private readonly TimeSpan _flushInterval; private readonly SemaphoreSlim _codeLocationLock = new(1,1); @@ -32,24 +31,12 @@ private readonly Lazy>> _b private readonly Task _loopTask; - /// - /// MetricAggregator constructor. - /// - /// The - /// The callback to be called to transmit aggregated metrics - /// The callback to be called to transmit new code locations - /// A - /// - /// A boolean value indicating whether the Loop to flush metrics should run, for testing only. - /// - /// An optional flushInterval, for testing only - internal MetricAggregator(SentryOptions options, Action> captureMetrics, - Action captureCodeLocations, CancellationTokenSource? shutdownSource = null, + internal MetricAggregator(SentryOptions options, IMetricHub metricHub, + CancellationTokenSource? shutdownSource = null, bool disableLoopTask = false, TimeSpan? flushInterval = null) { _options = options; - _captureMetrics = captureMetrics; - _captureCodeLocations = captureCodeLocations; + _metricHub = metricHub; _shutdownSource = shutdownSource ?? new CancellationTokenSource(); _flushInterval = flushInterval ?? TimeSpan.FromSeconds(5); @@ -161,6 +148,11 @@ public void Timing(string key, DateTimeOffset? timestamp = null, int stackLevel = 1) => Emit(MetricType.Distribution, key, value, unit, tags, timestamp, stackLevel + 1); + /// + public IDisposable StartTimer(string key, MeasurementUnit.Duration unit = MeasurementUnit.Duration.Second, + IDictionary? tags = null, int stackLevel = 1) + => new Timing(this, _metricHub, _options, key, unit, tags, stackLevel + 1); + private void Emit( MetricType type, string key, @@ -231,7 +223,7 @@ private ConcurrentDictionary GetOrAddTimeBucket(long bucketKey) { return existingBucket; } - + var timeBucket = new ConcurrentDictionary(); Buckets[bucketKey] = timeBucket; return timeBucket; @@ -247,7 +239,7 @@ private ConcurrentDictionary GetOrAddTimeBucket(long bucketKey) } } - internal void RecordCodeLocation( + internal virtual void RecordCodeLocation( MetricType type, string key, MeasurementUnit unit, @@ -381,7 +373,7 @@ public async Task FlushAsync(bool force = true, CancellationToken cancellationTo _bucketsLock.ExitWriteLock(); } - _captureMetrics(bucket.Values); + _metricHub.CaptureMetrics(bucket.Values); _options.LogDebug("Metric flushed for bucket {0}", key); } @@ -391,7 +383,7 @@ public async Task FlushAsync(bool force = true, CancellationToken cancellationTo _options.LogDebug("Flushing code locations: ", timestamp); var codeLocations = new CodeLocations(timestamp, locations); - _captureCodeLocations(codeLocations); + _metricHub.CaptureCodeLocations(codeLocations); _options.LogDebug("Code locations flushed: ", timestamp); } diff --git a/src/Sentry/Protocol/Metrics/CodeLocations.cs b/src/Sentry/Protocol/Metrics/CodeLocations.cs index a8bf10e2f7..914170381b 100644 --- a/src/Sentry/Protocol/Metrics/CodeLocations.cs +++ b/src/Sentry/Protocol/Metrics/CodeLocations.cs @@ -3,6 +3,9 @@ namespace Sentry.Protocol.Metrics; +/// +/// Represents a collection of code locations. +/// internal class CodeLocations(long timestamp, IReadOnlyDictionary locations) : IJsonSerializable { @@ -12,6 +15,7 @@ internal class CodeLocations(long timestamp, IReadOnlyDictionary public long Timestamp => timestamp; + /// public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) { writer.WriteStartObject(); diff --git a/src/Sentry/Protocol/Metrics/Metric.cs b/src/Sentry/Protocol/Metrics/Metric.cs index 7a9a5ca737..40c4236abc 100644 --- a/src/Sentry/Protocol/Metrics/Metric.cs +++ b/src/Sentry/Protocol/Metrics/Metric.cs @@ -4,12 +4,25 @@ namespace Sentry.Protocol.Metrics; +/// +/// Base class for metric instruments +/// internal abstract class Metric : IJsonSerializable, ISentrySerializable { + /// + /// Creates a new instance of . + /// protected Metric() : this(string.Empty) { } + /// + /// Creates a new instance of . + /// + /// The text key to be used to identify the metric + /// An optional that describes the values being tracked + /// An optional set of key/value paris that can be used to add dimensionality to metrics + /// An optional time when the metric was emitted. Defaults to DateTimeOffset.UtcNow protected Metric(string key, MeasurementUnit? unit = null, IDictionary? tags = null, DateTimeOffset? timestamp = null) { Key = key; @@ -18,16 +31,31 @@ protected Metric(string key, MeasurementUnit? unit = null, IDictionary + /// + /// + public SentryId EventId { get; } = SentryId.Create(); - public string Key { get; private set; } + /// + /// A text key identifying the metric + /// + public string Key { get; } - public DateTimeOffset Timestamp { get; private set; } + /// + /// The time when the metric was emitted. + /// + public DateTimeOffset Timestamp { get; } - public MeasurementUnit? Unit { get; private set; } + /// + /// A that describes the values being tracked + /// + public MeasurementUnit? Unit { get; } private IDictionary? _tags; + /// + /// A set of key/value paris providing dimensionality for the metric + /// public IDictionary Tags { get @@ -37,10 +65,17 @@ public IDictionary Tags } } + /// + /// Adds a value to the metric + /// public abstract void Add(double value); + /// + /// Serializes metric values to JSON + /// protected abstract void WriteValues(Utf8JsonWriter writer, IDiagnosticLogger? logger); + /// public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) { writer.WriteStartObject(); @@ -57,8 +92,14 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) writer.WriteEndObject(); } + /// + /// Concrete classes should implement this to return a list of values that should be serialized to statsd + /// protected abstract IEnumerable SerializedStatsdValues(); + /// + /// Serializes the metric asynchrounously in statsd format to the provided stream + /// public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger, CancellationToken cancellationToken = default) { /* @@ -111,6 +152,7 @@ async Task Write(string content) } } + /// public void Serialize(Stream stream, IDiagnosticLogger? logger) { SerializeAsync(stream, logger).GetAwaiter().GetResult(); diff --git a/src/Sentry/Protocol/Metrics/MetricResourceIdentifier.cs b/src/Sentry/Protocol/Metrics/MetricResourceIdentifier.cs index 3cd3a3906e..846f77c93f 100644 --- a/src/Sentry/Protocol/Metrics/MetricResourceIdentifier.cs +++ b/src/Sentry/Protocol/Metrics/MetricResourceIdentifier.cs @@ -1,7 +1,16 @@ namespace Sentry.Protocol.Metrics; +/// +/// Uniquely identifies a metric resource. +/// +/// +/// +/// internal record struct MetricResourceIdentifier(MetricType MetricType, string Key, MeasurementUnit Unit) { + /// + /// Returns a string representation of the metric resource identifier. + /// public override string ToString() => $"{MetricType.ToStatsdType()}:{MetricHelper.SanitizeKey(Key)}@{Unit}"; } diff --git a/src/Sentry/Protocol/Metrics/MetricType.cs b/src/Sentry/Protocol/Metrics/MetricType.cs index c323b914dd..0e624885a0 100644 --- a/src/Sentry/Protocol/Metrics/MetricType.cs +++ b/src/Sentry/Protocol/Metrics/MetricType.cs @@ -1,6 +1,19 @@ namespace Sentry.Protocol.Metrics; -internal enum MetricType : byte { Counter, Gauge, Distribution, Set } +/// +/// The metric instrument type +/// +internal enum MetricType : byte +{ + /// + Counter, + /// + Gauge, + /// + Distribution, + /// + Set +} internal static class MetricTypeExtensions { diff --git a/src/Sentry/Protocol/Metrics/SetMetric.cs b/src/Sentry/Protocol/Metrics/SetMetric.cs index ac102bdf37..53a42108cb 100644 --- a/src/Sentry/Protocol/Metrics/SetMetric.cs +++ b/src/Sentry/Protocol/Metrics/SetMetric.cs @@ -30,5 +30,5 @@ protected override void WriteValues(Utf8JsonWriter writer, IDiagnosticLogger? lo writer.WriteArrayIfNotEmpty("value", _value, logger); protected override IEnumerable SerializedStatsdValues() - => _value.Select(v => (IConvertible)v); + => _value.Cast(); } diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 4c1d52e07e..e7cd5ad280 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -23,11 +23,6 @@ public class SentryClient : ISentryClient, IDisposable internal IBackgroundWorker Worker { get; } - /// - /// - /// - public IMetricAggregator Metrics { get; } - internal SentryOptions Options => _options; /// @@ -77,15 +72,6 @@ internal SentryClient( options.LogDebug("Worker of type {0} was provided via Options.", worker.GetType().Name); Worker = worker; } - - if (options.ExperimentalMetrics is not null) - { - Metrics = new MetricAggregator(options, CaptureMetrics, CaptureCodeLocations); - } - else - { - Metrics = new DisabledMetricAggregator(); - } } /// @@ -241,24 +227,6 @@ public void CaptureTransaction(SentryTransaction transaction, Scope? scope, Hint return transaction; } - /// - /// Captures one or more metrics to be sent to Sentry. - /// - internal void CaptureMetrics(IEnumerable metrics) - { - _options.LogDebug("Capturing metrics."); - CaptureEnvelope(Envelope.FromMetrics(metrics)); - } - - /// - /// Captures one or more to be sent to Sentry. - /// - internal void CaptureCodeLocations(CodeLocations codeLocations) - { - _options.LogDebug("Capturing code locations for period: {0}", codeLocations.Timestamp); - CaptureEnvelope(Envelope.FromCodeLocations(codeLocations)); - } - /// public void CaptureSession(SessionUpdate sessionUpdate) { @@ -403,12 +371,8 @@ private SentryId DoSendEvent(SentryEvent @event, Hint? hint, Scope? scope) return null; } - /// - /// Capture an envelope and queue it. - /// - /// The envelope. - /// true if the enveloped was queued, false otherwise. - private bool CaptureEnvelope(Envelope envelope) + /// + public bool CaptureEnvelope(Envelope envelope) { if (Worker.EnqueueEnvelope(envelope)) { @@ -471,14 +435,12 @@ public void Dispose() try { - Metrics.FlushAsync().ContinueWith(_ => - // Worker should empty it's queue until SentryOptions.ShutdownTimeout - Worker.FlushAsync(_options.ShutdownTimeout) - ).ConfigureAwait(false).GetAwaiter().GetResult(); + // Worker should empty its queue until SentryOptions.ShutdownTimeout + Worker.FlushAsync(_options.ShutdownTimeout).ConfigureAwait(false).GetAwaiter().GetResult(); } catch { - _options.LogDebug("Failed to wait on metrics/worker to flush"); + _options.LogDebug("Failed to wait on worker to flush"); } } } diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 0d2916405e..ce29f57473 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -1,6 +1,8 @@ using Sentry.Extensibility; using Sentry.Infrastructure; using Sentry.Internal; +using Sentry.Protocol.Envelopes; +using Sentry.Protocol.Metrics; namespace Sentry; @@ -375,6 +377,12 @@ public static void ConfigureScope(Action configureScope) public static Task ConfigureScopeAsync(Func configureScope) => CurrentHub.ConfigureScopeAsync(configureScope); + /// + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool CaptureEnvelope(Envelope envelope) + => CurrentHub.CaptureEnvelope(envelope); + /// /// Captures the event, passing a hint, using the specified scope. /// diff --git a/src/Sentry/Timing.cs b/src/Sentry/Timing.cs index bdd206d2b1..d5052b4d9c 100644 --- a/src/Sentry/Timing.cs +++ b/src/Sentry/Timing.cs @@ -4,8 +4,7 @@ namespace Sentry; /// -/// Measures the time it takes to run a given code block and emits this as a metric. The class is -/// designed to be used in a using statement. +/// Measures the time it takes to run a given code block and emits this as a metric. /// /// /// using (var timing = new Timing("my-operation")) @@ -13,9 +12,11 @@ namespace Sentry; /// ... /// } /// -public class Timing: IDisposable +internal class Timing : IDisposable { - private readonly IHub _hub; + private readonly IMetricHub _metricHub; + private readonly SentryOptions _options; + private readonly MetricAggregator _metricAggregator; private readonly string _key; private readonly MeasurementUnit.Duration _unit; private readonly IDictionary? _tags; @@ -26,45 +27,26 @@ public class Timing: IDisposable /// /// Creates a new instance. /// - public Timing(string key, MeasurementUnit.Duration unit = MeasurementUnit.Duration.Second, - IDictionary? tags = null) - : this(SentrySdk.CurrentHub, key, unit, tags, stackLevel: 2 /* one for each constructor */) + internal Timing(MetricAggregator metricAggregator, IMetricHub metricHub, SentryOptions options, + string key, MeasurementUnit.Duration unit, IDictionary? tags, int stackLevel) { - } - - /// - /// Creates a new instance. - /// - public Timing(IHub hub, string key, MeasurementUnit.Duration unit = MeasurementUnit.Duration.Second, - IDictionary? tags = null) - : this(hub, key, unit, tags, stackLevel: 2 /* one for each constructor */) - { - } - - internal Timing(IHub hub, string key, MeasurementUnit.Duration unit, IDictionary? tags, - int stackLevel) - { - _hub = hub; + _metricHub = metricHub; + _options = options; + _metricAggregator = metricAggregator; _key = key; _unit = unit; _tags = tags; _stopwatch.Start(); - ITransactionTracer? currentTransaction = null; - hub.ConfigureScope(s => currentTransaction = s.Transaction); - _span = currentTransaction is {} transaction - ? transaction.StartChild("metric.timing", key) - : hub.StartTransaction("metric.timing", key); + + _span = metricHub.StartSpan("metric.timing", key); if (tags is not null) { _span.SetTags(tags); } // Report code locations here for better accuracy - if (hub.Metrics is MetricAggregator metrics) - { - metrics.RecordCodeLocation(MetricType.Distribution, key, unit, stackLevel + 1, _startTime); - } + _metricAggregator.RecordCodeLocation(MetricType.Distribution, key, unit, stackLevel + 1, _startTime); } /// @@ -86,11 +68,11 @@ public void Dispose() MeasurementUnit.Duration.Nanosecond => _stopwatch.Elapsed.TotalMilliseconds * 1000000, _ => throw new ArgumentOutOfRangeException(nameof(_unit), _unit, null) }; - _hub.Metrics.Timing(_key, value, _unit, _tags, _startTime); + _metricAggregator.Timing(_key, value, _unit, _tags, _startTime); } catch (Exception e) { - _hub.GetSentryOptions()?.LogError(e, "Error capturing timing '{0}'", _key); + _options.LogError(e, "Error capturing timing '{0}'", _key); } finally { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 5aeb3cded7..c8f0fc1543 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -224,6 +224,7 @@ namespace Sentry public interface IHub : Sentry.ISentryClient, Sentry.ISentryScopeManager { Sentry.SentryId LastEventId { get; } + Sentry.IMetricAggregator Metrics { get; } void BindException(System.Exception exception, Sentry.ISpan span); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope); @@ -249,6 +250,7 @@ namespace Sentry void Gauge(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Increment(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Set(string key, int value, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); + System.IDisposable StartTimer(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, int stackLevel = 1); void Timing(string key, double value, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); } public interface IScopeObserver @@ -262,7 +264,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.IMetricAggregator Metrics { get; } + bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.SentryTransaction transaction); @@ -490,7 +492,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.IMetricAggregator Metrics { get; } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.SentryTransaction transaction) { } @@ -728,6 +730,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } + public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1063,12 +1066,6 @@ namespace Sentry public override string ToString() { } public static Sentry.SubstringOrRegexPattern op_Implicit(string substringOrRegexPattern) { } } - public class Timing : System.IDisposable - { - public Timing(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public Timing(Sentry.IHub hub, string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public void Dispose() { } - } public class TransactionContext : Sentry.SpanContext, Sentry.ITransactionContext, Sentry.Protocol.ITraceContext { public TransactionContext(string name, string operation, Sentry.SpanId? spanId = default, Sentry.SpanId? parentSpanId = default, Sentry.SentryId? traceId = default, string? description = "", Sentry.SpanStatus? status = default, bool? isSampled = default, bool? isParentSampled = default, Sentry.TransactionNameSource nameSource = 0) { } @@ -1211,6 +1208,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1251,6 +1249,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 5aeb3cded7..c8f0fc1543 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -224,6 +224,7 @@ namespace Sentry public interface IHub : Sentry.ISentryClient, Sentry.ISentryScopeManager { Sentry.SentryId LastEventId { get; } + Sentry.IMetricAggregator Metrics { get; } void BindException(System.Exception exception, Sentry.ISpan span); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope); @@ -249,6 +250,7 @@ namespace Sentry void Gauge(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Increment(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Set(string key, int value, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); + System.IDisposable StartTimer(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, int stackLevel = 1); void Timing(string key, double value, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); } public interface IScopeObserver @@ -262,7 +264,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.IMetricAggregator Metrics { get; } + bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.SentryTransaction transaction); @@ -490,7 +492,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.IMetricAggregator Metrics { get; } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.SentryTransaction transaction) { } @@ -728,6 +730,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } + public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1063,12 +1066,6 @@ namespace Sentry public override string ToString() { } public static Sentry.SubstringOrRegexPattern op_Implicit(string substringOrRegexPattern) { } } - public class Timing : System.IDisposable - { - public Timing(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public Timing(Sentry.IHub hub, string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public void Dispose() { } - } public class TransactionContext : Sentry.SpanContext, Sentry.ITransactionContext, Sentry.Protocol.ITraceContext { public TransactionContext(string name, string operation, Sentry.SpanId? spanId = default, Sentry.SpanId? parentSpanId = default, Sentry.SentryId? traceId = default, string? description = "", Sentry.SpanStatus? status = default, bool? isSampled = default, bool? isParentSampled = default, Sentry.TransactionNameSource nameSource = 0) { } @@ -1211,6 +1208,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1251,6 +1249,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 78ff7adc7b..007d2fa570 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -225,6 +225,7 @@ namespace Sentry public interface IHub : Sentry.ISentryClient, Sentry.ISentryScopeManager { Sentry.SentryId LastEventId { get; } + Sentry.IMetricAggregator Metrics { get; } void BindException(System.Exception exception, Sentry.ISpan span); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope); @@ -250,6 +251,7 @@ namespace Sentry void Gauge(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Increment(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Set(string key, int value, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); + System.IDisposable StartTimer(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, int stackLevel = 1); void Timing(string key, double value, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); } public interface IScopeObserver @@ -263,7 +265,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.IMetricAggregator Metrics { get; } + bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.SentryTransaction transaction); @@ -491,7 +493,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.IMetricAggregator Metrics { get; } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.SentryTransaction transaction) { } @@ -729,6 +731,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } + public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1064,12 +1067,6 @@ namespace Sentry public override string ToString() { } public static Sentry.SubstringOrRegexPattern op_Implicit(string substringOrRegexPattern) { } } - public class Timing : System.IDisposable - { - public Timing(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public Timing(Sentry.IHub hub, string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public void Dispose() { } - } public class TransactionContext : Sentry.SpanContext, Sentry.ITransactionContext, Sentry.Protocol.ITraceContext { public TransactionContext(string name, string operation, Sentry.SpanId? spanId = default, Sentry.SpanId? parentSpanId = default, Sentry.SentryId? traceId = default, string? description = "", Sentry.SpanStatus? status = default, bool? isSampled = default, bool? isParentSampled = default, Sentry.TransactionNameSource nameSource = 0) { } @@ -1212,6 +1209,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1252,6 +1250,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 83e7cfd213..a7ca1d79c8 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -223,6 +223,7 @@ namespace Sentry public interface IHub : Sentry.ISentryClient, Sentry.ISentryScopeManager { Sentry.SentryId LastEventId { get; } + Sentry.IMetricAggregator Metrics { get; } void BindException(System.Exception exception, Sentry.ISpan span); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope); @@ -248,6 +249,7 @@ namespace Sentry void Gauge(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Increment(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); void Set(string key, int value, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); + System.IDisposable StartTimer(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, int stackLevel = 1); void Timing(string key, double value, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1); } public interface IScopeObserver @@ -261,7 +263,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.IMetricAggregator Metrics { get; } + bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.SentryTransaction transaction); @@ -489,7 +491,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.IMetricAggregator Metrics { get; } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.SentryTransaction transaction) { } @@ -725,6 +727,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } + public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1060,12 +1063,6 @@ namespace Sentry public override string ToString() { } public static Sentry.SubstringOrRegexPattern op_Implicit(string substringOrRegexPattern) { } } - public class Timing : System.IDisposable - { - public Timing(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public Timing(Sentry.IHub hub, string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary? tags = null) { } - public void Dispose() { } - } public class TransactionContext : Sentry.SpanContext, Sentry.ITransactionContext, Sentry.Protocol.ITraceContext { public TransactionContext(string name, string operation, Sentry.SpanId? spanId = default, Sentry.SpanId? parentSpanId = default, Sentry.SentryId? traceId = default, string? description = "", Sentry.SpanStatus? status = default, bool? isSampled = default, bool? isParentSampled = default, Sentry.TransactionNameSource nameSource = 0) { } @@ -1208,6 +1205,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.Hint? hint = null) { } @@ -1248,6 +1246,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } diff --git a/test/Sentry.Tests/MetricAggregatorTests.cs b/test/Sentry.Tests/MetricAggregatorTests.cs index 04d0daeeee..cbb68bad33 100644 --- a/test/Sentry.Tests/MetricAggregatorTests.cs +++ b/test/Sentry.Tests/MetricAggregatorTests.cs @@ -7,12 +7,12 @@ public class MetricAggregatorTests class Fixture { public SentryOptions Options { get; set; } = new(); - public Action> CaptureMetrics { get; set; } = (_ => { }); - public Action CaptureCodeLocations { get; set; } = (_ => { }); + public IHub Hub { get; set; } = Substitute.For(); + public IMetricHub MetricHub { get; set; } = Substitute.For(); public bool DisableFlushLoop { get; set; } = true; public TimeSpan? FlushInterval { get; set; } public MetricAggregator GetSut() - => new(Options, CaptureMetrics, CaptureCodeLocations, disableLoopTask: DisableFlushLoop, flushInterval: FlushInterval); + => new(Options, MetricHub, disableLoopTask: DisableFlushLoop, flushInterval: FlushInterval); } // private readonly Fixture _fixture = new(); @@ -177,13 +177,14 @@ public async Task GetFlushableBuckets_IsThreadsafe() MetricHelper.FlushShift = 0.0; _fixture.DisableFlushLoop = false; _fixture.FlushInterval = TimeSpan.FromMilliseconds(100); - _fixture.CaptureMetrics = metrics => - { - foreach (var metric in metrics) + _fixture.MetricHub.CaptureMetrics(Arg.Do>(metrics => { - Interlocked.Add(ref sent, (int)((CounterMetric)metric).Value); + foreach (var metric in metrics) + { + Interlocked.Add(ref sent, (int)((CounterMetric)metric).Value); + } } - }; + )); var sut = _fixture.GetSut(); // Act... spawn some threads that add loads of metrics