Skip to content

Commit

Permalink
Make activation of global logging more explicit
Browse files Browse the repository at this point in the history
  • Loading branch information
Mpdreamz committed Jun 4, 2024
1 parent 617ccc5 commit 2cc27fa
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 95 deletions.
1 change: 0 additions & 1 deletion src/Elastic.Apm/AgentComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ internal static IApmLogger GetGlobalLogger(IApmLogger fallbackLogger, LogLevel a
}

var effectiveLogLevel = LogLevelUtils.GetFinest(agentLogLevel, fileLogConfig.LogLevel);

if ((fileLogConfig.LogTargets & GlobalLogTarget.File) == GlobalLogTarget.File)
TraceLogger.TraceSource.Listeners.Add(new TextWriterTraceListener(fileLogConfig.AgentLogFilePath));
if ((fileLogConfig.LogTargets & GlobalLogTarget.StdOut) == GlobalLogTarget.StdOut)
Expand Down
165 changes: 84 additions & 81 deletions src/Elastic.Apm/Logging/GlobalLogConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,88 +19,87 @@ namespace Elastic.Apm.Profiler.Managed;
namespace ElasticApmStartupHook;
#else
using static Elastic.Apm.Logging.LogEnvironmentVariables;

namespace Elastic.Apm.Logging;
#endif

internal class EnvironmentLoggingConfiguration(IDictionary environmentVariables = null)
{
public IDictionary EnvironmentVariables { get; } = environmentVariables ?? Environment.GetEnvironmentVariables();

public string GetSafeEnvironmentVariable(string key)
{
var value = EnvironmentVariables.Contains(key) ? EnvironmentVariables[key]?.ToString() : null;
return value ?? string.Empty;
}

public LogLevel GetLogLevel(params string[] keys)
{
var level = keys
.Select(k => GetSafeEnvironmentVariable(k))
.Select<string, LogLevel?>(v => v.ToLowerInvariant() switch
{
"trace" => LogLevel.Trace,
"debug" => LogLevel.Debug,
"info" => LogLevel.Information,
"warn" => LogLevel.Warning,
"error" => LogLevel.Error,
"none" => LogLevel.None,
_ => null
})
.FirstOrDefault(l => l != null);
return level ?? LogLevel.Warning;
}

public string GetLogFilePath(params string[] keys)
{
var path = keys
.Select(k => GetSafeEnvironmentVariable(k))
.FirstOrDefault(p => !string.IsNullOrEmpty(p));

return path ?? GetDefaultLogDirectory();
}
public string GetSafeEnvironmentVariable(string key)
{
var value = EnvironmentVariables.Contains(key) ? EnvironmentVariables[key]?.ToString() : null;
return value ?? string.Empty;
}

public bool AnyConfigured(params string[] keys) =>
keys
.Select(k => GetSafeEnvironmentVariable(k))
.Any(p => !string.IsNullOrEmpty(p));
public LogLevel? GetLogLevel(params string[] keys)
{
var level = keys
.Select(k => GetSafeEnvironmentVariable(k))
.Select<string, LogLevel?>(v => v.ToLowerInvariant() switch
{
"trace" => LogLevel.Trace,
"debug" => LogLevel.Debug,
"info" => LogLevel.Information,
"warn" => LogLevel.Warning,
"error" => LogLevel.Error,
"none" => LogLevel.None,
_ => null
})
.FirstOrDefault(l => l != null);
return level;
}

public GlobalLogTarget ParseLogTargets(params string[] keys)
{
public string GetLogDirectory(params string[] keys)
{
var path = keys
.Select(k => GetSafeEnvironmentVariable(k))
.FirstOrDefault(p => !string.IsNullOrEmpty(p));

var targets = keys
.Select(k => GetSafeEnvironmentVariable(k))
.FirstOrDefault(p => !string.IsNullOrEmpty(p));
if (string.IsNullOrWhiteSpace(targets))
return GlobalLogTarget.File;
return path;
}

var logTargets = GlobalLogTarget.None;
var found = false;
public bool AnyConfigured(params string[] keys) =>
keys
.Select(k => GetSafeEnvironmentVariable(k))
.Any(p => !string.IsNullOrEmpty(p));

foreach (var target in targets.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
public GlobalLogTarget? ParseLogTargets(params string[] keys)
{
if (IsSet(target, "stdout"))
logTargets |= GlobalLogTarget.StdOut;
else if (IsSet(target, "file"))
logTargets |= GlobalLogTarget.File;
else if (IsSet(target, "none"))
logTargets |= GlobalLogTarget.None;
}
return !found ? GlobalLogTarget.File : logTargets;
var targets = keys
.Select(k => GetSafeEnvironmentVariable(k))
.FirstOrDefault(p => !string.IsNullOrEmpty(p));
if (string.IsNullOrWhiteSpace(targets))
return null;

bool IsSet(string k, string v)
{
var b = k.Trim().Equals(v, StringComparison.InvariantCultureIgnoreCase);
if (b)
found = true;
return b;
}
}
var logTargets = GlobalLogTarget.None;
var found = false;

internal static string GetDefaultLogDirectory() =>
Environment.OSVersion.Platform == PlatformID.Win32NT
? Path.Combine(Environment.GetEnvironmentVariable("PROGRAMDATA")!, "elastic", "apm-agent-dotnet", "logs")
: "/var/log/elastic/apm-agent-dotnet";
foreach (var target in targets.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
if (IsSet(target, "stdout"))
logTargets |= GlobalLogTarget.StdOut;
else if (IsSet(target, "file"))
logTargets |= GlobalLogTarget.File;
else if (IsSet(target, "none"))
logTargets |= GlobalLogTarget.None;
}
return !found ? null : logTargets;

bool IsSet(string k, string v)
{
var b = k.Trim().Equals(v, StringComparison.InvariantCultureIgnoreCase);
if (b)
found = true;
return b;
}
}

internal static string GetDefaultLogDirectory() =>
Environment.OSVersion.Platform == PlatformID.Win32NT
? Path.Combine(Environment.GetEnvironmentVariable("PROGRAMDATA")!, "elastic", "apm-agent-dotnet", "logs")
: "/var/log/elastic/apm-agent-dotnet";
}

