Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Polly.Extensions/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#nullable enable
Polly.Telemetry.TelemetryOptions.SeverityProvider.get -> System.Func<Polly.Telemetry.ResilienceEvent, Polly.Telemetry.ResilienceEventSeverity>?
Polly.Telemetry.TelemetryOptions.SeverityProvider.set -> void
Polly.Telemetry.TelemetryOptions.TelemetryOptions(Polly.Telemetry.TelemetryOptions! other) -> void
static Polly.PollyServiceCollectionExtensions.AddResiliencePipelines<TKey>(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Polly.DependencyInjection.AddResiliencePipelinesContext<TKey>!>! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Polly.DependencyInjection.AddResiliencePipelinesContext<TKey>
Polly.DependencyInjection.AddResiliencePipelinesContext<TKey>.AddResiliencePipeline(TKey key, System.Action<Polly.ResiliencePipelineBuilder!, Polly.DependencyInjection.AddResiliencePipelineContext<TKey>!>! configure) -> void
Expand Down
33 changes: 21 additions & 12 deletions src/Polly.Extensions/Telemetry/TelemetryListenerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal sealed class TelemetryListenerImpl : TelemetryListener
private readonly ILogger _logger;
private readonly Func<ResilienceContext, object?, object?> _resultFormatter;
private readonly List<TelemetryListener> _listeners;
private readonly Func<ResilienceEvent, ResilienceEventSeverity>? _severityProvider;
private readonly List<MeteringEnricher> _enrichers;

public TelemetryListenerImpl(TelemetryOptions options)
Expand All @@ -19,6 +20,7 @@ public TelemetryListenerImpl(TelemetryOptions options)
_logger = options.LoggerFactory.CreateLogger(TelemetryUtil.PollyDiagnosticSource);
_resultFormatter = options.ResultFormatter;
_listeners = options.TelemetryListeners.ToList();
_severityProvider = options.SeverityProvider;

Counter = Meter.CreateCounter<int>(
"resilience.polly.strategy.events",
Expand Down Expand Up @@ -52,8 +54,15 @@ public override void Write<TResult, TArgs>(in TelemetryEventArguments<TResult, T
}
}

LogEvent(in args);
MeterEvent(in args);
var severity = args.Event.Severity;

if (_severityProvider is { } provider)
{
severity = provider(args.Event);
}

LogEvent(in args, severity);
MeterEvent(in args, severity);
}

private static bool GetArgs<T, TArgs>(T inArgs, out TArgs outArgs)
Expand All @@ -68,13 +77,13 @@ private static bool GetArgs<T, TArgs>(T inArgs, out TArgs outArgs)
return false;
}

private static void AddCommonTags<TResult, TArgs>(in EnrichmentContext<TResult, TArgs> context)
private static void AddCommonTags<TResult, TArgs>(in EnrichmentContext<TResult, TArgs> context, ResilienceEventSeverity severity)
{
var source = context.TelemetryEvent.Source;
var ev = context.TelemetryEvent.Event;

context.Tags.Add(new(ResilienceTelemetryTags.EventName, context.TelemetryEvent.Event.EventName));
context.Tags.Add(new(ResilienceTelemetryTags.EventSeverity, context.TelemetryEvent.Event.Severity.AsString()));
context.Tags.Add(new(ResilienceTelemetryTags.EventSeverity, severity.AsString()));

if (source.PipelineName is not null)
{
Expand Down Expand Up @@ -102,7 +111,7 @@ private static void AddCommonTags<TResult, TArgs>(in EnrichmentContext<TResult,
}
}

private void MeterEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArgs> args)
private void MeterEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArgs> args, ResilienceEventSeverity severity)
{
var arguments = args.Arguments;

Expand All @@ -115,7 +124,7 @@ private void MeterEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArg

var tags = TagsList.Get();
var context = new EnrichmentContext<TResult, TArgs>(in args, tags.Tags);
UpdateEnrichmentContext(in context);
UpdateEnrichmentContext(in context, severity);
ExecutionDuration.Record(executionFinished.Duration.TotalMilliseconds, tags.TagsSpan);
TagsList.Return(tags);
}
Expand All @@ -128,7 +137,7 @@ private void MeterEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArg

var tags = TagsList.Get();
var context = new EnrichmentContext<TResult, TArgs>(in args, tags.Tags);
UpdateEnrichmentContext(in context);
UpdateEnrichmentContext(in context, severity);
context.Tags.Add(new(ResilienceTelemetryTags.AttemptNumber, executionAttempt.AttemptNumber.AsBoxedInt()));
context.Tags.Add(new(ResilienceTelemetryTags.AttemptHandled, executionAttempt.Handled.AsBoxedBool()));
AttemptDuration.Record(executionAttempt.Duration.TotalMilliseconds, tags.TagsSpan);
Expand All @@ -138,15 +147,15 @@ private void MeterEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArg
{
var tags = TagsList.Get();
var context = new EnrichmentContext<TResult, TArgs>(in args, tags.Tags);
UpdateEnrichmentContext(in context);
UpdateEnrichmentContext(in context, severity);
Counter.Add(1, tags.TagsSpan);
TagsList.Return(tags);
}
}

