diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt index 7a5d8b22a6..cd9c686fd1 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt @@ -1,14 +1,15 @@ +#nullable enable OpenTelemetry.Exporter.OtlpExporterOptions -OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions -OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.set -> void -OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.get -> System.Uri -OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.set -> void +~OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions +~OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.set -> void +~OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.get -> System.Uri +~OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.set -> void OpenTelemetry.Exporter.OtlpExporterOptions.ExportProcessorType.get -> OpenTelemetry.ExportProcessorType OpenTelemetry.Exporter.OtlpExporterOptions.ExportProcessorType.set -> void -OpenTelemetry.Exporter.OtlpExporterOptions.Headers.get -> string -OpenTelemetry.Exporter.OtlpExporterOptions.Headers.set -> void -OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.get -> System.Func -OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.set -> void +~OpenTelemetry.Exporter.OtlpExporterOptions.Headers.get -> string +~OpenTelemetry.Exporter.OtlpExporterOptions.Headers.set -> void +~OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.get -> System.Func +~OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.set -> void OpenTelemetry.Exporter.OtlpExporterOptions.OtlpExporterOptions() -> void OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.get -> OpenTelemetry.Exporter.OtlpExportProtocol OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.set -> void @@ -18,24 +19,24 @@ OpenTelemetry.Exporter.OtlpExportProtocol OpenTelemetry.Exporter.OtlpExportProtocol.Grpc = 0 -> OpenTelemetry.Exporter.OtlpExportProtocol OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf = 1 -> OpenTelemetry.Exporter.OtlpExportProtocol OpenTelemetry.Exporter.OtlpMetricExporter -OpenTelemetry.Exporter.OtlpMetricExporter.OtlpMetricExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void +~OpenTelemetry.Exporter.OtlpMetricExporter.OtlpMetricExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void OpenTelemetry.Exporter.OtlpTraceExporter -OpenTelemetry.Exporter.OtlpTraceExporter.OtlpTraceExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void +~OpenTelemetry.Exporter.OtlpTraceExporter.OtlpTraceExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void OpenTelemetry.Logs.OtlpLogExporterHelperExtensions OpenTelemetry.Metrics.OtlpMetricExporterExtensions OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions -override OpenTelemetry.Exporter.OtlpMetricExporter.Export(in OpenTelemetry.Batch metrics) -> OpenTelemetry.ExportResult +~override OpenTelemetry.Exporter.OtlpMetricExporter.Export(in OpenTelemetry.Batch metrics) -> OpenTelemetry.ExportResult override OpenTelemetry.Exporter.OtlpMetricExporter.OnShutdown(int timeoutMilliseconds) -> bool -override OpenTelemetry.Exporter.OtlpTraceExporter.Export(in OpenTelemetry.Batch activityBatch) -> OpenTelemetry.ExportResult +~override OpenTelemetry.Exporter.OtlpTraceExporter.Export(in OpenTelemetry.Batch activityBatch) -> OpenTelemetry.ExportResult override OpenTelemetry.Exporter.OtlpTraceExporter.OnShutdown(int timeoutMilliseconds) -> bool -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions -static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configureExporter) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporter) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action! configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action? configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action! configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.OtlpMetricExporterExtensions.AddOtlpExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, System.Action? configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Unshipped.txt index 508ace4d8e..f90e2e4dfb 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ +#nullable enable OpenTelemetry.Exporter.OtlpLogExporter -OpenTelemetry.Exporter.OtlpLogExporter.OtlpLogExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void -override OpenTelemetry.Exporter.OtlpLogExporter.Export(in OpenTelemetry.Batch logRecordBatch) -> OpenTelemetry.ExportResult \ No newline at end of file +OpenTelemetry.Exporter.OtlpLogExporter.OtlpLogExporter(OpenTelemetry.Exporter.OtlpExporterOptions! options) -> void +override OpenTelemetry.Exporter.OtlpLogExporter.Export(in OpenTelemetry.Batch logRecordBatch) -> OpenTelemetry.ExportResult +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, string? name, System.Action? configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, string? name, System.Action? configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index c6c60e92cc..71559a538c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,7 +2,18 @@ ## Unreleased -* Made `OpenTelemetry.Exporter.OtlpLogExporter` public. ([#4979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4979)) +* Made `OpenTelemetry.Exporter.OtlpLogExporter` public. + ([#4979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4979)) + +* Updated the `OpenTelemetryLoggerOptions.AddOtlpExporter` extension to retrieve + `OtlpExporterOptions` and `LogRecordExportProcessorOptions` using the + `IServiceProvider` / Options API so that they can be controlled via + `IConfiguration` (similar to metrics and traces). + ([#4916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4916)) + +* Added an `OpenTelemetryLoggerOptions.AddOtlpExporter` extension overload which + accepts a `name` parameter to support named options. + ([#4916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4916)) ## 1.7.0-alpha.1 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index f8669fc1dd..d08fc4037d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -199,12 +199,17 @@ public Uri Endpoint internal static void RegisterOtlpExporterOptionsFactory(IServiceCollection services) { - services.RegisterOptionsFactory( - (sp, configuration, name) => new OtlpExporterOptions( - configuration, - sp.GetRequiredService>().Get(name))); + services.RegisterOptionsFactory(CreateOtlpExporterOptions); } + internal static OtlpExporterOptions CreateOtlpExporterOptions( + IServiceProvider serviceProvider, + IConfiguration configuration, + string name) + => new( + configuration, + serviceProvider.GetRequiredService>().Get(name)); + private static string GetUserAgentString() { try diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs index ec2554fb0d..af8f4274eb 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Diagnostics; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; @@ -33,14 +35,14 @@ public sealed class OtlpLogExporter : BaseExporter private readonly IExportClient exportClient; private readonly OtlpLogRecordTransformer otlpLogRecordTransformer; - private OtlpResource.Resource processResource; + private OtlpResource.Resource? processResource; /// /// Initializes a new instance of the class. /// /// Configuration options for the exporter. public OtlpLogExporter(OtlpExporterOptions options) - : this(options, new(), null) + : this(options, sdkLimitOptions: new(), experimentalOptions: new(), exportClient: null) { } @@ -49,14 +51,17 @@ public OtlpLogExporter(OtlpExporterOptions options) /// /// Configuration options for the exporter. /// . + /// . /// Client used for sending export request. internal OtlpLogExporter( OtlpExporterOptions exporterOptions, SdkLimitOptions sdkLimitOptions, - IExportClient exportClient = null) + ExperimentalOptions experimentalOptions, + IExportClient? exportClient = null) { Debug.Assert(exporterOptions != null, "exporterOptions was null"); Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null"); + Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); // Each of the Otlp exporters: Traces, Metrics, and Logs set the same value for `OtlpKeyValueTransformer.LogUnsupportedAttributeType` // and `ConfigurationExtensions.LogInvalidEnvironmentVariable` so it should be fine even if these exporters are used together. @@ -76,13 +81,14 @@ public OtlpLogExporter(OtlpExporterOptions options) } else { - this.exportClient = exporterOptions.GetLogExportClient(); + this.exportClient = exporterOptions!.GetLogExportClient(); } - this.otlpLogRecordTransformer = new OtlpLogRecordTransformer(sdkLimitOptions, new()); + this.otlpLogRecordTransformer = new OtlpLogRecordTransformer(sdkLimitOptions!, experimentalOptions!); } - internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource(); + internal OtlpResource.Resource ProcessResource + => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource(); /// public override ExportResult Export(in Batch logRecordBatch) @@ -90,7 +96,7 @@ public override ExportResult Export(in Batch logRecordBatch) // Prevents the exporter's gRPC and HTTP operations from being instrumented. using var scope = SuppressInstrumentationScope.Begin(); - OtlpCollector.ExportLogsServiceRequest request = null; + OtlpCollector.ExportLogsServiceRequest? request = null; try { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs index 83bdd6907a..49431a3b1a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs @@ -14,8 +14,16 @@ // limitations under the License. // +#nullable enable + +using System.Diagnostics; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using OpenTelemetry.Exporter; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Internal; namespace OpenTelemetry.Logs; @@ -31,7 +39,7 @@ public static class OtlpLogExporterHelperExtensions /// options to use. /// The instance of to chain the calls. public static OpenTelemetryLoggerOptions AddOtlpExporter(this OpenTelemetryLoggerOptions loggerOptions) - => AddOtlpExporterInternal(loggerOptions, configure: null); + => AddOtlpExporter(loggerOptions, name: null, configure: null); /// /// Adds an OTLP Exporter to the OpenTelemetry . @@ -42,7 +50,35 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter(this OpenTelemetryLogge public static OpenTelemetryLoggerOptions AddOtlpExporter( this OpenTelemetryLoggerOptions loggerOptions, Action configure) - => AddOtlpExporterInternal(loggerOptions, configure); + => AddOtlpExporter(loggerOptions, name: null, configure); + + /// + /// Adds an OTLP Exporter to the OpenTelemetry . + /// + /// options to use. + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . + /// The instance of to chain the calls. + public static OpenTelemetryLoggerOptions AddOtlpExporter( + this OpenTelemetryLoggerOptions loggerOptions, + string? name, + Action? configure) + { + Guard.ThrowIfNull(loggerOptions); + + var finalOptionsName = name ?? Options.DefaultName; + + return loggerOptions.AddProcessor(sp => + { + var exporterOptions = GetOtlpExporterOptions(sp, name, finalOptionsName); + + var processorOptions = sp.GetRequiredService>().Get(finalOptionsName); + + configure?.Invoke(exporterOptions); + + return BuildOtlpLogExporter(sp, exporterOptions, processorOptions); + }); + } /// /// Adds an OTLP Exporter to the OpenTelemetry . @@ -53,28 +89,66 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter(this OpenTelemetryLogge public static OpenTelemetryLoggerOptions AddOtlpExporter( this OpenTelemetryLoggerOptions loggerOptions, Action configureExporterAndProcessor) + => AddOtlpExporter(loggerOptions, name: null, configureExporterAndProcessor); + + /// + /// Adds an OTLP Exporter to the OpenTelemetry . + /// + /// options to use. + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring and . + /// The instance of to chain the calls. + public static OpenTelemetryLoggerOptions AddOtlpExporter( + this OpenTelemetryLoggerOptions loggerOptions, + string? name, + Action? configureExporterAndProcessor) { - var exporterOptions = new OtlpExporterOptions(); - var processorOptions = new LogRecordExportProcessorOptions(); + Guard.ThrowIfNull(loggerOptions); + + var finalOptionsName = name ?? Options.DefaultName; - configureExporterAndProcessor?.Invoke(exporterOptions, processorOptions); + return loggerOptions.AddProcessor(sp => + { + var exporterOptions = GetOtlpExporterOptions(sp, name, finalOptionsName); + + var processorOptions = sp.GetRequiredService>().Get(finalOptionsName); + + configureExporterAndProcessor?.Invoke(exporterOptions, processorOptions); - return loggerOptions.AddProcessor(BuildOtlpLogExporter(exporterOptions, processorOptions)); + return BuildOtlpLogExporter(sp, exporterOptions, processorOptions); + }); } internal static BaseProcessor BuildOtlpLogExporter( + IServiceProvider sp, OtlpExporterOptions exporterOptions, LogRecordExportProcessorOptions processorOptions, - Func, BaseExporter> configureExporterInstance = null) + Func, BaseExporter>? configureExporterInstance = null) { - BaseExporter otlpExporter = new OtlpLogExporter(exporterOptions); + if (sp == null) + { + throw new ArgumentNullException(nameof(sp)); + } + + Debug.Assert(exporterOptions != null, "exporterOptions was null"); + Debug.Assert(processorOptions != null, "processorOptions was null"); + + var config = sp.GetRequiredService(); + + var sdkLimitOptions = new SdkLimitOptions(config); + var experimentalOptions = new ExperimentalOptions(config); + + BaseExporter otlpExporter = new OtlpLogExporter( + exporterOptions!, + sdkLimitOptions, + experimentalOptions); if (configureExporterInstance != null) { otlpExporter = configureExporterInstance(otlpExporter); } - if (processorOptions.ExportProcessorType == ExportProcessorType.Simple) + if (processorOptions!.ExportProcessorType == ExportProcessorType.Simple) { return new SimpleLogRecordExportProcessor(otlpExporter); } @@ -91,14 +165,38 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter(this OpenTelemetryLogge } } - private static OpenTelemetryLoggerOptions AddOtlpExporterInternal( - OpenTelemetryLoggerOptions loggerOptions, - Action configure) + private static OtlpExporterOptions GetOtlpExporterOptions(IServiceProvider sp, string? name, string finalName) { - var exporterOptions = new OtlpExporterOptions(); + // Note: If OtlpExporter has been registered for tracing and/or metrics + // then IOptionsFactory will be set by a call to + // OtlpExporterOptions.RegisterOtlpExporterOptionsFactory. However, if we + // are only using logging, we don't have an opportunity to do that + // registration so we manually create a factory. + + var optionsFactory = sp.GetRequiredService>(); + if (optionsFactory is not DelegatingOptionsFactory) + { + optionsFactory = new DelegatingOptionsFactory( + (c, n) => OtlpExporterOptions.CreateOtlpExporterOptions(sp, c, n), + sp.GetRequiredService(), + sp.GetServices>(), + sp.GetServices>(), + sp.GetServices>()); + + return optionsFactory.Create(finalName); + } - configure?.Invoke(exporterOptions); + if (name == null) + { + // If we are NOT using named options we create a new + // instance always. The reason for this is + // OtlpExporterOptions is shared by all signals. Without a + // name, delegates for all signals will mix together. + return optionsFactory.Create(finalName); + } - return loggerOptions.AddProcessor(BuildOtlpLogExporter(exporterOptions, new())); + // If we have a valid factory AND we are using named options, we can + // safely use the Options API fully. + return sp.GetRequiredService>().Get(finalName); } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs index 6150b58b91..7c7b08260b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -35,28 +37,28 @@ public static class OtlpMetricExporterExtensions /// builder to use. /// The instance of to chain the calls. public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder builder) - => AddOtlpExporter(builder, name: null, configureExporter: null); + => AddOtlpExporter(builder, name: null, configure: null); /// /// Adds to the . /// /// builder to use. - /// Callback action for configuring . + /// Callback action for configuring . /// The instance of to chain the calls. - public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder builder, Action configureExporter) - => AddOtlpExporter(builder, name: null, configureExporter); + public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder builder, Action configure) + => AddOtlpExporter(builder, name: null, configure); /// /// Adds to the . /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain the calls. public static MeterProviderBuilder AddOtlpExporter( this MeterProviderBuilder builder, - string name, - Action configureExporter) + string? name, + Action? configure) { Guard.ThrowIfNull(builder); @@ -64,11 +66,11 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui builder.ConfigureServices(services => { - if (name != null && configureExporter != null) + if (name != null && configure != null) { // If we are using named options we register the // configuration delegate into options pipeline. - services.Configure(finalOptionsName, configureExporter); + services.Configure(finalOptionsName, configure); } OtlpExporterOptions.RegisterOtlpExporterOptionsFactory(services); @@ -99,7 +101,7 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui exporterOptions = sp.GetRequiredService>().Create(finalOptionsName); // Configuration delegate is executed inline on the fresh instance. - configureExporter?.Invoke(exporterOptions); + configure?.Invoke(exporterOptions); } else { @@ -132,25 +134,25 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui /// Adds to the . /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for - /// configuring and Optional name which is used when retrieving options. + /// Optional callback action + /// for configuring and . /// The instance of to chain the calls. public static MeterProviderBuilder AddOtlpExporter( this MeterProviderBuilder builder, - string name, - Action configureExporterAndMetricReader) + string? name, + Action? configureExporterAndMetricReader) { Guard.ThrowIfNull(builder); - name ??= Options.DefaultName; + var finalOptionsName = name ?? Options.DefaultName; builder.ConfigureServices(services => { OtlpExporterOptions.RegisterOtlpExporterOptionsFactory(services); - services.AddOptions(name).Configure( + services.AddOptions(finalOptionsName).Configure( (readerOptions, config) => { var otlpTemporalityPreference = config[OtlpMetricExporterTemporalityPreferenceEnvVarKey]; @@ -164,8 +166,21 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui return builder.AddReader(sp => { - var exporterOptions = sp.GetRequiredService>().Get(name); - var metricReaderOptions = sp.GetRequiredService>().Get(name); + OtlpExporterOptions exporterOptions; + if (name == null) + { + // If we are NOT using named options we create a new + // instance always. The reason for this is + // OtlpExporterOptions is shared by all signals. Without a + // name, delegates for all signals will mix together. + exporterOptions = sp.GetRequiredService>().Create(finalOptionsName); + } + else + { + exporterOptions = sp.GetRequiredService>().Get(finalOptionsName); + } + + var metricReaderOptions = sp.GetRequiredService>().Get(finalOptionsName); configureExporterAndMetricReader?.Invoke(exporterOptions, metricReaderOptions); @@ -177,7 +192,7 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui OtlpExporterOptions exporterOptions, MetricReaderOptions metricReaderOptions, IServiceProvider serviceProvider, - Func, BaseExporter> configureExporterInstance = null) + Func, BaseExporter>? configureExporterInstance = null) { exporterOptions.TryEnableIHttpClientFactoryIntegration(serviceProvider, "OtlpMetricExporter"); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs index b25b5e847f..bb7c25ce57 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -49,13 +51,13 @@ public static TracerProviderBuilder AddOtlpExporter(this TracerProviderBuilder b /// Adds OpenTelemetry Protocol (OTLP) exporter to the TracerProvider. /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddOtlpExporter( this TracerProviderBuilder builder, - string name, - Action configure) + string? name, + Action? configure) { Guard.ThrowIfNull(builder); @@ -111,7 +113,7 @@ public static TracerProviderBuilder AddOtlpExporter(this TracerProviderBuilder b OtlpExporterOptions exporterOptions, SdkLimitOptions sdkLimitOptions, IServiceProvider serviceProvider, - Func, BaseExporter> configureExporterInstance = null) + Func, BaseExporter>? configureExporterInstance = null) { exporterOptions.TryEnableIHttpClientFactoryIntegration(serviceProvider, "OtlpTraceExporter"); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md index 94278f7a3d..ab02f63cc1 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md @@ -93,10 +93,30 @@ are ignored by exporter. Duplicate keys are exported as is. You can configure the `OtlpExporter` through `OtlpExporterOptions` and environment variables. -The `OtlpExporterOptions` type setters take precedence over the environment variables. -This can be achieved by providing an `Action` delegate to the -`AddOtlpExporter()` method or using `AddOptions()`. +> **Note** +> The `OtlpExporterOptions` type setters take precedence over the environment variables. + +This can be achieved by providing an `Action` delegate to +the `AddOtlpExporter()` method or using the `Configure()` +Options API extension. + +> **Note** +> The `OtlpExporterOptions` class is shared by logging, metrics, and tracing. To +> bind configuration specific to each signal use the `name` parameter on the +> `AddOtlpExporter` extensions: +> +> ```csharp +> appBuilder.Services.AddOpenTelemetry() +> .WithTracing(builder => builder.AddOtlpExporter("tracing", configure: null)) +> .WithMetrics(builder => builder.AddOtlpExporter("metrics", configure: null)); +> +> appBuilder.Logging.AddOpenTelemetry(builder => builder.AddOtlpExporter("logging", configure: null)); +> +> appBuilder.Services.Configure("tracing", appBuilder.Configuration.GetSection("OpenTelemetry:tracing:otlp")); +> appBuilder.Services.Configure("metrics", appBuilder.Configuration.GetSection("OpenTelemetry:metrics:otlp")); +> appBuilder.Services.Configure("logging", appBuilder.Configuration.GetSection("OpenTelemetry:logging:otlp")); +> ``` If additional services from the dependency injection are required, they can be configured like this: @@ -109,7 +129,7 @@ services.AddOptions().Configure((opts, svc) => { TODO: Show metrics specific configuration (i.e MetricReaderOptions). -## OtlpExporterOptions +### OtlpExporterOptions * `Protocol`: OTLP transport protocol. Supported values: `OtlpExportProtocol.Grpc` and `OtlpExportProtocol.HttpProtobuf`. @@ -143,6 +163,24 @@ The following options are only applicable to `OtlpTraceExporter`: See the [`TestOtlpExporter.cs`](../../examples/Console/TestOtlpExporter.cs) for an example of how to use the exporter. +### LogRecordExportProcessorOptions + +The `LogRecordExportProcessorOptions` class may be used to configure processor & +batch settings for logging: + +```csharp +// Set via code: +appBuilder.Services.Configure(o => +{ + o.BatchExportProcessorOptions.ScheduledDelayMilliseconds = 2000; + o.BatchExportProcessorOptions.MaxExportBatchSize = 5000; +}); + +// Set via configuration: +appBuilder.Services.Configure( + appBuilder.Configuration.GetSection("OpenTelemetry:Logging")); +``` + ## Environment Variables The following environment variables can be used to override the default diff --git a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt index ee99b01fee..77ed02bc8c 100644 --- a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt @@ -128,6 +128,7 @@ OpenTelemetry.Logs.LogRecordScope.LogRecordScope() -> void OpenTelemetry.Logs.LogRecordScope.Scope.get -> object? OpenTelemetry.Logs.OpenTelemetryLoggerOptions OpenTelemetry.Logs.OpenTelemetryLoggerOptions.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.AddProcessor(System.Func!>! implementationFactory) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.set -> void OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.get -> bool diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 2ebf6ad5f0..acecd41105 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -53,6 +53,11 @@ 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 ]`. ([#5063](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5063)) +* Added `AddProcessor` overload on `OpenTelemetryLoggerOptions` which exposes + the factory pattern `(Func> + implementationFactory)`. + ([#4916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4916)) + ## 1.7.0-alpha.1 Released 2023-Oct-16 diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs index e40f4e1692..a046295827 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs @@ -25,7 +25,7 @@ namespace OpenTelemetry.Logs; /// public class OpenTelemetryLoggerOptions { - internal readonly List> Processors = new(); + internal readonly List>> ProcessorFactories = new(); internal ResourceBuilder? ResourceBuilder; /// @@ -99,7 +99,22 @@ public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor processo { Guard.ThrowIfNull(processor); - this.Processors.Add(processor); + this.ProcessorFactories.Add(_ => processor); + + return this; + } + + /// + /// Adds a processor to the provider which will be retrieved using dependency injection. + /// + /// The factory that creates the service. + /// Returns for chaining. + public OpenTelemetryLoggerOptions AddProcessor( + Func> implementationFactory) + { + Guard.ThrowIfNull(implementationFactory); + + this.ProcessorFactories.Add(implementationFactory); return this; } diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs index 3c99fdb82f..ec7369766c 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs @@ -61,9 +61,9 @@ public OpenTelemetryLoggerProvider(IOptionsMonitor o builder.SetResourceBuilder(optionsInstance.ResourceBuilder); } - foreach (var processor in optionsInstance.Processors) + foreach (var processorFactory in optionsInstance.ProcessorFactories) { - builder.AddProcessor(processor); + builder.AddProcessor(processorFactory); } }) .Build(); diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs index e4cf260aa1..1dfbd6fe40 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs @@ -41,9 +41,9 @@ public static class OpenTelemetryLoggingExtensions /// This is safe to be called multiple times and by library authors. /// Only a single will be created /// for a given . - /// / - /// features (DI, Options, IConfiguration, etc.) are not available when - /// using . + /// features available to metrics and + /// traces (for example the "ConfigureServices" extension) are NOT available + /// when using . /// /// /// The to use. @@ -86,6 +86,8 @@ public static class OpenTelemetryLoggingExtensions // Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions RegisterLoggerProviderOptions(services); + services.AddOpenTelemetrySharedProviderBuilderServices(); + var loggingBuilder = new LoggerProviderBuilderBase(services).ConfigureBuilder( (sp, logging) => { @@ -98,12 +100,12 @@ public static class OpenTelemetryLoggingExtensions options.ResourceBuilder = null; } - foreach (var processor in options.Processors) + foreach (var processorFactory in options.ProcessorFactories) { - logging.AddProcessor(processor); + logging.AddProcessor(processorFactory); } - options.Processors.Clear(); + options.ProcessorFactories.Clear(); }); configureBuilder?.Invoke(loggingBuilder); diff --git a/src/OpenTelemetry/ProviderExtensions.cs b/src/OpenTelemetry/ProviderExtensions.cs index bf5a40a0e3..1bd963a6ed 100644 --- a/src/OpenTelemetry/ProviderExtensions.cs +++ b/src/OpenTelemetry/ProviderExtensions.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; @@ -31,7 +32,7 @@ public static class ProviderExtensions /// /// . /// if found otherwise . - public static Resource GetResource(this BaseProvider baseProvider) + public static Resource GetResource([AllowNull] this BaseProvider baseProvider) { if (baseProvider is TracerProviderSdk tracerProviderSdk) { @@ -58,14 +59,14 @@ public static Resource GetResource(this BaseProvider baseProvider) /// /// . /// if found otherwise . - public static Resource GetDefaultResource(this BaseProvider baseProvider) + public static Resource GetDefaultResource([AllowNull] this BaseProvider baseProvider) { var builder = ResourceBuilder.CreateDefault(); builder.ServiceProvider = GetServiceProvider(baseProvider); return builder.Build(); } - internal static IServiceProvider? GetServiceProvider(this BaseProvider baseProvider) + internal static IServiceProvider? GetServiceProvider(this BaseProvider? baseProvider) { if (baseProvider is TracerProviderSdk tracerProviderSdk) { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs index e72b54bd3d..f24829cd59 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs @@ -233,34 +233,38 @@ public void LogExportResultIsSuccess(OtlpExportProtocol protocol, string endpoin DelegatingExporter delegatingExporter = null; var exportResults = new List(); - var processorOptions = new LogRecordExportProcessorOptions(); - processorOptions.ExportProcessorType = exportProcessorType; - processorOptions.BatchExportProcessorOptions = new() + var processorOptions = new LogRecordExportProcessorOptions { - ScheduledDelayMilliseconds = ExportIntervalMilliseconds, + ExportProcessorType = exportProcessorType, + BatchExportProcessorOptions = new() + { + ScheduledDelayMilliseconds = ExportIntervalMilliseconds, + }, }; using var loggerFactory = LoggerFactory.Create(builder => { builder .AddOpenTelemetry(options => options - .AddProcessor(OtlpLogExporterHelperExtensions.BuildOtlpLogExporter( - exporterOptions, - processorOptions, - configureExporterInstance: otlpExporter => - { - delegatingExporter = new DelegatingExporter + .AddProcessor(sp => + OtlpLogExporterHelperExtensions.BuildOtlpLogExporter( + sp, + exporterOptions, + processorOptions, + configureExporterInstance: otlpExporter => { - OnExportFunc = (batch) => + delegatingExporter = new DelegatingExporter { - var result = otlpExporter.Export(batch); - exportResults.Add(result); - handle.Set(); - return result; - }, - }; - return delegatingExporter; - }))); + OnExportFunc = (batch) => + { + var result = otlpExporter.Export(batch); + exportResults.Add(result); + handle.Set(); + return result; + }, + }; + return delegatingExporter; + }))); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs index 172ec25456..090a950b75 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs @@ -21,6 +21,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; @@ -592,9 +593,10 @@ public void Export_WhenExportClientIsProvidedInCtor_UsesProvidedExportClient() var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); var sut = new OtlpLogExporter( - new OtlpExporterOptions(), - new SdkLimitOptions(), - fakeExportClient.Object); + new OtlpExporterOptions(), + new SdkLimitOptions(), + new ExperimentalOptions(), + fakeExportClient.Object); // Act. var result = sut.Export(emptyBatch); @@ -614,9 +616,10 @@ public void Export_WhenExportClientThrowsException_ReturnsExportResultFailure() .Setup(_ => _.SendExportRequest(It.IsAny(), default)) .Throws(new Exception("Test Exception")); var sut = new OtlpLogExporter( - new OtlpExporterOptions(), - new SdkLimitOptions(), - fakeExportClient.Object); + new OtlpExporterOptions(), + new SdkLimitOptions(), + new ExperimentalOptions(), + fakeExportClient.Object); // Act. var result = sut.Export(emptyBatch); @@ -636,9 +639,10 @@ public void Export_WhenExportIsSuccessful_ReturnsExportResultSuccess() .Setup(_ => _.SendExportRequest(It.IsAny(), default)) .Returns(true); var sut = new OtlpLogExporter( - new OtlpExporterOptions(), - new SdkLimitOptions(), - fakeExportClient.Object); + new OtlpExporterOptions(), + new SdkLimitOptions(), + new ExperimentalOptions(), + fakeExportClient.Object); // Act. var result = sut.Export(emptyBatch); @@ -1175,11 +1179,13 @@ public void AddOtlpLogExporterDefaultOptionsTest() options.AddOtlpExporter(); - var processors = (List>)typeof(OpenTelemetryLoggerOptions).GetField("Processors", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(options); + var provider = new OpenTelemetryLoggerProvider(new TestOptionsMonitor(options)); - Assert.Single(processors); + var processor = GetProcessor(provider); - var batchProcesor = processors[0] as BatchLogRecordExportProcessor; + Assert.NotNull(processor); + + var batchProcesor = processor as BatchLogRecordExportProcessor; Assert.NotNull(batchProcesor); @@ -1201,13 +1207,15 @@ public void AddOtlpLogExporterLogRecordProcessorOptionsTest(ExportProcessorType l.BatchExportProcessorOptions = new BatchExportLogRecordProcessorOptions() { ScheduledDelayMilliseconds = 1000 }; }); - var processors = (List>)typeof(OpenTelemetryLoggerOptions).GetField("Processors", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(options); + var provider = new OpenTelemetryLoggerProvider(new TestOptionsMonitor(options)); + + var processor = GetProcessor(provider); - Assert.Single(processors); + Assert.NotNull(processor); if (processorType == ExportProcessorType.Batch) { - var batchProcesor = processors[0] as BatchLogRecordExportProcessor; + var batchProcesor = processor as BatchLogRecordExportProcessor; Assert.NotNull(batchProcesor); @@ -1217,7 +1225,7 @@ public void AddOtlpLogExporterLogRecordProcessorOptionsTest(ExportProcessorType } else { - var simpleProcesor = processors[0] as SimpleLogRecordExportProcessor; + var simpleProcesor = processor as SimpleLogRecordExportProcessor; Assert.NotNull(simpleProcesor); } @@ -1281,8 +1289,153 @@ public void ValidateInstrumentationScope() Assert.Empty(OtlpLogRecordTransformer.LogListPool); } + [Theory] + [InlineData(null)] + [InlineData("logging")] + public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggerFactoryCreate(string optionsName) + { + RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( + optionsName, + configure => + { + var factory = LoggerFactory.Create(logging => + { + configure(logging.Services); + + logging.AddOpenTelemetry(o => o.AddOtlpExporter(optionsName, configure: null)); + }); + + return (factory, factory); + }); + } + + [Theory] + [InlineData(null)] + [InlineData("logging")] + public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggingBuilder(string optionsName) + { + RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( + optionsName, + configure => + { + var services = new ServiceCollection(); + + configure(services); + + services.AddLogging( + logging => logging.AddOpenTelemetry(o => + o.AddOtlpExporter(optionsName, configure: null))); + + var sp = services.BuildServiceProvider(); + + var factory = sp.GetRequiredService(); + + return (sp, factory); + }); + } + + private static void RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( + string optionsName, + Func, (IDisposable Container, ILoggerFactory LoggerFactory)> createLoggerFactoryFunc) + { + var values = new Dictionary() + { + [OtlpExporterOptions.EndpointEnvVarName] = "http://test:8888", + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(values) + .Build(); + + var configureDelegateCalled = false; + var configureExportProcessorOptionsCalled = false; + var configureBatchOptionsCalled = false; + + var tracingConfigureDelegateCalled = false; + var unnamedConfigureDelegateCalled = false; + var allConfigureDelegateCalled = false; + + var testState = createLoggerFactoryFunc(services => + { + services.AddSingleton(configuration); + + services.Configure(optionsName, o => + { + configureDelegateCalled = true; + Assert.Equal(new Uri("http://test:8888"), o.Endpoint); + }); + + services.Configure(optionsName, o => + { + configureExportProcessorOptionsCalled = true; + }); + + services.Configure(optionsName, o => + { + configureBatchOptionsCalled = true; + }); + + services.Configure("tracing", o => + { + tracingConfigureDelegateCalled = true; + }); + + services.Configure(o => + { + unnamedConfigureDelegateCalled = true; + }); + + services.ConfigureAll(o => + { + allConfigureDelegateCalled = true; + }); + }); + + using var container = testState.Container; + + var factory = testState.LoggerFactory; + + Assert.NotNull(factory); + + Assert.True(configureDelegateCalled); + Assert.True(configureExportProcessorOptionsCalled); + Assert.True(configureBatchOptionsCalled); + + Assert.False(tracingConfigureDelegateCalled); + + Assert.Equal(optionsName == null, unnamedConfigureDelegateCalled); + + Assert.True(allConfigureDelegateCalled); + } + private static OtlpCommon.KeyValue TryGetAttribute(OtlpLogs.LogRecord record, string key) { return record.Attributes.FirstOrDefault(att => att.Key == key); } + + private static BaseProcessor GetProcessor(OpenTelemetryLoggerProvider provider) + { + var sdkProvider = typeof(OpenTelemetryLoggerProvider).GetField("Provider", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(provider); + + return (BaseProcessor)sdkProvider.GetType().GetProperty("Processor", BindingFlags.Instance | BindingFlags.Public).GetMethod.Invoke(sdkProvider, null); + } + + private sealed class TestOptionsMonitor : IOptionsMonitor + { + private readonly T instance; + + public TestOptionsMonitor(T instance) + { + this.instance = instance; + } + + public T CurrentValue => this.instance; + + public T Get(string name) => this.instance; + + public IDisposable OnChange(Action listener) + { + throw new NotImplementedException(); + } + } } diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs index e4b8a33f91..308062be65 100644 --- a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs @@ -129,4 +129,48 @@ public void TestTrimmingCorrectnessOfOpenTelemetryLoggerOptions() Assert.True(prop.PropertyType.IsPrimitive, $"Property OpenTelemetryLoggerOptions.{prop.Name} doesn't have a primitive type. This is potentially a trim compatibility issue."); } } + + [Fact] + public void VerifyAddProcessorOverloadWithImplementationFactory() + { + // arrange + var services = new ServiceCollection(); + + services.AddSingleton(); + + services.AddLogging(logging => + logging.AddOpenTelemetry( + o => o.AddProcessor(sp => sp.GetRequiredService()))); + + // act + using var sp = services.BuildServiceProvider(); + + var loggerProvider = sp.GetRequiredService() as LoggerProviderSdk; + + // assert + Assert.NotNull(loggerProvider); + Assert.NotNull(loggerProvider.Processor); + Assert.True(loggerProvider.Processor is MyProcessor); + } + + [Fact] + public void VerifyExceptionIsThrownWhenImplementationFactoryIsNull() + { + // arrange + var services = new ServiceCollection(); + + services.AddLogging(logging => + logging.AddOpenTelemetry( + o => o.AddProcessor(implementationFactory: null!))); + + // act + using var sp = services.BuildServiceProvider(); + + // assert + Assert.Throws(() => sp.GetRequiredService() as LoggerProviderSdk); + } + + private class MyProcessor : BaseProcessor + { + } }