From cad3f278c816d50001c636d5a7e38d9270a8f0a1 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 4 Jun 2024 12:22:46 +0200 Subject: [PATCH 1/4] Make file log level and directory explicit settings as they enable file logging ootb --- .../ElasticOpenTelemetryOptions.cs | 76 ++++++++------- .../Configuration/EnvironmentVariables.cs | 12 ++- .../Diagnostics/LoggerMessages.cs | 4 +- .../Logging/AgentLoggingHelpers.cs | 2 +- .../Diagnostics/Logging/FileLogger.cs | 27 ++---- .../Diagnostics/LoggingEventListener.cs | 4 +- .../ElasticOpenTelemetryOptionsTests.cs | 93 ++++++++++--------- 7 files changed, 115 insertions(+), 103 deletions(-) diff --git a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs index 507b177..3cf9d9e 100644 --- a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs @@ -2,9 +2,11 @@ // 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.InteropServices; using Elastic.OpenTelemetry.Diagnostics.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; namespace Elastic.OpenTelemetry.Configuration; @@ -34,7 +36,7 @@ public class ElasticOpenTelemetryOptions private EnabledElasticDefaults? _elasticDefaults; private string? _fileLogDirectory; private ConfigSource _fileLogDirectorySource = ConfigSource.Default; - private string? _fileLogLevel; + private LogLevel? _fileLogLevel; private ConfigSource _fileLogLevelSource = ConfigSource.Default; private bool? _skipOtlpExporter; private ConfigSource _skipOtlpExporterSource = ConfigSource.Default; @@ -43,60 +45,69 @@ public class ElasticOpenTelemetryOptions private string? _loggingSectionLogLevel; + private readonly string _defaultLogDirectory; + /// /// Creates a new instance of with properties /// bound from environment variables. /// public ElasticOpenTelemetryOptions() { - SetFromEnvironment(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, ref _fileLogDirectory, - ref _fileLogDirectorySource, StringParser); - SetFromEnvironment(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, ref _fileLogLevel, - ref _fileLogLevelSource, StringParser); - SetFromEnvironment(EnvironmentVariables.ElasticOtelSkipOtlpExporter, ref _skipOtlpExporter, - ref _skipOtlpExporterSource, BoolParser); - SetFromEnvironment(EnvironmentVariables.ElasticOtelEnableElasticDefaults, ref _enabledElasticDefaults, - ref _enabledElasticDefaultsSource, StringParser); + _defaultLogDirectory = GetDefaultLogDirectory(); + SetFromEnvironment(ELASTIC_OTEL_LOG_DIRECTORY, ref _fileLogDirectory, ref _fileLogDirectorySource, StringParser); + SetFromEnvironment(ELASTIC_OTEL_LOG_LEVEL, ref _fileLogLevel, ref _fileLogLevelSource, LogLevelParser); + SetFromEnvironment(ELASTIC_OTEL_SKIP_OTLP_EXPORTER, ref _skipOtlpExporter, ref _skipOtlpExporterSource, BoolParser); + SetFromEnvironment(ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, ref _enabledElasticDefaults, ref _enabledElasticDefaultsSource, StringParser); } /// /// Creates a new instance of with properties /// bound from environment variables and an instance. /// - internal ElasticOpenTelemetryOptions(IConfiguration configuration) : this() + internal ElasticOpenTelemetryOptions(IConfiguration? configuration) : this() { - if (configuration is not null) - { - SetFromConfiguration(configuration, FileLogDirectoryConfigPropertyName, ref _fileLogDirectory, - ref _fileLogDirectorySource, StringParser); - SetFromConfiguration(configuration, FileLogLevelConfigPropertyName, ref _fileLogLevel, - ref _fileLogLevelSource, StringParser); - SetFromConfiguration(configuration, SkipOtlpExporterConfigPropertyName, ref _skipOtlpExporter, - ref _skipOtlpExporterSource, BoolParser); - SetFromConfiguration(configuration, EnabledElasticDefaultsConfigPropertyName, ref _enabledElasticDefaults, - ref _enabledElasticDefaultsSource, StringParser); - - BindFromLoggingSection(configuration); - } + if (configuration is null) return; + SetFromConfiguration(configuration, FileLogDirectoryConfigPropertyName, ref _fileLogDirectory, ref _fileLogDirectorySource, StringParser); + SetFromConfiguration(configuration, FileLogLevelConfigPropertyName, ref _fileLogLevel, ref _fileLogLevelSource, LogLevelParser); + SetFromConfiguration(configuration, SkipOtlpExporterConfigPropertyName, ref _skipOtlpExporter, ref _skipOtlpExporterSource, BoolParser); + SetFromConfiguration(configuration, EnabledElasticDefaultsConfigPropertyName, ref _enabledElasticDefaults, ref _enabledElasticDefaultsSource, StringParser); - void BindFromLoggingSection(IConfiguration configuration) + BindFromLoggingSection(configuration); + + void BindFromLoggingSection(IConfiguration config) { // This will be used as a fallback if a more specific configuration is not provided. // We also store the logging level to use it within the logging event listener to determine the most verbose level to subscribe to. - _loggingSectionLogLevel = configuration.GetValue($"Logging:LogLevel:{CompositeLogger.LogCategory}"); + _loggingSectionLogLevel = config.GetValue($"Logging:LogLevel:{CompositeLogger.LogCategory}"); // Fall back to the default logging level if the specific category is not configured. if (string.IsNullOrEmpty(_loggingSectionLogLevel)) - _loggingSectionLogLevel = configuration.GetValue("Logging:LogLevel:Default"); + _loggingSectionLogLevel = config.GetValue("Logging:LogLevel:Default"); if (!string.IsNullOrEmpty(_loggingSectionLogLevel) && _fileLogLevel is null) { - _fileLogLevel = _loggingSectionLogLevel; + _fileLogLevel = LogLevelHelpers.ToLogLevel(_loggingSectionLogLevel); _fileLogLevelSource = ConfigSource.IConfiguration; } } } + private static string GetDefaultLogDirectory() => + Environment.OSVersion.Platform == PlatformID.Win32NT + ? Path.Combine(Environment.GetEnvironmentVariable("PROGRAMDATA")!, "elastic", "apm-agent-dotnet", "logs") + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "elastic", "apm-agent-dotnet") + : "/var/log/elastic/apm-agent-dotnet"; + + /// + /// The default log directory if file logging was enabled but non was specified + /// Defaults to: + /// - %PROGRAMDATA%\elastic\apm-agent-dotnet (on Windows) + /// - /var/log/elastic/apm-agent-dotnet (on Linux) + /// - ~/Library/Application_Support/elastic/apm-agent-dotnet (on OSX) + /// + public string FileLogDirectoryDefault => _defaultLogDirectory; + /// /// The output directory where the Elastic distribution of OpenTelemetry will write log files. /// @@ -105,9 +116,9 @@ void BindFromLoggingSection(IConfiguration configuration) /// {ProcessName}_{UtcUnixTimeMilliseconds}_{ProcessId}.instrumentation.log. /// This log file includes log messages from the OpenTelemetry SDK and the Elastic distribution. /// - public string FileLogDirectory + public string? FileLogDirectory { - get => _fileLogDirectory ?? string.Empty; + get => _fileLogDirectory; init { _fileLogDirectory = value; @@ -130,9 +141,9 @@ public string FileLogDirectory /// TraceContain the most detailed messages. /// /// - public string FileLogLevel + public LogLevel? FileLogLevel { - get => _fileLogLevel ?? "Information"; + get => _fileLogLevel; init { _fileLogLevel = value; @@ -180,6 +191,9 @@ public string EnableElasticDefaults internal EnabledElasticDefaults EnabledDefaults => _elasticDefaults ?? GetEnabledElasticDefaults(); + private static (bool, LogLevel?) LogLevelParser(string? s) => + !string.IsNullOrEmpty(s) ? (true, LogLevelHelpers.ToLogLevel(s)) : (false, null); + private static (bool, string) StringParser(string? s) => !string.IsNullOrEmpty(s) ? (true, s) : (false, string.Empty); private static (bool, bool?) BoolParser(string? s) => bool.TryParse(s, out var boolValue) ? (true, boolValue) : (false, null); diff --git a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs index d578d88..63312a2 100644 --- a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs +++ b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs @@ -6,8 +6,12 @@ namespace Elastic.OpenTelemetry.Configuration; internal static class EnvironmentVariables { - public const string ElasticOtelSkipOtlpExporter = "ELASTIC_OTEL_SKIP_OTLP_EXPORTER"; - public const string ElasticOtelFileLogDirectoryEnvironmentVariable = "ELASTIC_OTEL_FILE_LOG_DIRECTORY"; - public const string ElasticOtelFileLogLevelEnvironmentVariable = "ELASTIC_OTEL_FILE_LOG_LEVEL"; - public const string ElasticOtelEnableElasticDefaults = "ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS"; + // ReSharper disable InconsistentNaming + public const string ELASTIC_OTEL_SKIP_OTLP_EXPORTER = nameof(ELASTIC_OTEL_SKIP_OTLP_EXPORTER); + + public const string ELASTIC_OTEL_LOG_DIRECTORY = nameof(ELASTIC_OTEL_LOG_DIRECTORY); + public const string ELASTIC_OTEL_LOG_LEVEL = nameof(ELASTIC_OTEL_LOG_LEVEL); + + public const string ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS = nameof(ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS); + // ReSharper enable InconsistentNaming } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs index b42818c..6bea4f5 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs @@ -66,8 +66,8 @@ public static void LogAgentPreamble(this ILogger logger) string[] environmentVariables = [ - EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, - EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable + EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, + EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL ]; foreach (var variable in environmentVariables) diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs b/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs index ea42bd4..666b66c 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs @@ -16,7 +16,7 @@ public static LogLevel GetElasticOtelLogLevelFromEnvironmentVariables() { var defaultLogLevel = DefaultLogLevel; - var logLevelEnvironmentVariable = Environment.GetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable); + var logLevelEnvironmentVariable = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL); if (string.IsNullOrEmpty(logLevelEnvironmentVariable)) return defaultLogLevel; diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs b/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs index 2472d70..f0a56c4 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs @@ -33,30 +33,21 @@ public FileLogger(ElasticOpenTelemetryOptions options) { _scopeProvider = new LoggerExternalScopeProvider(); - var configuredPath = options.FileLogDirectory ?? Environment.GetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable); - - if (string.IsNullOrEmpty(configuredPath)) + var logDirectory = options.FileLogDirectory; + var logLevel = options.FileLogLevel; + if (logLevel == LogLevel.None || (logLevel == null && logDirectory == null)) return; - if (!string.IsNullOrEmpty(options.FileLogLevel)) - { - var logLevel = LogLevelHelpers.ToLogLevel(options.FileLogLevel); - - if (logLevel != LogLevel.None) - _configuredLogLevel = logLevel; - } - else - { - _configuredLogLevel = AgentLoggingHelpers.GetElasticOtelLogLevelFromEnvironmentVariables(); - } + _configuredLogLevel = logLevel ?? LogLevel.Information; + logDirectory ??= options.FileLogDirectoryDefault; var process = Process.GetCurrentProcess(); - // When ordered by filename, we get see logs from the same process grouped, then ordered by oldest to newest, then the PID for that instance - LogFilePath = Path.Combine(configuredPath, $"{process.ProcessName}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{process.Id}.instrumentation.log"); + var logFileName = $"{process.ProcessName}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{process.Id}.instrumentation.log"; + LogFilePath = Path.Combine(logDirectory, logFileName); - if (!Directory.Exists(configuredPath)) - Directory.CreateDirectory(configuredPath); + if (!Directory.Exists(logDirectory)) + Directory.CreateDirectory(logDirectory); //StreamWriter.Dispose disposes underlying stream too var stream = new FileStream(LogFilePath, FileMode.OpenOrCreate, FileAccess.Write); diff --git a/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs b/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs index d6a5a56..e62bfc0 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs @@ -36,9 +36,9 @@ public LoggingEventListener(ILogger logger, ElasticOpenTelemetryOptions options) _logger = logger; // When both a file log level and a logging section log level are provided, the more verbose of the two is used. - // This insures we subscribes to the lowest level of events needed. + // This insures we subscribe to the lowest level of events needed. // The specific loggers will then determine if they should log the event based on their own log level. - var eventLevel = LogLevelHelpers.ToLogLevel(options.FileLogLevel); + var eventLevel = options.FileLogLevel; if (!string.IsNullOrEmpty(options.LoggingSectionLogLevel)) { var logLevel = LogLevelHelpers.ToLogLevel(options.LoggingSectionLogLevel); diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs index a351fd6..b373c02 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs @@ -10,16 +10,17 @@ using Xunit.Abstractions; using static Elastic.OpenTelemetry.Configuration.ElasticOpenTelemetryOptions; +using static Elastic.OpenTelemetry.Diagnostics.Logging.LogLevelHelpers; namespace Elastic.OpenTelemetry.Tests.Configuration; public sealed class ElasticOpenTelemetryOptionsTests(ITestOutputHelper output) : IDisposable { private readonly ITestOutputHelper _output = output; - private readonly string? _originalFileLogDirectoryEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable); - private readonly string? _originalFileLogLevelEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable); - private readonly string? _originalEnableElasticDefaultsEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults); - private readonly string? _originalSkipOtlpExporterEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter); + private readonly string? _originalFileLogDirectoryEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY); + private readonly string? _originalFileLogLevelEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL); + private readonly string? _originalEnableElasticDefaultsEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS); + private readonly string? _originalSkipOtlpExporterEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER); [Fact] public void EnabledElasticDefaults_NoneIncludesExpectedValues() @@ -34,15 +35,17 @@ public void EnabledElasticDefaults_NoneIncludesExpectedValues() [Fact] public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfigured() { - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); var sut = new ElasticOpenTelemetryOptions(); - sut.FileLogDirectory.Should().Be(string.Empty); - sut.FileLogLevel.Should().Be("Information"); + // these default to null because any other value would enable file logging + sut.FileLogDirectory.Should().Be(null); + sut.FileLogLevel.Should().Be(null); + sut.EnableElasticDefaults.Should().Be(string.Empty); sut.EnabledDefaults.Should().HaveFlag(EnabledElasticDefaults.Tracing); sut.EnabledDefaults.Should().HaveFlag(EnabledElasticDefaults.Metrics); @@ -69,15 +72,15 @@ public void DefaultCtor_LoadsConfigurationFromEnvironmentVariables() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, fileLogDirectory); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, fileLogLevel); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, enabledElasticDefaults); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, "true"); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, fileLogLevel); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"); var sut = new ElasticOpenTelemetryOptions(); sut.FileLogDirectory.Should().Be(fileLogDirectory); - sut.FileLogLevel.Should().Be(fileLogLevel); + sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); @@ -103,10 +106,10 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() const string enabledElasticDefaults = "None"; // Remove all env vars - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); var json = $$""" { @@ -134,7 +137,7 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() var sut = new ElasticOpenTelemetryOptions(config); sut.FileLogDirectory.Should().Be(@"C:\Temp"); - sut.FileLogLevel.Should().Be(fileLogLevel); + sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); @@ -160,10 +163,10 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT const string enabledElasticDefaults = "None"; // Remove all env vars - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); var json = $$""" { @@ -190,7 +193,7 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT var sut = new ElasticOpenTelemetryOptions(config); sut.FileLogDirectory.Should().Be(@"C:\Temp"); - sut.FileLogLevel.Should().Be(loggingSectionLogLevel); + sut.FileLogLevel.Should().Be(ToLogLevel(loggingSectionLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); @@ -216,10 +219,10 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT const string enabledElasticDefaults = "None"; // Remove all env vars - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); var json = $$""" { @@ -245,7 +248,7 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT var sut = new ElasticOpenTelemetryOptions(config); sut.FileLogDirectory.Should().Be(@"C:\Temp"); - sut.FileLogLevel.Should().Be(loggingSectionDefaultLogLevel); + sut.FileLogLevel.Should().Be(ToLogLevel(loggingSectionDefaultLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); @@ -271,10 +274,10 @@ public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, fileLogDirectory); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, fileLogLevel); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, enabledElasticDefaults); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, "true"); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, fileLogLevel); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"); var json = $$""" { @@ -296,7 +299,7 @@ public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() var sut = new ElasticOpenTelemetryOptions(config); sut.FileLogDirectory.Should().Be(fileLogDirectory); - sut.FileLogLevel.Should().Be(fileLogLevel); + sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); @@ -311,21 +314,21 @@ public void InitializedProperties_TakePrecedenceOver_EnvironmentValues() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, "C:\\Temp"); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, "Information"); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, "All"); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, "true"); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, "C:\\Temp"); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, "Information"); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, "All"); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"); var sut = new ElasticOpenTelemetryOptions { FileLogDirectory = fileLogDirectory, - FileLogLevel = fileLogLevel, + FileLogLevel = ToLogLevel(fileLogLevel), SkipOtlpExporter = false, EnableElasticDefaults = enabledElasticDefaults }; sut.FileLogDirectory.Should().Be(fileLogDirectory); - sut.FileLogLevel.Should().Be(fileLogLevel); + sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(false); @@ -468,10 +471,10 @@ public void TransactionId_IsNotAdded_WhenElasticDefaultsDoesNotIncludeTracing() private void ResetEnvironmentVariables() { - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogDirectoryEnvironmentVariable, _originalFileLogDirectoryEnvVar); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelFileLogLevelEnvironmentVariable, _originalFileLogLevelEnvVar); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelEnableElasticDefaults, _originalEnableElasticDefaultsEnvVar); - Environment.SetEnvironmentVariable(EnvironmentVariables.ElasticOtelSkipOtlpExporter, _originalSkipOtlpExporterEnvVar); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, _originalFileLogDirectoryEnvVar); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, _originalFileLogLevelEnvVar); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, _originalEnableElasticDefaultsEnvVar); + Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, _originalSkipOtlpExporterEnvVar); } public void Dispose() => ResetEnvironmentVariables(); From 6de3d9437d556f2b0b247eb337721310c082c18f Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 4 Jun 2024 17:26:41 +0200 Subject: [PATCH 2/4] Update tests to ensure defaults match existing agent --- examples/Example.MinimalApi/appsettings.json | 4 +- .../ElasticOpenTelemetryOptions.cs | 150 ++++++++++---- .../Configuration/EnvironmentVariables.cs | 1 + .../Configuration/LogTargets.cs | 26 +++ .../Logging/AgentLoggingHelpers.cs | 16 -- .../Diagnostics/Logging/FileLogger.cs | 11 +- .../Diagnostics/Logging/LogLevelHelpers.cs | 43 ++-- .../Diagnostics/LoggingEventListener.cs | 4 +- .../ElasticOpenTelemetryOptionsTests.cs | 187 +++++++----------- .../GlobalLogConfigurationTests.cs | 147 ++++++++++++++ 10 files changed, 385 insertions(+), 204 deletions(-) create mode 100644 src/Elastic.OpenTelemetry/Configuration/LogTargets.cs create mode 100644 tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs diff --git a/examples/Example.MinimalApi/appsettings.json b/examples/Example.MinimalApi/appsettings.json index c058ae2..9beeb3c 100644 --- a/examples/Example.MinimalApi/appsettings.json +++ b/examples/Example.MinimalApi/appsettings.json @@ -12,8 +12,8 @@ }, "Elastic": { "OpenTelemetry": { - "FileLogDirectory": "C:\\Logs\\OtelDistro", - "FileLogLevel": "Information" + "LogDirectory": "C:\\Logs\\OtelDistro", + "LogLevel": "Information" } } } diff --git a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs index 3cf9d9e..f5f622e 100644 --- a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.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.Collections; using System.Runtime.InteropServices; using Elastic.OpenTelemetry.Diagnostics.Logging; using Microsoft.Extensions.Configuration; @@ -26,36 +27,46 @@ namespace Elastic.OpenTelemetry.Configuration; public class ElasticOpenTelemetryOptions { private static readonly string ConfigurationSection = "Elastic:OpenTelemetry"; - private static readonly string FileLogDirectoryConfigPropertyName = "FileLogDirectory"; - private static readonly string FileLogLevelConfigPropertyName = "FileLogLevel"; + private static readonly string LogDirectoryConfigPropertyName = "LogDirectory"; + private static readonly string LogLevelConfigPropertyName = "LogLevel"; + private static readonly string LogTargetsConfigPropertyName = "LogTargets"; private static readonly string SkipOtlpExporterConfigPropertyName = "SkipOtlpExporter"; private static readonly string EnabledElasticDefaultsConfigPropertyName = "EnabledElasticDefaults"; // For a relatively limited number of properties, this is okay. If this grows significantly, consider a // more flexible approach similar to the layered configuration used in the Elastic APM Agent. private EnabledElasticDefaults? _elasticDefaults; - private string? _fileLogDirectory; - private ConfigSource _fileLogDirectorySource = ConfigSource.Default; - private LogLevel? _fileLogLevel; - private ConfigSource _fileLogLevelSource = ConfigSource.Default; - private bool? _skipOtlpExporter; - private ConfigSource _skipOtlpExporterSource = ConfigSource.Default; - private string? _enabledElasticDefaults; - private ConfigSource _enabledElasticDefaultsSource = ConfigSource.Default; - private string? _loggingSectionLogLevel; + private string? _logDirectory; + private ConfigSource _logDirectorySource = ConfigSource.Default; + + private LogLevel? _logLevel; + private ConfigSource _logLevelSource = ConfigSource.Default; + + private LogTargets? _logTargets; + private ConfigSource _logTargetsSource = ConfigSource.Default; + + private readonly bool? _skipOtlpExporter; + private readonly ConfigSource _skipOtlpExporterSource = ConfigSource.Default; + + private readonly string? _enabledElasticDefaults; + private readonly ConfigSource _enabledElasticDefaultsSource = ConfigSource.Default; + private string? _loggingSectionLogLevel; private readonly string _defaultLogDirectory; + private readonly IDictionary _environmentVariables; /// /// Creates a new instance of with properties /// bound from environment variables. /// - public ElasticOpenTelemetryOptions() + public ElasticOpenTelemetryOptions(IDictionary? environmentVariables = null) { _defaultLogDirectory = GetDefaultLogDirectory(); - SetFromEnvironment(ELASTIC_OTEL_LOG_DIRECTORY, ref _fileLogDirectory, ref _fileLogDirectorySource, StringParser); - SetFromEnvironment(ELASTIC_OTEL_LOG_LEVEL, ref _fileLogLevel, ref _fileLogLevelSource, LogLevelParser); + _environmentVariables = environmentVariables ?? Environment.GetEnvironmentVariables(); + SetFromEnvironment(ELASTIC_OTEL_LOG_DIRECTORY, ref _logDirectory, ref _logDirectorySource, StringParser); + SetFromEnvironment(ELASTIC_OTEL_LOG_LEVEL, ref _logLevel, ref _logLevelSource, LogLevelParser); + SetFromEnvironment(ELASTIC_OTEL_LOG_TARGETS, ref _logTargets, ref _logTargetsSource, LogTargetsParser); SetFromEnvironment(ELASTIC_OTEL_SKIP_OTLP_EXPORTER, ref _skipOtlpExporter, ref _skipOtlpExporterSource, BoolParser); SetFromEnvironment(ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, ref _enabledElasticDefaults, ref _enabledElasticDefaultsSource, StringParser); } @@ -64,11 +75,13 @@ public ElasticOpenTelemetryOptions() /// Creates a new instance of with properties /// bound from environment variables and an instance. /// - internal ElasticOpenTelemetryOptions(IConfiguration? configuration) : this() + internal ElasticOpenTelemetryOptions(IConfiguration? configuration, IDictionary? environmentVariables = null) + : this(environmentVariables) { if (configuration is null) return; - SetFromConfiguration(configuration, FileLogDirectoryConfigPropertyName, ref _fileLogDirectory, ref _fileLogDirectorySource, StringParser); - SetFromConfiguration(configuration, FileLogLevelConfigPropertyName, ref _fileLogLevel, ref _fileLogLevelSource, LogLevelParser); + SetFromConfiguration(configuration, LogDirectoryConfigPropertyName, ref _logDirectory, ref _logDirectorySource, StringParser); + SetFromConfiguration(configuration, LogLevelConfigPropertyName, ref _logLevel, ref _logLevelSource, LogLevelParser); + SetFromConfiguration(configuration, LogTargetsConfigPropertyName, ref _logTargets, ref _logTargetsSource, LogTargetsParser); SetFromConfiguration(configuration, SkipOtlpExporterConfigPropertyName, ref _skipOtlpExporter, ref _skipOtlpExporterSource, BoolParser); SetFromConfiguration(configuration, EnabledElasticDefaultsConfigPropertyName, ref _enabledElasticDefaults, ref _enabledElasticDefaultsSource, StringParser); @@ -84,11 +97,31 @@ void BindFromLoggingSection(IConfiguration config) if (string.IsNullOrEmpty(_loggingSectionLogLevel)) _loggingSectionLogLevel = config.GetValue("Logging:LogLevel:Default"); - if (!string.IsNullOrEmpty(_loggingSectionLogLevel) && _fileLogLevel is null) + if (!string.IsNullOrEmpty(_loggingSectionLogLevel) && _logLevel is null) + { + _logLevel = LogLevelHelpers.ToLogLevel(_loggingSectionLogLevel); + _logLevelSource = ConfigSource.IConfiguration; + } + } + } + + /// + /// Calculates whether global logging is enabled based on + /// , and + /// + public bool GlobalLogEnabled + { + get + { + var isActive = (_logLevel.HasValue || !string.IsNullOrWhiteSpace(_logDirectory) || _logTargets.HasValue); + if (isActive) { - _fileLogLevel = LogLevelHelpers.ToLogLevel(_loggingSectionLogLevel); - _fileLogLevelSource = ConfigSource.IConfiguration; + if (_logLevel is LogLevel.None) + isActive = false; + else if (_logTargets is LogTargets.None) + isActive = false; } + return isActive; } } @@ -106,7 +139,7 @@ private static string GetDefaultLogDirectory() => /// - /var/log/elastic/apm-agent-dotnet (on Linux) /// - ~/Library/Application_Support/elastic/apm-agent-dotnet (on OSX) /// - public string FileLogDirectoryDefault => _defaultLogDirectory; + public string LogDirectoryDefault => _defaultLogDirectory ; /// /// The output directory where the Elastic distribution of OpenTelemetry will write log files. @@ -116,13 +149,13 @@ private static string GetDefaultLogDirectory() => /// {ProcessName}_{UtcUnixTimeMilliseconds}_{ProcessId}.instrumentation.log. /// This log file includes log messages from the OpenTelemetry SDK and the Elastic distribution. /// - public string? FileLogDirectory + public string LogDirectory { - get => _fileLogDirectory; + get => _logDirectory ?? LogDirectoryDefault; init { - _fileLogDirectory = value; - _fileLogDirectorySource = ConfigSource.Property; + _logDirectory = value; + _logDirectorySource = ConfigSource.Property; } } @@ -141,13 +174,24 @@ public string? FileLogDirectory /// TraceContain the most detailed messages. /// /// - public LogLevel? FileLogLevel + public LogLevel LogLevel { - get => _fileLogLevel; + get => _logLevel ?? LogLevel.Warning; init { - _fileLogLevel = value; - _fileLogLevelSource = ConfigSource.Property; + _logLevel = value; + _logLevelSource = ConfigSource.Property; + } + } + + /// > + public LogTargets LogTargets + { + get => _logTargets ?? (GlobalLogEnabled ? LogTargets.File : LogTargets.None); + init + { + _logTargets = value; + _logTargetsSource = ConfigSource.Property; } } @@ -194,13 +238,42 @@ public string EnableElasticDefaults private static (bool, LogLevel?) LogLevelParser(string? s) => !string.IsNullOrEmpty(s) ? (true, LogLevelHelpers.ToLogLevel(s)) : (false, null); + private static (bool, LogTargets?) LogTargetsParser(string? s) + { + //var tokens = s?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries }); + if (string.IsNullOrWhiteSpace(s)) + return (false, null); + + var logTargets = LogTargets.None; + var found = false; + + foreach (var target in s.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (IsSet(target, "stdout")) + logTargets |= LogTargets.StdOut; + else if (IsSet(target, "file")) + logTargets |= LogTargets.File; + else if (IsSet(target, "none")) + logTargets |= LogTargets.None; + } + return !found ? (false, null) : (true, logTargets); + + bool IsSet(string k, string v) + { + var b = k.Trim().Equals(v, StringComparison.InvariantCultureIgnoreCase); + if (b) + found = true; + return b; + } + } + private static (bool, string) StringParser(string? s) => !string.IsNullOrEmpty(s) ? (true, s) : (false, string.Empty); private static (bool, bool?) BoolParser(string? s) => bool.TryParse(s, out var boolValue) ? (true, boolValue) : (false, null); - private static void SetFromEnvironment(string key, ref T field, ref ConfigSource configSourceField, Func parser) + private void SetFromEnvironment(string key, ref T field, ref ConfigSource configSourceField, Func parser) { - var (success, value) = parser(Environment.GetEnvironmentVariable(key)); + var (success, value) = parser(GetSafeEnvironmentVariable(key)); if (success) { @@ -264,13 +337,20 @@ private EnabledElasticDefaults GetEnabledElasticDefaults() static EnabledElasticDefaults All() => EnabledElasticDefaults.Tracing | EnabledElasticDefaults.Metrics | EnabledElasticDefaults.Logging; } + private string GetSafeEnvironmentVariable(string key) + { + var value = _environmentVariables.Contains(key) ? _environmentVariables[key]?.ToString() : null; + return value ?? string.Empty; + } + + internal void LogConfigSources(ILogger logger) { - logger.LogInformation("Configured value for {ConfigKey}: '{ConfigValue}' from [{ConfigSource}]", FileLogDirectoryConfigPropertyName, - _fileLogDirectory, _fileLogDirectorySource); + logger.LogInformation("Configured value for {ConfigKey}: '{ConfigValue}' from [{ConfigSource}]", LogDirectoryConfigPropertyName, + _logDirectory, _logDirectorySource); - logger.LogInformation("Configured value for {ConfigKey}: '{ConfigValue}' from [{ConfigSource}]", FileLogLevelConfigPropertyName, - _fileLogLevel, _fileLogLevelSource); + logger.LogInformation("Configured value for {ConfigKey}: '{ConfigValue}' from [{ConfigSource}]", LogLevelConfigPropertyName, + _logLevel, _logLevelSource); logger.LogInformation("Configured value for {ConfigKey}: '{ConfigValue}' from [{ConfigSource}]", SkipOtlpExporterConfigPropertyName, _skipOtlpExporter, _skipOtlpExporterSource); diff --git a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs index 63312a2..4417e5c 100644 --- a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs +++ b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs @@ -11,6 +11,7 @@ internal static class EnvironmentVariables public const string ELASTIC_OTEL_LOG_DIRECTORY = nameof(ELASTIC_OTEL_LOG_DIRECTORY); public const string ELASTIC_OTEL_LOG_LEVEL = nameof(ELASTIC_OTEL_LOG_LEVEL); + public const string ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS); public const string ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS = nameof(ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS); // ReSharper enable InconsistentNaming diff --git a/src/Elastic.OpenTelemetry/Configuration/LogTargets.cs b/src/Elastic.OpenTelemetry/Configuration/LogTargets.cs new file mode 100644 index 0000000..04ab903 --- /dev/null +++ b/src/Elastic.OpenTelemetry/Configuration/LogTargets.cs @@ -0,0 +1,26 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace Elastic.OpenTelemetry.Configuration; + +/// +/// Control how the distribution should globally log. +/// +[Flags] +public enum LogTargets +{ + + /// No global logging + None, + /// + /// Enable file logging. Use + /// and to set any values other than the defaults + /// + File = 1 << 0, //1 + /// + /// Write to standard out, useful in scenarios where file logging might not be an option or harder to set up. + /// e.g. containers, k8s, etc. + /// + StdOut = 1 << 1, //2 +} diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs b/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs index 666b66c..9fb96f7 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Logging/AgentLoggingHelpers.cs @@ -3,28 +3,12 @@ // See the LICENSE file in the project root for more information using System.Diagnostics; -using Elastic.OpenTelemetry.Configuration; using Microsoft.Extensions.Logging; namespace Elastic.OpenTelemetry.Diagnostics.Logging; internal static class AgentLoggingHelpers { - public static LogLevel DefaultLogLevel => LogLevel.Information; - - public static LogLevel GetElasticOtelLogLevelFromEnvironmentVariables() - { - var defaultLogLevel = DefaultLogLevel; - - var logLevelEnvironmentVariable = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL); - - if (string.IsNullOrEmpty(logLevelEnvironmentVariable)) - return defaultLogLevel; - - var parsedLogLevel = LogLevelHelpers.ToLogLevel(logLevelEnvironmentVariable); - return parsedLogLevel != LogLevel.None ? parsedLogLevel : defaultLogLevel; - } - public static void WriteLogLine(this ILogger logger, Activity? activity, int managedThreadId, DateTime dateTime, LogLevel logLevel, string logLine, string? spanId) { var state = new LogState diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs b/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs index f0a56c4..927861f 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs @@ -32,18 +32,17 @@ internal sealed class FileLogger : IDisposable, IAsyncDisposable, ILogger public FileLogger(ElasticOpenTelemetryOptions options) { _scopeProvider = new LoggerExternalScopeProvider(); + _configuredLogLevel = options.LogLevel; + FileLoggingEnabled = options.GlobalLogEnabled; - var logDirectory = options.FileLogDirectory; - var logLevel = options.FileLogLevel; - if (logLevel == LogLevel.None || (logLevel == null && logDirectory == null)) + if (!FileLoggingEnabled) return; - _configuredLogLevel = logLevel ?? LogLevel.Information; - logDirectory ??= options.FileLogDirectoryDefault; - var process = Process.GetCurrentProcess(); // When ordered by filename, we get see logs from the same process grouped, then ordered by oldest to newest, then the PID for that instance var logFileName = $"{process.ProcessName}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{process.Id}.instrumentation.log"; + + var logDirectory = options.LogDirectory; LogFilePath = Path.Combine(logDirectory, logFileName); if (!Directory.Exists(logDirectory)) diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs b/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs index a7db33f..82d43cd 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs @@ -16,32 +16,27 @@ internal static class LogLevelHelpers public const string Trace = "Trace"; public const string None = "None"; - public static LogLevel ToLogLevel(string logLevelString) + public static LogLevel? ToLogLevel(string logLevelString) { - var logLevel = LogLevel.None; - if (logLevelString.Equals(Trace, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Trace; - - else if (logLevelString.Equals(Debug, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Debug; - - else if (logLevelString.Equals(Information, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Information; - - else if (logLevelString.Equals(Information, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Information; - - else if (logLevelString.Equals(Warning, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Warning; - - else if (logLevelString.Equals(Error, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Error; - - else if (logLevelString.Equals(Critical, StringComparison.OrdinalIgnoreCase)) - logLevel = LogLevel.Critical; - - return logLevel; + return LogLevel.Trace; + if (logLevelString.Equals(Debug, StringComparison.OrdinalIgnoreCase)) + return LogLevel.Debug; + if (logLevelString.Equals("Info", StringComparison.OrdinalIgnoreCase)) + return LogLevel.Information; + if (logLevelString.Equals(Information, StringComparison.OrdinalIgnoreCase)) + return LogLevel.Information; + if (logLevelString.Equals("Warn", StringComparison.OrdinalIgnoreCase)) + return LogLevel.Warning; + if (logLevelString.Equals(Warning, StringComparison.OrdinalIgnoreCase)) + return LogLevel.Warning; + if (logLevelString.Equals(Error, StringComparison.OrdinalIgnoreCase)) + return LogLevel.Error; + if (logLevelString.Equals(Critical, StringComparison.OrdinalIgnoreCase)) + return LogLevel.Critical; + if (logLevelString.Equals(None, StringComparison.OrdinalIgnoreCase)) + return LogLevel.None; + return null; } public static string AsString(this LogLevel logLevel) => diff --git a/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs b/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs index e62bfc0..9c52487 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/LoggingEventListener.cs @@ -38,10 +38,10 @@ public LoggingEventListener(ILogger logger, ElasticOpenTelemetryOptions options) // When both a file log level and a logging section log level are provided, the more verbose of the two is used. // This insures we subscribe to the lowest level of events needed. // The specific loggers will then determine if they should log the event based on their own log level. - var eventLevel = options.FileLogLevel; + var eventLevel = options.LogLevel; if (!string.IsNullOrEmpty(options.LoggingSectionLogLevel)) { - var logLevel = LogLevelHelpers.ToLogLevel(options.LoggingSectionLogLevel); + var logLevel = LogLevelHelpers.ToLogLevel(options.LoggingSectionLogLevel) ?? LogLevel.None; if (logLevel < eventLevel) eventLevel = logLevel; diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs index b373c02..21490b1 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs @@ -2,26 +2,23 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Collections; using System.Text; using Elastic.OpenTelemetry.Configuration; using Elastic.OpenTelemetry.Extensions; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using OpenTelemetry; using Xunit.Abstractions; using static Elastic.OpenTelemetry.Configuration.ElasticOpenTelemetryOptions; +using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; using static Elastic.OpenTelemetry.Diagnostics.Logging.LogLevelHelpers; namespace Elastic.OpenTelemetry.Tests.Configuration; -public sealed class ElasticOpenTelemetryOptionsTests(ITestOutputHelper output) : IDisposable +public sealed class ElasticOpenTelemetryOptionsTests(ITestOutputHelper output) { - private readonly ITestOutputHelper _output = output; - private readonly string? _originalFileLogDirectoryEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY); - private readonly string? _originalFileLogLevelEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL); - private readonly string? _originalEnableElasticDefaultsEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS); - private readonly string? _originalSkipOtlpExporterEnvVar = Environment.GetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER); - [Fact] public void EnabledElasticDefaults_NoneIncludesExpectedValues() { @@ -35,16 +32,18 @@ public void EnabledElasticDefaults_NoneIncludesExpectedValues() [Fact] public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfigured() { - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); - - var sut = new ElasticOpenTelemetryOptions(); + var sut = new ElasticOpenTelemetryOptions(new Hashtable + { + {ELASTIC_OTEL_LOG_DIRECTORY, null}, + {ELASTIC_OTEL_LOG_LEVEL, null}, + {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null}, + {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null}, + }); + sut.GlobalLogEnabled.Should().Be(false); // these default to null because any other value would enable file logging - sut.FileLogDirectory.Should().Be(null); - sut.FileLogLevel.Should().Be(null); + sut.LogDirectory.Should().Be(sut.LogDirectoryDefault); + sut.LogLevel.Should().Be(LogLevel.Warning); sut.EnableElasticDefaults.Should().Be(string.Empty); sut.EnabledDefaults.Should().HaveFlag(EnabledElasticDefaults.Tracing); @@ -52,17 +51,13 @@ public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfig sut.EnabledDefaults.Should().HaveFlag(EnabledElasticDefaults.Logging); sut.SkipOtlpExporter.Should().Be(false); - var logger = new TestLogger(_output); + var logger = new TestLogger(output); sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); foreach (var message in logger.Messages) - { message.Should().EndWith("from [Default]"); - } - - ResetEnvironmentVariables(); } [Fact] @@ -72,30 +67,28 @@ public void DefaultCtor_LoadsConfigurationFromEnvironmentVariables() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, fileLogLevel); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"); - - var sut = new ElasticOpenTelemetryOptions(); - - sut.FileLogDirectory.Should().Be(fileLogDirectory); - sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); + var sut = new ElasticOpenTelemetryOptions(new Hashtable + { + {ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory}, + {ELASTIC_OTEL_LOG_LEVEL, fileLogLevel}, + {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults}, + {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"}, + }); + + sut.LogDirectory.Should().Be(fileLogDirectory); + sut.LogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); - var logger = new TestLogger(_output); + var logger = new TestLogger(output); sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); foreach (var message in logger.Messages) - { message.Should().EndWith("from [Environment]"); - } - ResetEnvironmentVariables(); } [Fact] @@ -105,12 +98,6 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - // Remove all env vars - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); - var json = $$""" { "Logging": { @@ -121,8 +108,8 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() }, "Elastic": { "OpenTelemetry": { - "FileLogDirectory": "C:\\Temp", - "FileLogLevel": "{{fileLogLevel}}", + "LogDirectory": "C:\\Temp", + "LogLevel": "{{fileLogLevel}}", "EnabledElasticDefaults": "{{enabledElasticDefaults}}", "SkipOtlpExporter": true } @@ -134,26 +121,22 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) .Build(); - var sut = new ElasticOpenTelemetryOptions(config); + var sut = new ElasticOpenTelemetryOptions(config, new Hashtable()); - sut.FileLogDirectory.Should().Be(@"C:\Temp"); - sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); + sut.LogDirectory.Should().Be(@"C:\Temp"); + sut.LogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); sut.LoggingSectionLogLevel.Should().Be(loggingSectionLogLevel); - var logger = new TestLogger(_output); + var logger = new TestLogger(output); sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); foreach (var message in logger.Messages) - { message.Should().EndWith("from [IConfiguration]"); - } - - ResetEnvironmentVariables(); } [Fact] @@ -162,12 +145,6 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT const string loggingSectionLogLevel = "Warning"; const string enabledElasticDefaults = "None"; - // Remove all env vars - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); - var json = $$""" { "Logging": { @@ -178,7 +155,7 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT }, "Elastic": { "OpenTelemetry": { - "FileLogDirectory": "C:\\Temp", + "LogDirectory": "C:\\Temp", "EnabledElasticDefaults": "{{enabledElasticDefaults}}", "SkipOtlpExporter": true } @@ -190,26 +167,23 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) .Build(); - var sut = new ElasticOpenTelemetryOptions(config); + var sut = new ElasticOpenTelemetryOptions(config, new Hashtable()); - sut.FileLogDirectory.Should().Be(@"C:\Temp"); - sut.FileLogLevel.Should().Be(ToLogLevel(loggingSectionLogLevel)); + sut.LogDirectory.Should().Be(@"C:\Temp"); + sut.LogLevel.Should().Be(ToLogLevel(loggingSectionLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); sut.LoggingSectionLogLevel.Should().Be(loggingSectionLogLevel); - var logger = new TestLogger(_output); + var logger = new TestLogger(output); sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); foreach (var message in logger.Messages) - { message.Should().EndWith("from [IConfiguration]"); - } - ResetEnvironmentVariables(); } [Fact] @@ -218,12 +192,6 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT const string loggingSectionDefaultLogLevel = "Information"; const string enabledElasticDefaults = "None"; - // Remove all env vars - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null); - var json = $$""" { "Logging": { @@ -233,7 +201,7 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT }, "Elastic": { "OpenTelemetry": { - "FileLogDirectory": "C:\\Temp", + "LogDirectory": "C:\\Temp", "EnabledElasticDefaults": "{{enabledElasticDefaults}}", "SkipOtlpExporter": true } @@ -245,26 +213,21 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) .Build(); - var sut = new ElasticOpenTelemetryOptions(config); + var sut = new ElasticOpenTelemetryOptions(config, new Hashtable()); - sut.FileLogDirectory.Should().Be(@"C:\Temp"); - sut.FileLogLevel.Should().Be(ToLogLevel(loggingSectionDefaultLogLevel)); + sut.LogDirectory.Should().Be(@"C:\Temp"); + sut.LogLevel.Should().Be(ToLogLevel(loggingSectionDefaultLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); sut.LoggingSectionLogLevel.Should().Be(loggingSectionDefaultLogLevel); - var logger = new TestLogger(_output); + var logger = new TestLogger(output); sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); - foreach (var message in logger.Messages) - { - message.Should().EndWith("from [IConfiguration]"); - } - - ResetEnvironmentVariables(); + foreach (var message in logger.Messages) message.Should().EndWith("from [IConfiguration]"); } [Fact] @@ -274,17 +237,12 @@ public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, fileLogLevel); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"); - var json = $$""" { "Elastic": { "OpenTelemetry": { - "FileLogDirectory": "C:\\Json", - "FileLogLevel": "Trace", + "LogDirectory": "C:\\Json", + "LogLevel": "Trace", "EnabledElasticDefaults": "All", "SkipOtlpExporter": false } @@ -296,15 +254,19 @@ public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) .Build(); - var sut = new ElasticOpenTelemetryOptions(config); - - sut.FileLogDirectory.Should().Be(fileLogDirectory); - sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); + var sut = new ElasticOpenTelemetryOptions(config, new Hashtable + { + {ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory}, + {ELASTIC_OTEL_LOG_LEVEL, fileLogLevel}, + {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults}, + {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"}, + }); + + sut.LogDirectory.Should().Be(fileLogDirectory); + sut.LogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(true); - - ResetEnvironmentVariables(); } [Fact] @@ -314,36 +276,33 @@ public void InitializedProperties_TakePrecedenceOver_EnvironmentValues() const string fileLogLevel = "Critical"; const string enabledElasticDefaults = "None"; - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, "C:\\Temp"); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, "Information"); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, "All"); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"); - - var sut = new ElasticOpenTelemetryOptions + var sut = new ElasticOpenTelemetryOptions(new Hashtable + { + {ELASTIC_OTEL_LOG_DIRECTORY, "C:\\Temp"}, + {ELASTIC_OTEL_LOG_LEVEL, "Information"}, + {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, "All"}, + {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"}, + }) { - FileLogDirectory = fileLogDirectory, - FileLogLevel = ToLogLevel(fileLogLevel), + LogDirectory = fileLogDirectory, + LogLevel = ToLogLevel(fileLogLevel) ?? LogLevel.None, SkipOtlpExporter = false, EnableElasticDefaults = enabledElasticDefaults }; - sut.FileLogDirectory.Should().Be(fileLogDirectory); - sut.FileLogLevel.Should().Be(ToLogLevel(fileLogLevel)); + sut.LogDirectory.Should().Be(fileLogDirectory); + sut.LogLevel.Should().Be(ToLogLevel(fileLogLevel)); sut.EnableElasticDefaults.Should().Be(enabledElasticDefaults); sut.EnabledDefaults.Should().Be(EnabledElasticDefaults.None); sut.SkipOtlpExporter.Should().Be(false); - var logger = new TestLogger(_output); + var logger = new TestLogger(output); sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); foreach (var message in logger.Messages) - { message.Should().EndWith("from [Property]"); - } - - ResetEnvironmentVariables(); } [Theory] @@ -433,7 +392,7 @@ public void TransactionId_IsNotAdded_WhenElasticDefaultsDoesNotIncludeTracing() { var options = new ElasticOpenTelemetryBuilderOptions { - Logger = new TestLogger(_output), + Logger = new TestLogger(output), DistroOptions = new ElasticOpenTelemetryOptions() { SkipOtlpExporter = true, @@ -468,14 +427,4 @@ public void TransactionId_IsNotAdded_WhenElasticDefaultsDoesNotIncludeTracing() transactionId.Should().BeNull(); } - - private void ResetEnvironmentVariables() - { - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, _originalFileLogDirectoryEnvVar); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL, _originalFileLogLevelEnvVar); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, _originalEnableElasticDefaultsEnvVar); - Environment.SetEnvironmentVariable(EnvironmentVariables.ELASTIC_OTEL_SKIP_OTLP_EXPORTER, _originalSkipOtlpExporterEnvVar); - } - - public void Dispose() => ResetEnvironmentVariables(); } diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs new file mode 100644 index 0000000..4fc5e42 --- /dev/null +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs @@ -0,0 +1,147 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections; +using Elastic.OpenTelemetry.Configuration; +using Microsoft.Extensions.Logging; +using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; + +namespace Elastic.OpenTelemetry.Tests.Configuration; + +public class GlobalLogConfigurationTests +{ + [Fact] + public void Check_Defaults() + { + var config = new ElasticOpenTelemetryOptions(new Hashtable()); + config.GlobalLogEnabled.Should().BeFalse(); + config.LogLevel.Should().Be(LogLevel.Warning); + config.LogDirectory.Should().Be(config.LogDirectoryDefault); + config.LogTargets.Should().Be(LogTargets.None); + } + + // + [Theory] + [InlineData(ELASTIC_OTEL_LOG_LEVEL, "Info")] + [InlineData(ELASTIC_OTEL_LOG_DIRECTORY, "1")] + //only if explicitly specified to 'none' should we not default to file logging. + [InlineData(ELASTIC_OTEL_LOG_TARGETS, "file")] + public void CheckActivation(string environmentVariable, string value) + { + var config = new ElasticOpenTelemetryOptions(new Hashtable { { environmentVariable, value } }); + config.GlobalLogEnabled.Should().BeTrue(); + config.LogTargets.Should().Be(LogTargets.File); + } + + // + [Theory] + [InlineData(ELASTIC_OTEL_LOG_LEVEL, "none")] + //only if explicitly specified to 'none' should we not default to file logging. + [InlineData(ELASTIC_OTEL_LOG_TARGETS, "none")] + public void CheckDeactivation(string environmentVariable, string value) + { + var config = new ElasticOpenTelemetryOptions(new Hashtable + { + { ELASTIC_OTEL_LOG_DIRECTORY, "" }, + { environmentVariable, value } + }); + config.GlobalLogEnabled.Should().BeFalse(); + config.LogTargets.Should().Be(LogTargets.None); + } + + [Theory] + //only specifying apm_log_level not sufficient, needs explicit directory configuration + //setting targets to none will result in no global trace logging + [InlineData(ELASTIC_OTEL_LOG_TARGETS, "None")] + //setting file log level to none will result in no global trace logging + [InlineData(ELASTIC_OTEL_LOG_LEVEL, "None")] + public void CheckNonActivation(string environmentVariable, string value) + { + var config = new ElasticOpenTelemetryOptions(new Hashtable { { environmentVariable, value } }); + config.GlobalLogEnabled.Should().BeFalse(); + } + + [Theory] + [InlineData("trace", LogLevel.Trace)] + [InlineData("Trace", LogLevel.Trace)] + [InlineData("TraCe", LogLevel.Trace)] + [InlineData("debug", LogLevel.Debug)] + [InlineData("info", LogLevel.Information)] + [InlineData("warn", LogLevel.Warning)] + [InlineData("error", LogLevel.Error)] + [InlineData("none", LogLevel.None)] + public void Check_LogLevelValues_AreMappedCorrectly(string envVarValue, LogLevel logLevel) + { + Check(ELASTIC_OTEL_LOG_LEVEL, envVarValue, logLevel); + return; + + static void Check(string key, string envVarValue, LogLevel level) + { + var config = CreateConfig(key, envVarValue); + config.LogLevel.Should().Be(level, "{0}", key); + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("foo")] + [InlineData("tracing")] + public void Check_InvalidLogLevelValues_AreMappedToDefaultWarn(string? envVarValue) + { + Check(ELASTIC_OTEL_LOG_LEVEL, envVarValue); + return; + + static void Check(string key, string? envVarValue) + { + var config = CreateConfig(key, envVarValue); + config.LogLevel.Should().Be(LogLevel.Warning, "{0}", key); + } + } + + [Fact] + public void Check_LogDir_IsEvaluatedCorrectly() + { + Check(ELASTIC_OTEL_LOG_DIRECTORY, "/foo/bar"); + return; + + static void Check(string key, string envVarValue) + { + var config = CreateConfig(key, envVarValue); + config.LogDirectory.Should().StartWith("/foo/bar", "{0}", key); + } + } + + [Theory] + [InlineData(null, LogTargets.None)] + [InlineData("", LogTargets.None)] + [InlineData("foo", LogTargets.None)] + [InlineData("foo,bar", LogTargets.None)] + [InlineData("foo;bar", LogTargets.None)] + [InlineData("file;foo;bar", LogTargets.File)] + [InlineData("file", LogTargets.File)] + [InlineData("stdout", LogTargets.StdOut)] + [InlineData("StdOut", LogTargets.StdOut)] + [InlineData("file;stdout", LogTargets.File | LogTargets.StdOut)] + [InlineData("FILE;StdOut", LogTargets.File | LogTargets.StdOut)] + [InlineData("file;stdout;file", LogTargets.File | LogTargets.StdOut)] + [InlineData("FILE;StdOut;stdout", LogTargets.File | LogTargets.StdOut)] + internal void Check_LogTargets_AreEvaluatedCorrectly(string? envVarValue, LogTargets? targets) + { + Check(ELASTIC_OTEL_LOG_TARGETS, envVarValue, targets); + return; + + static void Check(string key, string? envVarValue, LogTargets? targets) + { + var config = CreateConfig(key, envVarValue); + config.LogTargets.Should().Be(targets, "{0}", key); + } + } + + private static ElasticOpenTelemetryOptions CreateConfig(string key, string? envVarValue) + { + var environment = new Hashtable { { key, envVarValue } }; + return new ElasticOpenTelemetryOptions(environment); + } +} From 4ca53269623e1449309f8a0d0866aaec587704b0 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 4 Jun 2024 17:36:37 +0200 Subject: [PATCH 3/4] formatting --- .../Configuration/ElasticOpenTelemetryOptions.cs | 5 +++-- .../Configuration/ElasticOpenTelemetryOptionsTests.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs index f5f622e..699b823 100644 --- a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs @@ -78,7 +78,8 @@ public ElasticOpenTelemetryOptions(IDictionary? environmentVariables = null) internal ElasticOpenTelemetryOptions(IConfiguration? configuration, IDictionary? environmentVariables = null) : this(environmentVariables) { - if (configuration is null) return; + if (configuration is null) + return; SetFromConfiguration(configuration, LogDirectoryConfigPropertyName, ref _logDirectory, ref _logDirectorySource, StringParser); SetFromConfiguration(configuration, LogLevelConfigPropertyName, ref _logLevel, ref _logLevelSource, LogLevelParser); SetFromConfiguration(configuration, LogTargetsConfigPropertyName, ref _logTargets, ref _logTargetsSource, LogTargetsParser); @@ -139,7 +140,7 @@ private static string GetDefaultLogDirectory() => /// - /var/log/elastic/apm-agent-dotnet (on Linux) /// - ~/Library/Application_Support/elastic/apm-agent-dotnet (on OSX) /// - public string LogDirectoryDefault => _defaultLogDirectory ; + public string LogDirectoryDefault => _defaultLogDirectory; /// /// The output directory where the Elastic distribution of OpenTelemetry will write log files. diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs index 21490b1..570edf8 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs @@ -227,7 +227,8 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT sut.LogConfigSources(logger); logger.Messages.Count.Should().Be(4); - foreach (var message in logger.Messages) message.Should().EndWith("from [IConfiguration]"); + foreach (var message in logger.Messages) + message.Should().EndWith("from [IConfiguration]"); } [Fact] From c977467fc051909fb0466dca0094338d161c66d3 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 5 Jun 2024 11:35:28 +0200 Subject: [PATCH 4/4] use RuntimeInformation and use elastic-otel-dotnet as folder name --- .../ElasticOpenTelemetryOptions.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs index 699b823..2db95c3 100644 --- a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs @@ -7,6 +7,8 @@ using Elastic.OpenTelemetry.Diagnostics.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using static System.Environment; +using static System.Runtime.InteropServices.RuntimeInformation; using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; namespace Elastic.OpenTelemetry.Configuration; @@ -63,7 +65,7 @@ public class ElasticOpenTelemetryOptions public ElasticOpenTelemetryOptions(IDictionary? environmentVariables = null) { _defaultLogDirectory = GetDefaultLogDirectory(); - _environmentVariables = environmentVariables ?? Environment.GetEnvironmentVariables(); + _environmentVariables = environmentVariables ?? GetEnvironmentVariables(); SetFromEnvironment(ELASTIC_OTEL_LOG_DIRECTORY, ref _logDirectory, ref _logDirectorySource, StringParser); SetFromEnvironment(ELASTIC_OTEL_LOG_LEVEL, ref _logLevel, ref _logLevelSource, LogLevelParser); SetFromEnvironment(ELASTIC_OTEL_LOG_TARGETS, ref _logTargets, ref _logTargetsSource, LogTargetsParser); @@ -126,12 +128,15 @@ public bool GlobalLogEnabled } } - private static string GetDefaultLogDirectory() => - Environment.OSVersion.Platform == PlatformID.Win32NT - ? Path.Combine(Environment.GetEnvironmentVariable("PROGRAMDATA")!, "elastic", "apm-agent-dotnet", "logs") - : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) - ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "elastic", "apm-agent-dotnet") - : "/var/log/elastic/apm-agent-dotnet"; + private static string GetDefaultLogDirectory() + { + var applicationMoniker = "elastic-otel-dotnet"; + if (IsOSPlatform(OSPlatform.Windows)) + return Path.Combine(GetFolderPath(SpecialFolder.ApplicationData), "elastic", applicationMoniker); + if (IsOSPlatform(OSPlatform.OSX)) + return Path.Combine(GetFolderPath(SpecialFolder.LocalApplicationData), "elastic", applicationMoniker); + return $"/var/log/elastic/{applicationMoniker}"; + } /// /// The default log directory if file logging was enabled but non was specified