[Otlp] Add disk retry enablement (#5527)

Co-authored-by: Mikel Blanchard <mblanchard@macrosssoftware.com>
This commit is contained in:
Vishwesh Bankwar 2024-05-07 14:02:43 -07:00 committed by GitHub
parent c47dd7c1b1
commit 4215968f75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 30 deletions

View File

@ -13,6 +13,17 @@
`parent_is_remote` information. `parent_is_remote` information.
([#5563](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5563)) ([#5563](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5563))
* Introduced experimental support for automatically retrying export to the otlp
endpoint by storing the telemetry offline during transient network errors.
Users can enable this feature by setting the
`OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY` environment variable to `disk`. The
default path where the telemetry is stored is obtained by calling
[Path.GetTempPath()](https://learn.microsoft.com/dotnet/api/system.io.path.gettemppath)
or can be customized by setting
`OTEL_DOTNET_EXPERIMENTAL_OTLP_DISK_RETRY_DIRECTORY_PATH` environment
variable.
([#5527](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5527))
## 1.8.1 ## 1.8.1
Released 2024-Apr-17 Released 2024-Apr-17

View File

@ -17,6 +17,8 @@ internal sealed class ExperimentalOptions
public const string OtlpRetryEnvVar = "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY"; public const string OtlpRetryEnvVar = "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY";
public const string OtlpDiskRetryDirectoryPathEnvVar = "OTEL_DOTNET_EXPERIMENTAL_OTLP_DISK_RETRY_DIRECTORY_PATH";
public ExperimentalOptions() public ExperimentalOptions()
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) : this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
{ {
@ -29,9 +31,29 @@ internal sealed class ExperimentalOptions
this.EmitLogEventAttributes = emitLogEventAttributes; this.EmitLogEventAttributes = emitLogEventAttributes;
} }
if (configuration.TryGetStringValue(OtlpRetryEnvVar, out var retryPolicy) && retryPolicy != null && retryPolicy.Equals("in_memory", StringComparison.OrdinalIgnoreCase)) if (configuration.TryGetStringValue(OtlpRetryEnvVar, out var retryPolicy) && retryPolicy != null)
{ {
this.EnableInMemoryRetry = true; if (retryPolicy.Equals("in_memory", StringComparison.OrdinalIgnoreCase))
{
this.EnableInMemoryRetry = true;
}
else if (retryPolicy.Equals("disk", StringComparison.OrdinalIgnoreCase))
{
this.EnableDiskRetry = true;
if (configuration.TryGetStringValue(OtlpDiskRetryDirectoryPathEnvVar, out var path) && path != null)
{
this.DiskRetryDirectoryPath = path;
}
else
{
// Fallback to temp location.
this.DiskRetryDirectoryPath = Path.GetTempPath();
}
}
else
{
throw new NotSupportedException($"Retry Policy '{retryPolicy}' is not supported.");
}
} }
} }
@ -48,4 +70,14 @@ internal sealed class ExperimentalOptions
/// href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#retry"/>. /// href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#retry"/>.
/// </remarks> /// </remarks>
public bool EnableInMemoryRetry { get; } public bool EnableInMemoryRetry { get; }
/// <summary>
/// Gets a value indicating whether or not retry via disk should be enabled for transient errors.
/// </summary>
public bool EnableDiskRetry { get; }
/// <summary>
/// Gets the path on disk where the telemetry will be stored for retries at a later point.
/// </summary>
public string? DiskRetryDirectoryPath { get; }
} }

View File

@ -11,6 +11,8 @@ using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
#if NETSTANDARD2_1 || NET6_0_OR_GREATER #if NETSTANDARD2_1 || NET6_0_OR_GREATER
using Grpc.Net.Client; using Grpc.Net.Client;
#endif #endif
using System.Diagnostics;
using Google.Protobuf;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission;
using LogOtlpCollector = OpenTelemetry.Proto.Collector.Logs.V1; using LogOtlpCollector = OpenTelemetry.Proto.Collector.Logs.V1;
using MetricsOtlpCollector = OpenTelemetry.Proto.Collector.Metrics.V1; using MetricsOtlpCollector = OpenTelemetry.Proto.Collector.Metrics.V1;
@ -100,9 +102,29 @@ internal static class OtlpExporterOptionsExtensions
? httpTraceExportClient.HttpClient.Timeout.TotalMilliseconds ? httpTraceExportClient.HttpClient.Timeout.TotalMilliseconds
: options.TimeoutMilliseconds; : options.TimeoutMilliseconds;
return experimentalOptions.EnableInMemoryRetry if (experimentalOptions.EnableInMemoryRetry)
? new OtlpExporterRetryTransmissionHandler<TraceOtlpCollector.ExportTraceServiceRequest>(exportClient, timeoutMilliseconds) {
: new OtlpExporterTransmissionHandler<TraceOtlpCollector.ExportTraceServiceRequest>(exportClient, timeoutMilliseconds); return new OtlpExporterRetryTransmissionHandler<TraceOtlpCollector.ExportTraceServiceRequest>(exportClient, timeoutMilliseconds);
}
else if (experimentalOptions.EnableDiskRetry)
{
Debug.Assert(!string.IsNullOrEmpty(experimentalOptions.DiskRetryDirectoryPath), $"{nameof(experimentalOptions.DiskRetryDirectoryPath)} is null or empty");
return new OtlpExporterPersistentStorageTransmissionHandler<TraceOtlpCollector.ExportTraceServiceRequest>(
exportClient,
timeoutMilliseconds,
(byte[] data) =>
{
var request = new TraceOtlpCollector.ExportTraceServiceRequest();
request.MergeFrom(data);
return request;
},
Path.Combine(experimentalOptions.DiskRetryDirectoryPath, "traces"));
}
else
{
return new OtlpExporterTransmissionHandler<TraceOtlpCollector.ExportTraceServiceRequest>(exportClient, timeoutMilliseconds);
}
} }
public static OtlpExporterTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest> GetMetricsExportTransmissionHandler(this OtlpExporterOptions options, ExperimentalOptions experimentalOptions) public static OtlpExporterTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest> GetMetricsExportTransmissionHandler(this OtlpExporterOptions options, ExperimentalOptions experimentalOptions)
@ -116,9 +138,29 @@ internal static class OtlpExporterOptionsExtensions
? httpMetricsExportClient.HttpClient.Timeout.TotalMilliseconds ? httpMetricsExportClient.HttpClient.Timeout.TotalMilliseconds
: options.TimeoutMilliseconds; : options.TimeoutMilliseconds;
return experimentalOptions.EnableInMemoryRetry if (experimentalOptions.EnableInMemoryRetry)
? new OtlpExporterRetryTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest>(exportClient, timeoutMilliseconds) {
: new OtlpExporterTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest>(exportClient, timeoutMilliseconds); return new OtlpExporterRetryTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest>(exportClient, timeoutMilliseconds);
}
else if (experimentalOptions.EnableDiskRetry)
{
Debug.Assert(!string.IsNullOrEmpty(experimentalOptions.DiskRetryDirectoryPath), $"{nameof(experimentalOptions.DiskRetryDirectoryPath)} is null or empty");
return new OtlpExporterPersistentStorageTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest>(
exportClient,
timeoutMilliseconds,
(byte[] data) =>
{
var request = new MetricsOtlpCollector.ExportMetricsServiceRequest();
request.MergeFrom(data);
return request;
},
Path.Combine(experimentalOptions.DiskRetryDirectoryPath, "metrics"));
}
else
{
return new OtlpExporterTransmissionHandler<MetricsOtlpCollector.ExportMetricsServiceRequest>(exportClient, timeoutMilliseconds);
}
} }
public static OtlpExporterTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest> GetLogsExportTransmissionHandler(this OtlpExporterOptions options, ExperimentalOptions experimentalOptions) public static OtlpExporterTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest> GetLogsExportTransmissionHandler(this OtlpExporterOptions options, ExperimentalOptions experimentalOptions)
@ -128,9 +170,29 @@ internal static class OtlpExporterOptionsExtensions
? httpLogExportClient.HttpClient.Timeout.TotalMilliseconds ? httpLogExportClient.HttpClient.Timeout.TotalMilliseconds
: options.TimeoutMilliseconds; : options.TimeoutMilliseconds;
return experimentalOptions.EnableInMemoryRetry if (experimentalOptions.EnableInMemoryRetry)
? new OtlpExporterRetryTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest>(exportClient, timeoutMilliseconds) {
: new OtlpExporterTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest>(exportClient, timeoutMilliseconds); return new OtlpExporterRetryTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest>(exportClient, timeoutMilliseconds);
}
else if (experimentalOptions.EnableDiskRetry)
{
Debug.Assert(!string.IsNullOrEmpty(experimentalOptions.DiskRetryDirectoryPath), $"{nameof(experimentalOptions.DiskRetryDirectoryPath)} is null or empty");
return new OtlpExporterPersistentStorageTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest>(
exportClient,
timeoutMilliseconds,
(byte[] data) =>
{
var request = new LogOtlpCollector.ExportLogsServiceRequest();
request.MergeFrom(data);
return request;
},
Path.Combine(experimentalOptions.DiskRetryDirectoryPath, "logs"));
}
else
{
return new OtlpExporterTransmissionHandler<LogOtlpCollector.ExportLogsServiceRequest>(exportClient, timeoutMilliseconds);
}
} }
public static IExportClient<TraceOtlpCollector.ExportTraceServiceRequest> GetTraceExportClient(this OtlpExporterOptions options) => public static IExportClient<TraceOtlpCollector.ExportTraceServiceRequest> GetTraceExportClient(this OtlpExporterOptions options) =>

