diff --git a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs index 0b59964..714fa36 100644 --- a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.Collections; +using System.Diagnostics; using System.Runtime.InteropServices; using Elastic.OpenTelemetry.Diagnostics.Logging; using Microsoft.Extensions.Configuration; @@ -54,6 +55,9 @@ public class ElasticOpenTelemetryOptions private readonly string? _enabledElasticDefaults; private readonly ConfigSource _enabledElasticDefaultsSource = ConfigSource.Default; + private readonly bool? _runningInContainer; + private readonly ConfigSource _runningInContainerSource = ConfigSource.Default; + private string? _loggingSectionLogLevel; private readonly string _defaultLogDirectory; private readonly IDictionary _environmentVariables; @@ -66,8 +70,10 @@ public ElasticOpenTelemetryOptions(IDictionary? environmentVariables = null) { _defaultLogDirectory = GetDefaultLogDirectory(); _environmentVariables = environmentVariables ?? GetEnvironmentVariables(); - SetFromEnvironment(ELASTIC_OTEL_LOG_DIRECTORY, ref _logDirectory, ref _logDirectorySource, StringParser); - SetFromEnvironment(ELASTIC_OTEL_LOG_LEVEL, ref _logLevel, ref _logLevelSource, LogLevelParser); + SetFromEnvironment(DOTNET_RUNNING_IN_CONTAINER, ref _runningInContainer, ref _runningInContainerSource, BoolParser); + + SetFromEnvironment(OTEL_DOTNET_AUTO_LOG_DIRECTORY, ref _logDirectory, ref _logDirectorySource, StringParser); + SetFromEnvironment(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); @@ -116,7 +122,7 @@ public bool GlobalLogEnabled { get { - var isActive = _logLevel.HasValue || !string.IsNullOrWhiteSpace(_logDirectory) || _logTargets.HasValue; + var isActive = _logLevel is <= LogLevel.Debug || !string.IsNullOrWhiteSpace(_logDirectory) || _logTargets.HasValue; if (!isActive) return isActive; @@ -193,7 +199,9 @@ public LogLevel LogLevel /// > public LogTargets LogTargets { - get => _logTargets ?? (GlobalLogEnabled ? LogTargets.File : LogTargets.None); + get => _logTargets ?? (GlobalLogEnabled ? + _runningInContainer.HasValue && _runningInContainer.Value ? LogTargets.StdOut : LogTargets.File + : LogTargets.None); init { _logTargets = value; @@ -275,7 +283,13 @@ bool IsSet(string k, string v) 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 (bool, bool?) BoolParser(string? s) => + s switch + { + "1" => (true, true), + "0" => (true, false), + _ => bool.TryParse(s, out var boolValue) ? (true, boolValue) : (false, null) + }; private void SetFromEnvironment(string key, ref T field, ref ConfigSource configSourceField, Func parser) { diff --git a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs index 4417e5c..6827fa8 100644 --- a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs +++ b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs @@ -9,10 +9,13 @@ internal static class EnvironmentVariables // 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_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 ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS); public const string ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS = nameof(ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS); + + public const string DOTNET_RUNNING_IN_CONTAINER = nameof(DOTNET_RUNNING_IN_CONTAINER); // ReSharper enable InconsistentNaming } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs index a32ebe4..79f8751 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs @@ -14,23 +14,23 @@ internal static partial class LoggerMessages #pragma warning disable SYSLIB1006 // Multiple logging methods cannot use the same event id within a class // We explictly reuse the same event ID and this is the same log message, but with different types for the structured data - [LoggerMessage(EventId = 100, Level = LogLevel.Trace, Message = "{ProcessorName} found `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] + [LoggerMessage(EventId = 100, Level = LogLevel.Debug, Message = "{ProcessorName} found `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] internal static partial void FoundTag(this ILogger logger, string processorName, string attributeName, string attributeValue); - [LoggerMessage(EventId = 100, Level = LogLevel.Trace, Message = "{ProcessorName} found `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] + [LoggerMessage(EventId = 100, Level = LogLevel.Debug, Message = "{ProcessorName} found `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] internal static partial void FoundTag(this ILogger logger, string processorName, string attributeName, int attributeValue); - [LoggerMessage(EventId = 101, Level = LogLevel.Trace, Message = "{ProcessorName} set `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] + [LoggerMessage(EventId = 101, Level = LogLevel.Debug, Message = "{ProcessorName} set `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] internal static partial void SetTag(this ILogger logger, string processorName, string attributeName, string attributeValue); - [LoggerMessage(EventId = 101, Level = LogLevel.Trace, Message = "{ProcessorName} set `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] + [LoggerMessage(EventId = 101, Level = LogLevel.Debug, Message = "{ProcessorName} set `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] internal static partial void SetTag(this ILogger logger, string processorName, string attributeName, int attributeValue); #pragma warning restore SYSLIB1006 // Multiple logging methods cannot use the same event id within a class - [LoggerMessage(EventId = 20, Level = LogLevel.Trace, Message = "Added '{ProcessorTypeName}' processor to '{BuilderTypeName}'.")] + [LoggerMessage(EventId = 20, Level = LogLevel.Debug, Message = "Added '{ProcessorTypeName}' processor to '{BuilderTypeName}'.")] public static partial void LogProcessorAdded(this ILogger logger, string processorTypeName, string builderTypeName); - [LoggerMessage(EventId = 21, Level = LogLevel.Trace, Message = "Added '{MeterName}' meter to '{BuilderTypeName}'.")] + [LoggerMessage(EventId = 21, Level = LogLevel.Debug, Message = "Added '{MeterName}' meter to '{BuilderTypeName}'.")] public static partial void LogMeterAdded(this ILogger logger, string meterName, string builderTypeName); public static void LogAgentPreamble(this ILogger logger) @@ -66,8 +66,8 @@ public static void LogAgentPreamble(this ILogger logger) string[] environmentVariables = [ - EnvironmentVariables.ELASTIC_OTEL_LOG_DIRECTORY, - EnvironmentVariables.ELASTIC_OTEL_LOG_LEVEL + EnvironmentVariables.OTEL_DOTNET_AUTO_LOG_DIRECTORY, + EnvironmentVariables.OTEL_LOG_LEVEL ]; foreach (var variable in environmentVariables) diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs b/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs index 82d43cd..9e1f939 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs @@ -18,8 +18,11 @@ internal static class LogLevelHelpers public static LogLevel? ToLogLevel(string logLevelString) { + //TRACE does not exist in OTEL_LOG_LEVEL ensure we parse it to next granularity + //debug, NOTE that OpenTelemetry treats this as invalid and will parse to 'Information' + //We treat Debug & Trace as a signal global file logging should be enabled. if (logLevelString.Equals(Trace, StringComparison.OrdinalIgnoreCase)) - return LogLevel.Trace; + return LogLevel.Debug; if (logLevelString.Equals(Debug, StringComparison.OrdinalIgnoreCase)) return LogLevel.Debug; if (logLevelString.Equals("Info", StringComparison.OrdinalIgnoreCase)) diff --git a/src/Elastic.OpenTelemetry/ElasticOpenTelemetryBuilder.cs b/src/Elastic.OpenTelemetry/ElasticOpenTelemetryBuilder.cs index cd7ee7d..858dc44 100644 --- a/src/Elastic.OpenTelemetry/ElasticOpenTelemetryBuilder.cs +++ b/src/Elastic.OpenTelemetry/ElasticOpenTelemetryBuilder.cs @@ -132,7 +132,7 @@ internal static partial class LoggerMessages [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "ElasticOpenTelemetryBuilder initialized{newline}{StackTrace}.")] public static partial void LogElasticOpenTelemetryBuilderInitialized(this ILogger logger, string newline, StackTrace stackTrace); - [LoggerMessage(EventId = 1, Level = LogLevel.Trace, Message = "ElasticOpenTelemetryBuilder configured {Signal} via the {Provider}.")] + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = "ElasticOpenTelemetryBuilder configured {Signal} via the {Provider}.")] public static partial void LogConfiguredSignalProvider(this ILogger logger, string signal, string provider); [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "No Elastic defaults were enabled.")] diff --git a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs index f472a93..95417db 100644 --- a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs @@ -73,6 +73,6 @@ internal static IInstrumentationLifetime Build(this IOpenTelemetryBuilder builde internal static partial class LoggerMessages { - [LoggerMessage(EventId = 10, Level = LogLevel.Trace, Message = "ElasticOpenTelemetryBuilder built.")] + [LoggerMessage(EventId = 10, Level = LogLevel.Debug, Message = "ElasticOpenTelemetryBuilder built.")] public static partial void LogElasticOpenTelemetryBuilderBuilt(this ILogger logger); } diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs index 570edf8..6a075ad 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs @@ -34,8 +34,8 @@ public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfig { var sut = new ElasticOpenTelemetryOptions(new Hashtable { - {ELASTIC_OTEL_LOG_DIRECTORY, null}, - {ELASTIC_OTEL_LOG_LEVEL, null}, + {OTEL_DOTNET_AUTO_LOG_DIRECTORY, null}, + {OTEL_LOG_LEVEL, null}, {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, null}, {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null}, }); @@ -69,8 +69,8 @@ public void DefaultCtor_LoadsConfigurationFromEnvironmentVariables() var sut = new ElasticOpenTelemetryOptions(new Hashtable { - {ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory}, - {ELASTIC_OTEL_LOG_LEVEL, fileLogLevel}, + {OTEL_DOTNET_AUTO_LOG_DIRECTORY, fileLogDirectory}, + {OTEL_LOG_LEVEL, fileLogLevel}, {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults}, {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"}, }); @@ -257,8 +257,8 @@ public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() var sut = new ElasticOpenTelemetryOptions(config, new Hashtable { - {ELASTIC_OTEL_LOG_DIRECTORY, fileLogDirectory}, - {ELASTIC_OTEL_LOG_LEVEL, fileLogLevel}, + {OTEL_DOTNET_AUTO_LOG_DIRECTORY, fileLogDirectory}, + {OTEL_LOG_LEVEL, fileLogLevel}, {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, enabledElasticDefaults}, {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"}, }); @@ -279,8 +279,8 @@ public void InitializedProperties_TakePrecedenceOver_EnvironmentValues() var sut = new ElasticOpenTelemetryOptions(new Hashtable { - {ELASTIC_OTEL_LOG_DIRECTORY, "C:\\Temp"}, - {ELASTIC_OTEL_LOG_LEVEL, "Information"}, + {OTEL_DOTNET_AUTO_LOG_DIRECTORY, "C:\\Temp"}, + {OTEL_LOG_LEVEL, "Information"}, {ELASTIC_OTEL_ENABLE_ELASTIC_DEFAULTS, "All"}, {ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true"}, }) diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs index 4fc5e42..6e36970 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/GlobalLogConfigurationTests.cs @@ -23,8 +23,8 @@ public void Check_Defaults() // [Theory] - [InlineData(ELASTIC_OTEL_LOG_LEVEL, "Info")] - [InlineData(ELASTIC_OTEL_LOG_DIRECTORY, "1")] + [InlineData(OTEL_LOG_LEVEL, "Info")] + [InlineData(OTEL_DOTNET_AUTO_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) @@ -36,14 +36,14 @@ public void CheckActivation(string environmentVariable, string value) // [Theory] - [InlineData(ELASTIC_OTEL_LOG_LEVEL, "none")] + [InlineData(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, "" }, + { OTEL_DOTNET_AUTO_LOG_DIRECTORY, "" }, { environmentVariable, value } }); config.GlobalLogEnabled.Should().BeFalse(); @@ -55,7 +55,7 @@ public void CheckDeactivation(string environmentVariable, string value) //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")] + [InlineData(OTEL_LOG_LEVEL, "None")] public void CheckNonActivation(string environmentVariable, string value) { var config = new ElasticOpenTelemetryOptions(new Hashtable { { environmentVariable, value } }); @@ -63,23 +63,24 @@ public void CheckNonActivation(string environmentVariable, string value) } [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) + [InlineData("trace", LogLevel.Debug, true)] + [InlineData("Trace", LogLevel.Debug, true)] + [InlineData("TraCe", LogLevel.Debug, true)] + [InlineData("debug", LogLevel.Debug, true)] + [InlineData("info", LogLevel.Information, false)] + [InlineData("warn", LogLevel.Warning, false)] + [InlineData("error", LogLevel.Error, false)] + [InlineData("none", LogLevel.None, false)] + public void Check_LogLevelValues_AreMappedCorrectly(string envVarValue, LogLevel logLevel, bool globalLogEnabled) { - Check(ELASTIC_OTEL_LOG_LEVEL, envVarValue, logLevel); + Check(OTEL_LOG_LEVEL, envVarValue, logLevel, globalLogEnabled); return; - static void Check(string key, string envVarValue, LogLevel level) + static void Check(string key, string envVarValue, LogLevel level, bool enabled) { var config = CreateConfig(key, envVarValue); config.LogLevel.Should().Be(level, "{0}", key); + config.GlobalLogEnabled.Should().Be(enabled, "{0}", key); } } @@ -90,7 +91,7 @@ static void Check(string key, string envVarValue, LogLevel level) [InlineData("tracing")] public void Check_InvalidLogLevelValues_AreMappedToDefaultWarn(string? envVarValue) { - Check(ELASTIC_OTEL_LOG_LEVEL, envVarValue); + Check(OTEL_LOG_LEVEL, envVarValue); return; static void Check(string key, string? envVarValue) @@ -103,7 +104,7 @@ static void Check(string key, string? envVarValue) [Fact] public void Check_LogDir_IsEvaluatedCorrectly() { - Check(ELASTIC_OTEL_LOG_DIRECTORY, "/foo/bar"); + Check(OTEL_DOTNET_AUTO_LOG_DIRECTORY, "/foo/bar"); return; static void Check(string key, string envVarValue) @@ -139,6 +140,27 @@ static void Check(string key, string? envVarValue, LogTargets? targets) } } + [Theory] + [InlineData(LogLevel.Debug, null, LogTargets.StdOut, true, true)] + [InlineData(LogLevel.Information, null, LogTargets.None, true, false)] + [InlineData(LogLevel.Debug, null, LogTargets.File, false, true)] + [InlineData(LogLevel.Information, null, LogTargets.None, false, false)] + //Ensure explicit loglevel config always takes precedence + [InlineData(LogLevel.Debug, "file", LogTargets.File, true, true)] + [InlineData(LogLevel.Information, "file", LogTargets.File, false, true)] + internal void LogTargetDefaultsToStandardOutIfRunningInContainerWithLogLevelDebug(LogLevel level, string? logTargetsEnvValue, LogTargets targets, bool inContainer, bool globalLogging) + { + var env = new Hashtable { { OTEL_LOG_LEVEL, level.ToString() } }; + if (inContainer) + env.Add(DOTNET_RUNNING_IN_CONTAINER, "1"); + if (!string.IsNullOrWhiteSpace(logTargetsEnvValue)) + env.Add(ELASTIC_OTEL_LOG_TARGETS, logTargetsEnvValue); + + var config = new ElasticOpenTelemetryOptions(env); + config.GlobalLogEnabled.Should().Be(globalLogging); + config.LogTargets.Should().Be(targets); + } + private static ElasticOpenTelemetryOptions CreateConfig(string key, string? envVarValue) { var environment = new Hashtable { { key, envVarValue } };