Repository: App-vNext/Polly

Issue:
Expand the `ResilienceEvent` with the `Severity`
This allows us to report `Informational` events and use the common-infra.

Changes:
- Add/Modify methods on `ResilienceStrategyTelemetry` to accept the serverity.
- Make sure that `TelemetryResilienceStrategy` strategy reports unhealthy status only for events with high severity.
- Consider whether to introduce new enum or re-use some existing one.



Hints:

Patch:
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<Outcome<TResult>> ExecuteCoreAsync<TResult, TState>
             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<T> outcome, bool manual, ResilienceC
         if (priorState != CircuitState.Closed)
         {
             var args = new OutcomeArguments<T, OnCircuitClosedArguments>(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<T> outcome, TimeSpan breakDuration
         _circuitState = CircuitState.Open;
 
         var args = new OutcomeArguments<T, OnCircuitOpenedArguments>(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<Outcome<T>> ExecuteCallbackAsync<TState>(Func
 
         var onFallbackArgs = new OutcomeArguments<T, OnFallbackArguments>(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/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()
     /// <remarks>
     /// If the number of resilience events is greater than zero it's an indication that the execution was unhealthy.
     /// </remarks>
-    public IReadOnlyCollection<ResilienceEvent> ResilienceEvents => _resilienceEvents;
+    public IReadOnlyList<ResilienceEvent> ResilienceEvents => _resilienceEvents;
 
     /// <summary>
     /// Gets a <see cref="ResilienceContext"/> instance from the pool.
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<Outcome<T>> ExecuteCallbackAsync<TState>(Func
             }
 
             var onRetryArgs = new OutcomeArguments<T, OnRetryArguments>(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;
 /// <summary>
 /// Represents a resilience event that has been reported.
 /// </summary>
+/// <param name="Severity">The severity of the event.</param>
 /// <param name="EventName">The event name.</param>
 /// <remarks>
 /// Always use the constructor when creating this struct, otherwise we do not guarantee binary compatibility.
 /// </remarks>
-public readonly record struct ResilienceEvent(string EventName)
+public readonly record struct ResilienceEvent(ResilienceEventSeverity Severity, string EventName)
 {
     /// <summary>
     /// Returns an <see cref="EventName"/>.
diff --git a/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs
new file mode 100644
index 00000000000..31607d60184
--- /dev/null
+++ b/src/Polly.Core/Telemetry/ResilienceEventSeverity.cs
@@ -0,0 +1,37 @@
+﻿namespace Polly.Telemetry;
+
+/// <summary>
+/// The severity of reported resilience event.
+/// </summary>
+public enum ResilienceEventSeverity
+{
+    /// <summary>
+    /// The resilience event is not recorded.
+    /// </summary>
+    None = 0,
+
+    /// <summary>
+    /// The resilience event is used for debugging purposes only.
+    /// </summary>
+    Debug,
+
+    /// <summary>
+    /// The resilience event is informational.
+    /// </summary>
+    Information,
+
+    /// <summary>
+    /// The resilience event should be treated as a warning.
+    /// </summary>
+    Warning,
+
+    /// <summary>
+    /// The resilience event should be treated as an error.
+    /// </summary>
+    Error,
+
+    /// <summary>
+    /// The resilience event should be treated as a critical error.
+    /// </summary>
+    Critical,
+}
diff --git a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs
index 100fc9073a2..9062ac77c87 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.
     /// </summary>
     /// <typeparam name="TArgs">The arguments associated with this event.</typeparam>
-    /// <param name="eventName">The event name.</param>
+    /// <param name="resilienceEvent">The reported resilience event.</param>
     /// <param name="context">The resilience context associated with this event.</param>
     /// <param name="args">The event arguments.</param>
-    /// <exception cref="ArgumentNullException">Thrown when <paramref name="eventName"/> is <see langword="null"/>.</exception>
-    public void Report<TArgs>(string eventName, ResilienceContext context, TArgs args)
+    /// <exception cref="ArgumentNullException">Thrown when <paramref name="context"/> is <see langword="null"/>.</exception>
+    public void Report<TArgs>(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) || resilienceEvent.Severity == ResilienceEventSeverity.None)
         {
             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<TArgs>(string eventName, ResilienceContext context, TArgs arg
     /// </summary>
     /// <typeparam name="TArgs">The arguments associated with this event.</typeparam>
     /// <typeparam name="TResult">The type of the result.</typeparam>
-    /// <param name="eventName">The event name.</param>
+    /// <param name="resilienceEvent">The reported resilience event.</param>
     /// <param name="args">The event arguments.</param>
-    /// <exception cref="ArgumentNullException">Thrown when <paramref name="eventName"/> is <see langword="null"/>.</exception>
-    public void Report<TArgs, TResult>(string eventName, OutcomeArguments<TResult, TArgs> args)
+    public void Report<TArgs, TResult>(ResilienceEvent resilienceEvent, OutcomeArguments<TResult, TArgs> 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) || resilienceEvent.Severity == ResilienceEventSeverity.None)
         {
             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<TArgs>(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<TelemetryEventArguments> 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<object>? 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..d3c66c51aae 100644
--- a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs
+++ b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs
@@ -18,9 +18,9 @@ private TelemetryEventArguments()
     public ResilienceTelemetrySource Source { get; private set; } = null!;
 
     /// <summary>
-    /// Gets the event name.
+    /// Gets the event.
     /// </summary>
-    public string EventName { get; private set; } = null!;
+    public ResilienceEvent Event { get; private set; }
 
     /// <summary>
     /// 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<TResult>(
         }
 
         var attemptArgs = ExecutionAttemptArguments.Get(attempt, executionTime, handled);
-        telemetry.Report<ExecutionAttemptArguments, TResult>(ExecutionAttempt, new(context, outcome, attemptArgs));
+        telemetry.Report<ExecutionAttemptArguments, TResult>(
+            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<Outcome<TResult>> ExecuteCoreAsync<T
         if (isCancellationRequested && outcome.Exception is OperationCanceledException e && !previousToken.IsCancellationRequested)
         {
             var args = new OnTimeoutArguments(context, e, timeout);
-            _telemetry.Report(TimeoutConstants.OnTimeoutEvent, context, args);
+            _telemetry.Report(new(ResilienceEventSeverity.Error, TimeoutConstants.OnTimeoutEvent), context, args);
 
             if (OnTimeout is not null)
             {
diff --git a/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs b/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs
index 14d4c57ac8a..74ae6f8eba3 100644
--- a/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs
+++ b/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs
@@ -10,6 +10,8 @@ internal sealed class ReloadableResilienceStrategy : ResilienceStrategy
 
     public const string ReloadFailedEvent = "ReloadFailed";
 
+    public const string OnReloadEvent = "OnReload";
+
     private readonly Func<CancellationToken> _onReload;
     private readonly Func<ResilienceStrategy> _resilienceStrategyFactory;
     private readonly ResilienceStrategyTelemetry _telemetry;
@@ -50,16 +52,18 @@ private void RegisterOnReload(CancellationToken previousToken)
 
         _registration = token.Register(() =>
         {
+            var context = ResilienceContext.Get().Initialize<VoidResult>(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<VoidResult>(isSynchronous: true);
                 var args = new OutcomeArguments<VoidResult, ReloadFailedArguments>(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..c68896994a6 100644
--- a/src/Polly.Extensions/Telemetry/Log.cs
+++ b/src/Polly.Extensions/Telemetry/Log.cs
@@ -1,45 +1,26 @@
+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
 {
-    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 +29,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 +64,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/ResilienceContextExtensions.cs b/src/Polly.Extensions/Telemetry/ResilienceContextExtensions.cs
index 87df8f617c0..58b15866c96 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/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..c04a37efee7 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,25 @@ internal static class TelemetryUtil
         >= 0 and < MaxIntegers => Integers[value],
         _ => value,
     };
+
+    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,
+        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/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<Outcome<TResult>> ExecuteCoreAsync<TResult, T
         }
 
         var args = new OnRateLimiterRejectedArguments(context, lease, retryAfter);
-        _telemetry.Report(RateLimiterConstants.OnRateLimiterRejectedEvent, context, args);
+        _telemetry.Report(new(ResilienceEventSeverity.Error, RateLimiterConstants.OnRateLimiterRejectedEvent), context, args);
 
         if (OnLeaseRejected != null)
         {


Test patch:
diff --git a/test/Polly.Core.Tests/CircuitBreaker/Controller/CircuitStateControllerTests.cs b/test/Polly.Core.Tests/CircuitBreaker/Controller/CircuitStateControllerTests.cs
index b91f2fb830b..65460d9b9e6 100644
--- a/test/Polly.Core.Tests/CircuitBreaker/Controller/CircuitStateControllerTests.cs
+++ b/test/Polly.Core.Tests/CircuitBreaker/Controller/CircuitStateControllerTests.cs
@@ -59,7 +59,7 @@ public async Task IsolateAsync_Ok()
         _circuitBehavior.Setup(v => 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<string>(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<bool>(true);
             context.CancellationToken.Should().Be(cts.Token);
             context.Properties.Set(new ResiliencePropertyKey<int>("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..743a6d3a1cd 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<TelemetryEventArguments>();
                 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<TestArguments>();
-                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<int, TestArguments>(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<int, TestArguments>(context, Outcome.FromResult(1), new TestArguments()))).Should().NotThrow();
     }
 
     [Fact]
@@ -81,29 +71,42 @@ public void Report_Outcome_OK()
                 obj.Should().BeOfType<TelemetryEventArguments>();
                 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<TestArguments>();
-                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<int, TestArguments>(context, Outcome.FromResult(99), new TestArguments()));
+        _sut.Report(new(ResilienceEventSeverity.Warning, "dummy-event"), new OutcomeArguments<int, TestArguments>(context, Outcome.FromResult(99), new TestArguments()));
 
         _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<int, TestArguments>(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()
     {
         _diagnosticSource.Setup(o => o.IsEnabled("dummy-event")).Returns(false);
         var context = ResilienceContext.Get();
-        _sut.Report("dummy-event", new OutcomeArguments<int, TestArguments>(context, Outcome.FromResult(10), new TestArguments()));
+        _sut.Report(new(ResilienceEventSeverity.Warning, "dummy-event"), new OutcomeArguments<int, TestArguments>(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<object>("dummy"), "arg");
+        var args = TelemetryEventArguments.Get(_source, new ResilienceEvent(ResilienceEventSeverity.Warning, "ev"), context, Outcome.FromResult<object>("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<object>("dummy"), "arg");
+        var args = TelemetryEventArguments.Get(_source, new ResilienceEvent(ResilienceEventSeverity.Warning, "ev"), context, Outcome.FromResult<object>("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<ReloadableResilienceStrategy.OnReloadArguments>();
+
+        var args = _events[1]
             .Arguments
             .Should()
             .BeOfType<ReloadableResilienceStrategy.ReloadFailedArguments>()
diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs
index 54e30e5f294..1142e3ed357 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,29 @@ 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.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)
+    {
+        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,30 +176,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);
-        }
-
-        // verify reported state
-        var coll = messages[0].State.Should().BeAssignableTo<IReadOnlyList<KeyValuePair<string, object>>>().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<IndexOutOfRangeException>();
+        messages[0].LogLevel.Should().Be(LogLevel.Warning);
     }
 
     [Fact]
@@ -212,8 +211,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 +251,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 +300,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 +336,8 @@ private static void ReportEvent(
         Outcome<object>? 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 +347,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..18360068078 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<ResilienceEvent>)c.ResilienceEvents).Add(new ResilienceEvent("dummy"));
+                    ((List<ResilienceEvent>)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<ResilienceEvent>)c.ResilienceEvents).Add(new ResilienceEvent("dummy"));
+                    ((List<ResilienceEvent>)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"));
                 }
 
                 return true;
@@ -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<ResilienceEvent>)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy"));
+                    ((List<ResilienceEvent>)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy"));
+                }
+                else
+                {
+                    ((List<ResilienceEvent>)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy"));
+                    ((List<ResilienceEvent>)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<Action<EnrichmentContext>> { c => _enricher?.Invoke(c) });
 
     public void Dispose()
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<T>(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)
             {