View File

@ -627,10 +627,17 @@ want to solicit feedback from the community.
* `OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY` * `OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY`
When set to `in_memory`, it enables in-memory retry for transient errors * When set to `in_memory`, it enables in-memory retry for transient errors
encountered while sending telemetry. encountered while sending telemetry.
Added in `1.8.0`. Added in `1.8.0`.
* When set to `disk` along with setting
`OTEL_DOTNET_EXPERIMENTAL_OTLP_DISK_RETRY_DIRECTORY_PATH` to the path on
disk, it enables retries by storing telemetry on disk during transient
errors.
Added in **TBD** (Unreleased).
* Logs * Logs

View File

@ -4,6 +4,7 @@
#if NETFRAMEWORK #if NETFRAMEWORK
using System.Net.Http; using System.Net.Http;
#endif #endif
using Microsoft.Extensions.Configuration;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission;
@ -130,16 +131,34 @@ public class OtlpExporterOptionsExtensionsTests
} }
[Theory] [Theory]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcTraceExportClient), false, 10000)] [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcTraceExportClient), false, 10000, null)]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), false, 10000)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), false, 10000, null)]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), true, 8000)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), true, 8000, null)]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcMetricsExportClient), false, 10000)] [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcMetricsExportClient), false, 10000, null)]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), false, 10000)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), false, 10000, null)]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), true, 8000)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), true, 8000, null)]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcLogExportClient), false, 10000)] [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcLogExportClient), false, 10000, null)]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), false, 10000)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), false, 10000, null)]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), true, 8000)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), true, 8000, null)]
public void GetTransmissionHandler_InitializesCorrectExportClientAndTimeoutValue(OtlpExportProtocol protocol, Type exportClientType, bool customHttpClient, int expectedTimeoutMilliseconds) [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcTraceExportClient), false, 10000, "in_memory")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), false, 10000, "in_memory")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), true, 8000, "in_memory")]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcMetricsExportClient), false, 10000, "in_memory")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), false, 10000, "in_memory")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), true, 8000, "in_memory")]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcLogExportClient), false, 10000, "in_memory")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), false, 10000, "in_memory")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), true, 8000, "in_memory")]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcTraceExportClient), false, 10000, "disk")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), false, 10000, "disk")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient), true, 8000, "disk")]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcMetricsExportClient), false, 10000, "disk")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), false, 10000, "disk")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpMetricsExportClient), true, 8000, "disk")]
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcLogExportClient), false, 10000, "disk")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), false, 10000, "disk")]
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), true, 8000, "disk")]
public void GetTransmissionHandler_InitializesCorrectHandlerExportClientAndTimeoutValue(OtlpExportProtocol protocol, Type exportClientType, bool customHttpClient, int expectedTimeoutMilliseconds, string retryStrategy)
{ {
var exporterOptions = new OtlpExporterOptions() { Protocol = protocol }; var exporterOptions = new OtlpExporterOptions() { Protocol = protocol };
if (customHttpClient) if (customHttpClient)
@ -150,28 +169,45 @@ public class OtlpExporterOptionsExtensionsTests
}; };
} }
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string> { [ExperimentalOptions.OtlpRetryEnvVar] = retryStrategy })
.Build();
if (exportClientType == typeof(OtlpGrpcTraceExportClient) || exportClientType == typeof(OtlpHttpTraceExportClient)) if (exportClientType == typeof(OtlpGrpcTraceExportClient) || exportClientType == typeof(OtlpHttpTraceExportClient))
{ {
var transmissionHandler = exporterOptions.GetTraceExportTransmissionHandler(new ExperimentalOptions()); var transmissionHandler = exporterOptions.GetTraceExportTransmissionHandler(new ExperimentalOptions(configuration));
AssertTransmissionHandlerProperties(transmissionHandler, exportClientType, expectedTimeoutMilliseconds); AssertTransmissionHandler(transmissionHandler, exportClientType, expectedTimeoutMilliseconds, retryStrategy);
} }
else if (exportClientType == typeof(OtlpGrpcMetricsExportClient) || exportClientType == typeof(OtlpHttpMetricsExportClient)) else if (exportClientType == typeof(OtlpGrpcMetricsExportClient) || exportClientType == typeof(OtlpHttpMetricsExportClient))
{ {
var transmissionHandler = exporterOptions.GetMetricsExportTransmissionHandler(new ExperimentalOptions()); var transmissionHandler = exporterOptions.GetMetricsExportTransmissionHandler(new ExperimentalOptions(configuration));
AssertTransmissionHandlerProperties(transmissionHandler, exportClientType, expectedTimeoutMilliseconds); AssertTransmissionHandler(transmissionHandler, exportClientType, expectedTimeoutMilliseconds, retryStrategy);
} }
else else
{ {
var transmissionHandler = exporterOptions.GetLogsExportTransmissionHandler(new ExperimentalOptions()); var transmissionHandler = exporterOptions.GetLogsExportTransmissionHandler(new ExperimentalOptions(configuration));
AssertTransmissionHandlerProperties(transmissionHandler, exportClientType, expectedTimeoutMilliseconds); AssertTransmissionHandler(transmissionHandler, exportClientType, expectedTimeoutMilliseconds, retryStrategy);
} }
} }
private static void AssertTransmissionHandlerProperties<T>(OtlpExporterTransmissionHandler<T> transmissionHandler, Type exportClientType, int expectedTimeoutMilliseconds) private static void AssertTransmissionHandler<T>(OtlpExporterTransmissionHandler<T> transmissionHandler, Type exportClientType, int expectedTimeoutMilliseconds, string retryStrategy)
{ {
if (retryStrategy == "in_memory")
{
Assert.True(transmissionHandler is OtlpExporterRetryTransmissionHandler<T>);
}
else if (retryStrategy == "disk")
{
Assert.True(transmissionHandler is OtlpExporterPersistentStorageTransmissionHandler<T>);
}
else
{
Assert.True(transmissionHandler is OtlpExporterTransmissionHandler<T>);
}
Assert.Equal(exportClientType, transmissionHandler.ExportClient.GetType()); Assert.Equal(exportClientType, transmissionHandler.ExportClient.GetType());
Assert.Equal(expectedTimeoutMilliseconds, transmissionHandler.TimeoutMilliseconds); Assert.Equal(expectedTimeoutMilliseconds, transmissionHandler.TimeoutMilliseconds);