From 6798ff30a6b3870cc99a90dad9f3846b30d2f65d Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Thu, 4 Dec 2025 14:47:55 +0000 Subject: [PATCH 1/2] Enhance logging capabilities - Add more information in the logging premable. - Add logging of sources for env vars and the final effective values. - Introduce `BootstrapLogger` for detailed logging before main logger initialization, supporting automatic disposal after inactivity. This is potentially useful for support scenarios but is marked as experimental. - Refactor various classes to log public method invocations and configuration values to the bootstrap logger. - Add `StackTraceLoggerExtensions` for caller info logging and `LoadedAssemblyLogHelper` for assembly logging. --- .../AutoInstrumentationPlugin.cs | 53 ++-- .../BuilderOptions.cs | 4 +- .../CompositeElasticOpenTelemetryOptions.cs | 176 ++++++++++++-- .../ElasticOpenTelemetryOptions.cs | 5 + .../Configuration/EnvironmentVariables.cs | 77 +++--- .../Parsers/ConfigurationParser.cs | 10 +- .../Diagnostics/BootstrapLogger.cs | 207 ++++++++++++++++ .../Diagnostics/CompositeLogger.cs | 25 +- .../Diagnostics/DeferredLogger.cs | 11 +- .../Diagnostics/FileLogger.cs | 32 ++- .../Diagnostics/LoadedAssemblyLogHelper.cs | 55 +++++ .../Diagnostics/LogFormatter.cs | 4 +- .../Diagnostics/LoggerMessages.cs | 189 ++++++++------- .../Diagnostics/LoggingEventListener.cs | 74 +++++- .../Diagnostics/StackTraceLoggerExtensions.cs | 115 +++++++++ .../ElasticOpenTelemetry.cs | 38 ++- .../ElasticOpenTelemetryComponents.cs | 37 ++- .../Exporters/ElasticUserAgentHandler.cs | 4 +- .../ElasticTracerProviderBuilderExtensions.cs | 4 +- .../Extensions/LoggerFactoryExtensions.cs | 2 +- .../OpenTelemetryLoggerOptionsExtensions.cs | 8 +- .../SignalBuilder.cs | 9 +- .../HostApplicationBuilderExtensions.cs | 53 +++- .../LoggerProviderBuilderExtensions.cs | 115 ++++++++- .../MeterProviderBuilderExtensions.cs | 113 ++++++++- .../OpenTelemetryBuilderExtensions.cs | 228 ++++++++++++++++-- .../Extensions/ServiceCollectionExtensions.cs | 152 +++++++++++- .../TracerProviderBuilderExtensions.cs | 117 ++++++++- .../Hosting/ElasticOpenTelemetryService.cs | 19 +- 29 files changed, 1670 insertions(+), 266 deletions(-) create mode 100644 src/Elastic.OpenTelemetry.Core/Diagnostics/BootstrapLogger.cs create mode 100644 src/Elastic.OpenTelemetry.Core/Diagnostics/LoadedAssemblyLogHelper.cs create mode 100644 src/Elastic.OpenTelemetry.Core/Diagnostics/StackTraceLoggerExtensions.cs 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; From d04d7f8d15ecdb351e89da509149f9665025b870 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Thu, 4 Dec 2025 15:11:07 +0000 Subject: [PATCH 2/2] Update unit tests --- .../CompositeElasticOpenTelemetryOptionsTests.cs | 2 +- .../Configuration/GlobalLogConfigurationTests.cs | 6 +++--- .../Diagnostics/LoggingTests.cs | 4 ++-- .../InstrumentationScanningTests.cs | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) 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();