From 6229cf40e35640748e99812001fa609f009515af Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 27 Jun 2023 09:43:11 +0200 Subject: [PATCH 1/5] Introduce ResilienceEventSeverity --- .../TelemetryBenchmark.cs | 2 +- .../Controller/CircuitStateController.cs | 6 +- .../Fallback/FallbackResilienceStrategy.cs | 2 +- .../Hedging/HedgingResilienceStrategy.cs | 2 +- .../Retry/RetryResilienceStrategy.cs | 2 +- src/Polly.Core/Telemetry/ResilienceEvent.cs | 3 +- .../Telemetry/ResilienceEventSeverity.cs | 22 ++++++ .../Telemetry/ResilienceStrategyTelemetry.cs | 42 ++++-------- .../Telemetry/TelemetryEventArguments.Pool.cs | 6 +- .../Telemetry/TelemetryEventArguments.cs | 2 +- src/Polly.Core/Telemetry/TelemetryUtil.cs | 4 +- .../Timeout/TimeoutResilienceStrategy.cs | 2 +- .../Utils/ReloadableResilienceStrategy.cs | 10 ++- src/Polly.Extensions/Telemetry/Log.cs | 67 ++++++++++--------- .../ResilienceTelemetryDiagnosticSource.cs | 9 +-- .../Telemetry/ResilienceTelemetryTags.cs | 3 + .../Telemetry/TelemetryUtil.cs | 19 ++++++ .../RateLimiterResilienceStrategy.cs | 2 +- .../Controller/CircuitStateControllerTests.cs | 4 +- .../HedgingExecutionContextTests.cs | 6 +- .../Hedging/HedgingResilienceStrategyTests.cs | 4 +- .../ResilienceContextTests.cs | 6 +- .../Telemetry/ResilienceEventTests.cs | 11 +-- .../ResilienceStrategyTelemetryTests.cs | 30 +++------ .../Telemetry/TelemetryEventArgumentsTests.cs | 9 +-- .../ReloadableResilienceStrategyTests.cs | 13 +++- ...esilienceTelemetryDiagnosticSourceTests.cs | 47 ++++++++----- .../TelemetryResilienceStrategyTests.cs | 4 +- test/Polly.TestUtils/TestUtilities.cs | 8 +-- 29 files changed, 204 insertions(+), 143 deletions(-) create mode 100644 src/Polly.Core/Telemetry/ResilienceEventSeverity.cs diff --git a/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs b/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs index ab0e2048f24..fcd217b220c 100644 --- a/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs +++ b/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs @@ -78,7 +78,7 @@ protected override ValueTask> ExecuteCoreAsync ResilienceContext context, TState state) { - _telemetry.Report("DummyEvent", context, "dummy-args"); + _telemetry.Report(new ResilienceEvent(ResilienceEventSeverity.Warning, "DummyEvent"), context, "dummy-args"); return callback(context, state); } } diff --git a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs index 3414d238f80..553cc7dc04b 100644 --- a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs +++ b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs @@ -128,7 +128,7 @@ public ValueTask CloseCircuitAsync(ResilienceContext context) if (_circuitState == CircuitState.Open && PermitHalfOpenCircuitTest_NeedsLock()) { _circuitState = CircuitState.HalfOpen; - _telemetry.Report(CircuitBreakerConstants.OnHalfOpenEvent, context, new OnCircuitHalfOpenedArguments()); + _telemetry.Report(new(ResilienceEventSeverity.Warning, CircuitBreakerConstants.OnHalfOpenEvent), context, new OnCircuitHalfOpenedArguments()); isHalfOpen = true; } @@ -269,7 +269,7 @@ private void CloseCircuit_NeedsLock(Outcome outcome, bool manual, ResilienceC if (priorState != CircuitState.Closed) { var args = new OutcomeArguments(context, outcome, new OnCircuitClosedArguments(manual)); - _telemetry.Report(CircuitBreakerConstants.OnCircuitClosed, args); + _telemetry.Report(new(ResilienceEventSeverity.Information, CircuitBreakerConstants.OnCircuitClosed), args); if (_onClosed is not null) { @@ -320,7 +320,7 @@ private void OpenCircuitFor_NeedsLock(Outcome outcome, TimeSpan breakDuration _circuitState = CircuitState.Open; var args = new OutcomeArguments(context, outcome, new OnCircuitOpenedArguments(breakDuration, manual)); - _telemetry.Report(CircuitBreakerConstants.OnCircuitOpened, args); + _telemetry.Report(new(ResilienceEventSeverity.Error, CircuitBreakerConstants.OnCircuitOpened), args); if (_onOpened is not null) { diff --git a/src/Polly.Core/Fallback/FallbackResilienceStrategy.cs b/src/Polly.Core/Fallback/FallbackResilienceStrategy.cs index fd6db6966e3..f0939511d0d 100644 --- a/src/Polly.Core/Fallback/FallbackResilienceStrategy.cs +++ b/src/Polly.Core/Fallback/FallbackResilienceStrategy.cs @@ -29,7 +29,7 @@ protected override async ValueTask> ExecuteCallbackAsync(Func var onFallbackArgs = new OutcomeArguments(context, outcome, new OnFallbackArguments()); - _telemetry.Report(FallbackConstants.OnFallback, onFallbackArgs); + _telemetry.Report(new(ResilienceEventSeverity.Warning, FallbackConstants.OnFallback), onFallbackArgs); if (_onFallback is not null) { diff --git a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs index d96e0197d90..1034031ae64 100644 --- a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs +++ b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs @@ -126,7 +126,7 @@ private async ValueTask HandleOnHedgingAsync(ResilienceContext context, Outcome< outcome, args); - _telemetry.Report(HedgingConstants.OnHedgingEventName, onHedgingArgs); + _telemetry.Report(new(ResilienceEventSeverity.Warning, HedgingConstants.OnHedgingEventName), onHedgingArgs); if (OnHedging is not null) { diff --git a/src/Polly.Core/Retry/RetryResilienceStrategy.cs b/src/Polly.Core/Retry/RetryResilienceStrategy.cs index a633dcb6fe0..9a6da5108ed 100644 --- a/src/Polly.Core/Retry/RetryResilienceStrategy.cs +++ b/src/Polly.Core/Retry/RetryResilienceStrategy.cs @@ -74,7 +74,7 @@ protected override async ValueTask> ExecuteCallbackAsync(Func } var onRetryArgs = new OutcomeArguments(context, outcome, new OnRetryArguments(attempt, delay, executionTime)); - _telemetry.Report(RetryConstants.OnRetryEvent, onRetryArgs); + _telemetry.Report(new(ResilienceEventSeverity.Warning, RetryConstants.OnRetryEvent), onRetryArgs); if (OnRetry is not null) { diff --git a/src/Polly.Core/Telemetry/ResilienceEvent.cs b/src/Polly.Core/Telemetry/ResilienceEvent.cs index be87dca76ac..fdc4710bc43 100644 --- a/src/Polly.Core/Telemetry/ResilienceEvent.cs +++ b/src/Polly.Core/Telemetry/ResilienceEvent.cs @@ -3,11 +3,12 @@ namespace Polly.Telemetry; /// /// Represents a resilience event that has been reported. /// +/// The severity of the event. /// The event name. /// /// Always use the constructor when creating this struct, otherwise we do not guarantee binary compatibility. /// -public readonly record struct ResilienceEvent(string EventName) +public readonly record struct ResilienceEvent(ResilienceEventSeverity Severity, string EventName) { /// /// Returns an . diff --git a/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs new file mode 100644 index 00000000000..c8a33ae647a --- /dev/null +++ b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs @@ -0,0 +1,22 @@ +namespace Polly.Telemetry; + +/// +/// The severity of reported resilience event. +/// +public enum ResilienceEventSeverity +{ + /// + /// The resilience event is informational. + /// + Information, + + /// + /// The resilience event should be treated as a warning. + /// + Warning, + + /// + /// The resilience event should be treated as a warning. + /// + Error, +} diff --git a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs index 100fc9073a2..cb25acbea53 100644 --- a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs +++ b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs @@ -27,25 +27,24 @@ internal ResilienceStrategyTelemetry(ResilienceTelemetrySource source, Diagnosti /// Reports an event that occurred in a resilience strategy. /// /// The arguments associated with this event. - /// The event name. + /// The reported resilience event. /// The resilience context associated with this event. /// The event arguments. - /// Thrown when is . - public void Report(string eventName, ResilienceContext context, TArgs args) + /// Thrown when is . + public void Report(ResilienceEvent resilienceEvent, ResilienceContext context, TArgs args) { - Guard.NotNull(eventName); Guard.NotNull(context); - AddResilienceEvent(eventName, context, args); + context.AddResilienceEvent(resilienceEvent); - if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(eventName)) + if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(resilienceEvent.EventName)) { return; } - var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, eventName, context, null, args!); + var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, resilienceEvent, context, null, args!); - DiagnosticSource.Write(eventName, telemetryArgs); + DiagnosticSource.Write(resilienceEvent.EventName, telemetryArgs); TelemetryEventArguments.Return(telemetryArgs); } @@ -55,37 +54,22 @@ public void Report(string eventName, ResilienceContext context, TArgs arg /// /// The arguments associated with this event. /// The type of the result. - /// The event name. + /// The reported resilience event. /// The event arguments. - /// Thrown when is . - public void Report(string eventName, OutcomeArguments args) + public void Report(ResilienceEvent resilienceEvent, OutcomeArguments args) { - Guard.NotNull(eventName); + args.Context.AddResilienceEvent(resilienceEvent); - AddResilienceEvent(eventName, args.Context, args.Arguments); - - if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(eventName)) + if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(resilienceEvent.EventName)) { return; } - var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, eventName, args.Context, args.Outcome.AsOutcome(), args.Arguments!); + var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, resilienceEvent, args.Context, args.Outcome.AsOutcome(), args.Arguments!); - DiagnosticSource.Write(eventName, telemetryArgs); + DiagnosticSource.Write(resilienceEvent.EventName, telemetryArgs); TelemetryEventArguments.Return(telemetryArgs); } - - private static void AddResilienceEvent(string eventName, ResilienceContext context, TArgs args) - { - // ExecutionAttemptArguments is not reported as resilience event because that information is already contained - // in OnHedgingArguments and OnRetryArguments - if (args is ExecutionAttemptArguments attempt) - { - return; - } - - context.AddResilienceEvent(new ResilienceEvent(eventName)); - } } diff --git a/src/Polly.Core/Telemetry/TelemetryEventArguments.Pool.cs b/src/Polly.Core/Telemetry/TelemetryEventArguments.Pool.cs index 9d701004ecb..d115c9468ca 100644 --- a/src/Polly.Core/Telemetry/TelemetryEventArguments.Pool.cs +++ b/src/Polly.Core/Telemetry/TelemetryEventArguments.Pool.cs @@ -5,7 +5,7 @@ public sealed partial record class TelemetryEventArguments private static readonly ObjectPool Pool = new(() => new TelemetryEventArguments(), args => { args.Source = null!; - args.EventName = null!; + args.Event = default; args.Context = null!; args.Outcome = default; args.Arguments = null!; @@ -13,7 +13,7 @@ public sealed partial record class TelemetryEventArguments internal static TelemetryEventArguments Get( ResilienceTelemetrySource source, - string eventName, + ResilienceEvent resilienceEvent, ResilienceContext context, Outcome? outcome, object arguments) @@ -21,7 +21,7 @@ internal static TelemetryEventArguments Get( var args = Pool.Get(); args.Source = source; - args.EventName = eventName; + args.Event = resilienceEvent; args.Context = context; args.Outcome = outcome; args.Arguments = arguments; diff --git a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs index 1c98f3145f2..21ef00447c9 100644 --- a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs +++ b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs @@ -20,7 +20,7 @@ private TelemetryEventArguments() /// /// Gets the event name. /// - public string EventName { get; private set; } = null!; + public ResilienceEvent Event { get; private set; } /// /// Gets the resilience context. diff --git a/src/Polly.Core/Telemetry/TelemetryUtil.cs b/src/Polly.Core/Telemetry/TelemetryUtil.cs index 7486edd855f..f2afd372dec 100644 --- a/src/Polly.Core/Telemetry/TelemetryUtil.cs +++ b/src/Polly.Core/Telemetry/TelemetryUtil.cs @@ -34,7 +34,9 @@ public static void ReportExecutionAttempt( } var attemptArgs = ExecutionAttemptArguments.Get(attempt, executionTime, handled); - telemetry.Report(ExecutionAttempt, new(context, outcome, attemptArgs)); + telemetry.Report( + new(handled ? ResilienceEventSeverity.Warning : ResilienceEventSeverity.Information, ExecutionAttempt), + new(context, outcome, attemptArgs)); ExecutionAttemptArguments.Return(attemptArgs); } } diff --git a/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs b/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs index af3fb43a627..cfcf4e67327 100644 --- a/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs +++ b/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs @@ -60,7 +60,7 @@ protected internal override async ValueTask> ExecuteCoreAsync _onReload; private readonly Func _resilienceStrategyFactory; private readonly ResilienceStrategyTelemetry _telemetry; @@ -50,16 +52,18 @@ private void RegisterOnReload(CancellationToken previousToken) _registration = token.Register(() => { + var context = ResilienceContext.Get().Initialize(isSynchronous: true); + #pragma warning disable CA1031 // Do not catch general exception types try { + _telemetry.Report(new(ResilienceEventSeverity.Information, OnReloadEvent), context, new OnReloadArguments()); Strategy = _resilienceStrategyFactory(); } catch (Exception e) { - var context = ResilienceContext.Get().Initialize(isSynchronous: true); var args = new OutcomeArguments(context, Outcome.FromException(e), new ReloadFailedArguments(e)); - _telemetry.Report(ReloadFailedEvent, args); + _telemetry.Report(new(ResilienceEventSeverity.Error, ReloadFailedEvent), args); ResilienceContext.Return(context); } #pragma warning restore CA1031 // Do not catch general exception types @@ -70,4 +74,6 @@ private void RegisterOnReload(CancellationToken previousToken) } internal readonly record struct ReloadFailedArguments(Exception Exception); + + internal readonly record struct OnReloadArguments(); } diff --git a/src/Polly.Extensions/Telemetry/Log.cs b/src/Polly.Extensions/Telemetry/Log.cs index c249b71b708..30e1bbcb1da 100644 --- a/src/Polly.Extensions/Telemetry/Log.cs +++ b/src/Polly.Extensions/Telemetry/Log.cs @@ -6,40 +6,19 @@ namespace Polly.Extensions.Telemetry; internal static partial class Log { - private const string StrategyExecutedMessage = "Resilience strategy executed. " + - "Builder Name: '{BuilderName}', " + - "Strategy Key: '{StrategyKey}', " + - "Result Type: '{ResultType}', " + - "Result: '{Result}', " + - "Execution Health: '{ExecutionHealth}', " + - "Execution Time: {ExecutionTime}ms"; - - private const string ResilienceEventMessage = "Resilience event occurred. " + + [LoggerMessage( + EventId = 0, + Message = "Resilience event occurred. " + "EventName: '{EventName}', " + "Builder Name: '{BuilderName}', " + "Strategy Name: '{StrategyName}', " + "Strategy Type: '{StrategyType}', " + "Strategy Key: '{StrategyKey}', " + - "Result: '{Result}'"; - - private const string ExecutionAttemptMessage = "Execution attempt. " + - "Builder Name: '{BuilderName}', " + - "Strategy Name: '{StrategyName}', " + - "Strategy Type: '{StrategyType}', " + - "Strategy Key: '{StrategyKey}', " + - "Result: '{Result}', " + - "Handled: '{Handled}', " + - "Attempt: '{Attempt}', " + - "Execution Time: '{ExecutionTimeMs}'"; - - private const string StrategyExecutingMessage = "Resilience strategy executing. " + - "Builder Name: '{BuilderName}', " + - "Strategy Key: '{StrategyKey}', " + - "Result Type: '{ResultType}'"; - - [LoggerMessage(0, LogLevel.Warning, ResilienceEventMessage, EventName = "ResilienceEvent")] + "Result: '{Result}'", + EventName = "ResilienceEvent")] public static partial void ResilienceEvent( this ILogger logger, + LogLevel logLevel, string eventName, string? builderName, string? strategyName, @@ -48,14 +27,30 @@ public static partial void ResilienceEvent( object? result, Exception? exception); - [LoggerMessage(1, LogLevel.Debug, StrategyExecutingMessage, EventName = "StrategyExecuting")] + [LoggerMessage( + 1, + LogLevel.Debug, + "Resilience strategy executing. " + + "Builder Name: '{BuilderName}', " + + "Strategy Key: '{StrategyKey}', " + + "Result Type: '{ResultType}'", + EventName = "StrategyExecuting")] public static partial void ExecutingStrategy( this ILogger logger, string? builderName, string? strategyKey, string resultType); - [LoggerMessage(EventId = 2, Message = StrategyExecutedMessage, EventName = "StrategyExecuted")] + [LoggerMessage( + EventId = 2, + Message = "Resilience strategy executed. " + + "Builder Name: '{BuilderName}', " + + "Strategy Key: '{StrategyKey}', " + + "Result Type: '{ResultType}', " + + "Result: '{Result}', " + + "Execution Health: '{ExecutionHealth}', " + + "Execution Time: {ExecutionTime}ms", + EventName = "StrategyExecuted")] public static partial void StrategyExecuted( this ILogger logger, LogLevel logLevel, @@ -67,7 +62,19 @@ public static partial void StrategyExecuted( double executionTime, Exception? exception); - [LoggerMessage(EventId = 3, Message = ExecutionAttemptMessage, EventName = "ExecutionAttempt", SkipEnabledCheck = true)] + [LoggerMessage( + EventId = 3, + Message = "Execution attempt. " + + "Builder Name: '{BuilderName}', " + + "Strategy Name: '{StrategyName}', " + + "Strategy Type: '{StrategyType}', " + + "Strategy Key: '{StrategyKey}', " + + "Result: '{Result}', " + + "Handled: '{Handled}', " + + "Attempt: '{Attempt}', " + + "Execution Time: '{ExecutionTimeMs}'", + EventName = "ExecutionAttempt", + SkipEnabledCheck = true)] public static partial void ExecutionAttempt( this ILogger logger, LogLevel level, diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs index df7452dcbe7..8c9540826f5 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs @@ -47,7 +47,8 @@ public override void Write(string name, object? value) private static void AddCommonTags(TelemetryEventArguments args, ResilienceTelemetrySource source, EnrichmentContext enrichmentContext) { - enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.EventName, args.EventName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.EventName, args.Event.EventName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.EventSeverity, args.Event.Severity.AsString())); enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.BuilderName, source.BuilderName)); enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyName, source.StrategyName)); enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyType, source.StrategyType)); @@ -98,10 +99,10 @@ private void LogEvent(TelemetryEventArguments args) result = e.Message; } + var level = args.Event.Severity.AsLogLevel(); + if (args.Arguments is ExecutionAttemptArguments executionAttempt) { - var level = executionAttempt.Handled ? LogLevel.Warning : LogLevel.Debug; - if (_logger.IsEnabled(level)) { Log.ExecutionAttempt( @@ -120,7 +121,7 @@ private void LogEvent(TelemetryEventArguments args) } else { - Log.ResilienceEvent(_logger, args.EventName, args.Source.BuilderName, args.Source.StrategyName, args.Source.StrategyType, strategyKey, result, args.Outcome?.Exception); + Log.ResilienceEvent(_logger, level, args.Event.EventName, args.Source.BuilderName, args.Source.StrategyName, args.Source.StrategyType, strategyKey, result, args.Outcome?.Exception); } } } diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs index bbfbae10b50..0bf298bf67f 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs @@ -4,6 +4,8 @@ internal class ResilienceTelemetryTags { public const string EventName = "event-name"; + public const string EventSeverity = "event-severity"; + public const string BuilderName = "builder-name"; public const string StrategyName = "strategy-name"; @@ -21,4 +23,5 @@ internal class ResilienceTelemetryTags public const string AttemptNumber = "attempt-number"; public const string AttemptHandled = "attempt-handled"; + } diff --git a/src/Polly.Extensions/Telemetry/TelemetryUtil.cs b/src/Polly.Extensions/Telemetry/TelemetryUtil.cs index bf07a06b0f6..43c6a146d20 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryUtil.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryUtil.cs @@ -1,3 +1,6 @@ +using Microsoft.Extensions.Logging; +using Polly.Telemetry; + namespace Polly.Extensions.Telemetry; internal static class TelemetryUtil @@ -25,4 +28,20 @@ internal static class TelemetryUtil >= 0 and < MaxIntegers => Integers[value], _ => value, }; + + public static LogLevel AsLogLevel(this ResilienceEventSeverity severity) => severity switch + { + ResilienceEventSeverity.Information => LogLevel.Information, + ResilienceEventSeverity.Warning => LogLevel.Warning, + ResilienceEventSeverity.Error => LogLevel.Error, + _ => LogLevel.Information, + }; + + public static string AsString(this ResilienceEventSeverity severity) => severity switch + { + ResilienceEventSeverity.Information => nameof(ResilienceEventSeverity.Information), + ResilienceEventSeverity.Warning => nameof(ResilienceEventSeverity.Warning), + ResilienceEventSeverity.Error => nameof(ResilienceEventSeverity.Error), + _ => severity.ToString(), + }; } diff --git a/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs b/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs index 4e35648d823..3a284d12864 100644 --- a/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs +++ b/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs @@ -44,7 +44,7 @@ protected override async ValueTask> ExecuteCoreAsync v.OnCircuitClosed()); await controller.CloseCircuitAsync(ResilienceContext.Get()); await controller.OnActionPreExecuteAsync(ResilienceContext.Get()); - context.ResilienceEvents.Should().Contain(new ResilienceEvent("OnCircuitOpened")); + context.ResilienceEvents.Should().Contain(new ResilienceEvent(ResilienceEventSeverity.Error, "OnCircuitOpened")); } [Fact] @@ -93,7 +93,7 @@ public async Task BreakAsync_Ok() await controller.OnActionPreExecuteAsync(ResilienceContext.Get()); _circuitBehavior.VerifyAll(); - context.ResilienceEvents.Should().Contain(new ResilienceEvent("OnCircuitClosed")); + context.ResilienceEvents.Should().Contain(new ResilienceEvent(ResilienceEventSeverity.Information, "OnCircuitClosed")); } [Fact] diff --git a/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs b/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs index 08bc56967e6..437c3c1bec5 100644 --- a/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs +++ b/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs @@ -316,12 +316,12 @@ public async Task Complete_EnsureOriginalContextPreparedWithAcceptedOutcome(bool if (primary) { _resilienceContext.Properties.Should().HaveCount(1); - _resilienceContext.ResilienceEvents.Should().HaveCount(0); + _resilienceContext.ResilienceEvents.Should().HaveCount(1); } else { _resilienceContext.Properties.Should().HaveCount(2); - _resilienceContext.ResilienceEvents.Should().HaveCount(1); + _resilienceContext.ResilienceEvents.Should().HaveCount(4); } } @@ -456,7 +456,7 @@ private void ConfigureSecondaryTasks(params TimeSpan[] delays) return null; } - args.ActionContext.AddResilienceEvent(new ResilienceEvent("dummy-event")); + args.ActionContext.AddResilienceEvent(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy-event")); return async () => { diff --git a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs index c3ef4868e90..ec17aae0205 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs @@ -915,8 +915,8 @@ public async Task ExecuteAsync_EnsureOnHedgingTelemetry() var strategy = Create(); await strategy.ExecuteAsync((_, _) => new ValueTask(Failure), context, "state"); - context.ResilienceEvents.Should().HaveCount(_options.MaxHedgedAttempts); - context.ResilienceEvents.Select(v => v.EventName).Distinct().Should().ContainSingle("OnHedging"); + context.ResilienceEvents.Should().HaveCount(_options.MaxHedgedAttempts + 1); + context.ResilienceEvents.Select(v => v.EventName).Distinct().Should().HaveCount(2); } private void ConfigureHedging() diff --git a/test/Polly.Core.Tests/ResilienceContextTests.cs b/test/Polly.Core.Tests/ResilienceContextTests.cs index 8bf243a2003..67e28939fad 100644 --- a/test/Polly.Core.Tests/ResilienceContextTests.cs +++ b/test/Polly.Core.Tests/ResilienceContextTests.cs @@ -42,10 +42,10 @@ public void AddResilienceEvent_Ok() { var context = ResilienceContext.Get(); - context.AddResilienceEvent(new ResilienceEvent("Dummy")); + context.AddResilienceEvent(new ResilienceEvent(ResilienceEventSeverity.Information, "Dummy")); context.ResilienceEvents.Should().HaveCount(1); - context.ResilienceEvents.Should().Contain(new ResilienceEvent("Dummy")); + context.ResilienceEvents.Should().Contain(new ResilienceEvent(ResilienceEventSeverity.Information, "Dummy")); } [Fact] @@ -59,7 +59,7 @@ await TestUtilities.AssertWithTimeoutAsync(() => context.Initialize(true); context.CancellationToken.Should().Be(cts.Token); context.Properties.Set(new ResiliencePropertyKey("abc"), 10); - context.AddResilienceEvent(new ResilienceEvent("dummy")); + context.AddResilienceEvent(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy")); ResilienceContext.Return(context); AssertDefaults(context); diff --git a/test/Polly.Core.Tests/Telemetry/ResilienceEventTests.cs b/test/Polly.Core.Tests/Telemetry/ResilienceEventTests.cs index bfbf9a02e4f..e7ea2db478f 100644 --- a/test/Polly.Core.Tests/Telemetry/ResilienceEventTests.cs +++ b/test/Polly.Core.Tests/Telemetry/ResilienceEventTests.cs @@ -7,16 +7,19 @@ public class ResilienceEventTests [Fact] public void Ctor_Ok() { - var ev = new ResilienceEvent("A"); + var ev = new ResilienceEvent(ResilienceEventSeverity.Warning, "A"); ev.ToString().Should().Be("A"); + ev.Severity.Should().Be(ResilienceEventSeverity.Warning); + } [Fact] public void Equality_Ok() { - (new ResilienceEvent("A") == new ResilienceEvent("A")).Should().BeTrue(); - (new ResilienceEvent("A") != new ResilienceEvent("A")).Should().BeFalse(); - (new ResilienceEvent("A") == new ResilienceEvent("B")).Should().BeFalse(); + (new ResilienceEvent(ResilienceEventSeverity.Warning, "A") == new ResilienceEvent(ResilienceEventSeverity.Warning, "A")).Should().BeTrue(); + (new ResilienceEvent(ResilienceEventSeverity.Warning, "A") != new ResilienceEvent(ResilienceEventSeverity.Warning, "A")).Should().BeFalse(); + (new ResilienceEvent(ResilienceEventSeverity.Warning, "A") == new ResilienceEvent(ResilienceEventSeverity.Warning, "B")).Should().BeFalse(); + (new ResilienceEvent(ResilienceEventSeverity.Information, "A") == new ResilienceEvent(ResilienceEventSeverity.Warning, "A")).Should().BeFalse(); } } diff --git a/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs b/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs index ee53c762c14..2439901939f 100644 --- a/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs +++ b/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs @@ -22,38 +22,28 @@ public void Report_NoOutcome_OK() obj.Should().BeOfType(); var args = (TelemetryEventArguments)obj; - args.EventName.Should().Be("dummy-event"); + args.Event.EventName.Should().Be("dummy-event"); + args.Event.Severity.Should().Be(ResilienceEventSeverity.Warning); args.Outcome.Should().BeNull(); args.Source.StrategyName.Should().Be("strategy-name"); args.Source.StrategyType.Should().Be("strategy-type"); args.Source.BuilderProperties.Should().NotBeNull(); args.Arguments.Should().BeOfType(); - args.EventName.Should().Be("dummy-event"); args.Outcome.Should().BeNull(); args.Context.Should().NotBeNull(); }); - _sut.Report("dummy-event", ResilienceContext.Get(), new TestArguments()); + _sut.Report(new(ResilienceEventSeverity.Warning, "dummy-event"), ResilienceContext.Get(), new TestArguments()); _diagnosticSource.VerifyAll(); } - [Fact] - public void Report_Attempt_EnsureNotRecordedOnResilienceContext() - { - var context = ResilienceContext.Get(); - _diagnosticSource.Setup(o => o.IsEnabled("dummy-event")).Returns(false); - _sut.Report("dummy-event", context, ExecutionAttemptArguments.Get(0, TimeSpan.Zero, true)); - - context.ResilienceEvents.Should().BeEmpty(); - } - [Fact] public void Report_NoOutcomeWhenNotSubscribed_None() { _diagnosticSource.Setup(o => o.IsEnabled("dummy-event")).Returns(false); - _sut.Report("dummy-event", ResilienceContext.Get(), new TestArguments()); + _sut.Report(new(ResilienceEventSeverity.Warning, "dummy-event"), ResilienceContext.Get(), new TestArguments()); _diagnosticSource.VerifyAll(); _diagnosticSource.VerifyNoOtherCalls(); @@ -66,8 +56,8 @@ public void ResilienceStrategyTelemetry_NoDiagnosticSource_Ok() var sut = new ResilienceStrategyTelemetry(source, null); var context = ResilienceContext.Get(); - sut.Invoking(s => s.Report("dummy", context, new TestArguments())).Should().NotThrow(); - sut.Invoking(s => s.Report("dummy", new OutcomeArguments(context, Outcome.FromResult(1), new TestArguments()))).Should().NotThrow(); + sut.Invoking(s => s.Report(new(ResilienceEventSeverity.Warning, "dummy"), context, new TestArguments())).Should().NotThrow(); + sut.Invoking(s => s.Report(new(ResilienceEventSeverity.Warning, "dummy"), new OutcomeArguments(context, Outcome.FromResult(1), new TestArguments()))).Should().NotThrow(); } [Fact] @@ -81,19 +71,19 @@ public void Report_Outcome_OK() obj.Should().BeOfType(); var args = (TelemetryEventArguments)obj; - args.EventName.Should().Be("dummy-event"); + args.Event.EventName.Should().Be("dummy-event"); + args.Event.Severity.Should().Be(ResilienceEventSeverity.Warning); args.Source.StrategyName.Should().Be("strategy-name"); args.Source.StrategyType.Should().Be("strategy-type"); args.Source.BuilderProperties.Should().NotBeNull(); args.Arguments.Should().BeOfType(); - args.EventName.Should().Be("dummy-event"); args.Outcome.Should().NotBeNull(); args.Outcome!.Value.Result.Should().Be(99); args.Context.Should().NotBeNull(); }); var context = ResilienceContext.Get(); - _sut.Report("dummy-event", new OutcomeArguments(context, Outcome.FromResult(99), new TestArguments())); + _sut.Report(new(ResilienceEventSeverity.Warning, "dummy-event"), new OutcomeArguments(context, Outcome.FromResult(99), new TestArguments())); _diagnosticSource.VerifyAll(); } @@ -103,7 +93,7 @@ public void Report_OutcomeWhenNotSubscribed_None() { _diagnosticSource.Setup(o => o.IsEnabled("dummy-event")).Returns(false); var context = ResilienceContext.Get(); - _sut.Report("dummy-event", new OutcomeArguments(context, Outcome.FromResult(10), new TestArguments())); + _sut.Report(new(ResilienceEventSeverity.Warning, "dummy-event"), new OutcomeArguments(context, Outcome.FromResult(10), new TestArguments())); _diagnosticSource.VerifyAll(); _diagnosticSource.VerifyNoOtherCalls(); diff --git a/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs b/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs index e2e870dd196..710546db6ef 100644 --- a/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs +++ b/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs @@ -10,11 +10,12 @@ public class TelemetryEventArgumentsTests public void Get_Ok() { var context = ResilienceContext.Get(); - var args = TelemetryEventArguments.Get(_source, "ev", context, Outcome.FromResult("dummy"), "arg"); + var args = TelemetryEventArguments.Get(_source, new ResilienceEvent(ResilienceEventSeverity.Warning, "ev"), context, Outcome.FromResult("dummy"), "arg"); args.Outcome!.Value.Result.Should().Be("dummy"); args.Context.Should().Be(context); - args.EventName.Should().Be("ev"); + args.Event.EventName.Should().Be("ev"); + args.Event.Severity.Should().Be(ResilienceEventSeverity.Warning); args.Source.Should().Be(_source); args.Arguments.Should().BeEquivalentTo("arg"); args.Context.Should().Be(context); @@ -24,7 +25,7 @@ public void Get_Ok() public void Return_EnsurePropertiesCleared() { var context = ResilienceContext.Get(); - var args = TelemetryEventArguments.Get(_source, "ev", context, Outcome.FromResult("dummy"), "arg"); + var args = TelemetryEventArguments.Get(_source, new ResilienceEvent(ResilienceEventSeverity.Warning, "ev"), context, Outcome.FromResult("dummy"), "arg"); TelemetryEventArguments.Return(args); @@ -32,7 +33,7 @@ public void Return_EnsurePropertiesCleared() { args.Outcome.Should().BeNull(); args.Context.Should().BeNull(); - args.EventName.Should().BeNull(); + args.Event.EventName.Should().BeNullOrEmpty(); args.Source.Should().BeNull(); args.Arguments.Should().BeNull(); args.Context.Should().BeNull(); diff --git a/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs b/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs index de4fa05067c..bd6b8f881c7 100644 --- a/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs @@ -49,7 +49,8 @@ public void ChangeTriggered_StrategyReloaded() sut.Strategy.Should().NotBe(strategy); } - _events.Should().HaveCount(0); + _events.Where(e => e.Event.EventName == "ReloadFailed").Should().HaveCount(0); + _events.Where(e => e.Event.EventName == "OnReload").Should().HaveCount(10); } [Fact] @@ -61,8 +62,14 @@ public void ChangeTriggered_FactoryError_LastStrategyUsedAndErrorReported() _cancellationTokenSource.Cancel(); sut.Strategy.Should().Be(strategy); - _events.Should().HaveCount(1); - var args = _events[0] + _events.Should().HaveCount(2); + + _events[0] + .Arguments + .Should() + .BeOfType(); + + var args = _events[1] .Arguments .Should() .BeOfType() diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs index 54e30e5f294..a01a1ed7b00 100644 --- a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs @@ -71,7 +71,7 @@ public void WriteEvent_LoggingWithOutcome_Ok(bool noOutcome) if (noOutcome) { - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: '(null)'"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: ''"); } else { @@ -98,7 +98,7 @@ public void WriteEvent_LoggingWithException_Ok(bool noOutcome) if (noOutcome) { - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: '(null)'"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: ''"); } else { @@ -114,7 +114,26 @@ public void WriteEvent_LoggingWithoutStrategyKey_Ok() var messages = _logger.GetRecords(new EventId(0, "ResilienceEvent")).ToList(); - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: '(null)', Result: '(null)'"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: '', Result: ''"); + } + + [InlineData(ResilienceEventSeverity.Error, LogLevel.Error)] + [InlineData(ResilienceEventSeverity.Warning, LogLevel.Warning)] + [InlineData(ResilienceEventSeverity.Information, LogLevel.Information)] + [InlineData((ResilienceEventSeverity)99, LogLevel.Information)] + [Theory] + public void WriteEvent_EnsureSeverityRespected(ResilienceEventSeverity severity, LogLevel logLevel) + { + var telemetry = Create(); + ReportEvent(telemetry, null, severity: severity); + + var messages = _logger.GetRecords(new EventId(0, "ResilienceEvent")).ToList(); + + messages[0].LogLevel.Should().Be(logLevel); + + var events = GetEvents("resilience-events"); + events.Should().HaveCount(1); + var ev = events[0]["event-severity"].Should().Be(severity.ToString()); } [Fact] @@ -154,14 +173,7 @@ public void WriteExecutionAttempt_LoggingWithOutcome_Ok(bool noOutcome, bool han messages[0].Message.Should().Be($"Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: '200', Handled: '{handled}', Attempt: '4', Execution Time: '123'"); } - if (handled) - { - messages[0].LogLevel.Should().Be(LogLevel.Warning); - } - else - { - messages[0].LogLevel.Should().Be(LogLevel.Debug); - } + messages[0].LogLevel.Should().Be(LogLevel.Warning); // verify reported state var coll = messages[0].State.Should().BeAssignableTo>>().Subject; @@ -212,8 +224,9 @@ public void WriteEvent_MeteringWithoutEnrichers_Ok(bool noOutcome, bool exceptio events.Should().HaveCount(1); var ev = events[0]; - ev.Count.Should().Be(7); + ev.Count.Should().Be(8); ev["event-name"].Should().Be("my-event"); + ev["event-severity"].Should().Be("Warning"); ev["strategy-type"].Should().Be("my-strategy-type"); ev["strategy-name"].Should().Be("my-strategy"); ev["strategy-key"].Should().Be("my-strategy-key"); @@ -251,8 +264,9 @@ public void WriteExecutionAttemptEvent_Metering_Ok(bool noOutcome, bool exceptio events.Should().HaveCount(1); var ev = events[0]; - ev.Count.Should().Be(9); + ev.Count.Should().Be(10); ev["event-name"].Should().Be("my-event"); + ev["event-severity"].Should().Be("Warning"); ev["strategy-type"].Should().Be("my-strategy-type"); ev["strategy-name"].Should().Be("my-strategy"); ev["strategy-key"].Should().Be("my-strategy-key"); @@ -299,7 +313,7 @@ public void WriteEvent_MeteringWithEnrichers_Ok(int count) var events = GetEvents("resilience-events"); var ev = events[0]; - ev.Count.Should().Be(DefaultDimensions + count + 1); + ev.Count.Should().Be(DefaultDimensions + count + 2); ev["other"].Should().Be("other-value"); for (int i = 0; i < count; i++) @@ -335,7 +349,8 @@ private static void ReportEvent( Outcome? outcome, string? strategyKey = "my-strategy-key", ResilienceContext? context = null, - object? arg = null) + object? arg = null, + ResilienceEventSeverity severity = ResilienceEventSeverity.Warning) { context ??= ResilienceContext.Get(); var props = new ResilienceProperties(); @@ -345,7 +360,7 @@ private static void ReportEvent( } telemetry.ReportEvent( - "my-event", + new ResilienceEvent(severity, "my-event"), "my-builder", props, "my-strategy", diff --git a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs index a20e4a15e86..fb22830ac59 100644 --- a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs @@ -43,7 +43,7 @@ public void Execute_EnsureLogged(bool healthy) { if (!healthy) { - ((List)c.ResilienceEvents).Add(new ResilienceEvent("dummy")); + ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy")); } }, ResilienceContext.Get(), string.Empty); @@ -154,7 +154,7 @@ public void Execute_WithResult_EnsureMetered(bool healthy) { if (!healthy) { - ((List)c.ResilienceEvents).Add(new ResilienceEvent("dummy")); + ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy")); } return true; diff --git a/test/Polly.TestUtils/TestUtilities.cs b/test/Polly.TestUtils/TestUtilities.cs index 097de46aabd..b4e5aa7fbd0 100644 --- a/test/Polly.TestUtils/TestUtilities.cs +++ b/test/Polly.TestUtils/TestUtilities.cs @@ -78,7 +78,7 @@ void OnMeasurementRecorded(Instrument instrument, T measurement, ReadOnlySpan #pragma warning disable S107 // Methods should not have too many parameters public static void ReportEvent( this DiagnosticSource source, - string eventName, + ResilienceEvent resilienceEvent, string builderName, ResilienceProperties builderProperties, string strategyName, @@ -88,9 +88,9 @@ public static void ReportEvent( object arguments) #pragma warning restore S107 // Methods should not have too many parameters { - source.Write(eventName, TelemetryEventArguments.Get( + source.Write(resilienceEvent.EventName, TelemetryEventArguments.Get( new ResilienceTelemetrySource(builderName, builderProperties, strategyName, strategyType), - eventName, + resilienceEvent, context, outcome, arguments)); @@ -122,7 +122,7 @@ public override void Write(string name, object? value) } // copy the args because these are pooled and in tests we want to preserve them - args = TelemetryEventArguments.Get(args.Source, args.EventName, args.Context, args.Outcome, arguments); + args = TelemetryEventArguments.Get(args.Source, args.Event, args.Context, args.Outcome, arguments); lock (_syncRoot) { From 3734287c63b055b9f99453ec826e8dba6bd1403c Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 27 Jun 2023 14:07:59 +0200 Subject: [PATCH 2/5] cleanup --- src/Polly.Core/ResilienceContext.cs | 2 +- .../Telemetry/ResilienceContextExtensions.cs | 13 ++++++- .../TelemetryResilienceStrategyTests.cs | 36 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/ResilienceContext.cs b/src/Polly.Core/ResilienceContext.cs index 4cdde32d1e9..5ada98899c9 100644 --- a/src/Polly.Core/ResilienceContext.cs +++ b/src/Polly.Core/ResilienceContext.cs @@ -64,7 +64,7 @@ private ResilienceContext() /// /// If the number of resilience events is greater than zero it's an indication that the execution was unhealthy. /// - public IReadOnlyCollection ResilienceEvents => _resilienceEvents; + public IReadOnlyList ResilienceEvents => _resilienceEvents; /// /// Gets a instance from the pool. diff --git a/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs b/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs index 87df8f617c0..f6c5019b2e7 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs @@ -8,5 +8,16 @@ internal static class ResilienceContextExtensions public static string GetExecutionHealth(this ResilienceContext context) => context.IsExecutionHealthy() ? "Healthy" : "Unhealthy"; - public static bool IsExecutionHealthy(this ResilienceContext context) => context.ResilienceEvents.Count == 0; + public static bool IsExecutionHealthy(this ResilienceContext context) + { + for (int i = 0; i < context.ResilienceEvents.Count; i++) + { + if (context.ResilienceEvents[i].Severity != Polly.Telemetry.ResilienceEventSeverity.Information) + { + return false; + } + } + + return true; + } } diff --git a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs index fb22830ac59..18360068078 100644 --- a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs @@ -179,6 +179,42 @@ public void Execute_WithResult_EnsureMetered(bool healthy) } } + [InlineData(true)] + [InlineData(false)] + [Theory] + public void Execute_ExecutionHealth(bool healthy) + { + var strategy = CreateStrategy(); + strategy.Execute( + (c, _) => + { + if (healthy) + { + ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy")); + ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy")); + } + else + { + ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy")); + ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy")); + } + + return true; + }, + ResilienceContext.Get(), string.Empty); + + var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags; + + if (healthy) + { + ev["execution-health"].Should().Be("Healthy"); + } + else + { + ev["execution-health"].Should().Be("Unhealthy"); + } + } + private TelemetryResilienceStrategy CreateStrategy() => new("my-builder", "my-key", _loggerFactory, (_, r) => r, new List> { c => _enricher?.Invoke(c) }); public void Dispose() From 58dc37fa2b66f0f2f05e5e9001ede626824d4f24 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 27 Jun 2023 14:12:14 +0200 Subject: [PATCH 3/5] PR comments --- src/Polly.Core/Telemetry/ResilienceEventSeverity.cs | 2 +- src/Polly.Core/Telemetry/TelemetryEventArguments.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs index c8a33ae647a..fbfc48b18ee 100644 --- a/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs +++ b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs @@ -16,7 +16,7 @@ public enum ResilienceEventSeverity Warning, /// - /// The resilience event should be treated as a warning. + /// The resilience event should be treated as an error. /// Error, } diff --git a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs index 21ef00447c9..d3c66c51aae 100644 --- a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs +++ b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs @@ -18,7 +18,7 @@ private TelemetryEventArguments() public ResilienceTelemetrySource Source { get; private set; } = null!; /// - /// Gets the event name. + /// Gets the event. /// public ResilienceEvent Event { get; private set; } From 5073a0b8b1f633783032acc25aca757281ad0ea9 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 27 Jun 2023 14:27:54 +0200 Subject: [PATCH 4/5] PR comments --- .../Telemetry/ResilienceEventSeverity.cs | 15 +++++++++++++++ .../Telemetry/ResilienceStrategyTelemetry.cs | 4 ++-- .../Telemetry/ResilienceContextExtensions.cs | 2 +- src/Polly.Extensions/Telemetry/TelemetryUtil.cs | 7 ++++++- .../Telemetry/ResilienceStrategyTelemetryTests.cs | 13 +++++++++++++ .../ResilienceTelemetryDiagnosticSourceTests.cs | 5 ++++- 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs index fbfc48b18ee..31607d60184 100644 --- a/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs +++ b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs @@ -5,6 +5,16 @@ /// public enum ResilienceEventSeverity { + /// + /// The resilience event is not recorded. + /// + None = 0, + + /// + /// The resilience event is used for debugging purposes only. + /// + Debug, + /// /// The resilience event is informational. /// @@ -19,4 +29,9 @@ public enum ResilienceEventSeverity /// The resilience event should be treated as an error. /// Error, + + /// + /// The resilience event should be treated as a critical error. + /// + Critical, } diff --git a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs index cb25acbea53..9062ac77c87 100644 --- a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs +++ b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs @@ -37,7 +37,7 @@ public void Report(ResilienceEvent resilienceEvent, ResilienceContext con context.AddResilienceEvent(resilienceEvent); - if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(resilienceEvent.EventName)) + if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(resilienceEvent.EventName) || resilienceEvent.Severity == ResilienceEventSeverity.None) { return; } @@ -60,7 +60,7 @@ public void Report(ResilienceEvent resilienceEvent, OutcomeArgum { args.Context.AddResilienceEvent(resilienceEvent); - if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(resilienceEvent.EventName)) + if (DiagnosticSource is null || !DiagnosticSource.IsEnabled(resilienceEvent.EventName) || resilienceEvent.Severity == ResilienceEventSeverity.None) { return; } diff --git a/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs b/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs index f6c5019b2e7..58b15866c96 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs @@ -12,7 +12,7 @@ public static bool IsExecutionHealthy(this ResilienceContext context) { for (int i = 0; i < context.ResilienceEvents.Count; i++) { - if (context.ResilienceEvents[i].Severity != Polly.Telemetry.ResilienceEventSeverity.Information) + if (context.ResilienceEvents[i].Severity > Polly.Telemetry.ResilienceEventSeverity.Information) { return false; } diff --git a/src/Polly.Extensions/Telemetry/TelemetryUtil.cs b/src/Polly.Extensions/Telemetry/TelemetryUtil.cs index 43c6a146d20..c04a37efee7 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryUtil.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryUtil.cs @@ -31,17 +31,22 @@ internal static class TelemetryUtil public static LogLevel AsLogLevel(this ResilienceEventSeverity severity) => severity switch { + ResilienceEventSeverity.Debug => LogLevel.Debug, ResilienceEventSeverity.Information => LogLevel.Information, ResilienceEventSeverity.Warning => LogLevel.Warning, ResilienceEventSeverity.Error => LogLevel.Error, - _ => LogLevel.Information, + ResilienceEventSeverity.Critical => LogLevel.Critical, + _ => LogLevel.None, }; public static string AsString(this ResilienceEventSeverity severity) => severity switch { + ResilienceEventSeverity.None => nameof(ResilienceEventSeverity.None), + ResilienceEventSeverity.Debug => nameof(ResilienceEventSeverity.Debug), ResilienceEventSeverity.Information => nameof(ResilienceEventSeverity.Information), ResilienceEventSeverity.Warning => nameof(ResilienceEventSeverity.Warning), ResilienceEventSeverity.Error => nameof(ResilienceEventSeverity.Error), + ResilienceEventSeverity.Critical => nameof(ResilienceEventSeverity.Critical), _ => severity.ToString(), }; } diff --git a/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs b/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs index 2439901939f..743a6d3a1cd 100644 --- a/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs +++ b/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs @@ -88,6 +88,19 @@ public void Report_Outcome_OK() _diagnosticSource.VerifyAll(); } + [Fact] + public void Report_SeverityNone_Skipped() + { + _diagnosticSource.Setup(o => o.IsEnabled("dummy-event")).Returns(true); + + var context = ResilienceContext.Get(); + _sut.Report(new(ResilienceEventSeverity.None, "dummy-event"), new OutcomeArguments(context, Outcome.FromResult(99), new TestArguments())); + _sut.Report(new(ResilienceEventSeverity.None, "dummy-event"), ResilienceContext.Get(), new TestArguments()); + + _diagnosticSource.VerifyAll(); + _diagnosticSource.VerifyNoOtherCalls(); + } + [Fact] public void Report_OutcomeWhenNotSubscribed_None() { diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs index a01a1ed7b00..ad9b62066ac 100644 --- a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs @@ -120,7 +120,10 @@ public void WriteEvent_LoggingWithoutStrategyKey_Ok() [InlineData(ResilienceEventSeverity.Error, LogLevel.Error)] [InlineData(ResilienceEventSeverity.Warning, LogLevel.Warning)] [InlineData(ResilienceEventSeverity.Information, LogLevel.Information)] - [InlineData((ResilienceEventSeverity)99, LogLevel.Information)] + [InlineData(ResilienceEventSeverity.Debug, LogLevel.Debug)] + [InlineData(ResilienceEventSeverity.Critical, LogLevel.Critical)] + [InlineData(ResilienceEventSeverity.None, LogLevel.None)] + [InlineData((ResilienceEventSeverity)99, LogLevel.None)] [Theory] public void WriteEvent_EnsureSeverityRespected(ResilienceEventSeverity severity, LogLevel logLevel) { From 4587a7a361afc7eb566e6377b5796952281a5afc Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 27 Jun 2023 14:38:06 +0200 Subject: [PATCH 5/5] fix code coverage --- src/Polly.Extensions/Telemetry/Log.cs | 2 ++ .../ResilienceTelemetryDiagnosticSourceTests.cs | 16 ---------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Polly.Extensions/Telemetry/Log.cs b/src/Polly.Extensions/Telemetry/Log.cs index 30e1bbcb1da..c68896994a6 100644 --- a/src/Polly.Extensions/Telemetry/Log.cs +++ b/src/Polly.Extensions/Telemetry/Log.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; namespace Polly.Extensions.Telemetry; #pragma warning disable S107 // Methods should not have too many parameters +[ExcludeFromCodeCoverage] internal static partial class Log { [LoggerMessage( diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs index ad9b62066ac..1142e3ed357 100644 --- a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs @@ -177,22 +177,6 @@ public void WriteExecutionAttempt_LoggingWithOutcome_Ok(bool noOutcome, bool han } messages[0].LogLevel.Should().Be(LogLevel.Warning); - - // verify reported state - var coll = messages[0].State.Should().BeAssignableTo>>().Subject; - coll.Count.Should().Be(9); - coll.AsEnumerable().Should().HaveCount(9); - (coll as IEnumerable).GetEnumerator().Should().NotBeNull(); - - for (int i = 0; i < coll.Count; i++) - { - if (!noOutcome) - { - coll[i].Value.Should().NotBeNull(); - } - } - - coll.Invoking(c => c[coll.Count + 1]).Should().Throw(); } [Fact]