Skip to content

Commit

Permalink
Move closer to OpenTelemetry logging configuration
Browse files Browse the repository at this point in the history
This replaces `ELASTIC_OTEL_LOG_DIRECTORY` and `ELASTIC_OTEL_LOG_LEVEL` with `OTEL_DOTNET_AUTO_LOG_DIRECTORY` and `OTEL_LOG_LEVEL`.

- `OTEL_DOTNET_AUTO_LOG_DIRECTORY` enables global file logging both in auto instrumentation and manual instrumentation.
- `LogLevel.Trace` does not exist in OTEL, we fallback to `Debug` OTEL fallsback to `Information`.
- We treat `LogLevel.Debug` as a switch to enable global logging.
- If we are running in a container and global logging is enabled we now default to standardout and not file unless configured explicitly
  • Loading branch information
Mpdreamz committed Jul 15, 2024
1 parent 2a4f748 commit 0dd08a7
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -193,7 +199,9 @@ public LogLevel LogLevel
/// <inheritdoc cref="LogTargets"/>>
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;
Expand Down Expand Up @@ -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<T>(string key, ref T field, ref ConfigSource configSourceField, Func<string?, (bool, T)> parser)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
16 changes: 8 additions & 8 deletions src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.OpenTelemetry/ElasticOpenTelemetryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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},
});
Expand Down Expand Up @@ -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"},
});
Expand Down Expand Up @@ -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"},
});
Expand All @@ -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"},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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();
Expand All @@ -55,31 +55,32 @@ 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 } });
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)
[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);
}
}

Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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 } };
Expand Down

0 comments on commit 0dd08a7

Please sign in to comment.