diff --git a/examples/Example.Console/Example.Console.csproj b/examples/Example.Console/Example.Console.csproj
index 54f69987..19251a4a 100644
--- a/examples/Example.Console/Example.Console.csproj
+++ b/examples/Example.Console/Example.Console.csproj
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/examples/Example.MinimalApi/CustomProcessor.cs b/examples/Example.MinimalApi/CustomProcessor.cs
new file mode 100644
index 00000000..7f6488b8
--- /dev/null
+++ b/examples/Example.MinimalApi/CustomProcessor.cs
@@ -0,0 +1,18 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using System.Diagnostics;
+using OpenTelemetry;
+
+namespace Example.MinimalApi;
+
+public class CustomProcessor : BaseProcessor
+{
+ public override void OnEnd(Activity data)
+ {
+ Thread.Sleep(1000); // Enough time for the export to have happened
+
+ data.SetTag("custom-processor-tag", "ProcessedByCustomProcessor");
+ }
+}
diff --git a/examples/Example.MinimalApi/Example.MinimalApi.csproj b/examples/Example.MinimalApi/Example.MinimalApi.csproj
index 6421eb72..7101206b 100644
--- a/examples/Example.MinimalApi/Example.MinimalApi.csproj
+++ b/examples/Example.MinimalApi/Example.MinimalApi.csproj
@@ -1,9 +1,10 @@
-
+
net9.0
enable
enable
+ 4b1d6eb4-a323-4148-8195-1ec759984c7d
@@ -12,7 +13,7 @@
-
+
diff --git a/examples/Example.MinimalApi/Program.cs b/examples/Example.MinimalApi/Program.cs
index f79b9b6c..f207b9d6 100644
--- a/examples/Example.MinimalApi/Program.cs
+++ b/examples/Example.MinimalApi/Program.cs
@@ -5,18 +5,41 @@
using System.Diagnostics;
using Example.MinimalApi;
using OpenTelemetry;
+using OpenTelemetry.Exporter;
+using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
-//builder.AddElasticOpenTelemetry();
+var options = new Elastic.OpenTelemetry.ElasticOpenTelemetryOptions()
+{
+ // You can customize options here if needed
+};
-// This will add the OpenTelemetry services using Elastic defaults
-builder.AddServiceDefaults();
+//// Force small batch size and no delay for quicker exports in this example
+builder.Services.Configure(o =>
+{
+ o.BatchExportProcessorOptions.MaxExportBatchSize = 1;
+ o.BatchExportProcessorOptions.ScheduledDelayMilliseconds = 1;
+});
+
+//builder.Services.AddOpenTelemetry()
+// .WithTracing(t => t
+// .WithElasticDefaults(builder.Configuration, t => t
+// .AddSource(Api.ActivitySourceName)
+// .AddProcessor(new CustomProcessor())));
+
+builder.Services.AddOpenTelemetry()
+ .WithElasticTracing(builder.Configuration, t => t
+ .AddSource(Api.ActivitySourceName)
+ .AddProcessor(new CustomProcessor()));
+
+// Preferred method to add Elastic OpenTelemetry integration
+//builder.AddElasticOpenTelemetry(options, otelBuilder => otelBuilder
+// .WithTracing(t => t
+// .AddSource(Api.ActivitySourceName)
+// .AddProcessor(new CustomProcessor())));
-builder.Services
- .AddHttpClient() // Adds IHttpClientFactory
- .AddElasticOpenTelemetry() // Adds app specific tracing
- .WithTracing(t => t.AddSource(Api.ActivitySourceName));
+builder.AddServiceDefaults();
var app = builder.Build();
diff --git a/examples/ServiceDefaults/Extensions.cs b/examples/ServiceDefaults/Extensions.cs
index 7cea9cba..88da21b5 100644
--- a/examples/ServiceDefaults/Extensions.cs
+++ b/examples/ServiceDefaults/Extensions.cs
@@ -18,7 +18,7 @@ public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
- builder.ConfigureOpenTelemetry();
+ //builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
@@ -49,8 +49,6 @@ public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicati
}
});
- builder.AddOpenTelemetryExporters();
-
return builder;
}
diff --git a/examples/ServiceDefaults/ServiceDefaults.csproj b/examples/ServiceDefaults/ServiceDefaults.csproj
index 6caf2c8d..e568d85f 100644
--- a/examples/ServiceDefaults/ServiceDefaults.csproj
+++ b/examples/ServiceDefaults/ServiceDefaults.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net8.0;net9.0
enable
enable
true
@@ -9,13 +9,13 @@
-
+
-
-
-
-
+
+
+
+
diff --git a/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs b/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs
index 341b103d..58cbe41b 100644
--- a/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs
+++ b/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs
@@ -43,6 +43,7 @@ public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder
builder.ConfigureResource(r => r.WithElasticDefaultsCore(_components, null, null));
builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions));
+ logger.LogConfiguredOtlpExporterOptions();
TracerProvderBuilderExtensions.AddActivitySourceWithLogging(builder, logger, "Elastic.Transport", "");
TracerProvderBuilderExtensions.AddElasticProcessorsCore(builder, null, _components, null);
@@ -74,6 +75,7 @@ public MeterProviderBuilder BeforeConfigureMeterProvider(MeterProviderBuilder bu
builder.ConfigureServices(sc => sc
.Configure(OtlpExporterDefaults.OtlpExporterOptions)
.Configure(o => o.TemporalityPreference = MetricReaderTemporalityPreference.Delta));
+ logger.LogConfiguredOtlpExporterOptions();
logger.LogConfiguredSignalProvider(nameof(Signals.Metrics), nameof(MeterProviderBuilder), "");
@@ -91,8 +93,7 @@ public MeterProviderBuilder BeforeConfigureMeterProvider(MeterProviderBuilder bu
///
/// To configure logs SDK (the method name is the same as for other logs options).
///
- public void ConfigureLogsOptions(OpenTelemetryLoggerOptions options) =>
- options.WithElasticDefaults(_components.Logger);
+ public void ConfigureLogsOptions(OpenTelemetryLoggerOptions options) => options.WithElasticDefaults(_components.Logger);
///
/// To configure Resource.
diff --git a/src/Elastic.OpenTelemetry.Core/BuilderContext.cs b/src/Elastic.OpenTelemetry.Core/BuilderContext.cs
new file mode 100644
index 00000000..0ed1943f
--- /dev/null
+++ b/src/Elastic.OpenTelemetry.Core/BuilderContext.cs
@@ -0,0 +1,21 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elastic.OpenTelemetry.Core;
+
+///
+/// This is used internally to pass context around during the fluent builder configuration process.
+///
+internal sealed class BuilderContext where T : class
+{
+ internal required T Builder { get; init; }
+
+ internal required BuilderState BuilderState { get; init; }
+
+ internal required BuilderOptions BuilderOptions { get; init; }
+
+ internal IServiceCollection? Services { get; init; }
+}
diff --git a/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs b/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs
new file mode 100644
index 00000000..90b80d05
--- /dev/null
+++ b/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs
@@ -0,0 +1,9 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+namespace Elastic.OpenTelemetry.Core;
+
+internal readonly record struct BuilderOptions(
+ Action? UserProvidedConfigureBuilder,
+ bool DeferAddOtlpExporter) where T : class;
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs
index 7fc46501..0b7950b2 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs
@@ -4,7 +4,6 @@
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
-using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Elastic.OpenTelemetry.Diagnostics;
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs
new file mode 100644
index 00000000..b9f12bfb
--- /dev/null
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs
@@ -0,0 +1,109 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+using Elastic.OpenTelemetry.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Elastic.OpenTelemetry.Diagnostics;
+
+///
+/// Used to capture log entries before the file logger is initialized.
+///
+///
+/// Log entries are stored in a concurrent queue and drained to the first file logger that is available.
+/// Log entries are only captured if file logging is enabled in the options.
+///
+internal sealed class DeferredLogger : ILogger
+{
+ private readonly bool _isEnabled = false;
+ private readonly LogLevel _configuredLogLevel;
+ private readonly ConcurrentQueue _logQueue = new();
+ private readonly ILogger? _additionalLogger;
+
+ private static readonly Lock Lock = new();
+
+ ///
+ /// Create an instance of .
+ ///
+ /// The options used to configure the logger.
+ /// An optional additional logger to which log entries will be written. For example,
+ /// this may be available in ASP.NET Core scenarios and ensures the early log entries are sent to other sinks, such
+ /// as the console, when available.
+ private DeferredLogger(CompositeElasticOpenTelemetryOptions options, ILogger? additionalLogger = null)
+ {
+ _isEnabled = options.GlobalLogEnabled && options.LogTargets.HasFlag(LogTargets.File);
+
+ if (!_isEnabled)
+ return;
+
+ _configuredLogLevel = options.LogLevel;
+ _additionalLogger = additionalLogger;
+ }
+
+ public IDisposable BeginScope(TState state) where TState : notnull => NullScope.Instance;
+
+ public bool IsEnabled(LogLevel logLevel) => _isEnabled && _configuredLogLevel <= logLevel;
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ _additionalLogger?.Log(logLevel, eventId, state, exception, formatter);
+
+ if (!IsEnabled(logLevel))
+ return;
+
+ var logLine = LogFormatter.Format(logLevel, eventId, state, exception, formatter);
+
+ if (exception is not null)
+ logLine = $"{logLine}{Environment.NewLine}{exception}";
+
+ _logQueue.Enqueue(logLine);
+ }
+
+ internal void DrainAndRelease(StreamWriter streamWriter)
+ {
+ using (Lock.EnterScope())
+ {
+ while (_logQueue.TryDequeue(out var deferredLog))
+ {
+ streamWriter.WriteLine(deferredLog);
+ }
+
+ Instance = null;
+ }
+ }
+
+ private static DeferredLogger? Instance;
+
+ internal static ILogger GetOrCreate(CompositeElasticOpenTelemetryOptions options)
+ {
+ using (Lock.EnterScope())
+ {
+ // We want, at most, one instance of DeferredLogger for the application.
+ // We only create a DeferredFileLogger if file logging is enabled
+ if (options.GlobalLogEnabled && options.LogTargets.HasFlag(LogTargets.File))
+ return Instance ??= new DeferredLogger(options, options.AdditionalLogger);
+
+ return NullLogger.Instance;
+ }
+ }
+
+ internal static bool TryGetInstance([NotNullWhen(true)] out DeferredLogger? instance)
+ {
+ instance = Instance;
+ return instance is not null;
+ }
+
+ private class NullScope : IDisposable
+ {
+ public static readonly NullScope Instance = new();
+
+ public void Dispose()
+ {
+ // No-op
+ }
+ }
+}
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs
index dce157c7..77899bd3 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs
@@ -17,13 +17,12 @@ internal sealed class FileLogger : IDisposable, IAsyncDisposable, ILogger
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly StreamWriter _streamWriter;
private readonly LogLevel _configuredLogLevel;
+ private readonly LoggerExternalScopeProvider _scopeProvider;
private bool _disposed;
public bool FileLoggingEnabled { get; }
- private readonly LoggerExternalScopeProvider _scopeProvider;
-
public FileLogger(CompositeElasticOpenTelemetryOptions options)
{
_scopeProvider = new LoggerExternalScopeProvider();
@@ -40,8 +39,8 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options)
{
var process = Process.GetCurrentProcess();
- // When ordered by filename, we see logs from the same process grouped, then ordered by oldest to newest, then the PID for that instance
- var logFileName = $"EDOT.{process.ProcessName}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{process.Id}.log";
+ // This naming resembles the naming structure for OpenTelemetry log files.
+ var logFileName = $"edot-dotnet-{process.Id}-{process.ProcessName}-{DateTimeOffset.UtcNow:yyyyMMdd-hhMMssfffZ}.log";
var logDirectory = options.LogDirectory;
LogFilePath = Path.Combine(logDirectory, logFileName);
@@ -50,13 +49,20 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options)
Directory.CreateDirectory(logDirectory);
// StreamWriter.Dispose disposes underlying stream too.
- var stream = new FileStream(LogFilePath, FileMode.OpenOrCreate, FileAccess.Write);
+ var stream = new FileStream(LogFilePath, FileMode.Append, FileAccess.Write, FileShare.Read);
_streamWriter = new StreamWriter(stream, Encoding.UTF8);
_streamWriter.WriteLine("DateTime (UTC) Thread SpanId Level Message");
_streamWriter.WriteLine();
+ // Drain any deferred log entries captured before the file logger was initialized.
+ // These appear before the preamble to ensure correct timestamping order.
+ if (DeferredLogger.TryGetInstance(out var deferredLogger))
+ {
+ deferredLogger.DrainAndRelease(_streamWriter);
+ }
+
WritingTask = Task.Run(async () =>
{
var cancellationToken = _cancellationTokenSource.Token;
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs
index f3139c1c..7ecfda58 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs
@@ -7,6 +7,9 @@
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
using Microsoft.Extensions.Logging;
+#if NET || NETSTANDARD2_1
+using System.Runtime.CompilerServices;
+#endif
namespace Elastic.OpenTelemetry.Diagnostics;
@@ -54,22 +57,26 @@ internal static partial class LoggerMessages
"times across all {Target} instances.")]
public static partial void LogWithElasticDefaultsCallCount(this ILogger logger, int callCount, string target);
+ [LoggerMessage(EventId = 12, EventName = "ConfiguredOtlpExporterOptions", Level = LogLevel.Debug, Message = "The `OtlpExporterOptions` have been configured to use the" +
+ "`ElasticUserAgentHandler` to set the EDOT .NET user agent.")]
+ public static partial void LogConfiguredOtlpExporterOptions(this ILogger logger);
+
[LoggerMessage(EventId = 20, EventName = "ConfiguredSignalProvider", Level = LogLevel.Debug, Message = "Configured EDOT defaults for {Signal} via the {ProviderBuilderType} (instance:{BuilderInstanceId}).")]
public static partial void LogConfiguredSignalProvider(this ILogger logger, string signal, string providerBuilderType, string builderInstanceId);
- [LoggerMessage(EventId = 21, EventName = "SkippingOtlpExporter", Level = LogLevel.Information, Message = "Skipping OTLP exporter for {Signal} based on the provided `ElasticOpenTelemetryOptions` " +
+ [LoggerMessage(EventId = 21, EventName = "SkippedOtlpExporter", Level = LogLevel.Information, Message = "Skipped OTLP exporter for {Signal} based on the provided `ElasticOpenTelemetryOptions` " +
"via the {ProviderBuilderType} (instance:{BuilderInstanceId}).")]
- public static partial void LogSkippingOtlpExporter(this ILogger logger, string signal, string providerBuilderType, string builderInstanceId);
+ public static partial void LogSkippedOtlpExporter(this ILogger logger, string signal, string providerBuilderType, string builderInstanceId);
- [LoggerMessage(EventId = 22, EventName = "ProviderBuilderSignalDisabled", Level = LogLevel.Information, Message = "Skipping configuring and setting EDOT defaults for {Signal} on {ProviderBuilderType}" +
+ [LoggerMessage(EventId = 22, EventName = "ProviderBuilderSignalDisabled", Level = LogLevel.Information, Message = "Skipped configuring and setting EDOT defaults for {Signal} on {ProviderBuilderType}" +
"(instance:{BuilderInstanceId}), as these have been disabled via configuration.")]
public static partial void LogSignalDisabled(this ILogger logger, string signal, string providerBuilderType, string builderInstanceId);
[LoggerMessage(EventId = 23, EventName = "SkippingBootstrap", Level = LogLevel.Warning, Message = "Skipping EDOT bootstrap and provider configuration because the `Signals` configuration is set to `None`. " +
- "This likely represents a misconfiguration. If you do not want to use the EDOT for any signals, avoid calling `WithElasticDefaults` on the builder.")]
+ "This likely represents a misconfiguration. If you do not want to use the EDOT for any signals, avoid calling `AddElasticOpenTelemetry` or `WithElasticDefaults` on the builder.")]
public static partial void LogSkippingBootstrapWarning(this ILogger logger);
[LoggerMessage(EventId = 24, EventName = "BuilderAlreadyConfigured", Level = LogLevel.Debug, Message = "The {ProviderBuilderType} (instance:{BuilderInstanceId}) has already been configured with EDOT defaults.")]
@@ -78,6 +85,16 @@ internal static partial class LoggerMessages
[LoggerMessage(EventId = 25, EventName = "ConfiguringBuilder", Level = LogLevel.Information, Message = "Configuring the {ProviderBuilderType} (instance:{BuilderInstanceId}) with EDOT defaults.")]
public static partial void LogConfiguringBuilder(this ILogger logger, string providerBuilderType, string builderInstanceId);
+ [LoggerMessage(EventId = 26, EventName = "AddedOtlpExporter", Level = LogLevel.Debug, Message = "Added OTLP exporter for {Signal} for {ProviderBuilderType} (instance:{BuilderInstanceId}).")]
+ public static partial void LogAddedOtlpExporter(this ILogger logger, string signal, string providerBuilderType, string builderInstanceId);
+
+ [LoggerMessage(EventId = 27, EventName = "InvokedConfigureAction", Level = LogLevel.Debug, Message = "Invoked user-provided builder configuration action on" +
+ " the {ProviderBuilderType} (instance:{BuilderInstanceId}).")]
+ public static partial void LogInvokedConfigureAction(this ILogger logger, string providerBuilderType, string builderInstanceId);
+
+ [LoggerMessage(EventId = 28, EventName = "DeferredOtlpExporter", Level = LogLevel.Debug, Message = "Deferred adding OTLP exporter on {ProviderBuilderType} (instance:{BuilderInstanceId}).")]
+ public static partial void LogDeferredOtlpExporter(this ILogger logger, string providerBuilderType, string builderInstanceId);
+
[LoggerMessage(EventId = 30, EventName = "LocatedInstrumentationAssembly", Level = LogLevel.Debug, Message = "Located {AssemblyFilename} in {Path}.")]
@@ -208,6 +225,11 @@ public static void LogDistroPreamble(this ILogger logger, SdkActivationMethod ac
logger.LogDebug("Processor count: {ProcessorCount}", Environment.ProcessorCount);
logger.LogDebug("OS version: {OSVersion}", Environment.OSVersion);
logger.LogDebug("CLR version: {CLRVersion}", Environment.Version);
+#if NETFRAMEWORK || NETSTANDARD2_0
+ logger.LogDebug("Dynamic code supported: {IsDynamicCodeSupported}", true);
+#else
+ logger.LogDebug("Dynamic code supported: {IsDynamicCodeSupported}", RuntimeFeature.IsDynamicCodeSupported);
+#endif
string[] environmentVariables =
[
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs
index 93bccdbe..0dfdb965 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs
@@ -6,7 +6,6 @@
using System.Text;
using System.Text.RegularExpressions;
using Elastic.OpenTelemetry.Configuration;
-using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Elastic.OpenTelemetry.Diagnostics;
diff --git a/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs b/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs
index bb3390c6..4904d22e 100644
--- a/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs
+++ b/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs
@@ -24,6 +24,11 @@ internal static class ElasticOpenTelemetry
internal static ElasticOpenTelemetryComponents Bootstrap(SdkActivationMethod activationMethod) =>
Bootstrap(activationMethod, CompositeElasticOpenTelemetryOptions.DefaultOptions, null);
+ ///
+ /// This checks for any existing components on the IServiceCollection and reuse them if found.
+ /// It also attempts to used a shared components instance if available and suitable.
+ /// If neither are available, it will create a new instance.
+ ///
internal static ElasticOpenTelemetryComponents Bootstrap(CompositeElasticOpenTelemetryOptions options, IServiceCollection? services) =>
Bootstrap(SdkActivationMethod.NuGet, options, services);
diff --git a/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs b/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs
index fc37b587..7ae9b496 100644
--- a/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs
@@ -45,6 +45,9 @@ public static void WithElasticDefaults(this OpenTelemetryLoggerOptions options,
// - https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39304
// options.IncludeScopes = true;
+ // TODO - Verify if we can configure the OTLP exporter to add the user agent header.
+ // See: https://github.com/elastic/elastic-otel-dotnet/issues/338
+
logger.LogConfiguredSignalProvider(nameof(Signals.Logs), nameof(OpenTelemetryLoggerOptions), "");
}
}
diff --git a/src/Elastic.OpenTelemetry.Core/Extensions/ResourceBuilderExtensions.cs b/src/Elastic.OpenTelemetry.Core/Extensions/ResourceBuilderExtensions.cs
index c939051e..c1096c1b 100644
--- a/src/Elastic.OpenTelemetry.Core/Extensions/ResourceBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Extensions/ResourceBuilderExtensions.cs
@@ -43,9 +43,13 @@ internal static ResourceBuilder WithElasticDefaultsCore(
components.Logger.LogWithElasticDefaultsCallCount(callCount, nameof(ResourceBuilder));
- return SignalBuilder.WithElasticDefaults(builder, components.Options, components, services,
- (ResourceBuilder builder, BuilderState builderState, IServiceCollection? services) =>
+ var builderOptions = new BuilderOptions();
+
+ return SignalBuilder.WithElasticDefaults(builder, components.Options, components, services, builderOptions,
+ (BuilderContext builderContext) =>
{
+ var builderState = builderContext.BuilderState;
+
var attributes = new Dictionary
{
{ ResourceSemanticConventions.AttributeServiceInstanceId, ApplicationInstanceId },
diff --git a/src/Elastic.OpenTelemetry.Core/Extensions/TracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry.Core/Extensions/TracerProviderBuilderExtensions.cs
index 6fd853c5..1d48dd8d 100644
--- a/src/Elastic.OpenTelemetry.Core/Extensions/TracerProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Extensions/TracerProviderBuilderExtensions.cs
@@ -5,7 +5,6 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Elastic.OpenTelemetry;
-using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
using Elastic.OpenTelemetry.Diagnostics;
using Elastic.OpenTelemetry.Resources;
@@ -81,6 +80,9 @@ internal static TracerProviderBuilder AddElasticProcessorsCore(
ElasticOpenTelemetryComponents? components,
IServiceCollection? services)
{
+ // NOTE: This method doesn't currently add any processors and just configures the resource.
+ // Once we start adding processors, we might need to consider passing in the BuilderOptions
+
components ??= builderState?.Components;
var callCount = Interlocked.Increment(ref AddElasticProcessorsCallCount);
@@ -94,14 +96,22 @@ internal static TracerProviderBuilder AddElasticProcessorsCore(
{
// When we have existing builderState, this method is being invoked from the main WithElasticDefaults method.
// In that scenario, we skip configuring the resource, as it will have already been configured by the caller.
+ // We don't log here, as the main WithElasticDefaults method will log the complete set of processors added.
return builder;
}
- return SignalBuilder.WithElasticDefaults(builder, Signals.Traces, components?.Options, components, null, ConfigureBuilder);
+ // This is sufficient as the configure method doesn't register anything but the resource, for now.
+ var builderOptions = new BuilderOptions();
+
+ return SignalBuilder.WithElasticDefaults(builder, components?.Options, components, services, builderOptions, ConfigureBuilder);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- static void ConfigureBuilder(TracerProviderBuilder builder, BuilderState builderState, IServiceCollection? services)
+ static void ConfigureBuilder(BuilderContext builderContext)
{
+ var builder = builderContext.Builder;
+ var builderState = builderContext.BuilderState;
+ var services = builderContext.Services;
+
builder.ConfigureResource(r => r.WithElasticDefaultsCore(builderState, services, null));
}
}
@@ -109,6 +119,8 @@ static void ConfigureBuilder(TracerProviderBuilder builder, BuilderState builder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TracerProviderBuilder LogAndAddProcessor(this TracerProviderBuilder builder, BaseProcessor processor, BuilderState builderState)
{
+ // NOTE: This is not currently used but here for when we do have processors to register for any feature gaps we close.
+
builder.AddProcessor(processor);
builderState.Components.Logger.LogProcessorAdded(processor.GetType().ToString(), builderState.InstanceIdentifier);
return builder;
diff --git a/src/Elastic.OpenTelemetry.Core/Processors/SpanCompressionProcessor.cs b/src/Elastic.OpenTelemetry.Core/Processors/SpanCompressionProcessor.cs
index 91d5ea8a..a320e8c3 100644
--- a/src/Elastic.OpenTelemetry.Core/Processors/SpanCompressionProcessor.cs
+++ b/src/Elastic.OpenTelemetry.Core/Processors/SpanCompressionProcessor.cs
@@ -4,7 +4,6 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Elastic.OpenTelemetry.Core;
-using Elastic.OpenTelemetry.Processors;
using OpenTelemetry;
#pragma warning disable IDE0130 // Namespace does not match folder structure
diff --git a/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs b/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs
index 5aec7111..502510d0 100644
--- a/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs
+++ b/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs
@@ -10,6 +10,11 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using OpenTelemetry;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
namespace Elastic.OpenTelemetry.Core;
@@ -17,6 +22,18 @@ namespace Elastic.OpenTelemetry.Core;
/// Provides static helper methods which centralise provider builder logic used when registering EDOT
/// defaults on the various builders.
///
+///
+/// This class is used internally by the various builder extension methods to centralise common logic.
+///
+/// It is currently used when registering EDOT .NET defaults on the following builders:
+///
+///
+///
+///
+///
+///
+///
+///
internal static class SignalBuilder
{
#pragma warning disable IDE0028 // Simplify collection initialization
@@ -28,15 +45,28 @@ internal static class SignalBuilder
///
/// Returns the most relevant for builder extension methods to use.
///
- public static ILogger GetLogger(
+ internal static ILogger GetLogger(
ElasticOpenTelemetryComponents? components,
- CompositeElasticOpenTelemetryOptions? options) =>
- components?.Logger ?? options?.AdditionalLogger ?? NullLogger.Instance;
+ CompositeElasticOpenTelemetryOptions? options)
+ {
+ if (components?.Logger is not null)
+ return components.Logger;
+
+ if (options is not null)
+ {
+ var deferredLogger = DeferredLogger.GetOrCreate(options);
+
+ if (deferredLogger is NullLogger && options.AdditionalLogger is not null)
+ return options.AdditionalLogger;
+ }
+
+ return NullLogger.Instance;
+ }
///
/// Returns the most relevant for builder extension methods to use.
///
- public static ILogger GetLogger(
+ internal static ILogger GetLogger(
T builder,
ElasticOpenTelemetryComponents? components,
CompositeElasticOpenTelemetryOptions? options,
@@ -45,123 +75,127 @@ public static ILogger GetLogger(
if (builderState is not null)
return builderState.Components.Logger;
- var logger = components?.Logger ?? options?.AdditionalLogger ?? NullLogger.Instance;
-
if (BuilderStateTable.TryGetValue(builder, out builderState))
- logger = builderState.Components.Logger;
+ return builderState.Components.Logger;
- return logger;
+ return GetLogger(components, options);
}
- public static T WithElasticDefaults(
+ ///
+ /// This overload is needed to handle scenarios where we don't yet have a builder state and context.
+ ///
+ internal static T WithElasticDefaults(
T builder,
- Signals signal,
CompositeElasticOpenTelemetryOptions? options,
ElasticOpenTelemetryComponents? components,
IServiceCollection? services,
- Action configure) where T : class
+ in BuilderOptions builderOptions,
+ Action> configureBuilder) where T : class
{
- var providerBuilderName = builder.GetType().Name;
- var logger = GetLogger(components, options);
+ // FullName may return null so we fallback to Name when required.
+ var providerBuilderName = builder.GetType().FullName ?? builder.GetType().Name;
- BuilderState? builderState = null;
+ if (BuilderStateTable.TryGetValue(builder, out var existingBuilderState))
+ {
+ return HandleExistingBuilderState(builder, providerBuilderName, existingBuilderState);
+ }
+
+ var logger = GetLogger(components, options);
+ var builderInstanceId = Guid.NewGuid().ToString(); // Used in logging to track duplicate calls to the same builder
- try
+ // When dealing with a signal specific builder, we need to check if that signal is enabled.
+ // If not, we can log and return early.
+ if (builder is not IOpenTelemetryBuilder && builder is not ResourceBuilder)
{
- var builderInstanceId = "";
+ var configuredSignals = components?.Options.Signals ?? options?.Signals ?? Signals.All;
- if (BuilderStateTable.TryGetValue(builder, out var existingBuilderState))
+ var signal = Signals.None;
+ switch (builder)
{
- builderState = existingBuilderState;
- builderInstanceId = existingBuilderState.InstanceIdentifier;
+ case TracerProviderBuilder:
+ signal = Signals.Traces;
+ break;
+ case MeterProviderBuilder:
+ signal = Signals.Metrics;
+ break;
+ case LoggerProviderBuilder:
+ signal = Signals.Logs;
+ break;
}
- if (builderState is not null)
- logger = builderState.Components.Logger;
-
- // If the signal is disabled via configuration we skip any potential bootstrapping.
- var configuredSignals = components?.Options.Signals ?? options?.Signals ?? Signals.All;
- if (!configuredSignals.HasFlagFast(signal))
+ if (configuredSignals.HasFlagFast(signal) is false)
{
logger.LogSignalDisabled(signal.ToString().ToLower(), providerBuilderName, builderInstanceId);
return builder;
}
-
- return WithElasticDefaults(builder, options, components, services, configure);
}
- catch (Exception ex)
- {
- var signalNameForLogging = signal.ToStringFast().ToLowerInvariant();
- logger.LogError(new EventId(501, "BuilderDefaultsFailed"), ex, "Failed to fully register EDOT .NET " +
- "{Signal} defaults on the {ProviderBuilderName}.", signalNameForLogging, providerBuilderName);
- }
-
- return builder;
- }
-
- public static T WithElasticDefaults(
- T builder,
- CompositeElasticOpenTelemetryOptions? options,
- ElasticOpenTelemetryComponents? components,
- IServiceCollection? services,
- Action configure) where T : class
- {
- var providerBuilderName = builder.GetType().Name;
- var logger = GetLogger(components, options);
- try
+ // This should not be a hot path, so locking here is reasonable.
+ using (var scope = Lock.EnterScope())
{
- if (BuilderStateTable.TryGetValue(builder, out var builderState))
+ // Double check after acquiring the lock.
+ if (BuilderStateTable.TryGetValue(builder, out existingBuilderState))
{
- builderState.Components.Logger.LogBuilderAlreadyConfigured(providerBuilderName, builderState.InstanceIdentifier);
+ return HandleExistingBuilderState(builder, providerBuilderName, existingBuilderState);
+ }
- // This allows us to track the number of times a specific instance of a builder is configured.
- // We expect each builder to be configured at most once and log a warning if multiple invocations
- // are detected.
- builderState.IncrementWithElasticDefaults();
+ // We can't log to the file here as we don't yet have any bootstrapped components.
+ // Therefore, this message will only appear if the consumer provides an additional logger.
+ // This is fine as it's a trace level message for advanced debugging.
+ logger.LogNoExistingComponents(providerBuilderName, builderInstanceId);
- if (builderState.WithElasticDefaultsCounter > 1)
- builderState.Components.Logger.LogWarning("The `WithElasticDefaults` method has been called {WithElasticDefaultsCount} " +
- "times on the same `{BuilderType}` (instance: {BuilderInstanceId}). This method is " +
- "expected to be invoked a maximum of one time.", builderState.WithElasticDefaultsCounter, providerBuilderName,
- builderState.InstanceIdentifier);
+ options ??= CompositeElasticOpenTelemetryOptions.DefaultOptions;
- return builder;
- }
+ // This will check for any existing components on the IServiceCollection and reuse them if found.
+ // It would also attempt to used a shared components instance if available.
+ // If neither are available, it will create a new instance.
+ components = ElasticOpenTelemetry.Bootstrap(options, services);
+ var builderState = new BuilderState(components, builderInstanceId);
- // This should not be a hot path, so locking here is reasonable.
- using (var scope = Lock.EnterScope())
+ var builderContext = new BuilderContext
{
- var instanceId = Guid.NewGuid().ToString(); // Used in logging to track duplicate calls to the same builder
+ Builder = builder,
+ BuilderState = builderState,
+ BuilderOptions = builderOptions,
+ Services = services
+ };
- // We can't log to the file here as we don't yet have any bootstrapped components.
- // Therefore, this message will only appear if the consumer provides an additional logger.
- // This is fine as it's a trace level message for advanced debugging.
- logger.LogNoExistingComponents(providerBuilderName, instanceId);
+ configureBuilder(builderContext);
- options ??= CompositeElasticOpenTelemetryOptions.DefaultOptions;
- components = ElasticOpenTelemetry.Bootstrap(options, services);
- builderState = new BuilderState(components, instanceId);
+ components.Logger.LogStoringBuilderState(providerBuilderName, builderInstanceId);
+ BuilderStateTable.Add(builder, builderState);
+ }
- configure(builder, builderState, services);
+ return builder;
- components.Logger.LogStoringBuilderState(providerBuilderName, instanceId);
- BuilderStateTable.Add(builder, builderState);
- }
- }
- catch (Exception ex)
+ static T HandleExistingBuilderState(
+ T builder,
+ string providerBuilderName,
+ BuilderState builderState)
{
- logger.LogError(new EventId(502, "BuilderDefaultsFailed"), ex, "Failed to fully register EDOT .NET " +
- "defaults on the {ProviderBuilderName}.", builder.GetType().Name);
+ var logger = builderState.Components.Logger;
+ logger.LogBuilderAlreadyConfigured(providerBuilderName, builderState.InstanceIdentifier);
+
+ // This allows us to track the number of times a specific instance of a builder is configured.
+ // We expect each builder to be configured at most once and log a warning if multiple invocations
+ // are detected.
+ builderState.IncrementWithElasticDefaults();
+
+ if (builderState.WithElasticDefaultsCounter > 1)
+ // NOTE: Log using the logger from the existing builder state.
+ builderState.Components.Logger.LogWarning("The `WithElasticDefaults` method has been called {WithElasticDefaultsCount} " +
+ "times on the same `{BuilderType}` (instance: {BuilderInstanceId}). This method is " +
+ "expected to be invoked a maximum of one time.", builderState.WithElasticDefaultsCounter, providerBuilderName,
+ builderState.InstanceIdentifier);
+
+ return builder;
}
-
- return builder;
}
///
/// Identifies whether a specific instrumentation assembly is present alongside the executing application.
///
- public static bool InstrumentationAssemblyExists(string assemblyName)
+ internal static bool InstrumentationAssemblyExists(string assemblyName)
{
var assemblyLocation = Path.GetDirectoryName(AppContext.BaseDirectory);
@@ -171,8 +205,8 @@ public static bool InstrumentationAssemblyExists(string assemblyName)
return File.Exists(Path.Combine(assemblyLocation, assemblyName));
}
- [RequiresUnreferencedCode("Accesses assemblies and methods dynamically using refelction. This is by design and cannot be made trim compatible.")]
- public static void AddInstrumentationViaReflection(T builder, ElasticOpenTelemetryComponents components, ReadOnlySpan assemblyInfos, string builderInstanceId)
+ [RequiresUnreferencedCode("Accesses assemblies and methods dynamically using reflection. This is by design and cannot be made trim compatible.")]
+ internal static void AddInstrumentationViaReflection(T builder, ElasticOpenTelemetryComponents components, ReadOnlySpan assemblyInfos, string builderInstanceId)
where T : class
{
if (components.Options.SkipInstrumentationAssemblyScanning)
@@ -184,8 +218,8 @@ public static void AddInstrumentationViaReflection(T builder, ElasticOpenTele
AddInstrumentationViaReflection(builder, components.Logger, assemblyInfos, builderInstanceId);
}
- [RequiresUnreferencedCode("Accesses assemblies and methods dynamically using refelction. This is by design and cannot be made trim compatible.")]
- public static void AddInstrumentationViaReflection(T builder, ILogger logger, ReadOnlySpan assemblyInfos, string builderInstanceId)
+ [RequiresUnreferencedCode("Accesses assemblies and methods dynamically using reflection. This is by design and cannot be made trim compatible.")]
+ internal static void AddInstrumentationViaReflection(T builder, ILogger logger, ReadOnlySpan assemblyInfos, string builderInstanceId)
where T : class
{
var builderTypeName = builder.GetType().Name;
diff --git a/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs
index 1af0b26f..a4fcff4f 100644
--- a/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs
@@ -3,10 +3,14 @@
// See the LICENSE file in the project root for more information
using Elastic.OpenTelemetry;
+using Elastic.OpenTelemetry.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
#pragma warning disable IDE0130 // Namespace does not match folder structure
namespace Microsoft.Extensions.Hosting;
@@ -14,13 +18,36 @@ namespace Microsoft.Extensions.Hosting;
///
/// Elastic extension methods for used to register
-/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
///
public static class HostApplicationBuilderExtensions
{
///
- /// Registers the OpenTelemetry SDK with the application, configured with Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+ /// Registers the OpenTelemetry SDK with the application, configured with Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults for traces,
+ /// metrics and logs.
///
+ ///
+ ///
+ /// For applications built on the Generic Host (i.e. using Host.CreateDefaultBuilder), this is the prefered way
+ /// to register the OpenTelemetry SDK with EDOT .NET defaults. It enables EDOT .NET defaults for all signals, requiring
+ /// minimal code.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, you may need to further customize the OpenTelemetry SDK configuration
+ /// such as when you need to add additional sources, processors or instrumentations. You can do this by calling the overload
+ /// accepting a configuration action .
+ ///
+ ///
+ /// Avoid combining this method with calls to WithElasticDefaults, WithElasticMetrics, WithElasticLogs or WithElasticTraces
+ /// extension methods on the , , or
+ /// directly as this may lead to unexpected results.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
/// The for the application being configured.
/// Thrown when the is null.
/// The supplied for chaining calls.
@@ -42,93 +69,157 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
///
///
///
- /// Configuration is first bound from and then overridden by any options configured on
- /// the provided .
+ ///
+ /// For applications built on the Generic Host (i.e. using Host.CreateDefaultBuilder), this is the prefered way
+ /// to register the OpenTelemetry SDK with EDOT .NET defaults. It enables EDOT .NET defaults for all signals, requiring
+ /// minimal code.
+ ///
+ ///
+ /// Avoid combining this method with calls to WithElasticDefaults, WithElasticMetrics, WithElasticLogs or WithElasticTraces
+ /// extension methods on the , , or
+ /// directly as this may lead to unexpected results.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
///
///
- /// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
+ /// A configuration used to further customise
+ /// the OpenTelemetry SDK after Elastic Distribution of OpenTelemetry (EDOT) .NET defaults have been applied and before the OTLP
+ /// exporter is added.
/// Thrown when the is null.
- /// Thrown when the is null.
+ /// Thrown when the is null.
///
- public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, ElasticOpenTelemetryOptions options)
+ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, Action configure)
{
+ // TODO - Breaking change: In a future major release, rename this parameter to 'configureBuilder' for clarity and consistency.
+ // This would be a source breaking change only but we'll reserve it for a major version to avoid disrupting consumers.
#if NET
- ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configure);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
- if (options is null)
- throw new ArgumentNullException(nameof(options));
+ if (configure is null)
+ throw new ArgumentNullException(nameof(configure));
#endif
- builder.Services.AddElasticOpenTelemetry(builder.Configuration, options);
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ // We don't set defer as we expect the callee to handle executing the configure action at the correct time.
+ DeferAddOtlpExporter = false
+ };
+ builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration), builderOptions);
return builder;
}
///
///
///
+ ///
+ ///
+ /// Configuration is first bound from and then overridden by any options configured on
+ /// the provided .
+ ///
+ ///
+ /// For applications built on the Generic Host (i.e. using Host.CreateDefaultBuilder), this is the prefered way
+ /// to register the OpenTelemetry SDK with EDOT .NET defaults. It enables EDOT .NET defaults for all signals, requiring
+ /// minimal code.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, you may need to further customize the OpenTelemetry SDK configuration
+ /// such as when you need to add additional sources, processors or instrumentations. You can do this by calling the overload
+ /// accepting a configuration action .
+ ///
+ ///
+ /// Avoid combining this method with calls to WithElasticDefaults, WithElasticMetrics, WithElasticLogs or WithElasticTraces
+ /// extension methods on the , , or
+ /// directly as this may lead to unexpected results.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
///
/// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
- /// configuration action.
/// Thrown when the is null.
/// Thrown when the is null.
- /// Thrown when the is null.
///
- public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, ElasticOpenTelemetryOptions options, Action configure)
+ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, ElasticOpenTelemetryOptions options)
{
#if NET
- ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
- ArgumentNullException.ThrowIfNull(configure);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
if (options is null)
throw new ArgumentNullException(nameof(options));
-
- if (configure is null)
- throw new ArgumentNullException(nameof(configure));
#endif
- var otelBuilder = builder.Services
- .AddElasticOpenTelemetry(builder.Configuration, options);
-
- configure.Invoke(otelBuilder);
-
+ builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration, options), default);
return builder;
}
///
///
///
+ ///
+ ///
+ /// For applications built on the Generic Host (i.e. using Host.CreateDefaultBuilder), this is the prefered way
+ /// to register the OpenTelemetry SDK with EDOT .NET defaults. It enables EDOT .NET defaults for all signals, requiring
+ /// minimal code.
+ ///
+ ///
+ /// Avoid combining this method with calls to WithElasticDefaults, WithElasticMetrics, WithElasticLogs or WithElasticTraces
+ /// extension methods on the , , or
+ /// directly as this may lead to unexpected results.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
///
- /// configuration action.
+ ///
+ ///
/// Thrown when the is null.
+ /// Thrown when the is null.
/// Thrown when the is null.
///
- public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, Action configure)
+ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder,
+ ElasticOpenTelemetryOptions options, Action configure)
{
+ // TODO - Breaking change: In a future major release, rename this parameter to 'configureBuilder' for clarity and consistency.
+ // This would be a source breaking change only but we'll reserve it for a major version to avoid disrupting consumers.
#if NET
ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(configure);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+
if (configure is null)
throw new ArgumentNullException(nameof(configure));
#endif
-
- var otelBuilder = builder.Services
- .AddElasticOpenTelemetry(builder.Configuration);
-
- configure.Invoke(otelBuilder);
-
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ // We don't set defer as we expect the callee to handle executing the configure action at the correct time.
+ DeferAddOtlpExporter = false
+ };
+
+ builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration, options), builderOptions);
return builder;
}
}
diff --git a/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs
new file mode 100644
index 00000000..5cfae1aa
--- /dev/null
+++ b/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs
@@ -0,0 +1,439 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using System.Runtime.CompilerServices;
+using Elastic.OpenTelemetry;
+using Elastic.OpenTelemetry.Configuration;
+using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Diagnostics;
+using Elastic.OpenTelemetry.Exporters;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+using OpenTelemetry.Exporter;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+
+// Matching namespace with LoggerProviderBuilder
+#pragma warning disable IDE0130 // Namespace does not match folder structure
+namespace OpenTelemetry;
+#pragma warning restore IDE0130 // Namespace does not match folder structure
+
+///
+/// Provides extension methods on the used to register
+/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults
+/// for logging.
+///
+public static class LoggerProviderBuilderExtensions
+{
+ ///
+ /// Used to track the number of times any variation of `WithElasticDefaults` is invoked by consuming
+ /// code across all instances. This allows us to warn about potential
+ /// misconfigurations.
+ ///
+ private static int WithElasticDefaultsCallCount;
+
+ ///
+ /// Apply Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults
+ /// to the .
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. For example,
+ /// This ensures that your configuration is invoked after EDOT .NET defaults have been applied, but before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
+ /// The to configure.
+ /// Thrown when the is null.
+ ///
+ /// An instance of that can be used to further configure the logging signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+#endif
+
+ return WithElasticDefaultsCore(builder, null, null, null, default);
+ }
+
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. For example,
+ /// This ensures that your configuration is invoked after EDOT .NET defaults have been applied, but before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the action.
+ ///
+ ///
+ ///
+ ///
+ /// An used to further configure the .
+ /// This action is invoked after EDOT .NET defaults
+ /// have been applied, but before the OTLP exporter is added. This ensures that any custom processors run before the exporter.
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the logging signal.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the action to customise
+ /// the after EDOT .NET defaults are applied and before the OTLP exporter is added.
+ ///
+ ///
+ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
+ }
+
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. For example,
+ ///
+ /// This ensures that your configuration is invoked after EDOT .NET defaults have been applied, but before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
+ ///
+ /// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ /// An instance of that can be used to further configure the logging signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, ElasticOpenTelemetryOptions options)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(options);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+#endif
+
+ return WithElasticDefaultsCore(builder, new(options), null, null, default);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder,
+ ElasticOpenTelemetryOptions options, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
+ }
+
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. For example,
+ /// This ensures that your configuration is invoked after EDOT .NET defaults have been applied, but before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
+ ///
+ /// An instance from which to load the Elastic Distribution of
+ /// OpenTelemetry (EDOT) .NET options.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ /// An instance of that can be used to further configure the logging signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, IConfiguration configuration)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configuration);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
+#endif
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, default);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder,
+ IConfiguration configuration, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static LoggerProviderBuilder WithElasticDefaultsCore(
+ this LoggerProviderBuilder builder,
+ CompositeElasticOpenTelemetryOptions? options,
+ ElasticOpenTelemetryComponents? components,
+ IServiceCollection? services,
+ in BuilderOptions builderOptions)
+ {
+ var logger = SignalBuilder.GetLogger(builder, components, options, null);
+
+ var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
+
+ if (callCount > 1)
+ {
+ logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, nameof(LoggerProviderBuilder));
+ }
+ else
+ {
+ logger.LogWithElasticDefaultsCallCount(callCount, nameof(LoggerProviderBuilder));
+ }
+
+ return SignalBuilder.WithElasticDefaults(builder, options, components, services, builderOptions, ConfigureBuilder);
+
+ static void ConfigureBuilder(BuilderContext builderContext)
+ {
+ var builder = builderContext.Builder;
+ var builderState = builderContext.BuilderState;
+ var components = builderState.Components;
+ var logger = components.Logger;
+ var services = builderContext.Services;
+
+ // FullName may return null so we fallback to Name when required.
+ var loggingProviderBuilderName = builder.GetType().FullName ?? builder.GetType().Name;
+
+ logger.LogConfiguringBuilder(loggingProviderBuilderName, builderState.InstanceIdentifier);
+
+ builder.ConfigureResource(r => r.WithElasticDefaults(builderState, services));
+
+ // When services is not null here, the options will have already been configured by the calling code so
+ // we don't need to do it again.
+ if (services is null)
+ {
+ builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions));
+ logger.LogConfiguredOtlpExporterOptions();
+ }
+
+ builder.ConfigureServices(sc => sc.Configure(o => o.WithElasticDefaults(logger)));
+
+ // This check is to detect if ASP.NET Core is present in the application.
+ // If it is, we check if IncludeScopes is enabled and log a warning because the upstream OTLP exporter
+ // exports duplicate attributes which does not conform to the spec and breaks the EDOT Collector.
+ if (builder is IDeferredLoggerProviderBuilder deferredBuilder)
+ {
+ var httpContextType = Type.GetType("Microsoft.AspNetCore.Http.HttpContext, Microsoft.AspNetCore.Http.Abstractions");
+
+ if (httpContextType is not null)
+ {
+ var options = deferredBuilder.Configure((sp, _) =>
+ {
+ var options = sp.GetService>();
+
+ if (options is not null && options.Value.IncludeScopes == true)
+ {
+ logger.LogDetectedIncludeScopesWarning();
+ }
+ });
+ }
+ }
+
+ // Invoke any user-provided configuration.
+ var userProvidedConfigureBuilder = builderContext.BuilderOptions.UserProvidedConfigureBuilder;
+ if (userProvidedConfigureBuilder is not null)
+ {
+ userProvidedConfigureBuilder(builder);
+ logger.LogInvokedConfigureAction(loggingProviderBuilderName, builderState.InstanceIdentifier);
+ }
+
+ if (builderContext.BuilderOptions.DeferAddOtlpExporter)
+ {
+ // If a callee will register the OTLP exporter later, we skip adding it here.
+ logger.LogDeferredOtlpExporter(loggingProviderBuilderName, builderState.InstanceIdentifier);
+ }
+ else
+ {
+ if (components.Options.SkipOtlpExporter)
+ {
+ logger.LogSkippedOtlpExporter(nameof(Signals.Logs), loggingProviderBuilderName, builderState.InstanceIdentifier);
+ }
+ else
+ {
+ builder.AddOtlpExporter();
+ logger.LogAddedOtlpExporter(nameof(Signals.Logs), loggingProviderBuilderName, builderState.InstanceIdentifier);
+ }
+ }
+
+ logger.LogConfiguredSignalProvider(nameof(Signals.Logs), loggingProviderBuilderName, builderState.InstanceIdentifier);
+ }
+ }
+}
diff --git a/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs
deleted file mode 100644
index 7b0911a7..00000000
--- a/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-// Licensed to Elasticsearch B.V under one or more agreements.
-// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
-// See the LICENSE file in the project root for more information
-
-using System.Runtime.CompilerServices;
-using Elastic.OpenTelemetry;
-using Elastic.OpenTelemetry.Configuration;
-using Elastic.OpenTelemetry.Core;
-using Elastic.OpenTelemetry.Diagnostics;
-using Elastic.OpenTelemetry.Exporters;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
-using OpenTelemetry.Exporter;
-using OpenTelemetry.Logs;
-using OpenTelemetry.Resources;
-
-// Matching namespace with LoggerProviderBuilder
-#pragma warning disable IDE0130 // Namespace does not match folder structure
-namespace OpenTelemetry;
-#pragma warning restore IDE0130 // Namespace does not match folder structure
-
-///
-/// Provides extension methods on the used to register
-/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
-///
-public static class LoggingProviderBuilderExtensions
-{
- ///
- /// Used to track the number of times any variation of `WithElasticDefaults` is invoked by consuming
- /// code across all instances. This allows us to warn about potential
- /// misconfigurations.
- ///
- private static int WithElasticDefaultsCallCount;
-
- ///
- /// Use Elastic Distribution of OpenTelemetry (EDOT) .NET defaults for .
- ///
- /// The to configure.
- /// Thrown when the is null.
- /// The for chaining configuration.
- public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder)
- {
-#if NET
- ArgumentNullException.ThrowIfNull(builder);
-#else
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
-#endif
-
- return WithElasticDefaultsCore(builder, null, null, null);
- }
-
- ///
- ///
- ///
- ///
- /// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
- /// Thrown when the is null.
- /// Thrown when the is null.
- ///
- public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, ElasticOpenTelemetryOptions options)
- {
-#if NET
- ArgumentNullException.ThrowIfNull(builder);
- ArgumentNullException.ThrowIfNull(options);
-#else
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
-
- if (options is null)
- throw new ArgumentNullException(nameof(options));
-#endif
-
- return WithElasticDefaultsCore(builder, new(options), null, null);
- }
-
- ///
- ///
- ///
- ///
- /// An instance from which to load the OpenTelemetry SDK options.
- /// Thrown when the is null.
- /// Thrown when the is null.
- ///
- public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, IConfiguration configuration)
- {
-#if NET
- ArgumentNullException.ThrowIfNull(builder);
- ArgumentNullException.ThrowIfNull(configuration);
-#else
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
-
- if (configuration is null)
- throw new ArgumentNullException(nameof(configuration));
-#endif
- return WithElasticDefaultsCore(builder, new(configuration), null, null);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, ElasticOpenTelemetryComponents components, IServiceCollection? services) =>
- WithElasticDefaultsCore(builder, components.Options, components, services);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static LoggerProviderBuilder WithElasticDefaultsCore(
- this LoggerProviderBuilder builder,
- CompositeElasticOpenTelemetryOptions? options,
- ElasticOpenTelemetryComponents? components,
- IServiceCollection? services)
- {
- var logger = SignalBuilder.GetLogger(builder, components, options, null);
-
- var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
-
- if (callCount > 1)
- {
- logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, nameof(LoggerProviderBuilder));
- }
- else
- {
- logger.LogWithElasticDefaultsCallCount(callCount, nameof(LoggerProviderBuilder));
- }
-
- return SignalBuilder.WithElasticDefaults(builder, Signals.Traces, options, components, services, ConfigureBuilder);
-
- static void ConfigureBuilder(LoggerProviderBuilder builder, BuilderState builderState, IServiceCollection? services)
- {
- const string loggingProviderName = nameof(LoggerProviderBuilder);
- var components = builderState.Components;
- var logger = components.Logger;
-
- logger.LogConfiguringBuilder(loggingProviderName, builderState.InstanceIdentifier);
-
- builder.ConfigureResource(r => r.WithElasticDefaults(builderState, services));
-
- // When services is not null here, the options will have already been configured by the calling code.
- if (services is null)
- builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions));
-
- builder.ConfigureServices(sc => sc.Configure(o => o.WithElasticDefaults(logger)));
-
- if (builder is IDeferredLoggerProviderBuilder deferredBuilder)
- {
- var httpContextType = Type.GetType("Microsoft.AspNetCore.Http.HttpContext, Microsoft.AspNetCore.Http.Abstractions");
-
- if (httpContextType is not null)
- {
- var options = deferredBuilder.Configure((sp, _) =>
- {
- var options = sp.GetService>();
-
- if (options is not null && options.Value.IncludeScopes == true)
- {
- logger.LogDetectedIncludeScopesWarning();
- }
- });
- }
- }
-
- if (components.Options.SkipOtlpExporter)
- {
- logger.LogSkippingOtlpExporter(nameof(Signals.Logs), loggingProviderName, builderState.InstanceIdentifier);
- }
- else
- {
- builder.AddOtlpExporter();
- }
-
- logger.LogConfiguredSignalProvider(nameof(Signals.Logs), loggingProviderName, builderState.InstanceIdentifier);
- }
- }
-}
diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs
index e9e98e23..f3919099 100644
--- a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs
@@ -12,10 +12,13 @@
using Elastic.OpenTelemetry.Instrumentation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
+using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
// Matching namespace with MeterProviderBuilder
#pragma warning disable IDE0130 // Namespace does not match folder structure
@@ -24,7 +27,8 @@ namespace OpenTelemetry;
///
/// Provides extension methods on the used to register
-/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults
+/// for metrics.
///
public static class MeterProviderBuilderExtensions
{
@@ -39,12 +43,47 @@ public static class MeterProviderBuilderExtensions
/// Use Elastic Distribution of OpenTelemetry .NET defaults for .
///
///
- /// Calling this method is not neccesary if
- /// has been called previously as that automatically adds the defaults for all signals.
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
///
/// The to configure.
/// Thrown when the is null.
- /// The for chaining configuration.
+ ///
+ /// An instance of that can be used to further configure the metrics signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder)
{
#if NET
@@ -54,17 +93,111 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
throw new ArgumentNullException(nameof(builder));
#endif
- return WithElasticDefaultsCore(builder, null, null, null);
+ return WithElasticDefaultsCore(builder, null, null, null, default);
}
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ ///
+ ///
+ /// An used to further configure the .
+ /// This action is invoked after EDOT .NET defaults
+ /// have been applied, but before the OTLP exporter is added. This ensures that any custom processors run before the exporter.
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the metrics signal.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the action to customise
+ /// the after EDOT .NET defaults are applied and before the OTLP exporter is added.
+ ///
+ ///
+ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
+ }
+
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
///
/// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ /// An instance of that can be used to further configure the metrics signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder, ElasticOpenTelemetryOptions options)
{
#if NET
@@ -78,18 +211,85 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
throw new ArgumentNullException(nameof(options));
#endif
- return WithElasticDefaultsCore(builder, new(options), null, null);
+ return WithElasticDefaultsCore(builder, new(options), null, null, default);
}
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder,
+ ElasticOpenTelemetryOptions options, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
+ }
+
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
///
/// An instance from which to load the Elastic Distribution of
/// OpenTelemetry (EDOT) .NET options.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ /// An instance of that can be used to further configure the metrics signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder, IConfiguration configuration)
{
#if NET
@@ -102,28 +302,38 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
- return WithElasticDefaultsCore(builder, new(configuration), null, null);
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, default);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static MeterProviderBuilder WithElasticDefaults(
- this MeterProviderBuilder builder,
- ElasticOpenTelemetryComponents components,
- IServiceCollection serviceCollection) =>
- WithElasticDefaultsCore(builder, components.Options, components, serviceCollection);
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder,
+ IConfiguration configuration, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static MeterProviderBuilder WithElasticDefaults(
- this MeterProviderBuilder builder,
- IConfiguration configuration,
- IServiceCollection serviceCollection) =>
- WithElasticDefaultsCore(builder, new(configuration), null, serviceCollection);
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static MeterProviderBuilder WithElasticDefaults(
- this MeterProviderBuilder builder,
- IServiceCollection serviceCollection) =>
- WithElasticDefaultsCore(builder, null, null, serviceCollection);
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
+ }
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "The calls to `AddSqlClientInstrumentation` and `AssemblyScanning.AddInstrumentationViaReflection` " +
"are guarded by a RuntimeFeature.IsDynamicCodeSupported` check and therefore this method is safe to call in AoT scenarios.")]
@@ -131,7 +341,8 @@ internal static MeterProviderBuilder WithElasticDefaultsCore(
this MeterProviderBuilder builder,
CompositeElasticOpenTelemetryOptions? options,
ElasticOpenTelemetryComponents? components,
- IServiceCollection? services)
+ IServiceCollection? services,
+ in BuilderOptions builderOptions)
{
var logger = SignalBuilder.GetLogger(builder, components, options, null);
@@ -146,21 +357,30 @@ internal static MeterProviderBuilder WithElasticDefaultsCore(
logger.LogWithElasticDefaultsCallCount(callCount, nameof(MeterProviderBuilder));
}
- return SignalBuilder.WithElasticDefaults(builder, Signals.Traces, options, components, services, ConfigureBuilder);
+ return SignalBuilder.WithElasticDefaults(builder, options, components, services, builderOptions, ConfigureBuilder);
- static void ConfigureBuilder(MeterProviderBuilder builder, BuilderState builderState, IServiceCollection? services)
+ static void ConfigureBuilder(BuilderContext builderContext)
{
- const string meterProviderBuilderName = nameof(MeterProviderBuilder);
+ var builder = builderContext.Builder;
+ var builderState = builderContext.BuilderState;
var components = builderState.Components;
var logger = components.Logger;
+ var services = builderContext.Services;
+
+ // FullName may return null so we fallback to Name when required.
+ var meterProviderBuilderName = builder.GetType().FullName ?? builder.GetType().Name;
logger.LogConfiguringBuilder(meterProviderBuilderName, builderState.InstanceIdentifier);
builder.ConfigureResource(r => r.WithElasticDefaults(builderState, services));
- // When services is not null here, the options will have already been configured by the calling code.
+ // When services is not null here, the options will have already been configured by the calling code so
+ // we don't need to do it again.
if (services is null)
+ {
builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions));
+ logger.LogConfiguredOtlpExporterOptions();
+ }
builder.ConfigureServices(sc => sc.Configure(o =>
o.TemporalityPreference = MetricReaderTemporalityPreference.Delta));
@@ -227,16 +447,32 @@ static void ConfigureBuilder(MeterProviderBuilder builder, BuilderState builderS
ContribMetricsInstrumentation.GetMetricsInstrumentationAssembliesInfo(), builderState.InstanceIdentifier);
}
- if (components.Options.SkipOtlpExporter)
+ // Execute user-provided configuration action
+ var userProvidedConfigureBuilder = builderContext.BuilderOptions.UserProvidedConfigureBuilder;
+ if (userProvidedConfigureBuilder is not null)
+ {
+ userProvidedConfigureBuilder(builder);
+ logger.LogInvokedConfigureAction(meterProviderBuilderName, builderState.InstanceIdentifier);
+ }
+
+ if (builderContext.BuilderOptions.DeferAddOtlpExporter)
{
- logger.LogSkippingOtlpExporter(nameof(Signals.Traces), nameof(MeterProviderBuilder), builderState.InstanceIdentifier);
+ logger.LogDeferredOtlpExporter(meterProviderBuilderName, builderState.InstanceIdentifier);
}
else
{
- builder.AddOtlpExporter();
+ if (components.Options.SkipOtlpExporter)
+ {
+ logger.LogSkippedOtlpExporter(nameof(Signals.Metrics), meterProviderBuilderName, builderState.InstanceIdentifier);
+ }
+ else
+ {
+ builder.AddOtlpExporter();
+ logger.LogAddedOtlpExporter(nameof(Signals.Metrics), meterProviderBuilderName, builderState.InstanceIdentifier);
+ }
}
- logger.LogConfiguredSignalProvider(nameof(Signals.Logs), nameof(MeterProviderBuilder), builderState.InstanceIdentifier);
+ logger.LogConfiguredSignalProvider(nameof(Signals.Metrics), meterProviderBuilderName, builderState.InstanceIdentifier);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs
index 83419f7c..7549aaa5 100644
--- a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs
@@ -7,15 +7,11 @@
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
using Elastic.OpenTelemetry.Diagnostics;
-using Elastic.OpenTelemetry.Exporters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Diagnostics.Metrics;
-using Microsoft.Extensions.Logging;
-using OpenTelemetry.Exporter;
+using Microsoft.Extensions.Hosting;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
-using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
// Matching namespace with OpenTelemetryBuilder
@@ -25,7 +21,8 @@ namespace OpenTelemetry;
///
/// Provides extension methods on the
-/// used to register the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+/// used to register the Elastic Distribution of OpenTelemetry (EDOT) .NET
+/// defaults.
///
public static class OpenTelemetryBuilderExtensions
{
@@ -37,12 +34,45 @@ public static class OpenTelemetryBuilderExtensions
private static int WithElasticDefaultsCallCount;
///
- /// Enables collection of all signals using Elastic Distribution of OpenTelemetry .NET defaults.
+ /// Enables collection of all signals using Elastic Distribution of OpenTelemetry .NET
+ /// defaults.
///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticDefaults methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration action and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
+ ///
/// The being configured.
- ///
/// Thrown when the is null.
- /// The supplied for chaining calls.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration action and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder)
{
@@ -53,18 +83,103 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
throw new ArgumentNullException(nameof(builder));
#endif
- return WithElasticDefaultsCore(builder, CompositeElasticOpenTelemetryOptions.DefaultOptions);
+ return WithElasticDefaultsCore(builder, CompositeElasticOpenTelemetryOptions.DefaultOptions, default);
}
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticDefaults methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// The being configured.
+ /// A configuration action used to further customise
+ /// the OpenTelemetry SDK after Elastic Distribution of OpenTelemetry (EDOT) .NET defaults have been applied. This
+ /// callback is invoked before the OTLP exporter is added.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// The supplied for chaining calls.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the parameter to customise
+ /// the builder instead. This accepts an configuration action and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, CompositeElasticOpenTelemetryOptions.DefaultOptions, builderOptions);
+ }
+
+ ///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticDefaults methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
+ ///
///
/// An from which to attempt binding of Elastic Distribution of OpenTelemetry
/// (EDOT) options.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder, IConfiguration configuration)
{
#if NET
@@ -78,12 +193,58 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
throw new ArgumentNullException(nameof(configuration));
#endif
- return WithElasticDefaultsCore(builder, new(configuration));
+ return WithElasticDefaultsCore(builder, new(configuration), default);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder,
+ IConfiguration configuration, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(configuration), builderOptions);
}
///
///
///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticDefaults methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
+ ///
///
///
/// An instance used to configure the initial Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
@@ -92,7 +253,25 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
///
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithLogging,
+ /// WithTracing or WithMetrics will be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder, ElasticOpenTelemetryOptions options)
{
#if NET
@@ -106,55 +285,80 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
throw new ArgumentNullException(nameof(options));
#endif
- return WithElasticDefaultsCore(builder, new(options));
+ return WithElasticDefaultsCore(builder, new(options), default);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static IOpenTelemetryBuilder WithElasticDefaultsCore(
- this IOpenTelemetryBuilder builder,
- CompositeElasticOpenTelemetryOptions options)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder,
+ ElasticOpenTelemetryOptions options, Action configureBuilder)
{
- var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
-
- var providerBuilderName = builder.GetType().Name;
-
- var logger = SignalBuilder.GetLogger(builder, null, options, null);
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
- if (callCount > 1)
- {
- logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, nameof(IOpenTelemetryBuilder));
- }
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
- // If for some reason `WithElasticDefaults` is invoked with the `Signals` option set to
- // none, we skip bootstrapping entirely. We log this as a warning since it's best to
- // simply not call `WithElasticDefaults` in this scenario and it may indicate a misconfiguration.
- if (options.Signals == Signals.None)
- {
- logger.LogSkippingBootstrapWarning();
- return builder;
- }
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
- return SignalBuilder.WithElasticDefaults(builder, options, null, builder.Services, ConfigureBuilder);
+ return WithElasticDefaultsCore(builder, new(options), default);
}
///
- /// Adds metric services into the using Elastic Distribution of
- /// OpenTelemetry (EDOT) .NET defaults.
+ /// Adds logging services into the using Elastic Distribution of
+ /// OpenTelemetry (EDOT) .NET defaults.
///
- /// .
///
- /// Notes:
- ///
- /// - This is safe to be called multiple times and by library authors.
- /// Only a single will be created for a given
- /// .
- /// - This method automatically registers an named 'OpenTelemetry' into the .
- ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticLogging methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
///
+ ///
/// Thrown when the is null.
- /// The supplied for chaining calls.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithLogging will
+ /// be applied after the OTLP exporter has been added to the .
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilder builder)
{
#if NET
@@ -167,15 +371,36 @@ public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilde
return builder.WithLogging(lpb => lpb.WithElasticDefaults());
}
- ///
- ///
- ///
- ///
- ///
- /// configuration callback.
+ ///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticLogging methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ ///
+ ///
+ /// An used to further configure the .
+ /// This action is invoked after EDOT .NET defaults
+ /// have been applied, but before the OTLP exporter is added. This ensures that any custom processors run before the exporter.
+ ///
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithLogging will
+ /// be applied after the OTLP exporter has been added to the . Use the
+ /// parameter to apply customisations before the exporter is added.
+ ///
+ ///
public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilder builder, Action configure)
{
#if NET
@@ -189,30 +414,49 @@ public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilde
throw new ArgumentNullException(nameof(configure));
#endif
- return builder.WithLogging(lpb =>
- {
- lpb.WithElasticDefaults();
- configure?.Invoke(lpb);
- });
+ return builder.WithLogging(lpb => lpb.WithElasticDefaults(configure));
}
///
- /// Adds metric services into the using Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+ /// Adds metrics services into the using Elastic Distribution of
+ /// OpenTelemetry (EDOT) .NET defaults.
///
- /// .
///
- /// Notes:
- ///
- /// - This is safe to be called multiple times and by library authors.
- /// Only a single will be created for a given
- /// .
- /// - This method automatically registers an named 'OpenTelemetry' into the .
- ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticMetrics methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
///
+ ///
/// Thrown when the is null.
- /// The supplied for chaining calls.
+ ///
+ ///
+ /// An instance of that can be used to further configure the OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithMetrics will
+ /// be applied after the OTLP exporter has been added to the .
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder)
{
#if NET
@@ -225,76 +469,121 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
return builder.WithMetrics(mpb => mpb.WithElasticDefaults());
}
- ///
- ///
- ///
- ///
- ///
- /// An instance from which to load the Elastic Distribution of
- /// OpenTelemetry (EDOT) .NET options.
+ ///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticMetrics methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ ///
+ ///
+ /// An used to further configure the .
+ /// This action is invoked after EDOT .NET defaults
+ /// have been applied, but before the OTLP exporter is added. This ensures that any custom processors run before the exporter.
+ ///
/// Thrown when the is null.
- /// Thrown when the is null.
- ///
- public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, IConfiguration configuration)
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithMetrics will
+ /// be applied after the OTLP exporter has been added to the . Use the
+ /// parameter to apply customisations before the exporter is added.
+ ///
+ ///
+ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, Action configure)
{
#if NET
ArgumentNullException.ThrowIfNull(builder);
- ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(configure);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
- if (configuration is null)
- throw new ArgumentNullException(nameof(configuration));
+ if (configure is null)
+ throw new ArgumentNullException(nameof(configure));
#endif
- return builder.WithMetrics(mpb => mpb.WithElasticDefaults(configuration, builder.Services));
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return builder.WithMetrics(mpb => mpb.WithElasticDefaults(configure));
}
///
- ///
+ /// Adds metrics services into the using Elastic Distribution of
+ /// OpenTelemetry (EDOT) .NET defaults.
///
- ///
- ///
- /// configuration callback.
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticMetrics methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
+ ///
+ ///
+ ///
/// Thrown when the is null.
- /// Thrown when the is null.
- ///
- public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, Action configure)
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithMetrics will
+ /// be applied after the OTLP exporter has been added to the .
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, IConfiguration configuration)
{
#if NET
ArgumentNullException.ThrowIfNull(builder);
- ArgumentNullException.ThrowIfNull(configure);
+ ArgumentNullException.ThrowIfNull(configuration);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
- if (configure is null)
- throw new ArgumentNullException(nameof(configure));
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
#endif
- return builder.WithMetrics(mpb =>
- {
- mpb.WithElasticDefaults(builder.Services);
- configure?.Invoke(mpb);
- });
+ return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, default));
}
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ ///
+ ///
/// Thrown when the is null.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, IConfiguration configuration,
Action configure)
{
@@ -313,24 +602,50 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
throw new ArgumentNullException(nameof(configure));
#endif
- return builder.WithMetrics(mpb =>
- {
- mpb.WithElasticDefaults(configuration, builder.Services);
- configure?.Invoke(mpb);
- });
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, builderOptions));
}
///
- /// Adds tracing services into the using Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+ /// Adds tracing services into the using Elastic Distribution of
+ /// OpenTelemetry (EDOT) .NET defaults.
///
- /// .
///
- /// Note: This is safe to be called multiple times and by library authors.
- /// Only a single will be created for a given
- /// .
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticTracing methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
///
+ ///
/// Thrown when the is null.
- /// The supplied for chaining calls.
+ ///
+ ///
+ /// An instance of that can be used to further configure the OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithTracing will
+ /// be applied after the OTLP exporter has been added to the .
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder)
{
#if NET
@@ -343,76 +658,121 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
return builder.WithTracing(m => m.WithElasticDefaults());
}
- ///
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticTracing methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
///
- /// An instance from which to load the Elastic Distribution of
- /// OpenTelemetry (EDOT) .NET options.
+ ///
+ /// An used to further configure the .
+ /// This action is invoked after EDOT .NET defaults
+ /// have been applied, but before the OTLP exporter is added. This ensures that any custom processors run before the exporter.
+ ///
/// Thrown when the is null.
- /// Thrown when the is null.
- ///
- public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, IConfiguration configuration)
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithTracing will
+ /// be applied after the OTLP exporter has been added to the . Use the
+ /// parameter to apply customisations before the exporter is added.
+ ///
+ ///
+ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, Action configure)
{
#if NET
ArgumentNullException.ThrowIfNull(builder);
- ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(configure);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
- if (configuration is null)
- throw new ArgumentNullException(nameof(configuration));
+ if (configure is null)
+ throw new ArgumentNullException(nameof(configure));
#endif
- return builder.WithTracing(tpb => tpb.WithElasticDefaults(configuration, builder.Services));
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
///
- /// Adds tracing services into the builder using Elastic defaults.
+ /// Adds tracing services into the using Elastic Distribution of
+ /// OpenTelemetry (EDOT) .NET defaults.
///
- ///
+ ///
+ ///
+ /// Guidance: Prefer using the AddElasticOpenTelemetry method on the
+ /// or rather than calling this method on the directly.
+ ///
+ ///
+ /// The WithElasticTracing methods are primarily intended for advanced scenarios, non-host-based applications or when developing
+ /// libraries that need to customize the OpenTelemetry configuration.
+ ///
+ ///
+ /// Recommendation:When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures any customisations are applied before
+ /// the OTLP exporter is added.
+ ///
+ ///
///
- /// configuration callback.
+ ///
/// Thrown when the is null.
- /// Thrown when the is null.
- ///
- public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, Action configure)
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned by calling WithTracing will
+ /// be applied after the OTLP exporter has been added to the .
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, IConfiguration configuration)
{
#if NET
ArgumentNullException.ThrowIfNull(builder);
- ArgumentNullException.ThrowIfNull(configure);
+ ArgumentNullException.ThrowIfNull(configuration);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
- if (configure is null)
- throw new ArgumentNullException(nameof(configure));
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
#endif
- return builder.WithTracing(tpb =>
- {
- tpb.WithElasticDefaults();
- configure?.Invoke(tpb);
- });
+ return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, default));
}
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ ///
+ ///
/// Thrown when the is null.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, IConfiguration configuration,
Action configure)
{
@@ -431,21 +791,66 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
throw new ArgumentNullException(nameof(configure));
#endif
- return builder.WithTracing(tpb =>
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, builderOptions));
+ }
+
+ ///
+ /// Internal core implementation for the various overloads of WithElasticDefaults.
+ /// May also be called by other internal methods that need to apply EDOT .NET defaults.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static IOpenTelemetryBuilder WithElasticDefaultsCore(
+ this IOpenTelemetryBuilder builder,
+ CompositeElasticOpenTelemetryOptions options,
+ in BuilderOptions builderOptions)
+ {
+ var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
+
+ // FullName may return null so we fallback to Name when required.
+ var providerBuilderName = builder.GetType().FullName ?? builder.GetType().Name;
+
+ var logger = SignalBuilder.GetLogger(builder, null, options, null);
+
+ if (callCount > 1)
+ {
+ logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, providerBuilderName);
+ }
+
+ // If for some reason `WithElasticDefaults` is invoked with the `Signals` option set to
+ // none, we skip bootstrapping entirely. We log this as a warning since it's best to
+ // simply not call `WithElasticDefaults` in this scenario and it may indicate a misconfiguration.
+ if (options.Signals == Signals.None)
{
- tpb.WithElasticDefaults(configuration, builder.Services);
- configure?.Invoke(tpb);
- });
+ logger.LogSkippingBootstrapWarning();
+ return builder;
+ }
+
+ SignalBuilder.WithElasticDefaults(builder, options, null, builder.Services, builderOptions, ConfigureBuilder);
+
+ return builder;
}
- private static void ConfigureBuilder(IOpenTelemetryBuilder builder, BuilderState builderState, IServiceCollection? services)
+ private static void ConfigureBuilder(BuilderContext builderContext)
{
- var components = builderState.Components;
- var options = builderState.Components.Options;
+ var builder = builderContext.Builder;
+ var components = builderContext.BuilderState.Components;
+ var options = builderContext.BuilderState.Components.Options;
+ var builderState = builderContext.BuilderState;
+ var services = builderContext.Services;
+ // Configure tracing, if the signal is enabled.
if (options.Signals.HasFlagFast(Signals.Traces))
{
- builder.WithTracing(tpb => tpb.WithElasticDefaults(components, builder.Services));
+ // When the user has provided their own configuration callback for the IOpenTelemetryBuilder
+ // we don't want WithElasticDefaults for the TracerProviderBuilder to add the OTLP exporter so
+ // we defer it until after this method runs the user-provided callback.
+ var builderOptions = new BuilderOptions
+ {
+ DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null
+ };
+
+ builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(components.Options, components, services, builderOptions));
}
else
{
@@ -455,7 +860,15 @@ private static void ConfigureBuilder(IOpenTelemetryBuilder builder, BuilderState
if (options.Signals.HasFlagFast(Signals.Metrics))
{
- builder.WithMetrics(mpb => mpb.WithElasticDefaults(components, builder.Services));
+ // When the user has provided their own configuration callback for the IOpenTelemetryBuilder
+ // we don't want WithElasticDefaults for the MeterProviderBuilder to add the OTLP exporter so
+ // we defer it until after this method runs the user-provided callback.
+ var builderOptions = new BuilderOptions
+ {
+ DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null
+ };
+
+ builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(components.Options, components, builder.Services, builderOptions));
}
else
{
@@ -465,12 +878,55 @@ private static void ConfigureBuilder(IOpenTelemetryBuilder builder, BuilderState
if (options.Signals.HasFlagFast(Signals.Logs))
{
- builder.WithLogging(lpb => lpb.WithElasticDefaults(components, builder.Services));
+ // When the user has provided their own configuration callback for the IOpenTelemetryBuilder
+ // we don't want WithElasticDefaults for the MeterProviderBuilder to add the OTLP exporter so
+ // we defer it until after this method runs the user-provided callback.
+ var builderOptions = new BuilderOptions
+ {
+ DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null
+ };
+
+ builder.WithLogging(lpb => lpb.WithElasticDefaultsCore(components.Options, components, builder.Services, builderOptions));
}
else
{
components.Logger.LogSignalDisabled(Signals.Logs.ToString().ToLower(),
nameof(IOpenTelemetryBuilder), builderState.InstanceIdentifier);
}
+
+ // FullName may return null so we fallback to Name when required.
+ var builderTypeName = builder.GetType().FullName ?? builder.GetType().Name;
+
+ if (builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null)
+ {
+ // Run the user-provided builder configuration after the EDOT defaults have been applied.
+ builderContext.BuilderOptions.UserProvidedConfigureBuilder(builder);
+ components.Logger.LogInvokedConfigureAction(builderTypeName, builderState.InstanceIdentifier);
+
+ // Add OTLP exporters (if enabled) after user-provided configuration
+ HandleOtlpExporter(Signals.Traces, b => b.WithTracing(tpb => tpb.AddOtlpExporter()));
+ HandleOtlpExporter(Signals.Metrics, b => b.WithMetrics(tpb => tpb.AddOtlpExporter()));
+ HandleOtlpExporter(Signals.Logs, b => b.WithLogging(tpb => tpb.AddOtlpExporter()));
+ }
+
+ void HandleOtlpExporter(Signals signal, Action configure)
+ {
+ // If the signal is enabled and the user provided their own configuration callback,
+ // we need to ensure the OTLP exporter is added after running the user-provided callback.
+ if (options.Signals.HasFlagFast(signal))
+ {
+ if (builderState.Components.Options.SkipOtlpExporter)
+ {
+ components.Logger.LogSkippedOtlpExporter(signal.ToString(), builderTypeName, builderState.InstanceIdentifier);
+ }
+ else
+ {
+ configure(builder);
+ components.Logger.LogAddedOtlpExporter(signal.ToString(), builderTypeName, builderState.InstanceIdentifier);
+ }
+ }
+
+ // NOTE: We don't log signal disabled here since that will already have been logged above.
+ }
}
}
diff --git a/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs
index 133f67fb..4a725d91 100644
--- a/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs
@@ -5,10 +5,14 @@
using System.Runtime.CompilerServices;
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.Configuration;
+using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Diagnostics;
+using Elastic.OpenTelemetry.Exporters;
using Elastic.OpenTelemetry.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using OpenTelemetry;
+using OpenTelemetry.Exporter;
// ReSharper disable once CheckNamespace
#pragma warning disable IDE0130 // Namespace does not match folder structure
@@ -22,14 +26,31 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
///
- /// Registers the OpenTelemetry SDK services with the provided to include the
- /// OpenTelemetry SDK in the application, configured with Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+ /// Registers the OpenTelemetry SDK with the application, configured with Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults for traces,
+ /// metrics and logs.
///
/// The for adding services.
/// Thrown when the is null.
///
- /// An instance of that can be used to further configure the
- /// OpenTelemetry SDK.
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the overload
+ /// to customise the builder instead. This accepts an configuration action and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
///
public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services)
{
@@ -40,7 +61,54 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(services));
#endif
- return AddElasticOpenTelemetryCore(services, CompositeElasticOpenTelemetryOptions.DefaultOptions);
+ return AddElasticOpenTelemetryCore(services, CompositeElasticOpenTelemetryOptions.DefaultOptions, default);
+ }
+
+ ///
+ ///
+ ///
+ /// The for adding services.
+ /// A configuration action used to further customise
+ /// the OpenTelemetry SDK after Elastic Distribution of OpenTelemetry (EDOT) .NET defaults have been applied and
+ /// before the OTLP exporter is added.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the parameter to customise
+ /// the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services, Action configure)
+ {
+ // TODO - Breaking change: In a future major release, rename this parameter to 'configureBuilder' for clarity and consistency.
+ // This would be a source breaking change only but we'll reserve it for a major version to avoid disrupting consumers.
+#if NET
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(configure);
+#else
+ if (services is null)
+ throw new ArgumentNullException(nameof(services));
+
+ if (configure is null)
+ throw new ArgumentNullException(nameof(configure));
+#endif
+
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return AddElasticOpenTelemetryCore(services, CompositeElasticOpenTelemetryOptions.DefaultOptions, builderOptions);
}
///
@@ -52,7 +120,27 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
///
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload
+ /// to customise the builder instead. This accepts an configuration action and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services, ElasticOpenTelemetryOptions options)
{
#if NET
@@ -66,7 +154,62 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(options));
#endif
- return AddElasticOpenTelemetryCore(services, new(options));
+ return AddElasticOpenTelemetryCore(services, new(options), default);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the parameter to customise
+ /// the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services,
+ ElasticOpenTelemetryOptions options, Action configure)
+ {
+ // TODO - Breaking change: In a future major release, rename this parameter to 'configureBuilder' for clarity and consistency.
+ // This would be a source breaking change only but we'll reserve it for a major version to avoid disrupting consumers.
+#if NET
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(configure);
+#else
+ if (services is null)
+ throw new ArgumentNullException(nameof(services));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(configure));
+#endif
+
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return AddElasticOpenTelemetryCore(services, new(options), builderOptions);
}
///
@@ -78,7 +221,23 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
///
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation: When not using AddElasticOpenTelemetry, it is strongly recommended to use the
+ /// overload instead. This accepts
+ /// an configuration callback and ensures anycustomisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services, IConfiguration configuration)
{
#if NET
@@ -92,29 +251,47 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(configuration));
#endif
- return AddElasticOpenTelemetryCore(services, new(configuration));
+ return AddElasticOpenTelemetryCore(services, new(configuration), default);
}
///
- ///
+ ///
///
- ///
- /// Configuration is first bound from and then overridden by any options configured on
- /// the provided .
- ///
///
- /// An instance from which to attempt binding of configuration values.
- /// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
+ ///
+ ///
/// Thrown when the is null.
/// Thrown when the is null.
- /// Thrown when the is null.
- ///
- internal static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services, IConfiguration configuration, ElasticOpenTelemetryOptions options)
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the
+ /// OpenTelemetry SDK.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the parameter to customise
+ /// the builder instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// can be used to prevent automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
+ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services,
+ IConfiguration configuration, Action configure)
{
+ // TODO - Breaking change: In a future major release, rename this parameter to 'configureBuilder' for clarity and consistency.
+ // This would be a source breaking change only but we'll reserve it for a major version to avoid disrupting consumers.
#if NET
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
- ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(configure);
#else
if (services is null)
throw new ArgumentNullException(nameof(services));
@@ -122,19 +299,28 @@ internal static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceColle
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
- if (options is null)
- throw new ArgumentNullException(nameof(options));
+ if (configure is null)
+ throw new ArgumentNullException(nameof(configure));
#endif
- return AddElasticOpenTelemetryCore(services, new(configuration, options));
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ return AddElasticOpenTelemetryCore(services, new(configuration), builderOptions);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static IOpenTelemetryBuilder AddElasticOpenTelemetryCore(IServiceCollection services, CompositeElasticOpenTelemetryOptions options)
+ internal static IOpenTelemetryBuilder AddElasticOpenTelemetryCore(
+ this IServiceCollection services,
+ CompositeElasticOpenTelemetryOptions options,
+ in BuilderOptions builderOptions)
{
- var builder = services.AddOpenTelemetry().WithElasticDefaultsCore(options);
+ var logger = DeferredLogger.GetOrCreate(options);
+
+ services.Configure(OtlpExporterDefaults.OtlpExporterOptions);
+ logger.LogConfiguredOtlpExporterOptions();
+
+ var builder = services.AddOpenTelemetry().WithElasticDefaultsCore(options, builderOptions);
- if (!services.Any((ServiceDescriptor d) => d.ServiceType == typeof(IHostedService) && d.ImplementationType == typeof(ElasticOpenTelemetryService)))
+ if (!services.Any(d => d.ServiceType == typeof(IHostedService) && d.ImplementationType == typeof(ElasticOpenTelemetryService)))
{
services.Insert(0, ServiceDescriptor.Singleton());
}
diff --git a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs
index 58037d8a..bdb1d559 100644
--- a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs
@@ -14,6 +14,7 @@
using Elastic.OpenTelemetry.Instrumentation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
@@ -26,43 +27,177 @@ namespace OpenTelemetry;
///
/// Provides extension methods on the used to register
-/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
+/// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
///
public static class TracerProviderBuilderExtensions
{
private static int WithElasticDefaultsCallCount;
///
- /// Use Elastic Distribution of OpenTelemetry .NET defaults for .
+ /// Apply Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults
+ /// to the .
///
///
- /// This is not neccesary if
- /// has been called previously as that automatically adds the .
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
///
/// The to configure.
/// Thrown when the is null.
- /// The for chaining configuration.
+ ///
+ /// An instance of that can be used to further configure the trace signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ /// overload instead. This accepts an configuration callback and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder)
{
#if NET
- ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
- return WithElasticDefaultsCore(builder, null, null, null);
+ return WithElasticDefaultsCore(builder, null, null, null, default);
}
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. For example,
+ /// This ensures that your configuration is invoked after EDOT .NET defaults have been applied, but before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the action.
+ ///
+ ///
+ ///
+ ///
+ /// An used to further configure the .
+ /// This action is invoked after EDOT .NET defaults
+ /// have been applied, but before the OTLP exporter is added. This ensures that any custom processors run before the exporter.
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ ///
+ /// An instance of that can be used to further configure the trace signal.
+ ///
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the action to customise
+ /// the after EDOT .NET defaults are applied and before the OTLP exporter is added.
+ ///
+ ///
+ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
+ }
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
///
/// used to configure the Elastic Distribution of OpenTelemetry (EDOT) .NET.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ /// An instance of that can be used to further configure the trace signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ ///
+ /// overload instead. This accepts an configuration action and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options)
{
#if NET
@@ -76,17 +211,86 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
throw new ArgumentNullException(nameof(options));
#endif
- return WithElasticDefaultsCore(builder, new(options), null, null);
+ return WithElasticDefaultsCore(builder, new(options), null, null, default);
}
- ///
- ///
- ///
+ ///
+ ///
///
- /// An instance from which to load the OpenTelemetry SDK options.
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder,
+ ElasticOpenTelemetryOptions options, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
+ }
+
+ ///
+ ///
+ ///
+ /// This method is intended for advanced scenarios where per signal Elastic Distribution of OpenTelemetry (EDOT) .NET
+ /// defaults are being enabled. In most scenarios, prefer the AddElasticOpenTelemetry method on
+ /// or .
+ ///
+ ///
+ /// Warning: Avoid calling this method when using the AddElasticOpenTelemetry method on
+ /// or . In those scenarios, the OpenTelemetry SDK will have already been configured with
+ /// defaults, including the OTLP exporter. Calling this method again may lead to unexpected behavior.
+ ///
+ ///
+ /// When using AddElasticOpenTelemetry, use the overload on the
+ /// or which accepts a configuration
+ /// to further customize the SDK setup. This action is invoked after EDOT .NET defaults have been applied, but
+ /// before the OTLP exporter is added.
+ ///
+ ///
+ /// Note: If you wish to customise the after the EDOT .NET defaults have been applied,
+ /// use the overload that accepts a
+ /// configuration action.
+ ///
+ ///
+ ///
+ /// An instance from which to bind the OpenTelemetry SDK options.
/// Thrown when the is null.
/// Thrown when the is null.
- ///
+ ///
+ /// An instance of that can be used to further configure the trace signal.
+ ///
+ /// Warning: Any further configuration applied via the returned will
+ /// be applied after the OTLP exporter has been added.
+ ///
+ ///
+ /// Recommendation:It is strongly recommended to use the
+ ///
+ /// overload instead. This accepts an configuration action and ensures any
+ /// customisations are applied before the OTLP exporter is added.
+ ///
+ ///
+ /// Alternatively, the
+ /// option
+ /// automatic addition of the OTLP exporter, allowing you to add it manually at a later stage.
+ ///
+ ///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder, IConfiguration configuration)
{
#if NET
@@ -99,27 +303,46 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
- return WithElasticDefaultsCore(builder, new(configuration), null, null);
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, default);
}
- internal static TracerProviderBuilder WithElasticDefaults(
- this TracerProviderBuilder builder,
- IConfiguration configuration,
- IServiceCollection serviceCollection) =>
- WithElasticDefaultsCore(builder, new(configuration), null, serviceCollection);
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ /// Thrown when the is null.
+ ///
+ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder,
+ IConfiguration configuration, Action configureBuilder)
+ {
+#if NET
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(configureBuilder);
+#else
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
- internal static TracerProviderBuilder WithElasticDefaults(
- this TracerProviderBuilder builder,
- ElasticOpenTelemetryComponents components,
- IServiceCollection? services) =>
- WithElasticDefaultsCore(builder, components.Options, components, services);
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
+
+ if (configureBuilder is null)
+ throw new ArgumentNullException(nameof(configureBuilder));
+#endif
+ var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static TracerProviderBuilder WithElasticDefaultsCore(
- TracerProviderBuilder builder,
+ internal static TracerProviderBuilder WithElasticDefaultsCore(
+ this TracerProviderBuilder builder,
CompositeElasticOpenTelemetryOptions? options,
ElasticOpenTelemetryComponents? components,
- IServiceCollection? services)
+ IServiceCollection? services,
+ in BuilderOptions builderOptions)
{
var logger = SignalBuilder.GetLogger(builder, components, options, null);
@@ -134,25 +357,34 @@ private static TracerProviderBuilder WithElasticDefaultsCore(
logger.LogWithElasticDefaultsCallCount(callCount, nameof(TracerProviderBuilder));
}
- return SignalBuilder.WithElasticDefaults(builder, Signals.Traces, options, components, services, ConfigureBuilder);
+ return SignalBuilder.WithElasticDefaults(builder, options, components, services, builderOptions, ConfigureBuilder);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "The call to AssemblyScanning.AddInstrumentationViaReflection` " +
"is guarded by a RuntimeFeature.IsDynamicCodeSupported` check and, therefore, this method is safe to call in AoT scenarios.")]
- private static void ConfigureBuilder(TracerProviderBuilder builder, BuilderState builderState, IServiceCollection? services)
+ private static void ConfigureBuilder(BuilderContext builderContext)
{
- const string tracerProviderBuilderName = nameof(TracerProviderBuilder);
-
+ var builder = builderContext.Builder;
+ var builderState = builderContext.BuilderState;
var components = builderState.Components;
var logger = components.Logger;
+ var services = builderContext.Services;
+
+ // FullName may return null so we fallback to Name when required.
+ var tracerProviderBuilderName = builder.GetType().FullName ?? builder.GetType().Name;
logger.LogConfiguringBuilder(tracerProviderBuilderName, builderState.InstanceIdentifier);
builder.ConfigureResource(r => r.WithElasticDefaults(builderState, services));
+ // When services is not null here, the options will have already been configured by the calling code so
+ // we don't need to do it again.
if (services is null)
+ {
builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions));
+ logger.LogConfiguredOtlpExporterOptions();
+ }
#if NET9_0_OR_GREATER
// .NET 9 introduced semantic convention compatible instrumentation in System.Net.Http so it's recommended to no longer
@@ -190,16 +422,32 @@ private static void ConfigureBuilder(TracerProviderBuilder builder, BuilderState
TracerProvderBuilderExtensions.AddElasticProcessorsCore(builder, builderState, null, services);
- if (components.Options.SkipOtlpExporter)
+ var userProvidedConfigureBuilder = builderContext.BuilderOptions.UserProvidedConfigureBuilder;
+ if (userProvidedConfigureBuilder is not null)
+ {
+ userProvidedConfigureBuilder(builder);
+ logger.LogInvokedConfigureAction(tracerProviderBuilderName, builderState.InstanceIdentifier);
+ }
+
+ // If a callee will register the OTLP exporter later, we skip adding it here.
+ if (builderContext.BuilderOptions.DeferAddOtlpExporter)
{
- logger.LogSkippingOtlpExporter(nameof(Signals.Traces), nameof(TracerProviderBuilder), builderState.InstanceIdentifier);
+ logger.LogDeferredOtlpExporter(tracerProviderBuilderName, builderState.InstanceIdentifier);
}
else
{
- builder.AddOtlpExporter();
+ if (components.Options.SkipOtlpExporter)
+ {
+ logger.LogSkippedOtlpExporter(nameof(Signals.Traces), tracerProviderBuilderName, builderState.InstanceIdentifier);
+ }
+ else
+ {
+ builder.AddOtlpExporter();
+ logger.LogAddedOtlpExporter(nameof(Signals.Traces), tracerProviderBuilderName, builderState.InstanceIdentifier);
+ }
}
- logger.LogConfiguredSignalProvider(nameof(Signals.Traces), nameof(TracerProviderBuilder), builderState.InstanceIdentifier);
+ logger.LogConfiguredSignalProvider(nameof(Signals.Traces), tracerProviderBuilderName, builderState.InstanceIdentifier);
}
[UnconditionalSuppressMessage("DynamicCode", "IL2026", Justification = "The call to this method is guarded by a RuntimeFeature.IsDynamicCodeSupported` " +
diff --git a/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/ApmUIBrowserContext.cs b/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/ApmUIBrowserContext.cs
index 299e7ad2..3a22c9f2 100644
--- a/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/ApmUIBrowserContext.cs
+++ b/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/ApmUIBrowserContext.cs
@@ -2,7 +2,6 @@
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information
-using System.IO.Compression;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright;
diff --git a/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DistributedApplicationFixture.cs b/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DistributedApplicationFixture.cs
index 4cf85006..bad61def 100644
--- a/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DistributedApplicationFixture.cs
+++ b/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DistributedApplicationFixture.cs
@@ -6,7 +6,6 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Configuration;
-using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Nullean.Xunit.Partitions.Sdk;
using Xunit.Abstractions;
diff --git a/tests/Elastic.OpenTelemetry.Tests/Extensions/ServiceCollectionTests.cs b/tests/Elastic.OpenTelemetry.Tests/Extensions/ServiceCollectionTests.cs
index 66eacfef..3ee1a60f 100644
--- a/tests/Elastic.OpenTelemetry.Tests/Extensions/ServiceCollectionTests.cs
+++ b/tests/Elastic.OpenTelemetry.Tests/Extensions/ServiceCollectionTests.cs
@@ -157,7 +157,7 @@ public void AddElasticOpenTelemetry_AppliesConfigAndOptions_InExpectedOrder()
.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)))
.Build();
- serviceCollection.AddElasticOpenTelemetry(config, options);
+ serviceCollection.AddElasticOpenTelemetryCore(new(config, options), default);
using var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/tests/Elastic.OpenTelemetry.Tests/TestLogger.cs b/tests/Elastic.OpenTelemetry.Tests/TestLogger.cs
index 6145958e..675156ff 100644
--- a/tests/Elastic.OpenTelemetry.Tests/TestLogger.cs
+++ b/tests/Elastic.OpenTelemetry.Tests/TestLogger.cs
@@ -22,9 +22,17 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
{
var message = LogFormatter.Format(logLevel, eventId, state, exception, formatter);
_messages.Add(message);
- _testOutputHelper.WriteLine(message);
- if (exception != null)
- _testOutputHelper.WriteLine(exception.ToString());
+
+ try
+ {
+ _testOutputHelper.WriteLine(message);
+ if (exception != null)
+ _testOutputHelper.WriteLine(exception.ToString());
+ }
+ catch
+ {
+ // Swallow exceptions from the test output helper
+ }
}
private class NoopDisposable : IDisposable