diff --git a/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs b/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs
index ad280d50..f39509e6 100644
--- a/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs
+++ b/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs
@@ -25,10 +25,17 @@ namespace Elastic.OpenTelemetry;
///
public class AutoInstrumentationPlugin
{
- private readonly ElasticOpenTelemetryComponents _components;
+ // NOTE: We don't use nameof + string interpolation for the bootstrap log messages.
+ // This avoids cluttering the code with a check to see if bootstrap logging is enabled before the log message.
+ // This class will change rarely so the risk of renaming issues is low.
- ///
- public AutoInstrumentationPlugin() => _components = ElasticOpenTelemetry.Bootstrap(SdkActivationMethod.AutoInstrumentation);
+ private static readonly ElasticOpenTelemetryComponents Components;
+
+ static AutoInstrumentationPlugin()
+ {
+ BootstrapLogger.LogWithStackTrace("AutoInstrumentationPlugin: Initializing via static constructor");
+ Components = ElasticOpenTelemetry.Bootstrap(SdkActivationMethod.AutoInstrumentation, new(), null);
+ }
///
/// Configure Resource Builder for Logs, Metrics and Traces
@@ -37,7 +44,8 @@ public class AutoInstrumentationPlugin
/// Returns for chaining.
public ResourceBuilder ConfigureResource(ResourceBuilder builder)
{
- builder.WithElasticDefaultsCore(_components, null, null);
+ BootstrapLogger.Log("AutoInstrumentationPlugin: ConfigureResource invoked");
+ builder.WithElasticDefaultsCore(Components, null, null);
return builder;
}
@@ -46,14 +54,15 @@ public ResourceBuilder ConfigureResource(ResourceBuilder builder)
///
public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder builder)
{
- var logger = _components.Logger;
+ BootstrapLogger.Log("AutoInstrumentationPlugin: BeforeConfigureTracerProvider invoked");
+ var logger = Components.Logger;
try
{
logger.LogInformation("Configuring Elastic Distribution of OpenTelemetry .NET defaults for tracing auto-instrumentation.");
ElasticTracerProviderBuilderExtensions.AddActivitySourceWithLogging(builder, logger, "Elastic.Transport", "");
- ElasticTracerProviderBuilderExtensions.AddElasticProcessorsCore(builder, null, _components, null);
+ ElasticTracerProviderBuilderExtensions.AddElasticProcessorsCore(builder, null, Components, null);
logger.LogConfiguredSignalProvider("Traces", nameof(TracerProviderBuilder), "");
@@ -72,13 +81,21 @@ public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder
/// Configure traces OTLP exporter options.
///
/// Otlp options.
- public void ConfigureTracesOptions(OtlpExporterOptions options) => ConfigureOtlpExporter(options, "traces");
+ public void ConfigureTracesOptions(OtlpExporterOptions options)
+ {
+ BootstrapLogger.Log("AutoInstrumentationPlugin: ConfigureTracesOptions(OtlpExporterOptions) invoked");
+ ConfigureOtlpExporter(options, "traces");
+ }
///
/// Configure metrics OTLP exporter options
///
/// Otlp options
- public void ConfigureMetricsOptions(OtlpExporterOptions options) => ConfigureOtlpExporter(options, "metrics");
+ public void ConfigureMetricsOptions(OtlpExporterOptions options)
+ {
+ BootstrapLogger.Log("AutoInstrumentationPlugin: ConfigureMetricsOptions(OtlpExporterOptions) invoked");
+ ConfigureOtlpExporter(options, "metrics");
+ }
///
/// Configure metrics OTLP exporter options
@@ -86,7 +103,8 @@ public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder
/// Otlp options
public void ConfigureMetricsOptions(MetricReaderOptions options)
{
- var logger = _components.Logger;
+ BootstrapLogger.Log("AutoInstrumentationPlugin: ConfigureMetricsOptions(MetricReaderOptions) invoked");
+ var logger = Components.Logger;
options.TemporalityPreference = MetricReaderTemporalityPreference.Delta;
logger.LogInformation("Configured Elastic Distribution of OpenTelemetry .NET defaults for logging auto-instrumentation.");
}
@@ -95,17 +113,24 @@ public void ConfigureMetricsOptions(MetricReaderOptions options)
/// Configure logging OTLP exporter options.
///
/// Otlp options.
- public void ConfigureLogsOptions(OtlpExporterOptions options) => ConfigureOtlpExporter(options, "logs");
+ public void ConfigureLogsOptions(OtlpExporterOptions options)
+ {
+ BootstrapLogger.Log("AutoInstrumentationPlugin: ConfigureLogsOptions(OtlpExporterOptions) invoked");
+ ConfigureOtlpExporter(options, "logs");
+ }
///
/// 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)
+ {
+ BootstrapLogger.Log("AutoInstrumentationPlugin: ConfigureLogsOptions(OpenTelemetryLoggerOptions) invoked");
+ options.WithElasticDefaults(Components.Logger);
+ }
- private void ConfigureOtlpExporter(OtlpExporterOptions options, string signal)
+ private static void ConfigureOtlpExporter(OtlpExporterOptions options, string signal)
{
- var logger = _components.Logger;
options.ConfigureElasticUserAgent();
- logger.LogConfiguredOtlpExporterOptions(signal);
+ Components.Logger.LogConfiguredOtlpExporterOptions(signal);
}
}
diff --git a/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs b/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs
index 90b80d05..ada39859 100644
--- a/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs
+++ b/src/Elastic.OpenTelemetry.Core/BuilderOptions.cs
@@ -6,4 +6,6 @@ namespace Elastic.OpenTelemetry.Core;
internal readonly record struct BuilderOptions(
Action? UserProvidedConfigureBuilder,
- bool DeferAddOtlpExporter) where T : class;
+ bool DeferAddOtlpExporter,
+ bool SkipLogCallerInfo,
+ string? CalleeName = null) where T : class;
diff --git a/src/Elastic.OpenTelemetry.Core/Configuration/CompositeElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry.Core/Configuration/CompositeElasticOpenTelemetryOptions.cs
index 55e805da..41aa516a 100644
--- a/src/Elastic.OpenTelemetry.Core/Configuration/CompositeElasticOpenTelemetryOptions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Configuration/CompositeElasticOpenTelemetryOptions.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Elastic.OpenTelemetry.Configuration.Instrumentations;
using Elastic.OpenTelemetry.Configuration.Parsers;
@@ -34,7 +35,21 @@ namespace Elastic.OpenTelemetry.Configuration;
///
internal sealed class CompositeElasticOpenTelemetryOptions
{
- private readonly EventLevel _eventLevel = EventLevel.Informational;
+ // These are the options that users can set via IConfiguration
+ private static readonly string[] ElasticOpenTelemetryConfigKeys =
+ [
+ nameof(LogDirectory),
+ nameof(LogLevel),
+ nameof(LogTargets),
+ nameof(SkipOtlpExporter),
+ nameof(SkipInstrumentationAssemblyScanning)
+ ];
+
+ internal static CompositeElasticOpenTelemetryOptions DefaultOptions = new();
+
+ internal Guid InstanceId { get; } = Guid.NewGuid();
+
+ private readonly EventLevel _eventLevel = EventLevel.Warning;
private readonly ConfigCell _logDirectory = new(nameof(LogDirectory), null);
private readonly ConfigCell _logTargets = new(nameof(LogTargets), null);
@@ -50,9 +65,7 @@ internal sealed class CompositeElasticOpenTelemetryOptions
private readonly ConfigCell _logging = new(nameof(Logging), LogInstrumentations.All);
private readonly IDictionary _environmentVariables;
-
- internal static CompositeElasticOpenTelemetryOptions DefaultOptions = new();
- internal static CompositeElasticOpenTelemetryOptions SkipOtlpOptions = new() { SkipOtlpExporter = true };
+ private readonly IConfiguration? _configuration;
///
/// Creates a new instance of with properties
@@ -60,13 +73,21 @@ internal sealed class CompositeElasticOpenTelemetryOptions
///
internal CompositeElasticOpenTelemetryOptions() : this((IDictionary?)null)
{
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.LogWithStackTrace($"{nameof(CompositeElasticOpenTelemetryOptions)}: Instance '{InstanceId}' created via parameterless ctor.");
}
internal CompositeElasticOpenTelemetryOptions(IDictionary? environmentVariables)
{
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.LogWithStackTrace($"{nameof(CompositeElasticOpenTelemetryOptions)}: Instance '{InstanceId}' created via ctor `(IDictionary? environmentVariables)`.");
+
LogDirectoryDefault = GetDefaultLogDirectory();
_environmentVariables = environmentVariables ?? GetEnvironmentVariables();
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"CompositeElasticOpenTelemetryOptions(IDictionary): Read {_environmentVariables.Count} environment variables");
+
SetFromEnvironment(DOTNET_RUNNING_IN_CONTAINER, _runningInContainer, BoolParser);
SetFromEnvironment(OTEL_DOTNET_AUTO_LOG_DIRECTORY, _logDirectory, StringParser);
SetFromEnvironment(OTEL_LOG_LEVEL, _logLevel, LogLevelParser);
@@ -74,6 +95,11 @@ internal CompositeElasticOpenTelemetryOptions(IDictionary? environmentVariables)
SetFromEnvironment(ELASTIC_OTEL_SKIP_OTLP_EXPORTER, _skipOtlpExporter, BoolParser);
SetFromEnvironment(ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING, _skipInstrumentationAssemblyScanning, BoolParser);
+ _eventLevel = ConfigurationParser.LogLevelToEventLevel(_logLevel.Value);
+
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"CompositeElasticOpenTelemetryOptions(IDictionary): Event log level set to: {_eventLevel}");
+
var parser = new EnvironmentParser(_environmentVariables);
parser.ParseInstrumentationVariables(_signals, _tracing, _metrics, _logging);
}
@@ -83,8 +109,20 @@ internal CompositeElasticOpenTelemetryOptions(IDictionary? environmentVariables)
internal CompositeElasticOpenTelemetryOptions(IConfiguration? configuration, IDictionary? environmentVariables = null)
: this(environmentVariables)
{
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.LogWithStackTrace($"{nameof(CompositeElasticOpenTelemetryOptions)}: Instance '{InstanceId}' created via ctor `(IConfiguration? configuration, IDictionary? environmentVariables)`.");
+
if (configuration is null)
+ {
+ BootstrapLogger.Log($"CompositeElasticOpenTelemetryOptions: Param `configuration` was `null`.");
return;
+ }
+
+ if (environmentVariables is null)
+ {
+ BootstrapLogger.Log($"CompositeElasticOpenTelemetryOptions: Param `environmentVariables` was `null`.");
+ return;
+ }
var parser = new ConfigurationParser(configuration);
@@ -93,6 +131,8 @@ internal CompositeElasticOpenTelemetryOptions(IConfiguration? configuration, IDi
parser.ParseLogLevel(_logLevel, ref _eventLevel);
parser.ParseSkipOtlpExporter(_skipOtlpExporter);
parser.ParseSkipInstrumentationAssemblyScanning(_skipInstrumentationAssemblyScanning);
+
+ _configuration = configuration;
}
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Manually verified")]
@@ -100,6 +140,12 @@ internal CompositeElasticOpenTelemetryOptions(IConfiguration? configuration, IDi
internal CompositeElasticOpenTelemetryOptions(IConfiguration configuration, ElasticOpenTelemetryOptions options)
: this((IDictionary?)null)
{
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(CompositeElasticOpenTelemetryOptions)}: Instance '{InstanceId}'." +
+ $"{NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
var parser = new ConfigurationParser(configuration);
parser.ParseLogDirectory(_logDirectory);
@@ -108,6 +154,9 @@ internal CompositeElasticOpenTelemetryOptions(IConfiguration configuration, Elas
parser.ParseSkipOtlpExporter(_skipOtlpExporter);
parser.ParseSkipInstrumentationAssemblyScanning(_skipInstrumentationAssemblyScanning);
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(CompositeElasticOpenTelemetryOptions)}: Configuration binding from IConfiguration completed.");
+
if (options.SkipOtlpExporter.HasValue)
_skipOtlpExporter.Assign(options.SkipOtlpExporter.Value, ConfigSource.Options);
@@ -124,11 +173,23 @@ internal CompositeElasticOpenTelemetryOptions(IConfiguration configuration, Elas
_skipInstrumentationAssemblyScanning.Assign(options.SkipInstrumentationAssemblyScanning.Value, ConfigSource.Options);
AdditionalLogger = options.AdditionalLogger ?? options.AdditionalLoggerFactory?.CreateElasticLogger();
+
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(CompositeElasticOpenTelemetryOptions)}: Configuration binding from user-provided `ElasticOpenTelemetryOptions` completed.");
+
+ _configuration = configuration;
}
internal CompositeElasticOpenTelemetryOptions(ElasticOpenTelemetryOptions options)
: this((IDictionary?)null)
{
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(CompositeElasticOpenTelemetryOptions)}: Instance '{InstanceId}' created via ctor `(ElasticOpenTelemetryOptions options)`." +
+ $"{NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}' with hash '{RuntimeHelpers.GetHashCode(options)}'.");
+ }
+
+ // This should not happen, but just in case
if (options is null)
return;
@@ -151,10 +212,10 @@ internal CompositeElasticOpenTelemetryOptions(ElasticOpenTelemetryOptions option
_skipInstrumentationAssemblyScanning.Assign(options.SkipInstrumentationAssemblyScanning.Value, ConfigSource.Options);
AdditionalLogger = options.AdditionalLogger ?? options.AdditionalLoggerFactory?.CreateElasticLogger();
- }
- internal CompositeElasticOpenTelemetryOptions(IConfiguration configuration, ILoggerFactory loggerFactory) :
- this(configuration) => AdditionalLogger = loggerFactory?.CreateElasticLogger();
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(CompositeElasticOpenTelemetryOptions)}: Configuration binding from user-provided `ElasticOpenTelemetryOptions` completed.");
+ }
///
/// Calculates whether global logging is enabled based on
@@ -182,13 +243,18 @@ private static string GetDefaultLogDirectory()
{
const string applicationMoniker = "elastic-otel-dotnet";
+ string? directory;
+
if (IsOSPlatform(OSPlatform.Windows))
- return Path.Combine(GetFolderPath(SpecialFolder.ApplicationData), "elastic", applicationMoniker);
+ directory = Path.Combine(GetFolderPath(SpecialFolder.ApplicationData), "elastic", applicationMoniker);
+ else if (IsOSPlatform(OSPlatform.OSX))
+ directory = Path.Combine(GetFolderPath(SpecialFolder.LocalApplicationData), "elastic", applicationMoniker);
+ else
+ directory = $"/var/log/elastic/{applicationMoniker}";
- if (IsOSPlatform(OSPlatform.OSX))
- return Path.Combine(GetFolderPath(SpecialFolder.LocalApplicationData), "elastic", applicationMoniker);
+ BootstrapLogger.Log($"Default log directory resolved to: {directory}");
- return $"/var/log/elastic/{applicationMoniker}";
+ return directory;
}
///
@@ -298,6 +364,7 @@ public override bool Equals(object? obj)
LogLevel == other.LogLevel &&
LogTargets == other.LogTargets &&
SkipOtlpExporter == other.SkipOtlpExporter &&
+ SkipInstrumentationAssemblyScanning == other.SkipInstrumentationAssemblyScanning &&
Signals == other.Signals &&
Tracing.SetEquals(other.Tracing) &&
Metrics.SetEquals(other.Metrics) &&
@@ -312,6 +379,7 @@ public override int GetHashCode()
^ LogLevel.GetHashCode()
^ LogTargets.GetHashCode()
^ SkipOtlpExporter.GetHashCode()
+ ^ SkipInstrumentationAssemblyScanning.GetHashCode()
^ Signals.GetHashCode()
^ Tracing.GetHashCode()
^ Metrics.GetHashCode()
@@ -320,7 +388,8 @@ public override int GetHashCode()
#else
var hash1 = HashCode.Combine(LogDirectory, LogLevel, LogTargets, SkipOtlpExporter);
var hash2 = HashCode.Combine(Signals, Tracing, Metrics, Logging, AdditionalLogger);
- return HashCode.Combine(hash1, hash2);
+ var hash3 = HashCode.Combine(SkipInstrumentationAssemblyScanning);
+ return HashCode.Combine(hash1, hash2, hash3);
#endif
}
@@ -328,12 +397,6 @@ private void SetFromEnvironment(string key, ConfigCell field, Func(string key, ConfigCell field, Func(ILogger logger, ConfigCell cell)
+ {
+ // To reduce noise, we log as info only if not default, otherwise, log as debug
+ if (cell.Source == ConfigSource.Default)
+ logger.LogDebug("Configured value for {Configuration}", cell);
+ else
+ logger.LogInformation("Configured value for {Configuration}", cell);
+ }
+ }
+
+ internal void LogApplicationConfigurationValues(ILogger logger)
+ {
+ if (_configuration is null)
+ return;
+
+ if (_configuration is IConfigurationRoot configRoot)
+ {
+ if (configRoot != null)
+ {
+ foreach (var provider in configRoot.Providers)
+ {
+ var providerName = provider.ToString();
+
+ var keys = provider.GetChildKeys([], null);
+
+ foreach (var key in keys)
+ {
+ if (!key.StartsWith("OTEL_", StringComparison.Ordinal))
+ continue;
+
+ if (provider.TryGet(key, out var value) && value is not null)
+ {
+ if (SensitiveEnvironmentVariables.Contains(key))
+ {
+ value = "";
+ }
+
+ logger.LogInformation("IConfiguration [{providerName}] '{Key}' = '{Value}'", providerName, key, value);
+ }
+ }
+
+ foreach (var key in ElasticOpenTelemetryConfigKeys)
+ {
+ if (provider.TryGet($"Elastic:OpenTelemetry:{key}", out var value) && value is not null)
+ {
+ logger.LogInformation("IConfiguration [{providerName}] '{Key}' = '{Value}'", providerName, key, value);
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/src/Elastic.OpenTelemetry.Core/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry.Core/Configuration/ElasticOpenTelemetryOptions.cs
index e179f09e..a1b45684 100644
--- a/src/Elastic.OpenTelemetry.Core/Configuration/ElasticOpenTelemetryOptions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Configuration/ElasticOpenTelemetryOptions.cs
@@ -15,6 +15,11 @@ namespace Elastic.OpenTelemetry;
///
public class ElasticOpenTelemetryOptions
{
+ ///
+ /// Used internally for bootstrap logging to identify this options instance.
+ ///
+ internal Guid InstanceId { get; } = Guid.NewGuid();
+
///
/// The output directory where the Elastic Distribution of OpenTelemetry .NET will write log files.
///
diff --git a/src/Elastic.OpenTelemetry.Core/Configuration/EnvironmentVariables.cs b/src/Elastic.OpenTelemetry.Core/Configuration/EnvironmentVariables.cs
index 01991ddc..8809c255 100644
--- a/src/Elastic.OpenTelemetry.Core/Configuration/EnvironmentVariables.cs
+++ b/src/Elastic.OpenTelemetry.Core/Configuration/EnvironmentVariables.cs
@@ -6,40 +6,45 @@ namespace Elastic.OpenTelemetry.Configuration;
internal static class EnvironmentVariables
{
- // ReSharper disable InconsistentNaming
- // ReSharper disable IdentifierTypo
- public const string ELASTIC_OTEL_SKIP_OTLP_EXPORTER = nameof(ELASTIC_OTEL_SKIP_OTLP_EXPORTER);
- public const string ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING = nameof(ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING);
- public const string ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS);
-
- public const string OTEL_DOTNET_AUTO_LOG_DIRECTORY = nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY);
- public const string OTEL_LOG_LEVEL = nameof(OTEL_LOG_LEVEL);
-
- public const string DOTNET_RUNNING_IN_CONTAINER = nameof(DOTNET_RUNNING_IN_CONTAINER);
- public const string OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED);
-
- public const string OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED);
- public const string OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED);
- public const string OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED);
-
- public const string OTEL_EXPORTER_OTLP_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_ENDPOINT);
- public const string OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);
- public const string OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT);
- public const string OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT);
-
- public const string OTEL_EXPORTER_OTLP_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_PROTOCOL);
- public const string OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL);
- public const string OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_METRICS_PROTOCOL);
- public const string OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_LOGS_PROTOCOL);
-
- public const string OTEL_EXPORTER_OTLP_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_TIMEOUT);
- public const string OTEL_EXPORTER_OTLP_TRACES_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_TRACES_TIMEOUT);
- public const string OTEL_EXPORTER_OTLP_METRICS_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT);
- public const string OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT);
- public const string OTEL_EXPORTER_OTLP_HEADERS = nameof(OTEL_EXPORTER_OTLP_HEADERS);
- public const string OTEL_EXPORTER_OTLP_TRACES_HEADERS = nameof(OTEL_EXPORTER_OTLP_TRACES_HEADERS);
- public const string OTEL_EXPORTER_OTLP_METRICS_HEADERS = nameof(OTEL_EXPORTER_OTLP_METRICS_HEADERS);
- public const string OTEL_EXPORTER_OTLP_LOGS_HEADERS = nameof(OTEL_EXPORTER_OTLP_LOGS_HEADERS);
- // ReSharper enable IdentifierTypo
- // ReSharper enable InconsistentNaming
+ internal const string ELASTIC_OTEL_SKIP_OTLP_EXPORTER = nameof(ELASTIC_OTEL_SKIP_OTLP_EXPORTER);
+ internal const string ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING = nameof(ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING);
+ internal const string ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS);
+
+ internal const string OTEL_DOTNET_AUTO_LOG_DIRECTORY = nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY);
+ internal const string OTEL_LOG_LEVEL = nameof(OTEL_LOG_LEVEL);
+
+ internal const string DOTNET_RUNNING_IN_CONTAINER = nameof(DOTNET_RUNNING_IN_CONTAINER);
+ internal const string OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED);
+
+ internal const string OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED);
+ internal const string OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED);
+ internal const string OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED);
+
+ internal const string OTEL_EXPORTER_OTLP_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_ENDPOINT);
+ internal const string OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);
+ internal const string OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT);
+ internal const string OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT);
+
+ internal const string OTEL_EXPORTER_OTLP_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_PROTOCOL);
+ internal const string OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL);
+ internal const string OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_METRICS_PROTOCOL);
+ internal const string OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_LOGS_PROTOCOL);
+
+ internal const string OTEL_EXPORTER_OTLP_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_TIMEOUT);
+ internal const string OTEL_EXPORTER_OTLP_TRACES_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_TRACES_TIMEOUT);
+ internal const string OTEL_EXPORTER_OTLP_METRICS_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT);
+ internal const string OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT);
+
+ internal const string OTEL_EXPORTER_OTLP_HEADERS = nameof(OTEL_EXPORTER_OTLP_HEADERS);
+ internal const string OTEL_EXPORTER_OTLP_TRACES_HEADERS = nameof(OTEL_EXPORTER_OTLP_TRACES_HEADERS);
+ internal const string OTEL_EXPORTER_OTLP_METRICS_HEADERS = nameof(OTEL_EXPORTER_OTLP_METRICS_HEADERS);
+ internal const string OTEL_EXPORTER_OTLP_LOGS_HEADERS = nameof(OTEL_EXPORTER_OTLP_LOGS_HEADERS);
+
+ internal static readonly string[] SensitiveEnvironmentVariables =
+ [
+ OTEL_EXPORTER_OTLP_HEADERS,
+ OTEL_EXPORTER_OTLP_TRACES_HEADERS,
+ OTEL_EXPORTER_OTLP_METRICS_HEADERS,
+ OTEL_EXPORTER_OTLP_LOGS_HEADERS
+ ];
}
diff --git a/src/Elastic.OpenTelemetry.Core/Configuration/Parsers/ConfigurationParser.cs b/src/Elastic.OpenTelemetry.Core/Configuration/Parsers/ConfigurationParser.cs
index 64e8bc06..38967b69 100644
--- a/src/Elastic.OpenTelemetry.Core/Configuration/Parsers/ConfigurationParser.cs
+++ b/src/Elastic.OpenTelemetry.Core/Configuration/Parsers/ConfigurationParser.cs
@@ -77,17 +77,19 @@ public void ParseLogLevel(ConfigCell logLevel, ref EventLevel eventLe
eventLogLevel = sectionLogLevel;
}
- eventLevel = eventLogLevel switch
+ eventLevel = LogLevelToEventLevel(eventLogLevel);
+ }
+
+ internal static EventLevel LogLevelToEventLevel(LogLevel? eventLogLevel) =>
+ eventLogLevel switch
{
- LogLevel.Trace => EventLevel.Verbose,
+ LogLevel.Trace or LogLevel.Debug => EventLevel.LogAlways,
LogLevel.Information => EventLevel.Informational,
LogLevel.Warning => EventLevel.Warning,
LogLevel.Error => EventLevel.Error,
LogLevel.Critical => EventLevel.Critical,
-
_ => EventLevel.Informational // fallback to info level
};
- }
public void ParseSkipOtlpExporter(ConfigCell skipOtlpExporter) =>
SetFromConfiguration(_configuration, skipOtlpExporter, BoolParser);
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/BootstrapLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/BootstrapLogger.cs
new file mode 100644
index 00000000..bdc453a8
--- /dev/null
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/BootstrapLogger.cs
@@ -0,0 +1,207 @@
+// 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 System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Elastic.OpenTelemetry.Core;
+
+namespace Elastic.OpenTelemetry.Diagnostics;
+
+///
+/// Used to log bootstrap information before the main logger is initialized.
+/// This is an experimental feature, mostly for debugging purposes but can be useful in certain support scenarios.
+/// This logger writes to a file in the directory specified by the OTEL_DOTNET_AUTO_LOG_DIRECTORY environment variable.
+/// It supports automatic disposal after 1 minute of inactivity to avoid unnecessary resource usage.
+///
+internal static class BootstrapLogger
+{
+ private static StreamWriter? Writer;
+ private static System.Timers.Timer? AutoCloseTimer;
+ private static DateTime LastActivityUtc;
+
+ static BootstrapLogger()
+ {
+ try
+ {
+ var logDirectory = Environment.GetEnvironmentVariable("OTEL_DOTNET_AUTO_LOG_DIRECTORY");
+ var logLevel = Environment.GetEnvironmentVariable("OTEL_LOG_LEVEL");
+ var enableBootstrapLogging = Environment.GetEnvironmentVariable("ELASTIC_OTEL_EXPERIMENTAL_ENABLE_BOOTSTRAP_LOGGING");
+
+ if (string.IsNullOrEmpty(logDirectory) ||
+ string.IsNullOrEmpty(logLevel) ||
+ string.IsNullOrEmpty(enableBootstrapLogging) ||
+ !logLevel.Equals("debug", StringComparison.OrdinalIgnoreCase) ||
+ !bool.TryParse(enableBootstrapLogging, out var isEnabled) ||
+ !isEnabled)
+ {
+ IsEnabled = false;
+ return;
+ }
+
+ IsEnabled = true;
+
+ Directory.CreateDirectory(logDirectory);
+
+ Writer = new StreamWriter(Path.Combine(logDirectory, $"{FileLogger.FileNamePrefix}bootstrap-{FileLogger.FileNameSuffix}"), append: true) { AutoFlush = true };
+
+ try
+ {
+ // This code is essentially a copy of the premable we log to the main logger.
+ // As this doesn't change often and differs subtly between use cases, we duplicate it here for simplicity.
+ // This might be useful in scenarios where the main logger fails to initialize.
+
+ try
+ {
+ var process = Process.GetCurrentProcess();
+
+ Writer.WriteLine("Process ID: {0}", process.Id);
+ Writer.WriteLine("Process name: {0}", process.ProcessName);
+ Writer.WriteLine("Process started: {0:O}", process.StartTime.ToUniversalTime());
+ Writer.WriteLine("Process working set: {0} bytes", process.WorkingSet64);
+ Writer.WriteLine("Thread count: {0}", process.Threads.Count);
+ }
+ catch
+ {
+ // GetCurrentProcess can throw PlatformNotSupportedException
+ }
+
+#if NET
+ Writer.WriteLine("Process path: {0}", Environment.ProcessPath);
+#elif NETSTANDARD
+ Writer.WriteLine("Process path: {0}", "");
+#elif NETFRAMEWORK
+ Writer.WriteLine("Process path: {0}", "");
+#endif
+
+ Writer.WriteLine("Process architecture: {0}", RuntimeInformation.ProcessArchitecture);
+
+ Writer.WriteLine("Current AppDomain name: {0}", AppDomain.CurrentDomain.FriendlyName);
+ Writer.WriteLine("Is default AppDomain: {0}", AppDomain.CurrentDomain.IsDefaultAppDomain());
+
+ Writer.WriteLine("Machine name: {0}", Environment.MachineName);
+ Writer.WriteLine("Process username: {0}", Environment.UserName);
+ Writer.WriteLine("User domain name: {0}", Environment.UserDomainName);
+ Writer.WriteLine("Application base directory: {0}", AppDomain.CurrentDomain.BaseDirectory);
+ Writer.WriteLine("Command current directory: {0}", Environment.CurrentDirectory);
+ Writer.WriteLine("Processor count: {0}", Environment.ProcessorCount);
+ Writer.WriteLine("GC is server GC: {0}", System.Runtime.GCSettings.IsServerGC);
+
+ Writer.WriteLine("OS architecture: {0}", RuntimeInformation.OSArchitecture);
+ Writer.WriteLine("OS description: {0}", RuntimeInformation.OSDescription);
+ Writer.WriteLine("OS version: {0}", Environment.OSVersion);
+
+ Writer.WriteLine(".NET framework: {0}", RuntimeInformation.FrameworkDescription);
+ Writer.WriteLine("CLR version: {0}", Environment.Version);
+
+ Writer.WriteLine("Current culture: {0}", CultureInfo.CurrentCulture.Name);
+ Writer.WriteLine("Current UI culture: {0}", CultureInfo.CurrentUICulture.Name);
+#if NETFRAMEWORK || NETSTANDARD2_0
+ Writer.WriteLine("Dynamic code supported: {0}", true);
+#else
+ Writer.WriteLine("Dynamic code supported: {0}", RuntimeFeature.IsDynamicCodeSupported);
+#endif
+ // We don't log environment variables here as if those are wrong, we won't even get this far.
+
+ Writer.Flush();
+ }
+ catch
+ {
+ // Swallow any exceptions to avoid impacting the application startup.
+ }
+
+ LastActivityUtc = DateTime.UtcNow;
+ AutoCloseTimer = new System.Timers.Timer(60_000); // 1 minute in milliseconds
+ AutoCloseTimer.Elapsed += (_, __) => CheckAndDisposeLogger();
+ AutoCloseTimer.AutoReset = true;
+ AutoCloseTimer.Start();
+
+ AppDomain.CurrentDomain.ProcessExit += (_, __) => DisposeLogger();
+ Console.CancelKeyPress += (_, __) => DisposeLogger();
+ }
+ catch
+ {
+ // Swallow any exceptions to avoid impacting the application startup.
+ Console.Error.WriteLine("Failed to initialize BootstrapLogger.");
+ IsEnabled = false;
+ }
+ }
+
+ public static bool IsEnabled { get; }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Log(string message)
+ {
+ try
+ {
+ if (!IsEnabled || Writer is null)
+ return;
+
+ LastActivityUtc = DateTime.UtcNow;
+ Writer.WriteLine($"[{DateTime.UtcNow:O}] {message}");
+ }
+ catch
+ {
+ // Swallow any exceptions to avoid impacting the application.
+ }
+ }
+
+ public static void LogWithStackTrace(string message)
+ {
+ // We don't inline this as it will be called less frequently and the stack trace generation is more expensive.
+
+ try
+ {
+ if (!IsEnabled || Writer is null)
+ return;
+
+ var stack = new StackTrace(skipFrames: 1, fNeedFileInfo: true);
+
+ LastActivityUtc = DateTime.UtcNow;
+ Writer.WriteLine($"[{DateTime.UtcNow:O}] {message}{Environment.NewLine}{stack}");
+ }
+ catch
+ {
+ // Swallow any exceptions to avoid impacting the application.
+ }
+ }
+
+ public static void LogBuilderOptions(BuilderOptions builderOptions, string type, string method) where T : class =>
+ Log($"{type}.{method} BuilderOptions:" +
+ $"{Environment.NewLine} {nameof(BuilderOptions<>.CalleeName)}: '{builderOptions.CalleeName}'" +
+ $"{Environment.NewLine} {nameof(BuilderOptions<>.SkipLogCallerInfo)}: '{builderOptions.SkipLogCallerInfo}'" +
+ $"{Environment.NewLine} {nameof(BuilderOptions<>.DeferAddOtlpExporter)}: '{builderOptions.DeferAddOtlpExporter}'" +
+ $"{Environment.NewLine} {nameof(BuilderOptions<>.UserProvidedConfigureBuilder)}: " +
+ $"'{(builderOptions.UserProvidedConfigureBuilder is null ? "`null`" : "not `null`")}'");
+
+ private static void CheckAndDisposeLogger()
+ {
+ try
+ {
+ if ((DateTime.UtcNow - LastActivityUtc).TotalMinutes >= 2)
+ {
+ Log("Disposing BootstrapLogger due to 1 minute of inactivity.");
+ DisposeLogger();
+ AutoCloseTimer?.Stop();
+ AutoCloseTimer?.Dispose();
+ AutoCloseTimer = null;
+ }
+ }
+ catch
+ {
+ Console.Error.WriteLine("Failed to check and dispose BootstrapLogger.");
+ }
+ }
+
+ private static void DisposeLogger()
+ {
+ Writer?.Dispose();
+ Writer = null;
+
+ AutoCloseTimer?.Stop();
+ AutoCloseTimer?.Dispose();
+ AutoCloseTimer = null;
+ }
+}
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs
index bf3dcbd8..1d0190ee 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs
@@ -2,6 +2,7 @@
// 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.Configuration;
using Elastic.OpenTelemetry.Core;
using Microsoft.Extensions.Logging;
@@ -15,16 +16,32 @@ namespace Elastic.OpenTelemetry.Diagnostics;
///
/// If disposed, triggers disposal of the .
///
-internal sealed class CompositeLogger(CompositeElasticOpenTelemetryOptions options) : IDisposable, IAsyncDisposable, ILogger
+internal sealed class CompositeLogger : IDisposable, IAsyncDisposable, ILogger
{
public const string LogCategory = "Elastic.OpenTelemetry";
- public FileLogger FileLogger { get; } = new(options);
- public StandardOutLogger ConsoleLogger { get; } = new(options);
+ internal Guid InstanceId { get; } = Guid.NewGuid();
- private ILogger? _additionalLogger = options.AdditionalLogger;
+ public FileLogger FileLogger { get; }
+ public StandardOutLogger ConsoleLogger { get; }
+
+ private ILogger? _additionalLogger;
private bool _isDisposed;
+ public CompositeLogger(CompositeElasticOpenTelemetryOptions options)
+ {
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(CompositeLogger)}: Instance '{InstanceId}' created via ctor." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
+ FileLogger = new(options);
+ ConsoleLogger = new(options);
+
+ _additionalLogger = options.AdditionalLogger;
+ }
+
public void Dispose()
{
_isDisposed = true;
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs
index b9f12bfb..2dc78b3a 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/DeferredLogger.cs
@@ -21,11 +21,12 @@ 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();
+ private ConcurrentQueue? _logQueue = new();
+
///
/// Create an instance of .
///
@@ -52,7 +53,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
{
_additionalLogger?.Log(logLevel, eventId, state, exception, formatter);
- if (!IsEnabled(logLevel))
+ if (!IsEnabled(logLevel) || _logQueue is null)
return;
var logLine = LogFormatter.Format(logLevel, eventId, state, exception, formatter);
@@ -61,18 +62,24 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
logLine = $"{logLine}{Environment.NewLine}{exception}";
_logQueue.Enqueue(logLine);
+
+ _additionalLogger?.Log(logLevel, eventId, state, exception, formatter);
}
internal void DrainAndRelease(StreamWriter streamWriter)
{
using (Lock.EnterScope())
{
+ if (_logQueue is null)
+ return;
+
while (_logQueue.TryDequeue(out var deferredLog))
{
streamWriter.WriteLine(deferredLog);
}
Instance = null;
+ _logQueue = null;
}
}
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs
index c636bb5c..dbec87a8 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs
@@ -12,6 +12,22 @@ namespace Elastic.OpenTelemetry.Diagnostics;
internal sealed class FileLogger : IDisposable, IAsyncDisposable, ILogger
{
+ internal static readonly string FileNamePrefix = "edot-dotnet-";
+ internal static readonly string FileNameSuffix;
+
+ static FileLogger()
+ {
+ var process = Process.GetCurrentProcess();
+
+ if (process is null)
+ {
+ FileNameSuffix = $"unknown-{DateTimeOffset.UtcNow:yyyyMMdd-HHmmssfffZ}.log";
+ return;
+ }
+
+ FileNameSuffix = $"{process.Id}-{process.ProcessName}-{DateTimeOffset.UtcNow:yyyyMMdd-HHmmssfffZ}.log";
+ }
+
private readonly ConcurrentQueue _logQueue = new();
private readonly SemaphoreSlim _logSemaphore = new(0);
private readonly CancellationTokenSource _cancellationTokenSource = new();
@@ -21,10 +37,18 @@ internal sealed class FileLogger : IDisposable, IAsyncDisposable, ILogger
private int _disposed;
+ internal Guid InstanceId { get; } = Guid.NewGuid();
+
public bool FileLoggingEnabled { get; }
public FileLogger(CompositeElasticOpenTelemetryOptions options)
{
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(FileLogger)}: Instance '{InstanceId}' created via ctor." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
_scopeProvider = new LoggerExternalScopeProvider();
_configuredLogLevel = options.LogLevel;
_streamWriter = StreamWriter.Null;
@@ -37,10 +61,8 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options)
try
{
- var process = Process.GetCurrentProcess();
-
// This naming resembles the naming structure for OpenTelemetry log files.
- var logFileName = $"edot-dotnet-{process.Id}-{process.ProcessName}-{DateTimeOffset.UtcNow:yyyyMMdd-HHmmssfffZ}.log";
+ var logFileName = $"{FileNamePrefix}{FileNameSuffix}";
var logDirectory = options.LogDirectory;
LogFilePath = Path.Combine(logDirectory, logFileName);
@@ -53,7 +75,7 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options)
_streamWriter = new StreamWriter(stream, Encoding.UTF8);
- _streamWriter.WriteLine("DateTime (UTC) Thread SpanId Level Message");
+ _streamWriter.WriteLine("DateTime (UTC) Thread SpanId Level Message");
_streamWriter.WriteLine();
// Drain any deferred log entries captured before the file logger was initialized.
@@ -128,6 +150,8 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options)
}
catch (Exception ex)
{
+ BootstrapLogger.Log($"{nameof(FileLogger)}: [ERROR] An exception occurred while initializing the file logger: {ex.Message}.");
+
if (options?.AdditionalLogger is not null)
options?.AdditionalLogger.LogError(new EventId(530, "FileLoggingFailure"), ex, "Failed to set up file logging due to exception: {ExceptionMessage}.", ex.Message);
else
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoadedAssemblyLogHelper.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoadedAssemblyLogHelper.cs
new file mode 100644
index 00000000..972a0d6c
--- /dev/null
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoadedAssemblyLogHelper.cs
@@ -0,0 +1,55 @@
+// 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.Logging;
+
+namespace Elastic.OpenTelemetry.Core.Diagnostics;
+
+internal sealed class LoadedAssemblyLogHelper
+{
+ private static HashSet? LoggedAssemblies;
+
+ internal static void LogLoadedAssemblies(ILogger logger)
+ {
+ if (!logger.IsEnabled(LogLevel.Debug))
+ return;
+
+ try
+ {
+ var assemblies = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(a => a.GetName().Name?.StartsWith("OpenTelemetry", StringComparison.OrdinalIgnoreCase) == true)
+ .OrderBy(a => a.GetName().Name)
+ .ToList();
+
+ if (assemblies.Count == 0)
+ {
+ return;
+ }
+
+ LoggedAssemblies ??= new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var assembly in assemblies)
+ {
+ var assemblyName = assembly.GetName();
+ var name = assemblyName.Name;
+
+ if (name is null)
+ continue;
+
+ var fileVersion = assembly.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(System.Reflection.AssemblyFileVersionAttribute))?.ConstructorArguments[0].Value;
+
+ var version = fileVersion ?? assemblyName.Version?.ToString() ?? "unknown";
+
+ if (LoggedAssemblies != null && !LoggedAssemblies.Add(name))
+ continue;
+
+ logger.LogDebug("OpenTelemetry assembly found: {AssemblyName} (v{Version})", name, version);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Unable to log loaded assemblies");
+ }
+ }
+}
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LogFormatter.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LogFormatter.cs
index bb9bf5a5..506869ab 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/LogFormatter.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LogFormatter.cs
@@ -85,7 +85,7 @@ private static void WriteLogPrefix(int managedThreadId, DateTime dateTime, LogLe
}
builder.Append('[')
- .Append(dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"))
+ .Append(dateTime.ToString("O"))
.Append("][")
.Append(threadId)
.Append("][")
@@ -95,7 +95,7 @@ private static void WriteLogPrefix(int managedThreadId, DateTime dateTime, LogLe
.Append(']');
var length = builder.Length;
- var padding = 55 - length;
+ var padding = 60 - length;
for (var i = 0; i < padding; i++)
builder.Append(' ');
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs
index e4ac32af..aeca849a 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs
@@ -2,11 +2,14 @@
// 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.Buffers;
+using System.Collections;
using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
using Microsoft.Extensions.Logging;
+
#if NET || NETSTANDARD2_1
using System.Runtime.CompilerServices;
#endif
@@ -163,8 +166,6 @@ internal static partial class LoggerMessages
[LoggerMessage(EventId = 60, EventName = "DetectedIncludeScopes", Level = LogLevel.Warning, Message = "IncludeScopes is enabled and may cause export issues. See https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/dotnet/troubleshooting.html#missing-log-records")]
internal static partial void LogDetectedIncludeScopesWarning(this ILogger logger);
-
-
public static void LogDistroPreamble(this ILogger logger, SdkActivationMethod activationMethod, ElasticOpenTelemetryComponents components)
{
// This occurs once per initialisation, so we don't use `LoggerMessage`s.
@@ -202,12 +203,15 @@ public static void LogDistroPreamble(this ILogger logger, SdkActivationMethod ac
logger.LogDebug("Process ID: {ProcessId}", process.Id);
logger.LogDebug("Process name: {ProcessName}", process.ProcessName);
-
- logger.LogDebug("Process started: {ProcessStartTime:yyyy-MM-dd HH:mm:ss.fff}", process.StartTime.ToUniversalTime());
+ logger.LogDebug("Process started: {ProcessStartTime:O}", process.StartTime.ToUniversalTime());
+ logger.LogDebug("Process working set: {WorkingSet} bytes", process.WorkingSet64);
+ logger.LogDebug("Thread count: {ThreadCount}", process.Threads.Count);
}
- catch
+ catch (Exception ex)
{
// GetCurrentProcess can throw PlatformNotSupportedException
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(LoggerMessage)}.{nameof(LogDistroPreamble)}: Unable to get current process information due to '{ex.Message}'.");
}
#if NET
@@ -218,114 +222,137 @@ public static void LogDistroPreamble(this ILogger logger, SdkActivationMethod ac
logger.LogDebug("Process path: {ProcessPath}", "");
#endif
+ logger.LogDebug("Process architecture: {ProcessArchitecture}", RuntimeInformation.ProcessArchitecture);
+
+ logger.LogDebug("Current AppDomain name: {AppDomainName}", AppDomain.CurrentDomain.FriendlyName);
+ logger.LogDebug("Is default AppDomain: {IsDefaultAppDomain}", AppDomain.CurrentDomain.IsDefaultAppDomain());
+
logger.LogDebug("Machine name: {MachineName}", Environment.MachineName);
logger.LogDebug("Process username: {UserName}", Environment.UserName);
logger.LogDebug("User domain name: {UserDomainName}", Environment.UserDomainName);
+ logger.LogDebug("Application base directory: {BaseDirectory}", AppDomain.CurrentDomain.BaseDirectory);
logger.LogDebug("Command current directory: {CurrentDirectory}", Environment.CurrentDirectory);
logger.LogDebug("Processor count: {ProcessorCount}", Environment.ProcessorCount);
+ logger.LogDebug("GC is server GC: {IsServerGC}", System.Runtime.GCSettings.IsServerGC);
+
+ logger.LogDebug("OS architecture: {OSArchitecture}", RuntimeInformation.OSArchitecture);
+ logger.LogDebug("OS description: {OSDescription}", RuntimeInformation.OSDescription);
logger.LogDebug("OS version: {OSVersion}", Environment.OSVersion);
+
+ logger.LogDebug(".NET framework: {FrameworkDescription}", RuntimeInformation.FrameworkDescription);
logger.LogDebug("CLR version: {CLRVersion}", Environment.Version);
+
+ logger.LogDebug("Current culture: {CurrentCulture}", CultureInfo.CurrentCulture.Name);
+ logger.LogDebug("Current UI culture: {CurrentUICulture}", CultureInfo.CurrentUICulture.Name);
#if NETFRAMEWORK || NETSTANDARD2_0
logger.LogDebug("Dynamic code supported: {IsDynamicCodeSupported}", true);
#else
logger.LogDebug("Dynamic code supported: {IsDynamicCodeSupported}", RuntimeFeature.IsDynamicCodeSupported);
#endif
- string[] environmentVariables =
- [
- EnvironmentVariables.OTEL_DOTNET_AUTO_LOG_DIRECTORY,
- EnvironmentVariables.OTEL_LOG_LEVEL,
- EnvironmentVariables.ELASTIC_OTEL_LOG_TARGETS,
- EnvironmentVariables.DOTNET_RUNNING_IN_CONTAINER,
- EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_ENDPOINT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_TIMEOUT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_PROTOCOL,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
- ];
-
- foreach (var variable in environmentVariables)
+ if (logger.IsEnabled(LogLevel.Information))
{
- var envVarValue = Environment.GetEnvironmentVariable(variable);
-
- if (string.IsNullOrEmpty(envVarValue))
- {
- logger.LogDebug("Environment variable '{EnvironmentVariable}' is not configured.", variable);
- }
- else
+ LogPrefixedEnvironmentVariables(logger, "OTEL_");
+ LogPrefixedEnvironmentVariables(logger, "ELASTIC_OTEL_");
+
+ string[] microsoftEnvironmentVariables =
+ [
+ EnvironmentVariables.DOTNET_RUNNING_IN_CONTAINER,
+ "COR_ENABLE_PROFILING",
+ "COR_PROFILER",
+ "COR_PROFILER_PATH_32",
+ "COR_PROFILER_PATH_64",
+ "CORECLR_ENABLE_PROFILING",
+ "CORECLR_PROFILER",
+ "CORECLR_PROFILER_PATH",
+ "CORECLR_PROFILER_PATH_32",
+ "CORECLR_PROFILER_PATH_64",
+ "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES",
+ "DOTNET_ADDITIONAL_DEPS",
+ "DOTNET_SHARED_STORE",
+ "DOTNET_STARTUP_HOOKS"
+ ];
+
+ foreach (var variable in microsoftEnvironmentVariables)
{
- logger.LogDebug("Environment variable '{EnvironmentVariable}' = '{EnvironmentVariableValue}'.", variable, envVarValue);
+ var envVarValue = Environment.GetEnvironmentVariable(variable);
+
+ if (envVarValue is not null)
+ logger.LogDebug("Effective runtime environment variable '{EnvironmentVariable}' = '{EnvironmentVariableValue}'.", variable, envVarValue);
}
}
- // This next set of env vars might include sensitive information, so we redact the values.
- string[] headerEnvironmentVariables =
- [
- EnvironmentVariables.OTEL_EXPORTER_OTLP_HEADERS,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_TRACES_HEADERS,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
- EnvironmentVariables.OTEL_EXPORTER_OTLP_LOGS_HEADERS,
- ];
+ components.Options.LogApplicationConfigurationValues(logger);
+ components.Options.LogConfigSources(logger);
- foreach (var variable in headerEnvironmentVariables)
+ static void LogPrefixedEnvironmentVariables(ILogger logger, string prefix)
{
- var envVarValue = Environment.GetEnvironmentVariable(variable);
+ if (!logger.IsEnabled(LogLevel.Information))
+ return;
- const string redacted = "=";
-
- if (string.IsNullOrEmpty(envVarValue))
- {
- logger.LogDebug("Environment variable '{EnvironmentVariable}' is not configured.", variable);
- }
- else
+ try
{
- var valueSpan = envVarValue.AsSpan();
- var buffer = ArrayPool.Shared.Rent(1024);
- var bufferSpan = buffer.AsSpan();
- var position = 0;
- var count = 0;
-
- while (true)
+ if (logger.IsEnabled(LogLevel.Debug))
{
- var indexOfComma = valueSpan.IndexOf(',');
- var header = valueSpan.Slice(0, indexOfComma > 0 ? indexOfComma : valueSpan.Length);
+ var systemEnvVars = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine);
- var indexOfEquals = valueSpan.IndexOf('=');
-
- if (indexOfEquals > 0)
+ foreach (DictionaryEntry entry in systemEnvVars)
{
- var key = header.Slice(0, indexOfEquals);
- var value = header.Slice(indexOfEquals + 1);
-
- if (count++ > 0)
- bufferSpan[position++] = ',';
+ if (entry.Key is not string key || !key.StartsWith(prefix))
+ continue;
- key.CopyTo(bufferSpan.Slice(position));
- position += key.Length;
- redacted.AsSpan().CopyTo(bufferSpan.Slice(position));
- position += redacted.Length;
+ var value = entry.Value?.ToString() ?? string.Empty;
+ logger.LogDebug("System-level environment variable '{EnvironmentVariable}' = '{EnvironmentVariableValue}'", key, value);
}
- if (indexOfComma <= 0)
- break;
+ var userEnvVars = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
- valueSpan = valueSpan.Slice(indexOfComma + 1);
+ foreach (DictionaryEntry entry in userEnvVars)
+ {
+ if (entry.Key is not string key || !key.StartsWith(prefix))
+ continue;
+
+ var value = entry.Value?.ToString() ?? string.Empty;
+ logger.LogDebug("User-level environment variable '{EnvironmentVariable}' = '{EnvironmentVariableValue}'", key, value);
+ }
}
- logger.LogDebug("Environment variable '{EnvironmentVariable}' = '{EnvironmentVariableValue}'.", variable, bufferSpan.Slice(0, position).ToString());
+ var allVars = Environment.GetEnvironmentVariables();
+
+ foreach (DictionaryEntry entry in allVars)
+ {
+ if (entry.Key is not string key || !key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ continue;
+
+ var value = entry.Value?.ToString() ?? string.Empty;
+ var source = "unknown";
+
+ // Check process, user, and machine scopes
+ var processValue = Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.Process);
+ var userValue = Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.User);
+ var machineValue = Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.Machine);
+
+ if (!string.IsNullOrEmpty(processValue))
+ source = "process";
+ else if (!string.IsNullOrEmpty(userValue))
+ source = "user";
+ else if (!string.IsNullOrEmpty(machineValue))
+ source = "system";
+ else
+ source = "unknown";
+
+ if (EnvironmentVariables.SensitiveEnvironmentVariables.Contains(key, StringComparer.OrdinalIgnoreCase))
+ {
+ value = "";
+ }
- ArrayPool.Shared.Return(buffer);
+ logger.LogInformation("Effective environment variable '{EnvironmentVariable}' = '{EnvironmentVariableValue}' (source: {Source})", key, value, source);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "An error occurred while reading environment variables with prefix '{Prefix}'.", prefix);
}
}
-
- components.Options.LogConfigSources(logger);
}
}
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs
index 0dfdb965..08dec349 100644
--- a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggingEventListener.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information
using System.Diagnostics.Tracing;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using Elastic.OpenTelemetry.Configuration;
@@ -17,13 +18,10 @@ internal sealed
#if NET8_0_OR_GREATER
partial
#endif
- class LoggingEventListener(ILogger logger, CompositeElasticOpenTelemetryOptions options) : EventListener, IAsyncDisposable
+ class LoggingEventListener : EventListener, IAsyncDisposable
{
public const string OpenTelemetrySdkEventSourceNamePrefix = "OpenTelemetry-";
- private readonly ILogger _logger = logger;
- private readonly EventLevel _eventLevel = options.EventLogLevel;
-
private const string TraceParentRegularExpressionString = "^\\d{2}-[a-f0-9]{32}-[a-f0-9]{16}-\\d{2}$";
#if NET8_0_OR_GREATER
[GeneratedRegex(TraceParentRegularExpressionString)]
@@ -33,6 +31,47 @@ class LoggingEventListener(ILogger logger, CompositeElasticOpenTelemetryOptions
private static Regex TraceParentRegex() => TraceParentRegexExpression;
#endif
+ private readonly CompositeLogger _logger;
+ private readonly EventLevel _eventLevel;
+ private readonly List? _eventSourcesBeforeConstructor = [];
+ private readonly Lock _lock = new();
+
+ public LoggingEventListener(CompositeLogger logger, CompositeElasticOpenTelemetryOptions options)
+ {
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(LoggingEventListener)}: Instance '{InstanceId}' created via ctor." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeLogger)}` instance '{logger.InstanceId}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
+ BootstrapLogger.Log($"{nameof(LoggingEventListener)}: {nameof(CompositeElasticOpenTelemetryOptions)}.{nameof(CompositeElasticOpenTelemetryOptions.EventLogLevel)} = '{options.EventLogLevel}'");
+ }
+
+ _logger = logger;
+ _eventLevel = options.EventLogLevel;
+
+ _logger.LogDebug("LoggingEventListener event level set to: `{EventLevel}`", _eventLevel.ToString());
+
+ List? eventSources;
+
+ using (_lock.EnterScope())
+ {
+ eventSources = _eventSourcesBeforeConstructor;
+ _eventSourcesBeforeConstructor = null;
+ }
+
+ if (eventSources is not null)
+ {
+ foreach (var eventSource in eventSources)
+ {
+ _logger.LogDebug("LoggingEventListener subscribed to: {EventSourceName}", eventSource.Name);
+ EnableEvents(eventSource, _eventLevel, EventKeywords.All);
+ }
+ }
+ }
+
+ internal Guid InstanceId { get; } = Guid.NewGuid();
+
public override void Dispose()
{
if (_logger is IDisposable d)
@@ -46,15 +85,33 @@ public ValueTask DisposeAsync() =>
protected override void OnEventSourceCreated(EventSource eventSource)
{
- if (eventSource.Name.StartsWith(OpenTelemetrySdkEventSourceNamePrefix, StringComparison.Ordinal))
+ // When instantiating an EventListener, the callbacks to OnEventSourceCreated and OnEventWritten can happen before the constructor has completed.
+ // Take care when you initialize instance members used in those callbacks.
+ // See https://learn.microsoft.com/dotnet/api/system.diagnostics.tracing.eventlistener
+ if (eventSource.Name.StartsWith(OpenTelemetrySdkEventSourceNamePrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ if (_eventSourcesBeforeConstructor is not null)
+ {
+ using (_lock.EnterScope())
+ {
+ if (_eventSourcesBeforeConstructor is not null)
+ {
+ _eventSourcesBeforeConstructor.Add(eventSource);
+ return;
+ }
+ }
+ }
+
+ _logger.LogDebug("LoggingEventListener subscribed to: {EventSourceName}", eventSource.Name);
EnableEvents(eventSource, _eventLevel, EventKeywords.All);
+ }
base.OnEventSourceCreated(eventSource);
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
- if (!eventData.EventSource.Name.StartsWith(OpenTelemetrySdkEventSourceNamePrefix, StringComparison.Ordinal))
+ if (!eventData.EventSource.Name.StartsWith(OpenTelemetrySdkEventSourceNamePrefix, StringComparison.OrdinalIgnoreCase))
{
// Workaround for https://github.com/dotnet/runtime/issues/31927
// EventCounters are published to all EventListeners, regardless of
@@ -104,7 +161,7 @@ static LogLevel GetLogLevel(EventWrittenEventArgs eventData) =>
if (eventData.EventSource.Name.StartsWith(OpenTelemetrySdkEventSourceNamePrefix) && eventData.Message is not null)
{
- builder.Append($"OTEL-SDK: [{threadId}] ");
+ builder.Append($"OTEL-SDK ({eventData.EventSource.Name}): [{threadId}] ");
if (eventData.Payload is null)
{
@@ -132,9 +189,6 @@ static LogLevel GetLogLevel(EventWrittenEventArgs eventData) =>
var payload = eventData.Payload[i];
if (payload is not null)
-#if NETFRAMEWORK
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
-#endif
builder.Append(payload.ToString() ?? "null");
else
builder.Append("null");
diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/StackTraceLoggerExtensions.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/StackTraceLoggerExtensions.cs
new file mode 100644
index 00000000..4d03d488
--- /dev/null
+++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/StackTraceLoggerExtensions.cs
@@ -0,0 +1,115 @@
+// 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 System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Logging;
+
+#if NET8_0 || NETSTANDARD2_1
+using System.Runtime.CompilerServices;
+#endif
+
+namespace Elastic.OpenTelemetry.Core.Diagnostics;
+
+internal static class StackTraceLoggerExtensions
+{
+ [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026: RequiresUnreferencedCode", Justification = "The calls to `GetMethod`" +
+ " are guarded by a RuntimeFeature.IsDynamicCodeSupported` check and therefore this method is safe to call in AoT scenarios")]
+ public static void LogCallerInfo(this ILogger logger, BuilderOptions builderOptions) where T : class
+ {
+ if (!logger.IsEnabled(LogLevel.Debug) || builderOptions.CalleeName is null || builderOptions.SkipLogCallerInfo)
+ return;
+
+ var calleeName = builderOptions.CalleeName;
+
+ try
+ {
+#if NET8_0 || NETSTANDARD2_1
+ // For now, we skip this log line entirely for AOT
+ // TODO: We should be able to provide some fallback and/or even use GetMethod safely in this scenario
+ // We need to test these scenarios and enhance in a future PR.
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ return;
+#endif
+ var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true);
+
+ if (stackTrace is null)
+ return;
+
+ foreach (var frame in stackTrace.GetFrames() ?? [])
+ {
+#if NETFRAMEWORK || NETSTANDARD2_0 || NET8_0 || NETSTANDARD2_1
+ var caller = $"{frame.GetType().AssemblyQualifiedName}.{frame.GetMethod()}";
+
+ var method = frame.GetMethod();
+ var declaringAssemblyName = method?.DeclaringType?.Assembly?.GetName().FullName;
+
+ if (method is null ||
+ declaringAssemblyName is null ||
+ declaringAssemblyName.StartsWith("Elastic", StringComparison.Ordinal) ||
+ declaringAssemblyName.StartsWith("OpenTelemetry", StringComparison.Ordinal))
+ continue;
+
+ var file = frame.GetFileName() ?? "";
+ var line = frame.GetFileLineNumber();
+
+ if (method.DeclaringType?.FullName is not null)
+ {
+ if (line > 0)
+ {
+ logger.LogDebug("{Callee} invoked by {DeclaringType}.{MethodName} in {DeclaringAssembly} at {File}:{Line}",
+ calleeName, method.DeclaringType.FullName, method.Name, declaringAssemblyName, file, line);
+ }
+ else
+ {
+ logger.LogDebug("{Callee} invoked by {DeclaringType}.{MethodName} in {DeclaringAssembly}",
+ calleeName, method.DeclaringType.FullName, method.Name, declaringAssemblyName);
+ }
+ }
+ else
+ {
+ if (line > 0)
+ {
+ logger.LogDebug("{Callee} invoked by {MethodName} in {DeclaringAssembly} at {File}:{Line}",
+ calleeName, method.Name, declaringAssemblyName, file, line);
+ }
+ else
+ {
+ logger.LogDebug("{Callee} invoked by {MethodName} in {DeclaringAssembly}",
+ calleeName, method.Name, declaringAssemblyName);
+ }
+ }
+ break;
+#elif NET9_0_OR_GREATER
+ var method = DiagnosticMethodInfo.Create(frame);
+
+ if (method is null ||
+ method.DeclaringAssemblyName.StartsWith("Elastic", StringComparison.Ordinal) ||
+ method.DeclaringAssemblyName.StartsWith("OpenTelemetry", StringComparison.Ordinal))
+ continue;
+
+ var file = frame.GetFileName() ?? "";
+ var line = frame.GetFileLineNumber();
+
+ if (line > 0)
+ {
+ logger.LogDebug("{Callee} invoked by {DeclaringType}.{MethodName} in {DeclaringAssembly} at {File}:{Line}",
+ calleeName, method.DeclaringTypeName, method.Name, method.DeclaringAssemblyName, file, line);
+ }
+ else
+ {
+ logger.LogDebug("{Callee} invoked by {DeclaringType}.{MethodName} in {DeclaringAssembly}",
+ calleeName, method.DeclaringTypeName, method.Name, method.DeclaringAssemblyName);
+ }
+
+ break;
+#endif
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Unable to log caller info.");
+ }
+ }
+}
diff --git a/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs b/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs
index 4904d22e..bffcf6d8 100644
--- a/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs
+++ b/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetry.cs
@@ -42,6 +42,16 @@ internal static ElasticOpenTelemetryComponents Bootstrap(
CompositeElasticOpenTelemetryOptions options,
IServiceCollection? services)
{
+ var invocationCount = Interlocked.Increment(ref BootstrapCounter);
+
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(Bootstrap)}: Static ctor invoked with count {invocationCount}." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Services is {(services is null ? "`null`" : "not `null`")}");
+ }
+
ActivationMethod = activationMethod;
ElasticOpenTelemetryComponents components;
@@ -49,12 +59,10 @@ internal static ElasticOpenTelemetryComponents Bootstrap(
// We only expect this to be allocated a handful of times, generally once.
var stackTrace = new StackTrace(true);
- var invocationCount = Interlocked.Increment(ref BootstrapCounter);
-
// Strictly speaking, we probably don't require locking here as the registration of
// OpenTelemetry is expected to run sequentially. That said, the overhead is low
// since this is called infrequently.
- using (var scope = Lock.EnterScope())
+ using (Lock.EnterScope())
{
// If an IServiceCollection is provided, we attempt to access any existing
// components to reuse them before accessing any potential shared components.
@@ -62,15 +70,30 @@ internal static ElasticOpenTelemetryComponents Bootstrap(
{
if (TryGetExistingComponentsFromServiceCollection(services, out var existingComponents))
{
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Existing components instance '{existingComponents.InstanceId}' loaded from the `IServiceCollection`." +
+ $"{Environment.NewLine} With `{nameof(CompositeLogger)}` instance '{existingComponents.Logger.InstanceId}'.");
+ }
+
existingComponents.Logger.LogBootstrapInvoked(invocationCount);
return existingComponents;
}
+
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: No existing components available from the `IServiceCollection`.");
}
// We don't have components assigned for this IServiceCollection, attempt to use
// the existing SharedComponents, or create components.
if (TryGetSharedComponents(options, out var sharedComponents))
{
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Existing components loaded from the shared components.");
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Existing ElasticOpenTelemetryComponents instance '{sharedComponents.InstanceId}'.");
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Existing CompositeLogger instance '{sharedComponents.Logger.InstanceId}'.");
+ }
+
components = sharedComponents;
}
else
@@ -123,10 +146,19 @@ static ElasticOpenTelemetryComponents CreateComponents(
CompositeElasticOpenTelemetryOptions options,
StackTrace stackTrace)
{
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: CreateComponents invoked.");
+
var logger = new CompositeLogger(options);
var eventListener = new LoggingEventListener(logger, options);
var components = new ElasticOpenTelemetryComponents(logger, eventListener, options);
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Created new CompositeLogger instance '{logger.InstanceId}' via CreateComponents.");
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Created new LoggingEventListener instance '{eventListener.InstanceId}' via CreateComponents.");
+ BootstrapLogger.Log($"{nameof(Bootstrap)}: Created new ElasticOpenTelemetryComponents instance '{components.InstanceId}' via CreateComponents.");
+ }
+
logger.LogDistroPreamble(activationMethod, components);
logger.LogComponentsCreated(Environment.NewLine, stackTrace);
diff --git a/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetryComponents.cs b/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetryComponents.cs
index 7061a0a8..b9544ed4 100644
--- a/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetryComponents.cs
+++ b/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetryComponents.cs
@@ -2,6 +2,7 @@
// 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.Configuration;
using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.Logging;
@@ -9,19 +10,41 @@
namespace Elastic.OpenTelemetry.Core;
-internal sealed class ElasticOpenTelemetryComponents(
- CompositeLogger logger,
- LoggingEventListener loggingEventListener,
- CompositeElasticOpenTelemetryOptions options) : IDisposable, IAsyncDisposable
+internal sealed class ElasticOpenTelemetryComponents : IDisposable, IAsyncDisposable
{
- public CompositeLogger Logger { get; } = logger;
- public LoggingEventListener LoggingEventListener { get; } = loggingEventListener;
- public CompositeElasticOpenTelemetryOptions Options { get; } = options;
+ internal Guid InstanceId { get; } = Guid.NewGuid();
+
+ public CompositeLogger Logger { get; }
+ public LoggingEventListener LoggingEventListener { get; }
+ public CompositeElasticOpenTelemetryOptions Options { get; }
+
+ public ElasticOpenTelemetryComponents(
+ CompositeLogger logger,
+ LoggingEventListener loggingEventListener,
+ CompositeElasticOpenTelemetryOptions options)
+ {
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.LogWithStackTrace($"{nameof(ElasticOpenTelemetryComponents)}: Instance '{InstanceId}' created via ctor." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeLogger)}` instance '{logger.InstanceId}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(OpenTelemetry.Diagnostics.LoggingEventListener)}` instance '{loggingEventListener.InstanceId}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
+ Logger = logger;
+ LoggingEventListener = loggingEventListener;
+ Options = options;
+ }
internal void SetAdditionalLogger(ILogger logger, SdkActivationMethod activationMethod)
{
if (logger is not NullLogger)
+ {
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(ElasticOpenTelemetryComponents)}: Setting additional logger.");
+
Logger.SetAdditionalLogger(logger, activationMethod, this);
+ }
}
public void Dispose()
diff --git a/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs b/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs
index c481e835..6e5ba97c 100644
--- a/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs
+++ b/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs
@@ -5,10 +5,8 @@
#if NETFRAMEWORK
using System.Net.Http;
#endif
-#pragma warning disable IDE0130 // Namespace does not match folder structure
-
-using Microsoft.Extensions.Logging;
+#pragma warning disable IDE0130 // Namespace does not match folder structure
namespace Elastic.OpenTelemetry.Exporters;
#pragma warning restore IDE0130 // Namespace does not match folder structure
diff --git a/src/Elastic.OpenTelemetry.Core/Extensions/ElasticTracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry.Core/Extensions/ElasticTracerProviderBuilderExtensions.cs
index bd71b680..648c09f6 100644
--- a/src/Elastic.OpenTelemetry.Core/Extensions/ElasticTracerProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Extensions/ElasticTracerProviderBuilderExtensions.cs
@@ -35,7 +35,7 @@ internal static class ElasticTracerProviderBuilderExtensions
/// processors should be added.
/// Thrown when the is null.
/// The for chaining.
- public static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder)
+ internal static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder)
{
#if NET
ArgumentNullException.ThrowIfNull(builder);
@@ -62,7 +62,7 @@ public static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuil
/// Thrown when the is null.
/// Thrown when the is null.
/// The for chaining.
- public static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options)
+ internal static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options)
{
#if NET
ArgumentNullException.ThrowIfNull(builder);
diff --git a/src/Elastic.OpenTelemetry.Core/Extensions/LoggerFactoryExtensions.cs b/src/Elastic.OpenTelemetry.Core/Extensions/LoggerFactoryExtensions.cs
index 77ccb46e..3bef5d35 100644
--- a/src/Elastic.OpenTelemetry.Core/Extensions/LoggerFactoryExtensions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Extensions/LoggerFactoryExtensions.cs
@@ -11,6 +11,6 @@ namespace Elastic.OpenTelemetry.Core;
internal static class LoggerFactoryExtensions
{
- public static ILogger CreateElasticLogger(this ILoggerFactory loggerFactory) =>
+ internal static ILogger CreateElasticLogger(this ILoggerFactory loggerFactory) =>
loggerFactory.CreateLogger(CompositeLogger.LogCategory);
}
diff --git a/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs b/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs
index 7ae9b496..94e00e26 100644
--- a/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs
+++ b/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs
@@ -2,6 +2,7 @@
// 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.Configuration;
using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.Logging;
@@ -24,8 +25,13 @@ internal static class OpenTelemetryLoggerOptionsExtensions
/// An to use for diagnostic logging.
/// Thrown when the is null.
/// Thrown when the is null.
- public static void WithElasticDefaults(this OpenTelemetryLoggerOptions options, ILogger logger)
+ internal static void WithElasticDefaults(this OpenTelemetryLoggerOptions options, ILogger logger)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(OpenTelemetryLoggerOptionsExtensions)}.{nameof(WithElasticDefaults)}(this OpenTelemetryLoggerOptions options, ILogger logger) invoked " +
+ $"on options with object hash '{RuntimeHelpers.GetHashCode(options)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(logger);
diff --git a/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs b/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs
index 502510d0..d32c7c2c 100644
--- a/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs
+++ b/src/Elastic.OpenTelemetry.Core/SignalBuilder.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using Elastic.OpenTelemetry.Configuration;
+using Elastic.OpenTelemetry.Core.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -139,9 +140,6 @@ internal static T WithElasticDefaults(
return HandleExistingBuilderState(builder, providerBuilderName, existingBuilderState);
}
- // 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);
options ??= CompositeElasticOpenTelemetryOptions.DefaultOptions;
@@ -152,6 +150,11 @@ internal static T WithElasticDefaults(
components = ElasticOpenTelemetry.Bootstrap(options, services);
var builderState = new BuilderState(components, builderInstanceId);
+ // We will have flushed the deferred logger at this point so we can now use the final logger.
+ logger = components.Logger;
+
+ LoadedAssemblyLogHelper.LogLoadedAssemblies(components.Logger);
+
var builderContext = new BuilderContext
{
Builder = builder,
diff --git a/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs
index a4fcff4f..e731c91c 100644
--- a/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs
@@ -2,11 +2,12 @@
// 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.Core;
+using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
@@ -22,6 +23,17 @@ namespace Microsoft.Extensions.Hosting;
///
public static class HostApplicationBuilderExtensions
{
+ // We define these statically for now. One important caveat is that if we add/modify methods, we need to update these accordingly.
+ // Since we don't expect to change the public API very often, this is an acceptable trade-off to avoid calculating this at runtime.
+ // We could consider a source generator to produce these automatically in the future if needed for all public methods in this class.
+ // These are used for diagnostics/logging purposes only.
+ private static readonly string ClassName = typeof(HostApplicationBuilderExtensions).FullName ?? nameof(HostApplicationBuilderExtensions);
+ private static readonly string AddElasticOpenTelemetryMethodNoArgs = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IHostApplicationBuilder builder)";
+ private static readonly string AddElasticOpenTelemetryMethodWithConfigureAction = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IHostApplicationBuilder builder, Action configure)";
+ private static readonly string AddElasticOpenTelemetryMethodWithOptions = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IHostApplicationBuilder builder, ElasticOpenTelemetryOptions options)";
+ private static readonly string AddElasticOpenTelemetryMethodWithOptionsAndConfigureAction = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}" +
+ "(this IHostApplicationBuilder builder, ElasticOpenTelemetryOptions options, Action configure)";
+
///
/// Registers the OpenTelemetry SDK with the application, configured with Elastic Distribution of OpenTelemetry (EDOT) .NET
/// defaults for traces,
@@ -53,14 +65,23 @@ public static class HostApplicationBuilderExtensions
/// The supplied for chaining calls.
public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
- ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
- builder.Services.AddElasticOpenTelemetry(builder.Configuration);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = AddElasticOpenTelemetryMethodNoArgs
+ };
+
+ builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration), builderOptions);
return builder;
}
@@ -95,6 +116,11 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
{
// 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);
@@ -110,7 +136,8 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
{
UserProvidedConfigureBuilder = configure,
// We don't set defer as we expect the callee to handle executing the configure action at the correct time.
- DeferAddOtlpExporter = false
+ DeferAddOtlpExporter = false,
+ CalleeName = AddElasticOpenTelemetryMethodWithConfigureAction
};
builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration), builderOptions);
@@ -152,6 +179,10 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
///
public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, ElasticOpenTelemetryOptions options)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithOptions} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -163,7 +194,12 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
throw new ArgumentNullException(nameof(options));
#endif
- builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration, options), default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = AddElasticOpenTelemetryMethodWithOptions
+ };
+
+ builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration, options), builderOptions);
return builder;
}
@@ -198,6 +234,10 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
{
// 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithOptionsAndConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -216,7 +256,8 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat
{
UserProvidedConfigureBuilder = configure,
// We don't set defer as we expect the callee to handle executing the configure action at the correct time.
- DeferAddOtlpExporter = false
+ DeferAddOtlpExporter = false,
+ CalleeName = AddElasticOpenTelemetryMethodWithOptionsAndConfigureAction
};
builder.Services.AddElasticOpenTelemetryCore(new(builder.Configuration, options), builderOptions);
diff --git a/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs
index 68825b26..9ce6e1e8 100644
--- a/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs
@@ -6,6 +6,7 @@
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Core.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics;
using Elastic.OpenTelemetry.Exporters;
using Microsoft.Extensions.Configuration;
@@ -14,6 +15,7 @@
using Microsoft.Extensions.Options;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
@@ -29,6 +31,20 @@ namespace OpenTelemetry;
///
public static class LoggerProviderBuilderExtensions
{
+ // We define these statically for now. One important caveat is that if we add/modify methods, we need to update these accordingly.
+ // Since we don't expect to change the public API very often, this is an acceptable trade-off to avoid calculating this at runtime.
+ // We could consider a source generator to produce these automatically in the future if needed for all public methods in this class.
+ // These are used for diagnostics/logging purposes only.
+ private static readonly string ClassName = typeof(LoggerProviderBuilderExtensions).FullName ?? nameof(LoggerProviderBuilderExtensions);
+ private static readonly string WithElasticDefaultsMethodNoArgs = $"{ClassName}.{nameof(WithElasticDefaults)}(this LoggerProviderBuilder builder)";
+ private static readonly string WithElasticDefaultsMethodWithConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}(this LoggerProviderBuilder builder, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithOptions = $"{ClassName}.{nameof(WithElasticDefaults)}(this LoggerProviderBuilder builder, ElasticOpenTelemetryOptions options)";
+ private static readonly string WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this LoggerProviderBuilder builder, ElasticOpenTelemetryOptions options, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithIConfiguration = $"{ClassName}.{nameof(WithElasticDefaults)}(this LoggerProviderBuilder builder, IConfiguration configuration)";
+ private static readonly string WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this LoggerProviderBuilder builder, IConfiguration configuration, Action configureBuilder)";
+
///
/// 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
@@ -85,14 +101,22 @@ public static class LoggerProviderBuilderExtensions
///
public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodNoArgs
+ };
- return WithElasticDefaultsCore(builder, null, null, null, default);
+ return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
}
///
@@ -141,6 +165,10 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
///
public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configureBuilder);
@@ -151,7 +179,12 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
}
@@ -203,6 +236,11 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
///
public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, ElasticOpenTelemetryOptions options)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptions} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -213,8 +251,12 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
if (options is null)
throw new ArgumentNullException(nameof(options));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithOptions
+ };
- return WithElasticDefaultsCore(builder, new(options), null, null, default);
+ return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
}
///
@@ -229,6 +271,11 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder,
ElasticOpenTelemetryOptions options, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -243,7 +290,12 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
}
@@ -295,6 +347,10 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
///
public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfiguration} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -305,7 +361,12 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
- return WithElasticDefaultsCore(builder, new(configuration), null, null, default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithIConfiguration
+ };
+
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
}
///
@@ -320,6 +381,10 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuilder builder,
IConfiguration configuration, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -334,7 +399,12 @@ public static LoggerProviderBuilder WithElasticDefaults(this LoggerProviderBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
}
@@ -346,10 +416,39 @@ internal static LoggerProviderBuilder WithElasticDefaultsCore(
IServiceCollection? services,
in BuilderOptions builderOptions)
{
- var logger = SignalBuilder.GetLogger(builder, components, options, null);
-
var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{ClassName}.{nameof(WithElasticDefaultsCore)} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $" Invokation count: {callCount}.");
+
+ if (options is null)
+ {
+ BootstrapLogger.Log($"Invoked with `null` {nameof(CompositeElasticOpenTelemetryOptions)} instance.");
+ }
+ else
+ {
+ BootstrapLogger.Log($"Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
+ if (components is null)
+ {
+ BootstrapLogger.Log($"Invoked with `null` {nameof(ElasticOpenTelemetryComponents)} instance.");
+ }
+ else
+ {
+ BootstrapLogger.Log($"Invoked with `{nameof(ElasticOpenTelemetryComponents)}` instance '{components.InstanceId}'.");
+ }
+
+ BootstrapLogger.Log($"Param `services` is {(services is null ? "`null`" : "not `null`")}");
+ BootstrapLogger.LogBuilderOptions(builderOptions, nameof(LoggerProviderBuilder), nameof(WithElasticDefaultsCore));
+ }
+
+ var logger = SignalBuilder.GetLogger(builder, components, options, null);
+ logger.LogCallerInfo(builderOptions);
+
if (callCount > 1)
{
logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, nameof(LoggerProviderBuilder));
diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs
index 02b7d2cf..27da8541 100644
--- a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs
@@ -7,6 +7,7 @@
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Core.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics;
using Elastic.OpenTelemetry.Exporters;
using Elastic.OpenTelemetry.Instrumentation;
@@ -32,6 +33,20 @@ namespace OpenTelemetry;
///
public static class MeterProviderBuilderExtensions
{
+ // We define these statically for now. One important caveat is that if we add/modify methods, we need to update these accordingly.
+ // Since we don't expect to change the public API very often, this is an acceptable trade-off to avoid calculating this at runtime.
+ // We could consider a source generator to produce these automatically in the future if needed for all public methods in this class.
+ // These are used for diagnostics/logging purposes only.
+ private static readonly string ClassName = typeof(MeterProviderBuilderExtensions).FullName ?? nameof(MeterProviderBuilderExtensions);
+ private static readonly string WithElasticDefaultsMethodNoArgs = $"{ClassName}.{nameof(WithElasticDefaults)}(this MeterProviderBuilder builder)";
+ private static readonly string WithElasticDefaultsMethodWithConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}(this MeterProviderBuilder builder, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithOptions = $"{ClassName}.{nameof(WithElasticDefaults)}(this MeterProviderBuilder builder, ElasticOpenTelemetryOptions options)";
+ private static readonly string WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this MeterProviderBuilder builder, ElasticOpenTelemetryOptions options, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithIConfiguration = $"{ClassName}.{nameof(WithElasticDefaults)}(this MeterProviderBuilder builder, IConfiguration configuration)";
+ private static readonly string WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this MeterProviderBuilder builder, IConfiguration configuration, Action configureBuilder)";
+
///
/// 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
@@ -86,14 +101,22 @@ public static class MeterProviderBuilderExtensions
///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodNoArgs
+ };
- return WithElasticDefaultsCore(builder, null, null, null, default);
+ return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
}
///
@@ -138,6 +161,10 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configureBuilder);
@@ -148,8 +175,12 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithConfigureBuilderAction
+ };
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
}
@@ -200,6 +231,11 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder, ElasticOpenTelemetryOptions options)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptions} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -210,8 +246,12 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
if (options is null)
throw new ArgumentNullException(nameof(options));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithOptions
+ };
- return WithElasticDefaultsCore(builder, new(options), null, null, default);
+ return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
}
///
@@ -226,6 +266,11 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder,
ElasticOpenTelemetryOptions options, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -240,7 +285,12 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
}
@@ -292,6 +342,10 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
///
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfiguration} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -302,7 +356,12 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
- return WithElasticDefaultsCore(builder, new(configuration), null, null, default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithIConfiguration
+ };
+
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
}
///
@@ -317,6 +376,10 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder builder,
IConfiguration configuration, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -331,7 +394,12 @@ public static MeterProviderBuilder WithElasticDefaults(this MeterProviderBuilder
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
}
@@ -344,10 +412,39 @@ internal static MeterProviderBuilder WithElasticDefaultsCore(
IServiceCollection? services,
in BuilderOptions builderOptions)
{
- var logger = SignalBuilder.GetLogger(builder, components, options, null);
-
var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{ClassName}.{nameof(WithElasticDefaultsCore)} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $" Invokation count: {callCount}.");
+
+ if (options is null)
+ {
+ BootstrapLogger.Log($"Invoked with `null` {nameof(CompositeElasticOpenTelemetryOptions)} instance.");
+ }
+ else
+ {
+ BootstrapLogger.Log($"Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
+ if (components is null)
+ {
+ BootstrapLogger.Log($"Invoked with `null` {nameof(ElasticOpenTelemetryComponents)} instance.");
+ }
+ else
+ {
+ BootstrapLogger.Log($"Invoked with `{nameof(ElasticOpenTelemetryComponents)}` instance '{components.InstanceId}'.");
+ }
+
+ BootstrapLogger.Log($"Param `services` is {(services is null ? "`null`" : "not `null`")}");
+ BootstrapLogger.LogBuilderOptions(builderOptions, nameof(MeterProviderBuilderExtensions), nameof(WithElasticDefaultsCore));
+ }
+
+ var logger = SignalBuilder.GetLogger(builder, components, options, null);
+ logger.LogCallerInfo(builderOptions);
+
if (callCount > 1)
{
logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, nameof(MeterProviderBuilder));
diff --git a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs
index 7549aaa5..d6fc173c 100644
--- a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs
@@ -6,6 +6,7 @@
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Core.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -26,6 +27,32 @@ namespace OpenTelemetry;
///
public static class OpenTelemetryBuilderExtensions
{
+ // We define these statically for now. One important caveat is that if we add/modify methods, we need to update these accordingly.
+ // Since we don't expect to change the public API very often, this is an acceptable trade-off to avoid calculating this at runtime.
+ // We could consider a source generator to produce these automatically in the future if needed for all public methods in this class.
+ // These are used for diagnostics/logging purposes only.
+ private static readonly string ClassName = typeof(OpenTelemetryBuilderExtensions).FullName ?? nameof(OpenTelemetryBuilderExtensions);
+ private static readonly string WithElasticDefaultsMethodNoArgs = $"{ClassName}.{nameof(WithElasticDefaults)}(this IOpenTelemetryBuilder builder)";
+ private static readonly string WithElasticDefaultsMethodWithConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}(this IOpenTelemetryBuilder builder, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithIConfiguration = $"{ClassName}.{nameof(WithElasticDefaults)}(this IOpenTelemetryBuilder builder, IConfiguration configuration)";
+ private static readonly string WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this IOpenTelemetryBuilder builder, IConfiguration configuration, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithOptions = $"{ClassName}.{nameof(WithElasticDefaults)}(this IOpenTelemetryBuilder builder, ElasticOpenTelemetryOptions options)";
+ private static readonly string WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this IOpenTelemetryBuilder builder, ElasticOpenTelemetryOptions options, Action configureBuilder)";
+ private static readonly string WithElasticLoggingMethodNoArgs = $"{ClassName}.{nameof(WithElasticLogging)}(this IOpenTelemetryBuilder builder)";
+ private static readonly string WithElasticLoggingMethodWithConfigureAction = $"{ClassName}.{nameof(WithElasticLogging)}(this IOpenTelemetryBuilder builder, Action configure)";
+ private static readonly string WithElasticMetricsMethodNoArgs = $"{ClassName}.{nameof(WithElasticMetrics)}(this IOpenTelemetryBuilder builder)";
+ private static readonly string WithElasticMetricsMethodWithConfigureAction = $"{ClassName}.{nameof(WithElasticMetrics)}(this IOpenTelemetryBuilder builder, Action configure)";
+ private static readonly string WithElasticMetricsMethodWithIConfiguration = $"{ClassName}.{nameof(WithElasticMetrics)}(this IOpenTelemetryBuilder builder, IConfiguration configuration)";
+ private static readonly string WithElasticMetricsMethodWithIConfigurationAndConfigureAction = $"{ClassName}.{nameof(WithElasticMetrics)}" +
+ "(this IOpenTelemetryBuilder builder, IConfiguration configuration, Action configure)";
+ private static readonly string WithElasticTracingMethodNoArgs = $"{ClassName}.{nameof(WithElasticTracing)}(this IOpenTelemetryBuilder builder)";
+ private static readonly string WithElasticTracingMethodWithConfigureAction = $"{ClassName}.{nameof(WithElasticTracing)}(this IOpenTelemetryBuilder builder, Action configure)";
+ private static readonly string WithElasticTracingMethodWithIConfiguration = $"{ClassName}.{nameof(WithElasticTracing)}(this IOpenTelemetryBuilder builder, IConfiguration configuration)";
+ private static readonly string WithElasticTracingMethodWithIConfigurationAndConfigureAction = $"{ClassName}.{nameof(WithElasticTracing)}" +
+ "(this IOpenTelemetryBuilder builder, IConfiguration configuration, Action configure)";
+
///
/// 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
@@ -76,14 +103,22 @@ public static class OpenTelemetryBuilderExtensions
///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodNoArgs
+ };
- return WithElasticDefaultsCore(builder, CompositeElasticOpenTelemetryOptions.DefaultOptions, default);
+ return WithElasticDefaultsCore(builder, CompositeElasticOpenTelemetryOptions.DefaultOptions, builderOptions);
}
///
@@ -125,6 +160,10 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configureBuilder);
@@ -135,7 +174,12 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, CompositeElasticOpenTelemetryOptions.DefaultOptions, builderOptions);
}
@@ -182,6 +226,10 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfiguration} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -192,8 +240,12 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithIConfiguration
+ };
- return WithElasticDefaultsCore(builder, new(configuration), default);
+ return WithElasticDefaultsCore(builder, new(configuration), builderOptions);
}
///
@@ -208,6 +260,10 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder,
IConfiguration configuration, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -222,7 +278,12 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, new(configuration), builderOptions);
}
@@ -274,6 +335,10 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
///
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder, ElasticOpenTelemetryOptions options)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptions} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -284,8 +349,12 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
if (options is null)
throw new ArgumentNullException(nameof(options));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithOptions
+ };
- return WithElasticDefaultsCore(builder, new(options), default);
+ return WithElasticDefaultsCore(builder, new(options), builderOptions);
}
///
@@ -300,6 +369,10 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuilder builder,
ElasticOpenTelemetryOptions options, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -314,8 +387,13 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction
+ };
- return WithElasticDefaultsCore(builder, new(options), default);
+ return WithElasticDefaultsCore(builder, new(options), builderOptions);
}
///
@@ -361,14 +439,22 @@ public static IOpenTelemetryBuilder WithElasticDefaults(this IOpenTelemetryBuild
///
public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticLoggingMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticLoggingMethodNoArgs
+ };
- return builder.WithLogging(lpb => lpb.WithElasticDefaults());
+ return builder.WithLogging(lpb => lpb.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
///
@@ -403,6 +489,13 @@ public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilder 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticLoggingMethodWithConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);
@@ -413,8 +506,13 @@ public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilde
if (configure is null)
throw new ArgumentNullException(nameof(configure));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = WithElasticLoggingMethodWithConfigureAction
+ };
- return builder.WithLogging(lpb => lpb.WithElasticDefaults(configure));
+ return builder.WithLogging(lpb => lpb.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
///
@@ -459,14 +557,22 @@ public static IOpenTelemetryBuilder WithElasticLogging(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticMetricsMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticMetricsMethodNoArgs
+ };
- return builder.WithMetrics(mpb => mpb.WithElasticDefaults());
+ return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
///
@@ -501,6 +607,13 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, Action configure)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticMetricsMethodWithConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
+ // 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(configure);
@@ -511,9 +624,13 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
if (configure is null)
throw new ArgumentNullException(nameof(configure));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = WithElasticMetricsMethodWithConfigureAction
+ };
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
- return builder.WithMetrics(mpb => mpb.WithElasticDefaults(configure));
+ return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
///
@@ -561,6 +678,10 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticMetricsMethodWithIConfiguration} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -571,8 +692,12 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticMetricsMethodWithIConfiguration
+ };
- return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, default));
+ return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, builderOptions));
}
///
@@ -587,6 +712,13 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilder builder, 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticMetricsMethodWithIConfigurationAndConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -601,8 +733,12 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
if (configure is null)
throw new ArgumentNullException(nameof(configure));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = WithElasticMetricsMethodWithIConfigurationAndConfigureAction
+ };
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
return builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, builderOptions));
}
@@ -648,14 +784,22 @@ public static IOpenTelemetryBuilder WithElasticMetrics(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticTracingMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticTracingMethodNoArgs
+ };
- return builder.WithTracing(m => m.WithElasticDefaults());
+ return builder.WithTracing(m => m.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
///
@@ -690,6 +834,13 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticTracingMethodWithConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);
@@ -701,7 +852,12 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
throw new ArgumentNullException(nameof(configure));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = WithElasticTracingMethodWithConfigureAction
+ };
+
return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(null, null, builder.Services, builderOptions));
}
@@ -750,6 +906,10 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
///
public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticTracingMethodWithIConfiguration} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -760,8 +920,12 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticTracingMethodWithIConfiguration
+ };
- return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, default));
+ return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, builderOptions));
}
///
@@ -776,6 +940,13 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilder builder, 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticTracingMethodWithIConfigurationAndConfigureAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -790,8 +961,12 @@ public static IOpenTelemetryBuilder WithElasticTracing(this IOpenTelemetryBuilde
if (configure is null)
throw new ArgumentNullException(nameof(configure));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = WithElasticTracingMethodWithIConfigurationAndConfigureAction
+ };
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
return builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(new(configuration), null, builder.Services, builderOptions));
}
@@ -807,10 +982,20 @@ internal static IOpenTelemetryBuilder WithElasticDefaultsCore(
{
var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{nameof(OpenTelemetryBuilderExtensions)}.{nameof(WithElasticDefaultsCore)} invoked " +
+ $"on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'. Invokation count: {callCount}." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ BootstrapLogger.LogBuilderOptions(builderOptions, nameof(OpenTelemetryBuilderExtensions), nameof(WithElasticDefaultsCore));
+ }
+
// 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);
+ logger.LogCallerInfo(builderOptions);
if (callCount > 1)
{
@@ -847,7 +1032,8 @@ private static void ConfigureBuilder(BuilderContext build
// we defer it until after this method runs the user-provided callback.
var builderOptions = new BuilderOptions
{
- DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null
+ DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null,
+ SkipLogCallerInfo = builderContext.BuilderOptions.SkipLogCallerInfo
};
builder.WithTracing(tpb => tpb.WithElasticDefaultsCore(components.Options, components, services, builderOptions));
@@ -865,7 +1051,8 @@ private static void ConfigureBuilder(BuilderContext build
// we defer it until after this method runs the user-provided callback.
var builderOptions = new BuilderOptions
{
- DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null
+ DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null,
+ SkipLogCallerInfo = builderContext.BuilderOptions.SkipLogCallerInfo
};
builder.WithMetrics(mpb => mpb.WithElasticDefaultsCore(components.Options, components, builder.Services, builderOptions));
@@ -883,7 +1070,8 @@ private static void ConfigureBuilder(BuilderContext build
// we defer it until after this method runs the user-provided callback.
var builderOptions = new BuilderOptions
{
- DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null
+ DeferAddOtlpExporter = builderContext.BuilderOptions.UserProvidedConfigureBuilder is not null,
+ SkipLogCallerInfo = builderContext.BuilderOptions.SkipLogCallerInfo
};
builder.WithLogging(lpb => lpb.WithElasticDefaultsCore(components.Options, components, builder.Services, builderOptions));
diff --git a/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs
index 33f9edae..82fa2c71 100644
--- a/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs
@@ -2,10 +2,12 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Core.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics;
using Elastic.OpenTelemetry.Exporters;
using Elastic.OpenTelemetry.Hosting;
@@ -25,6 +27,22 @@ namespace Microsoft.Extensions.DependencyInjection;
///
public static class ServiceCollectionExtensions
{
+ // We define these statically for now. One important caveat is that if we add/modify methods, we need to update these accordingly.
+ // Since we don't expect to change the public API very often, this is an acceptable trade-off to avoid calculating this at runtime.
+ // We could consider a source generator to produce these automatically in the future if needed for all public methods in this class.
+ // These are used for diagnostics/logging purposes only.
+ private static readonly string ClassName = typeof(ServiceCollectionExtensions).FullName ?? nameof(ServiceCollectionExtensions);
+ private static readonly string AddElasticOpenTelemetryMethodNoArgs = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IServiceCollection services)";
+ private static readonly string AddElasticOpenTelemetryMethodWithConfigureAction = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IServiceCollection services, Action configure)";
+ private static readonly string AddElasticOpenTelemetryMethodWithOptions = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IServiceCollection services, ElasticOpenTelemetryOptions options)";
+ private static readonly string AddElasticOpenTelemetryMethodWithOptionsAndConfigureAction = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}" +
+ "(this IServiceCollection services, ElasticOpenTelemetryOptions options, Action configure)";
+ private static readonly string AddElasticOpenTelemetryMethodWithIConfiguration = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}(this IServiceCollection services, IConfiguration configuration)";
+ private static readonly string AddElasticOpenTelemetryMethodWithIConfigurationAndConfigureAction = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}" +
+ "(this IServiceCollection services, IConfiguration configuration, Action configure)";
+ private static readonly string AddElasticOpenTelemetryMethodWithIConfigurationAndOptions = $"{ClassName}.{nameof(AddElasticOpenTelemetry)}" +
+ "(this IServiceCollection services, IConfiguration configuration, ElasticOpenTelemetryOptions options)";
+
///
/// Registers the OpenTelemetry SDK with the application, configured with Elastic Distribution of OpenTelemetry (EDOT) .NET
/// defaults for traces,
@@ -54,6 +72,10 @@ public static class ServiceCollectionExtensions
///
public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodNoArgs} invoked on `IServiceCollection`.");
+
#if NET
ArgumentNullException.ThrowIfNull(services);
#else
@@ -61,7 +83,12 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(services));
#endif
- return AddElasticOpenTelemetryCore(services, CompositeElasticOpenTelemetryOptions.DefaultOptions, default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = AddElasticOpenTelemetryMethodNoArgs
+ };
+
+ return AddElasticOpenTelemetryCore(services, CompositeElasticOpenTelemetryOptions.DefaultOptions, builderOptions);
}
///
@@ -96,6 +123,11 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
{
// 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithConfigureAction} invoked on `IServiceCollection`.");
+
#if NET
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configure);
@@ -107,7 +139,12 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(configure));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = AddElasticOpenTelemetryMethodWithConfigureAction
+ };
+
return AddElasticOpenTelemetryCore(services, CompositeElasticOpenTelemetryOptions.DefaultOptions, builderOptions);
}
@@ -143,6 +180,11 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
///
public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services, ElasticOpenTelemetryOptions options)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithOptions} invoked on `IServiceCollection`." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(options);
@@ -154,7 +196,12 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(options));
#endif
- return AddElasticOpenTelemetryCore(services, new(options), default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = AddElasticOpenTelemetryMethodWithOptions
+ };
+
+ return AddElasticOpenTelemetryCore(services, new(options), builderOptions);
}
///
@@ -193,6 +240,12 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
{
// 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithOptionsAndConfigureAction} invoked on `IServiceCollection`." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(options);
@@ -208,7 +261,12 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(configure));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = AddElasticOpenTelemetryMethodWithOptionsAndConfigureAction
+ };
+
return AddElasticOpenTelemetryCore(services, new(options), builderOptions);
}
@@ -240,6 +298,10 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
///
public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollection services, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithIConfiguration} invoked on `IServiceCollection`.");
+
#if NET
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
@@ -251,7 +313,12 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(configuration));
#endif
- return AddElasticOpenTelemetryCore(services, new(configuration), default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = AddElasticOpenTelemetryMethodWithIConfiguration
+ };
+
+ return AddElasticOpenTelemetryCore(services, new(configuration), builderOptions);
}
///
@@ -288,6 +355,11 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
{
// 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.
+
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithIConfigurationAndConfigureAction} invoked on `IServiceCollection`.");
+
#if NET
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
@@ -303,22 +375,88 @@ public static IOpenTelemetryBuilder AddElasticOpenTelemetry(this IServiceCollect
throw new ArgumentNullException(nameof(configure));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configure };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configure,
+ CalleeName = AddElasticOpenTelemetryMethodWithIConfigurationAndConfigureAction
+ };
+
return AddElasticOpenTelemetryCore(services, new(configuration), builderOptions);
}
+ ///
+ ///
+ ///
+ ///
+ /// 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)
+ {
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{AddElasticOpenTelemetryMethodWithIConfigurationAndOptions} invoked on `IServiceCollection`." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
+#if NET
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(options);
+#else
+ if (services is null)
+ throw new ArgumentNullException(nameof(services));
+
+ if (configuration is null)
+ throw new ArgumentNullException(nameof(configuration));
+
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+#endif
+
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = AddElasticOpenTelemetryMethodWithIConfigurationAndOptions
+ };
+
+ return AddElasticOpenTelemetryCore(services, new(configuration, options), builderOptions);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static IOpenTelemetryBuilder AddElasticOpenTelemetryCore(
this IServiceCollection services,
CompositeElasticOpenTelemetryOptions options,
in BuilderOptions builderOptions)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{nameof(ServiceCollectionExtensions)}.{nameof(AddElasticOpenTelemetryCore)} invoked." +
+ $"{Environment.NewLine} Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ BootstrapLogger.LogBuilderOptions(builderOptions, nameof(ServiceCollectionExtensions), nameof(AddElasticOpenTelemetryCore));
+ }
+
var logger = DeferredLogger.GetOrCreate(options);
+ logger.LogCallerInfo(builderOptions);
+
+ // From this point on, we skip logging caller info as we have already logged the main caller entrypoint.
+ var nextBuilderOptions = builderOptions.SkipLogCallerInfo ? builderOptions : builderOptions with
+ {
+ SkipLogCallerInfo = true
+ };
+
services.Configure(OtlpExporterDefaults.OtlpExporterOptions);
logger.LogConfiguredOtlpExporterOptions("all signals");
- var builder = services.AddOpenTelemetry().WithElasticDefaultsCore(options, builderOptions);
+ var builder = services.AddOpenTelemetry().WithElasticDefaultsCore(options, nextBuilderOptions);
if (!services.Any(d => d.ServiceType == typeof(IHostedService) && d.ImplementationType == typeof(ElasticOpenTelemetryService)))
{
diff --git a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs
index 88bec52f..1e1bf77c 100644
--- a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs
+++ b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs
@@ -9,6 +9,7 @@
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.Configuration;
using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Core.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics;
using Elastic.OpenTelemetry.Exporters;
using Elastic.OpenTelemetry.Instrumentation;
@@ -17,6 +18,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
+using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
@@ -31,6 +33,20 @@ namespace OpenTelemetry;
///
public static class TracerProviderBuilderExtensions
{
+ // We define these statically for now. One important caveat is that if we add/modify methods, we need to update these accordingly.
+ // Since we don't expect to change the public API very often, this is an acceptable trade-off to avoid calculating this at runtime.
+ // We could consider a source generator to produce these automatically in the future if needed for all public methods in this class.
+ // These are used for diagnostics/logging purposes only.
+ private static readonly string ClassName = typeof(TracerProviderBuilderExtensions).FullName ?? nameof(TracerProviderBuilderExtensions);
+ private static readonly string WithElasticDefaultsMethodNoArgs = $"{ClassName}.{nameof(WithElasticDefaults)}(this TracerProviderBuilder builder)";
+ private static readonly string WithElasticDefaultsMethodWithConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}(this TracerProviderBuilder builder, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithOptions = $"{ClassName}.{nameof(WithElasticDefaults)}(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options)";
+ private static readonly string WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options, Action configureBuilder)";
+ private static readonly string WithElasticDefaultsMethodWithIConfiguration = $"{ClassName}.{nameof(WithElasticDefaults)}(this TracerProviderBuilder builder, IConfiguration configuration)";
+ private static readonly string WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction = $"{ClassName}.{nameof(WithElasticDefaults)}" +
+ "(this TracerProviderBuilder builder, IConfiguration configuration, Action configureBuilder)";
+
private static int WithElasticDefaultsCallCount;
///
@@ -82,14 +98,22 @@ public static class TracerProviderBuilderExtensions
///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodNoArgs} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
#else
if (builder is null)
throw new ArgumentNullException(nameof(builder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodNoArgs
+ };
- return WithElasticDefaultsCore(builder, null, null, null, default);
+ return builder.WithElasticDefaultsCore(null, null, null, builderOptions);
}
///
@@ -138,6 +162,10 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configureBuilder);
@@ -148,8 +176,13 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
- return WithElasticDefaultsCore(builder, null, null, null, builderOptions);
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithConfigureBuilderAction
+ };
+
+ return builder.WithElasticDefaultsCore(null, null, null, builderOptions);
}
///
@@ -200,6 +233,11 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptions} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -210,8 +248,12 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
if (options is null)
throw new ArgumentNullException(nameof(options));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithOptions
+ };
- return WithElasticDefaultsCore(builder, new(options), null, null, default);
+ return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
}
///
@@ -226,6 +268,11 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder,
ElasticOpenTelemetryOptions options, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $"{Environment.NewLine} Invoked with `{nameof(ElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
@@ -240,8 +287,12 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithOptionsAndConfigureBuilderAction
+ };
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
return WithElasticDefaultsCore(builder, new(options), null, null, builderOptions);
}
@@ -293,6 +344,10 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
///
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder, IConfiguration configuration)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfiguration} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -303,7 +358,12 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
#endif
- return WithElasticDefaultsCore(builder, new(configuration), null, null, default);
+ var builderOptions = new BuilderOptions
+ {
+ CalleeName = WithElasticDefaultsMethodWithIConfiguration
+ };
+
+ return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
}
///
@@ -318,6 +378,10 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuilder builder,
IConfiguration configuration, Action configureBuilder)
{
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'.");
+
#if NET
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configuration);
@@ -332,7 +396,12 @@ public static TracerProviderBuilder WithElasticDefaults(this TracerProviderBuild
if (configureBuilder is null)
throw new ArgumentNullException(nameof(configureBuilder));
#endif
- var builderOptions = new BuilderOptions { UserProvidedConfigureBuilder = configureBuilder };
+ var builderOptions = new BuilderOptions
+ {
+ UserProvidedConfigureBuilder = configureBuilder,
+ CalleeName = WithElasticDefaultsMethodWithIConfigurationAndConfigureBuilderAction
+ };
+
return WithElasticDefaultsCore(builder, new(configuration), null, null, builderOptions);
}
@@ -344,10 +413,39 @@ internal static TracerProviderBuilder WithElasticDefaultsCore(
IServiceCollection? services,
in BuilderOptions builderOptions)
{
- var logger = SignalBuilder.GetLogger(builder, components, options, null);
-
var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
+ // We don't capture the stack trace here as we'll have that logged deeper in the call stack if needed.
+ if (BootstrapLogger.IsEnabled)
+ {
+ BootstrapLogger.Log($"{nameof(TracerProviderBuilderExtensions)}.{nameof(WithElasticDefaultsCore)} invoked on builder with object hash '{RuntimeHelpers.GetHashCode(builder)}'." +
+ $" Invokation count: {callCount}.");
+
+ if (options is null)
+ {
+ BootstrapLogger.Log($"Invoked with `null` {nameof(CompositeElasticOpenTelemetryOptions)} instance.");
+ }
+ else
+ {
+ BootstrapLogger.Log($"Invoked with `{nameof(CompositeElasticOpenTelemetryOptions)}` instance '{options.InstanceId}'.");
+ }
+
+ if (components is null)
+ {
+ BootstrapLogger.Log($"Invoked with `null` {nameof(ElasticOpenTelemetryComponents)} instance.");
+ }
+ else
+ {
+ BootstrapLogger.Log($"Invoked with `{nameof(ElasticOpenTelemetryComponents)}` instance '{components.InstanceId}'.");
+ }
+
+ BootstrapLogger.Log($"Param `services` is {(services is null ? "`null`" : "not `null`")}");
+ BootstrapLogger.LogBuilderOptions(builderOptions, nameof(TracerProviderBuilderExtensions), nameof(WithElasticDefaultsCore));
+ }
+
+ var logger = SignalBuilder.GetLogger(builder, components, options, null);
+ logger.LogCallerInfo(builderOptions);
+
if (callCount > 1)
{
logger.LogMultipleWithElasticDefaultsCallsWarning(callCount, nameof(TracerProviderBuilder));
@@ -362,7 +460,6 @@ internal static TracerProviderBuilder WithElasticDefaultsCore(
[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(BuilderContext builderContext)
{
var builder = builderContext.Builder;
diff --git a/src/Elastic.OpenTelemetry/Hosting/ElasticOpenTelemetryService.cs b/src/Elastic.OpenTelemetry/Hosting/ElasticOpenTelemetryService.cs
index c2486951..72c25e94 100644
--- a/src/Elastic.OpenTelemetry/Hosting/ElasticOpenTelemetryService.cs
+++ b/src/Elastic.OpenTelemetry/Hosting/ElasticOpenTelemetryService.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information
using Elastic.OpenTelemetry.Core;
+using Elastic.OpenTelemetry.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -14,16 +15,28 @@ namespace Elastic.OpenTelemetry.Hosting;
/// Used to attempt to attach an additional logger, typically in ASP.NET Core scenarios, so that logs
/// are written to any configured destinations.
///
-internal sealed class ElasticOpenTelemetryService(IServiceProvider serviceProvider) : IHostedLifecycleService
+internal sealed class ElasticOpenTelemetryService : IHostedLifecycleService
{
private ElasticOpenTelemetryComponents? _components;
+ private readonly IServiceProvider _serviceProvider;
+
+ public ElasticOpenTelemetryService(IServiceProvider serviceProvider)
+ {
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(ElasticOpenTelemetryService)}: Created via ctor.");
+
+ _serviceProvider = serviceProvider;
+ }
public Task StartingAsync(CancellationToken cancellationToken)
{
- var loggerFactory = serviceProvider.GetService();
+ var loggerFactory = _serviceProvider.GetService();
var logger = loggerFactory?.CreateElasticLogger() ?? NullLogger.Instance;
- _components = serviceProvider.GetService();
+ if (BootstrapLogger.IsEnabled)
+ BootstrapLogger.Log($"{nameof(StartingAsync)}: Invoked.");
+
+ _components = _serviceProvider.GetService();
_components?.SetAdditionalLogger(logger, ElasticOpenTelemetry.ActivationMethod);
return Task.CompletedTask;
diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs
index 149366c9..b38d7269 100644
--- a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs
+++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs
@@ -14,7 +14,7 @@ namespace Elastic.OpenTelemetry.Tests.Configuration;
public class CompositeElasticOpenTelemetryOptionsTests(ITestOutputHelper output)
{
- private const int ExpectedLogsLength = 7;
+ private const int ExpectedLogsLength = 9;
[Fact]
public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfigured()
diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs
index d8c62fe4..1721bd98 100644
--- a/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs
+++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs
@@ -66,9 +66,9 @@ public void CheckNonActivation(string environmentVariable, string value)
}
[Theory]
- [InlineData("trace", LogLevel.Debug, true)]
- [InlineData("Trace", LogLevel.Debug, true)]
- [InlineData("TraCe", LogLevel.Debug, true)]
+ [InlineData("trace", LogLevel.Trace, true)]
+ [InlineData("Trace", LogLevel.Trace, true)]
+ [InlineData("TraCe", LogLevel.Trace, true)]
[InlineData("debug", LogLevel.Debug, true)]
[InlineData("info", LogLevel.Information, false)]
[InlineData("warn", LogLevel.Warning, false)]
diff --git a/tests/Elastic.OpenTelemetry.Tests/Diagnostics/LoggingTests.cs b/tests/Elastic.OpenTelemetry.Tests/Diagnostics/LoggingTests.cs
index 2bef281f..baefcd8f 100644
--- a/tests/Elastic.OpenTelemetry.Tests/Diagnostics/LoggingTests.cs
+++ b/tests/Elastic.OpenTelemetry.Tests/Diagnostics/LoggingTests.cs
@@ -13,10 +13,10 @@ public partial class LoggingTests(ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
- [GeneratedRegex(@"\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Information\]\s+Elastic Distribution of OpenTelemetry \(EDOT\) \.NET:.*")]
+ [GeneratedRegex(@"\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z\]\[\d{6}\]\[-*\]\[Information\]\s+Elastic Distribution of OpenTelemetry \(EDOT\) \.NET:.*")]
private static partial Regex EdotPreamble();
- [GeneratedRegex(@"\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Debug\]\s+Reusing existing shared components\.\s+")]
+ [GeneratedRegex(@"\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z\]\[\d{6}\]\[-*\]\[Debug\]\s+Reusing existing shared components\.\s+")]
private static partial Regex UsingSharedComponents();
[Fact]
diff --git a/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs b/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs
index 20f30ed1..ef4a892a 100644
--- a/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs
+++ b/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs
@@ -29,16 +29,16 @@ public partial class InstrumentationScanningTests(WebApplicationFactory
private readonly ITestOutputHelper _output = output;
#if NET8_0
- [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Debug\]\s+Added contrib instrumentation 'HTTP' to TracerProviderBuilder*")]
+ [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z\]\[\d{6}\]\[-*\]\[Debug\]\s+Added contrib instrumentation 'HTTP' to TracerProviderBuilder*")]
private static partial Regex HttpTracerProviderBuilderRegex();
- [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Debug\]\s+Added contrib instrumentation 'HTTP' to MeterProviderBuilder*")]
+ [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z\]\[\d{6}\]\[-*\]\[Debug\]\s+Added contrib instrumentation 'HTTP' to MeterProviderBuilder*")]
private static partial Regex HttpMeterProviderBuilderRegex();
#elif NET9_0_OR_GREATER
- [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Debug\]\s+Added 'System.Net.Http' to TracerProviderBuilder.*")]
+ [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z\]\[\d{6}\]\[-*\]\[Debug\]\s+Added 'System.Net.Http' to TracerProviderBuilder.*")]
private static partial Regex HttpTracerProviderBuilderRegex();
- [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Debug\]\s+Added 'System.Net.Http' meter to MeterProviderBuilder.*")]
+ [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z\]\[\d{6}\]\[-*\]\[Debug\]\s+Added 'System.Net.Http' meter to MeterProviderBuilder.*")]
private static partial Regex HttpMeterProviderBuilderRegex();
#endif
@@ -71,7 +71,7 @@ public async Task InstrumentationAssemblyScanning_AddsAspNetCoreInstrumentation(
public async Task InstrumentationAssemblyScanning_AddsHttpInstrumentation()
{
// NOTE: When this runs on NET8, we expect the contrib library to be used.
- // On NET9, the library dependency is not included with Elastic.OpenTelemetry,
+ // On NET9+, the library dependency is not included with Elastic.OpenTelemetry,
// so we expect the native instrumentation to be used.
var exportedItems = new List();