[Flags]
Expand Down Expand Up @@ -135,7 +134,8 @@ public static class LogEnvironmentVariables

internal readonly struct GlobalLogConfiguration
{
private GlobalLogConfiguration(bool isActive, LogLevel logLevel, GlobalLogTarget logTarget, string logFileDirectory, string logFilePrefix) : this()
private GlobalLogConfiguration(bool isActive, LogLevel logLevel, GlobalLogTarget logTarget, string logFileDirectory, string logFilePrefix) :
this()
{
IsActive = isActive;
LogLevel = logLevel;
Expand All @@ -160,23 +160,28 @@ internal static GlobalLogConfiguration FromEnvironment(IDictionary environmentVa
{
var config = new EnvironmentLoggingConfiguration(environmentVariables);
var logLevel = config.GetLogLevel(ELASTIC_OTEL_LOG_LEVEL, ELASTIC_APM_PROFILER_LOG, ELASTIC_APM_LOG_LEVEL);
var logFileDirectory = config.GetLogFilePath(ELASTIC_OTEL_LOG_DIRECTORY, ELASTIC_APM_PROFILER_LOG_DIR, ELASTIC_APM_LOG_DIRECTORY);
var logFileDirectory = config.GetLogDirectory(ELASTIC_OTEL_LOG_DIRECTORY, ELASTIC_APM_PROFILER_LOG_DIR, ELASTIC_APM_LOG_DIRECTORY);
var logFilePrefix = GetLogFilePrefix();
var logTarget = config.ParseLogTargets(ELASTIC_OTEL_LOG_TARGETS, ELASTIC_APM_PROFILER_LOG_TARGETS);

var isActive = config.AnyConfigured(
ELASTIC_OTEL_LOG_LEVEL,
ELASTIC_OTEL_LOG_DIRECTORY,
ELASTIC_OTEL_LOG_TARGETS,
ELASTIC_APM_LOG_DIRECTORY,
ELASTIC_APM_PROFILER_LOG,
ELASTIC_APM_PROFILER_LOG_DIR,
ELASTIC_APM_PROFILER_LOG_TARGETS,
ELASTIC_APM_STARTUP_HOOKS_LOGGING
//The presence of some variables enable file logging for historical purposes
var isActive = config.AnyConfigured(ELASTIC_APM_STARTUP_HOOKS_LOGGING);
if (logLevel.HasValue || !string.IsNullOrWhiteSpace(logFileDirectory) || logTarget.HasValue)
{
isActive = true;
if (logLevel is LogLevel.None)
isActive = false;
else if (logTarget is GlobalLogTarget.None)
isActive = false;
}

) && logTarget != GlobalLogTarget.None && logLevel != LogLevel.None;
// now that we know what's actively configured, assign defaults
logFileDirectory ??= EnvironmentLoggingConfiguration.GetDefaultLogDirectory();
var level = logLevel ?? LogLevel.Warning;

return new(isActive, logLevel, logTarget, logFileDirectory, logFilePrefix);
var target = logTarget ?? (isActive ? GlobalLogTarget.File : GlobalLogTarget.None);

return new(isActive, level, target, logFileDirectory, logFilePrefix);
}

private static string GetLogFilePrefix()
Expand All @@ -190,6 +195,4 @@ public string CreateLogFileName(string applicationName = "agent")
var logFileName = Path.Combine(LogFileDirectory, $"{LogFilePrefix}.{applicationName}.log");
return logFileName;
}

}

43 changes: 30 additions & 13 deletions test/Elastic.Apm.Tests/Config/GlobalLogConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,46 @@ public void Check_Defaults()
config.LogLevel.Should().Be(LogLevel.Warning);
config.AgentLogFilePath.Should().StartWith(EnvironmentLoggingConfiguration.GetDefaultLogDirectory());
config.AgentLogFilePath.Should().EndWith(".agent.log");
config.LogTargets.Should().Be(GlobalLogTarget.File);
//because is active is false log targets defaults to none;
config.LogTargets.Should().Be(GlobalLogTarget.None);
}