private void UpdateEnrichmentContext<TResult, TArgs>(in EnrichmentContext<TResult, TArgs> context)
private void UpdateEnrichmentContext<TResult, TArgs>(in EnrichmentContext<TResult, TArgs> context, ResilienceEventSeverity severity)
{
AddCommonTags(in context);
AddCommonTags(in context, severity);

if (_enrichers.Count != 0)
{
Expand All @@ -157,10 +166,10 @@ private void UpdateEnrichmentContext<TResult, TArgs>(in EnrichmentContext<TResul
}
}

private void LogEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArgs> args)
private void LogEvent<TResult, TArgs>(in TelemetryEventArguments<TResult, TArgs> args, ResilienceEventSeverity severity)
{
var result = GetResult(args.Context, args.Outcome);
var level = args.Event.Severity.AsLogLevel();
var level = severity.AsLogLevel();

if (GetArgs<TArgs, PipelineExecutingArguments>(args.Arguments, out _))
{
Expand Down
31 changes: 31 additions & 0 deletions src/Polly.Extensions/Telemetry/TelemetryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Polly.Utils;

namespace Polly.Telemetry;

Expand All @@ -10,6 +11,28 @@ namespace Polly.Telemetry;
/// </summary>
public class TelemetryOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="TelemetryOptions"/> class.
/// </summary>
public TelemetryOptions()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="TelemetryOptions"/> class.
/// </summary>
/// <param name="other">The telemetry options instance to copy the data from.</param>
public TelemetryOptions(TelemetryOptions other)
{
Guard.NotNull(other);

TelemetryListeners = other.TelemetryListeners.ToList();
LoggerFactory = other.LoggerFactory;
MeteringEnrichers = other.MeteringEnrichers.ToList();
ResultFormatter = other.ResultFormatter;
SeverityProvider = other.SeverityProvider;
}

/// <summary>
/// Gets the collection of telemetry listeners.
/// </summary>
Expand Down Expand Up @@ -48,4 +71,12 @@ public class TelemetryOptions
HttpResponseMessage response => (int)response.StatusCode,
_ => result,
};

/// <summary>
/// Gets or sets the resilience event severity provider that allows customizing the severity of resilience events.
/// </summary>
/// <value>
/// The default value is <see langword="null"/>.
/// </value>
public Func<ResilienceEvent, ResilienceEventSeverity>? SeverityProvider { get; set; }
}
32 changes: 32 additions & 0 deletions src/Snippets/Docs/Telemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,36 @@ public override void Enrich<TResult, TArgs>(in EnrichmentContext<TResult, TArgs>
}

#endregion

public static void SeverityOverrides()
{
var services = new ServiceCollection();

#region telemetry-severity-override

services.AddResiliencePipeline("my-strategy", (builder, context) =>
{
// Create a new instance of telemetry options by using copy-constructor of the global ones.
// This ensures that common configuration is preserved.
var telemetryOptions = new TelemetryOptions(context.GetOptions<TelemetryOptions>());

telemetryOptions.SeverityProvider = ev =>
{
if (ev.EventName == "OnRetry")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we would expose all the telemetry event name constants via a simple static class then the branching logic would safer. It would also help users of the API to use switch expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would require to expose a lot of public members for a limited audience. Let's wait and see. My vote would be to not expose anything unless absolutely necessary. Consumers of this API can just hardcode the strings, these won't change anyway as they are considered public contract as part of telemetry.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this specific case you could also do nameof(RetryStrategyOptions<>.OnRetry) (I think).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nameof won't work for all cases. For example

  • in case of rate limiter OnRateLimiterRejected event name vs OnRejected options property
  • in case of circuit breaker OnCircuitHalfOpened event name vs OnHalfOpened options property
  • in case of latency Chaos.OnLatency event name vs OnLatencyInjected options property
  • etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would require to expose a lot of public members for a limited audience. Let's wait and see. My vote would be to not expose anything unless absolutely necessary. Consumers of this API can just hardcode the strings, these won't change anyway as they are considered public contract as part of telemetry.

If you are saying that it is already implicitly part of the public contract then I don't see why it is a problem to make it explicit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just more public area to cover :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can consider adding it at a later date if this functionality becomes popular and there's user demand for it.

{
// Decrease the severity of particular event.
return ResilienceEventSeverity.Debug;
}

return ev.Severity;
};

builder.AddTimeout(TimeSpan.FromSeconds(1));

// Override the telemetry configuration for this pipeline.
builder.ConfigureTelemetry(telemetryOptions);
});

#endregion
}
}