Skip to content

Commit

Permalink
Update tests to ensure defaults match existing agent
Browse files Browse the repository at this point in the history
  • Loading branch information
Mpdreamz committed Jun 4, 2024
1 parent cad3f27 commit 6de3d94
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 204 deletions.
4 changes: 2 additions & 2 deletions examples/Example.MinimalApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
},
"Elastic": {
"OpenTelemetry": {
"FileLogDirectory": "C:\\Logs\\OtelDistro",
"FileLogLevel": "Information"
"LogDirectory": "C:\\Logs\\OtelDistro",
"LogLevel": "Information"
}
}
}
150 changes: 115 additions & 35 deletions src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/// <summary>
/// Creates a new instance of <see cref="ElasticOpenTelemetryOptions"/> with properties
/// bound from environment variables.
/// </summary>
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);
}
Expand All @@ -64,11 +75,13 @@ public ElasticOpenTelemetryOptions()
/// Creates a new instance of <see cref="ElasticOpenTelemetryOptions"/> with properties
/// bound from environment variables and an <see cref="IConfiguration"/> instance.
/// </summary>
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);

Expand All @@ -84,11 +97,31 @@ void BindFromLoggingSection(IConfiguration config)
if (string.IsNullOrEmpty(_loggingSectionLogLevel))
_loggingSectionLogLevel = config.GetValue<string>("Logging:LogLevel:Default");

if (!string.IsNullOrEmpty(_loggingSectionLogLevel) && _fileLogLevel is null)
if (!string.IsNullOrEmpty(_loggingSectionLogLevel) && _logLevel is null)
{
_logLevel = LogLevelHelpers.ToLogLevel(_loggingSectionLogLevel);
_logLevelSource = ConfigSource.IConfiguration;
}
}
}

/// <summary>
/// Calculates whether global logging is enabled based on
/// <see cref="LogTargets"/>, <see cref="LogDirectory"/> and <see cref="LogLevel"/>
/// </summary>
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;
}
}

Expand All @@ -106,7 +139,7 @@ private static string GetDefaultLogDirectory() =>
/// <para> - /var/log/elastic/apm-agent-dotnet (on Linux)</para>
/// <para> - ~/Library/Application_Support/elastic/apm-agent-dotnet (on OSX)</para>
/// </summary>
public string FileLogDirectoryDefault => _defaultLogDirectory;
public string LogDirectoryDefault => _defaultLogDirectory ;

/// <summary>
/// The output directory where the Elastic distribution of OpenTelemetry will write log files.
Expand All @@ -116,13 +149,13 @@ private static string GetDefaultLogDirectory() =>
/// <c>{ProcessName}_{UtcUnixTimeMilliseconds}_{ProcessId}.instrumentation.log</c>.
/// This log file includes log messages from the OpenTelemetry SDK and the Elastic distribution.
/// </remarks>
public string? FileLogDirectory
public string LogDirectory
{
get => _fileLogDirectory;
get => _logDirectory ?? LogDirectoryDefault;
init
{
_fileLogDirectory = value;
_fileLogDirectorySource = ConfigSource.Property;
_logDirectory = value;
_logDirectorySource = ConfigSource.Property;
}
}

Expand All @@ -141,13 +174,24 @@ public string? FileLogDirectory
/// <item><term>Trace</term><description>Contain the most detailed messages.</description></item>
/// </list>
/// </remarks>
public LogLevel? FileLogLevel
public LogLevel LogLevel
{
get => _fileLogLevel;
get => _logLevel ?? LogLevel.Warning;
init
{
_fileLogLevel = value;
_fileLogLevelSource = ConfigSource.Property;
_logLevel = value;
_logLevelSource = ConfigSource.Property;
}
}

/// <inheritdoc cref="LogTargets"/>>
public LogTargets LogTargets
{
get => _logTargets ?? (GlobalLogEnabled ? LogTargets.File : LogTargets.None);
init
{
_logTargets = value;
_logTargetsSource = ConfigSource.Property;
}
}

Expand Down Expand Up @@ -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<T>(string key, ref T field, ref ConfigSource configSourceField, Func<string?, (bool, T)> parser)
private void SetFromEnvironment<T>(string key, ref T field, ref ConfigSource configSourceField, Func<string?, (bool, T)> parser)
{
var (success, value) = parser(Environment.GetEnvironmentVariable(key));
var (success, value) = parser(GetSafeEnvironmentVariable(key));

if (success)
{
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions src/Elastic.OpenTelemetry/Configuration/LogTargets.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Control how the distribution should globally log.
/// </summary>
[Flags]
public enum LogTargets
{

/// <summary>No global logging </summary>
None,
/// <summary>
/// Enable file logging. Use <see cref="ElasticOpenTelemetryOptions.LogLevel"/>
/// and <see cref="ElasticOpenTelemetryOptions.LogDirectoryDefault"/> to set any values other than the defaults
/// </summary>
File = 1 << 0, //1
/// <summary>
/// Write to standard out, useful in scenarios where file logging might not be an option or harder to set up.
/// <para>e.g. containers, k8s, etc.</para>
/// </summary>
StdOut = 1 << 1, //2
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions src/Elastic.OpenTelemetry/Diagnostics/Logging/FileLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
43 changes: 19 additions & 24 deletions src/Elastic.OpenTelemetry/Diagnostics/Logging/LogLevelHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
Loading

0 comments on commit 6de3d94

Please sign in to comment.