//
[Theory]
[InlineData(ELASTIC_OTEL_LOG_LEVEL, "Info")]
[InlineData(ELASTIC_APM_PROFILER_LOG, "Info")]
//only if explicitly specified to 'none' should we not default to file logging.
[InlineData(ELASTIC_OTEL_LOG_LEVEL, "BadEnumValue")]
[InlineData(ELASTIC_APM_PROFILER_LOG, "BadEnumValue")]

[InlineData(ELASTIC_OTEL_LOG_DIRECTORY, "1")]
[InlineData(ELASTIC_APM_LOG_DIRECTORY, "1")]
[InlineData(ELASTIC_APM_PROFILER_LOG_DIR, "1")]
[InlineData(ELASTIC_APM_STARTUP_HOOKS_LOGGING, "1")]
//only if explicitly specified to 'none' should we not default to file logging.
[InlineData(ELASTIC_OTEL_LOG_TARGETS, "file")]
[InlineData(ELASTIC_OTEL_LOG_TARGETS, "BadEnumValue")]
[InlineData(ELASTIC_APM_PROFILER_LOG_TARGETS, "file")]
[InlineData(ELASTIC_APM_PROFILER_LOG_TARGETS, "BadEnumValue")]
public void CheckActivation(string environmentVariable, string value)
{
var config = GlobalLogConfiguration.FromEnvironment(new Hashtable { { environmentVariable, value } });
config.IsActive.Should().BeTrue();
config.LogTargets.Should().Be(GlobalLogTarget.File);
}

//
[Theory]
[InlineData(ELASTIC_OTEL_LOG_LEVEL, "none")]
[InlineData(ELASTIC_APM_PROFILER_LOG, "None")]
//only if explicitly specified to 'none' should we not default to file logging.
[InlineData(ELASTIC_OTEL_LOG_TARGETS, "none")]
[InlineData(ELASTIC_APM_PROFILER_LOG_TARGETS, "none")]
public void CheckDeactivation(string environmentVariable, string value)
{
var config = GlobalLogConfiguration.FromEnvironment(new Hashtable
{
{ ELASTIC_OTEL_LOG_DIRECTORY, "" },
{ environmentVariable, value }
});
config.IsActive.Should().BeFalse();
config.LogTargets.Should().Be(GlobalLogTarget.None);
}

[Theory]
Expand Down Expand Up @@ -121,11 +138,11 @@ static void Check(string key, string envVarValue)
}

[Theory]
[InlineData(null, GlobalLogTarget.File)]
[InlineData("", GlobalLogTarget.File)]
[InlineData("foo", GlobalLogTarget.File)]
[InlineData("foo,bar", GlobalLogTarget.File)]
[InlineData("foo;bar", GlobalLogTarget.File)]
[InlineData(null, GlobalLogTarget.None)]
[InlineData("", GlobalLogTarget.None)]
[InlineData("foo", GlobalLogTarget.None)]
[InlineData("foo,bar", GlobalLogTarget.None)]
[InlineData("foo;bar", GlobalLogTarget.None)]
[InlineData("file;foo;bar", GlobalLogTarget.File)]
[InlineData("file", GlobalLogTarget.File)]
[InlineData("stdout", GlobalLogTarget.StdOut)]
Expand All @@ -134,13 +151,13 @@ static void Check(string key, string envVarValue)
[InlineData("FILE;StdOut", GlobalLogTarget.File | GlobalLogTarget.StdOut)]
[InlineData("file;stdout;file", GlobalLogTarget.File | GlobalLogTarget.StdOut)]
[InlineData("FILE;StdOut;stdout", GlobalLogTarget.File | GlobalLogTarget.StdOut)]
internal void Check_LogTargets_AreEvaluatedCorrectly(string envVarValue, GlobalLogTarget targets)
internal void Check_LogTargets_AreEvaluatedCorrectly(string envVarValue, GlobalLogTarget? targets)
{
Check(ELASTIC_APM_PROFILER_LOG_TARGETS, envVarValue, targets);
Check(ELASTIC_OTEL_LOG_TARGETS, envVarValue, targets);
return;

static void Check(string key, string envVarValue, GlobalLogTarget targets)
static void Check(string key, string envVarValue, GlobalLogTarget? targets)
{
var config = CreateConfig(key, envVarValue);
config.LogTargets.Should().Be(targets, "{0}", key);
Expand Down

0 comments on commit 2cc27fa

Please sign in to comment.