Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
31 changes: 31 additions & 0 deletions docs/advanced/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,34 @@ Each resilience strategy can generate telemetry data through the [`ResilienceStr
To leverage this telemetry data, users should assign a `TelemetryListener` instance to `ResiliencePipelineBuilder.TelemetryListener` and then consume the `TelemetryEventArguments`.

For common scenarios, it is expected that users would make use of `Polly.Extensions`. This extension enables telemetry configuration through the `ResiliencePipelineBuilder.ConfigureTelemetry(...)` method, which processes `TelemetryEventArguments` to generate logs and metrics.

## Customizing the severity of telemetry events

To customize the severity of telemetry events, set the [`SeverityProvider`](xref:Polly.Telemetry.TelemetryOptions.SeverityProvider) delegate that allows changing the default severity of resilience events:

<!-- snippet: telemetry-severity-override -->
```cs
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 = @event =>
{
if (@event.Event.EventName == "OnRetry")
{
// Decrease the severity of particular event.
return ResilienceEventSeverity.Debug;
}

return @event.Event.Severity;
};

builder.AddTimeout(TimeSpan.FromSeconds(1));
Copy link
Contributor

Choose a reason for hiding this comment

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

This example seems a bit pointless because

  • you have a single Timeout strategy
  • and you override the OnRetry telemetry event's severity

Can you either override the OnTimeout or add a Retry strategy to the pipeline?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


// Override the telemetry configuration for this pipeline.
builder.ConfigureTelemetry(telemetryOptions);
});
```
<!-- endSnippet -->
8 changes: 7 additions & 1 deletion src/Polly.Extensions/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#nullable enable
Polly.Telemetry.TelemetryOptions.SeverityProvider.get -> System.Func<Polly.Telemetry.ResilienceEvent, Polly.Telemetry.ResilienceEventSeverity>?
Polly.Telemetry.SeverityProviderArguments
Polly.Telemetry.SeverityProviderArguments.Context.get -> Polly.ResilienceContext!
Polly.Telemetry.SeverityProviderArguments.Event.get -> Polly.Telemetry.ResilienceEvent
Polly.Telemetry.SeverityProviderArguments.SeverityProviderArguments() -> void
Polly.Telemetry.SeverityProviderArguments.SeverityProviderArguments(Polly.Telemetry.ResilienceTelemetrySource! source, Polly.Telemetry.ResilienceEvent resilienceEvent, Polly.ResilienceContext! context) -> void
Polly.Telemetry.SeverityProviderArguments.Source.get -> Polly.Telemetry.ResilienceTelemetrySource!
Polly.Telemetry.TelemetryOptions.SeverityProvider.get -> System.Func<Polly.Telemetry.SeverityProviderArguments, 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!
Expand Down
37 changes: 37 additions & 0 deletions src/Polly.Extensions/Telemetry/SeverityProviderArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Polly.Telemetry;

#pragma warning disable CA1815 // Override equals and operator equals on value types

/// <summary>
/// Arguments used by <see cref="TelemetryOptions.SeverityProvider"/>.
/// </summary>
public readonly struct SeverityProviderArguments
{
/// <summary>
/// Initializes a new instance of the <see cref="SeverityProviderArguments"/> struct.
/// </summary>
/// <param name="source">The source that produced the resilience event.</param>
/// <param name="resilienceEvent">The resilience event.</param>
/// <param name="context">The resilience context.</param>
public SeverityProviderArguments(ResilienceTelemetrySource source, ResilienceEvent resilienceEvent, ResilienceContext context)
{
Source = source;
Event = resilienceEvent;
Context = context;
}

/// <summary>
/// Gets the source that produced the resilience event.
/// </summary>
public ResilienceTelemetrySource Source { get; }

/// <summary>
/// Gets the resilience event.
/// </summary>
public ResilienceEvent Event { get; }

/// <summary>
/// Gets the resilience context.
/// </summary>
public ResilienceContext Context { get; }
}
4 changes: 2 additions & 2 deletions src/Polly.Extensions/Telemetry/TelemetryListenerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +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 Func<SeverityProviderArguments, ResilienceEventSeverity>? _severityProvider;
private readonly List<MeteringEnricher> _enrichers;

public TelemetryListenerImpl(TelemetryOptions options)
Expand Down Expand Up @@ -58,7 +58,7 @@ public override void Write<TResult, TArgs>(in TelemetryEventArguments<TResult, T

if (_severityProvider is { } provider)
{
severity = provider(args.Event);
severity = provider(new SeverityProviderArguments(args.Source, args.Event, args.Context));
}

LogEvent(in args, severity);
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Extensions/Telemetry/TelemetryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@ public TelemetryOptions(TelemetryOptions other)
/// <value>
/// The default value is <see langword="null"/>.
/// </value>
public Func<ResilienceEvent, ResilienceEventSeverity>? SeverityProvider { get; set; }
public Func<SeverityProviderArguments, ResilienceEventSeverity>? SeverityProvider { get; set; }
}
6 changes: 3 additions & 3 deletions src/Snippets/Docs/Telemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,15 @@ public static void SeverityOverrides()
// This ensures that common configuration is preserved.
var telemetryOptions = new TelemetryOptions(context.GetOptions<TelemetryOptions>());

telemetryOptions.SeverityProvider = ev =>
telemetryOptions.SeverityProvider = @event =>
{
if (ev.EventName == "OnRetry")
if (@event.Event.EventName == "OnRetry")
{
// Decrease the severity of particular event.
return ResilienceEventSeverity.Debug;
}

return ev.Severity;
return @event.Event.Severity;
};

builder.AddTimeout(TimeSpan.FromSeconds(1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,33 @@ public void PipelineExecuted_ShouldBeSkipped()
events.Should().HaveCount(0);
}

[Fact]
public void SeverityProvider_EnsureRespected()
{
var called = false;
var telemetry = Create(configure: options =>
{
options.SeverityProvider = args =>
{
args.Event.Severity.Should().Be(ResilienceEventSeverity.Warning);
args.Source.Should().NotBeNull();
args.Context.Should().NotBeNull();
called = true;
return ResilienceEventSeverity.Critical;
};
});

ReportEvent(telemetry, null, severity: ResilienceEventSeverity.Warning);

var records = _logger.GetRecords();

records.Single().LogLevel.Should().Be(LogLevel.Critical);
called.Should().BeTrue();
}

private List<Dictionary<string, object?>> GetEvents(string eventName) => _events.Where(e => e.Name == eventName).Select(v => v.Tags).ToList();

private TelemetryListenerImpl Create(IEnumerable<MeteringEnricher>? enrichers = null)
private TelemetryListenerImpl Create(IEnumerable<MeteringEnricher>? enrichers = null, Action<TelemetryOptions>? configure = null)
{
var options = new TelemetryOptions
{
Expand All @@ -475,6 +499,8 @@ private TelemetryListenerImpl Create(IEnumerable<MeteringEnricher>? enrichers =
}
}

configure?.Invoke(options);

return new(options);
}

Expand Down
27 changes: 27 additions & 0 deletions test/Polly.Extensions.Tests/Telemetry/TelemetryOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Net;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using Polly.Telemetry;

namespace Polly.Extensions.Tests.Telemetry;
Expand All @@ -21,4 +23,29 @@ public void Ctor_EnsureDefaults()
using var response = new HttpResponseMessage(HttpStatusCode.OK);
options.ResultFormatter(resilienceContext, response).Should().Be(200);
}

[Fact]
public void CopyCtor_Ok()
{
var options = new TelemetryOptions
{
LoggerFactory = Substitute.For<ILoggerFactory>(),
SeverityProvider = _ => ResilienceEventSeverity.Error,
ResultFormatter = (_, _) => "x",
};

options.MeteringEnrichers.Add(Substitute.For<MeteringEnricher>());
options.TelemetryListeners.Add(Substitute.For<TelemetryListener>());

var other = new TelemetryOptions(options);

other.ResultFormatter.Should().Be(options.ResultFormatter);
other.LoggerFactory.Should().Be(options.LoggerFactory);
other.SeverityProvider.Should().Be(options.SeverityProvider);
other.MeteringEnrichers.Should().BeEquivalentTo(options.MeteringEnrichers);
other.TelemetryListeners.Should().BeEquivalentTo(options.TelemetryListeners);

other.TelemetryListeners.Should().NotBeSameAs(options.TelemetryListeners);
other.MeteringEnrichers.Should().NotBeSameAs(options.MeteringEnrichers);
}
}