diff --git a/docs/setup-auto-instrumentation.asciidoc b/docs/setup-auto-instrumentation.asciidoc index 19ae58724..9bff72f74 100644 --- a/docs/setup-auto-instrumentation.asciidoc +++ b/docs/setup-auto-instrumentation.asciidoc @@ -398,7 +398,7 @@ A semi-colon separated list of APM service names to exclude from auto-instrument Values defined are checked against the value of <> environment variable. -`ELASTIC_APM_PROFILER_LOG` _(optional)_:: +`ELASTIC_OTEL_LOG_LEVEL` _(optional)_:: The log level at which the profiler should log. Valid values are @@ -409,11 +409,15 @@ The log level at which the profiler should log. Valid values are * error * none + The default value is `warn`. More verbose log levels like `trace` and `debug` can affect the runtime performance of profiler auto instrumentation, so are recommended _only_ for diagnostics purposes. -`ELASTIC_APM_PROFILER_LOG_DIR` _(optional)_:: +This takes precedence over the now deprecated `ELASTIC_APM_PROFILER_LOG` + + +`ELASTIC_OTEL_LOG_DIRECTORY` _(optional)_:: The directory in which to write profiler log files. If unset, defaults to @@ -424,6 +428,8 @@ If the default directory cannot be written to for some reason, the profiler will try to write log files to a `logs` directory in the home directory specified by `ELASTIC_APM_PROFILER_HOME` environment variable. +This takes precedence over the now deprecated `ELASTIC_APM_PROFILER_LOG_DIR` + [IMPORTANT] -- The user account under which the profiler process runs must have permission to @@ -432,7 +438,8 @@ on IIS, the https://learn.microsoft.com/en-us/iis/manage/configuring-security/ap has write permissions in the target directory. -- -`ELASTIC_APM_PROFILER_LOG_TARGETS` _(optional)_:: + +`ELASTIC_OTEL_LOG_TARGETS` _(optional)_:: A semi-colon separated list of targets for profiler logs. Valid values are @@ -441,3 +448,5 @@ A semi-colon separated list of targets for profiler logs. Valid values are The default value is `file`, which logs to the directory specified by `ELASTIC_APM_PROFILER_LOG_DIR` environment variable. + +This takes precedence over the now deprecated `ELASTIC_APM_PROFILER_LOG_TARGETS` \ No newline at end of file diff --git a/docs/troubleshooting.asciidoc b/docs/troubleshooting.asciidoc index ba23c0943..2e9d962f0 100644 --- a/docs/troubleshooting.asciidoc +++ b/docs/troubleshooting.asciidoc @@ -32,7 +32,58 @@ If you don't see anything suspicious in the agent logs (no warning or error), it [[collect-agent-logs]] === Collecting agent logs -The way to collect logs depends on the setup of your application. +[float] +[[collect-logs-globally]] +==== Enable global file logging. + +The easiest way to get debug information from the Agent, regardless of the way it's run, is to enable global file logging. + +Specifying at least one of the following environment variables will ensure the agent logs to a file + +`OTEL_LOG_LEVEL` _(optional)_:: + +The log level at which the profiler should log. Valid values are + +* trace +* debug +* info +* warn +* error +* none + + +The default value is `warn`. More verbose log levels like `trace` and `debug` can +affect the runtime performance of profiler auto instrumentation, so are recommended +_only_ for diagnostics purposes. + +NOTE: if `ELASTIC_OTEL_LOG_TARGETS` is not explicitly set to include `file` global file logging will only +be enabled when configured with `trace` or `debug`. + +`OTEL_DOTNET_AUTO_LOG_DIRECTORY` _(optional)_:: + +The directory in which to write log files. If unset, defaults to + +* `%PROGRAMDATA%\elastic\apm-agent-dotnet\logs` on Windows +* `/var/log/elastic/apm-agent-dotnet` on Linux + + +[IMPORTANT] +-- +The user account under which the profiler process runs must have permission to +write to the destination log directory. Specifically, ensure that when running +on IIS, the https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities[AppPool identity] +has write permissions in the target directory. +-- + +`ELASTIC_OTEL_LOG_TARGETS` _(optional)_:: + +A semi-colon separated list of targets for profiler logs. Valid values are + +* file +* stdout + +The default value is `file` if `OTEL_DOTNET_AUTO_LOG_DIRECTORY` is set or `OTEL_LOG_LEVEL` is set to `trace` or `debug`. + [float] [[collect-logs-core]] @@ -62,49 +113,9 @@ For example, the following configuration in `appsettings.json` limits APM agents ---- <1> Control the verbosity of the agent logs by setting the log level for the `Elastic.Apm` category -[float] -[[collect-logs-classic]] -==== ASP.NET Classic - -ASP.NET (classic) does not have a predefined logging system. By default, the agent is configured to -emit log messages to a -https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracesource[`System.Diagnostics.TraceSource`] -with the source name `"Elastic.Apm"`. The TraceSource adheres to the log levels defined in the -APM agent configuration. Typically, you will configure a preferred log level using an application setting in `web.config`. - -[IMPORTANT] --- -System.Diagnostics.TraceSource requires the https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/how-to-compile-conditionally-with-trace-and-debug[`TRACE` compiler directive to be specified], which is specified -by default for both Debug and Release build configurations. --- - -https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracelistener[TraceListeners] -can be configured to monitor log messages for the trace source, using the https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/trace-debug/system-diagnostics-element[``] section of -web.config. For example, the following web.config section writes Elastic.Apm log messages to a file -named my_log_file.log: - -[source,xml] ----- - - - - - <1> - - - - - - - ----- -<1> Define listeners under a source with name `"Elastic.Apm"` to capture agent logs - [float] [[collect-logs-class-other-logging-systems]] -===== Other logging systems +==== Other logging systems If you have a logging system in place such as https://nlog-project.org/[NLog], https://serilog.net/[Serilog], or similar, you can direct the agent logs into your logging system by creating an adapter between @@ -242,17 +253,7 @@ The agent is only supported on IIS7 and higher where the `Integrated Pipeline Mo [[startup-hook-failure]] === Startup hooks failure -If the <> integration throws an exception, additional detail can be obtained by -setting the `ELASTIC_APM_STARTUP_HOOKS_LOGGING` environment variable before starting the application - -[source,sh] ----- -set ELASTIC_APM_STARTUP_HOOKS_LOGGING=1 ----- - -and then running the application in a context where the environment variable will be visible. In setting this value, -an `ElasticApmAgentStartupHook.log` file is written to the directory containing the startup hook assembly, in addition to -writing to standard output. +If the <> integration throws an exception, additional detail can be obtained through <>. [float] [[agent-overhead]] diff --git a/src/Elastic.Apm/AgentComponents.cs b/src/Elastic.Apm/AgentComponents.cs index 917a747d7..ded9d166f 100644 --- a/src/Elastic.Apm/AgentComponents.cs +++ b/src/Elastic.Apm/AgentComponents.cs @@ -52,7 +52,7 @@ internal AgentComponents( var config = CreateConfiguration(logger, configurationReader); hostNameDetector ??= new HostNameDetector(); - Logger = logger ?? CheckForProfilerLogger(DefaultLogger(null, configurationReader), config.LogLevel); + Logger = logger ?? GetGlobalLogger(DefaultLogger(null, configurationReader), config.LogLevel); ConfigurationStore = new ConfigurationStore(new RuntimeConfigurationSnapshot(config), Logger); Service = Service.GetDefaultService(config, Logger); @@ -199,38 +199,39 @@ private static IConfigurationReader CreateConfiguration(IApmLogger logger, IConf #endif } - - // - // This is the hooking point that checks for the existence of profiler-related - // logging settings. - // If no agent logging is configured but we detect profiler logging settings, those - // will be honoured. - // The finer-grained log-level (agent vs profiler) will be used. - // This has the benefit that users will also get agent logs in addition to profiler-only - // logs. - // - internal static IApmLogger CheckForProfilerLogger(IApmLogger fallbackLogger, LogLevel agentLogLevel, IDictionary environmentVariables = null) + /// + /// This ensures agents will respect externally provided loggers. + /// If the agent is started as part of profiling it should adhere to profiling configuration + /// If file logging environment variables are set we should always log to that location + /// + /// + /// + /// + /// + internal static IApmLogger GetGlobalLogger(IApmLogger fallbackLogger, LogLevel agentLogLevel, IDictionary environmentVariables = null) { try { - var profilerLogConfig = ProfilerLogConfig.Check(environmentVariables); - if (profilerLogConfig.IsActive) + var fileLogConfig = GlobalLogConfiguration.FromEnvironment(environmentVariables); + if (!fileLogConfig.IsActive) { - var effectiveLogLevel = LogLevelUtils.GetFinest(agentLogLevel, profilerLogConfig.LogLevel); + fallbackLogger.Info()?.Log("No system wide logging configured, defaulting to fallback logger"); + return fallbackLogger; + } - if ((profilerLogConfig.LogTargets & ProfilerLogTarget.File) == ProfilerLogTarget.File) - TraceLogger.TraceSource.Listeners.Add(new TextWriterTraceListener(profilerLogConfig.LogFilePath)); - if ((profilerLogConfig.LogTargets & ProfilerLogTarget.StdOut) == ProfilerLogTarget.StdOut) - TraceLogger.TraceSource.Listeners.Add(new TextWriterTraceListener(Console.Out)); + 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) + TraceLogger.TraceSource.Listeners.Add(new TextWriterTraceListener(Console.Out)); - var logger = new TraceLogger(effectiveLogLevel); - logger.Info()?.Log($"{nameof(ProfilerLogConfig)} - {profilerLogConfig}"); - return logger; - } + var logger = new TraceLogger(effectiveLogLevel); + logger.Info()?.Log($"{nameof(fileLogConfig)} - {fileLogConfig}"); + return logger; } catch (Exception e) { - fallbackLogger.Warning()?.LogException(e, "Error in CheckForProfilerLogger"); + fallbackLogger.Warning()?.LogException(e, "Error in GetGlobalLogger"); } return fallbackLogger; } diff --git a/src/Elastic.Apm/Config/Net4FullFramework/FullFrameworkDefaultImplementations.cs b/src/Elastic.Apm/Config/Net4FullFramework/FullFrameworkDefaultImplementations.cs index 888d652cb..73b79054e 100644 --- a/src/Elastic.Apm/Config/Net4FullFramework/FullFrameworkDefaultImplementations.cs +++ b/src/Elastic.Apm/Config/Net4FullFramework/FullFrameworkDefaultImplementations.cs @@ -22,7 +22,7 @@ internal static IApmLogger CreateDefaultLogger(LogLevel? configuredDefault) if (!string.IsNullOrEmpty(logLevel)) Enum.TryParse(logLevel, true, out level); - return AgentComponents.CheckForProfilerLogger(new TraceLogger(level), level); + return AgentComponents.GetGlobalLogger(new TraceLogger(level), level); } /// diff --git a/src/Elastic.Apm/Config/ProfilerLogConfig.cs b/src/Elastic.Apm/Config/ProfilerLogConfig.cs deleted file mode 100644 index 5af70ca99..000000000 --- a/src/Elastic.Apm/Config/ProfilerLogConfig.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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; -using System.Collections; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using Elastic.Apm.Logging; - -namespace Elastic.Apm.Config -{ - internal readonly struct ProfilerLogConfig - { - private ProfilerLogConfig(bool isActive, LogLevel logLevel, ProfilerLogTarget logTarget, string logFilePath) : this() - { - IsActive = isActive; - LogLevel = logLevel; - LogTargets = logTarget; - LogFilePath = logFilePath; - } - - internal bool IsActive { get; } - internal ProfilerLogTarget LogTargets { get; } - internal string LogFilePath { get; } - internal LogLevel LogLevel { get; } - - public override string ToString() => - $"IsActive: '{IsActive}', LogLevel: '{LogLevel}', LogTargets: '{LogTargets}', LogFilePath: '{LogFilePath}'"; - - internal static string GetDefaultProfilerLogDirectory() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.Combine(Environment.GetEnvironmentVariable("PROGRAMDATA"), "elastic", "apm-agent-dotnet", "logs") - : "/var/log/elastic/apm-agent-dotnet"; - - internal static ProfilerLogConfig Check(IDictionary environmentVariables = null) - { - environmentVariables ??= Environment.GetEnvironmentVariables(); - - string GetSafeEnvironmentVariable(string key) - { - var value = environmentVariables.Contains(key) ? environmentVariables[key]?.ToString() : null; - return value ?? string.Empty; - } - - var v = GetSafeEnvironmentVariable("ELASTIC_APM_PROFILER_LOG"); - - var isActive = !string.IsNullOrEmpty(v); - - var logLevel = v.ToLowerInvariant() switch - { - "trace" => LogLevel.Trace, - "debug" => LogLevel.Debug, - "info" => LogLevel.Information, - "warn" => LogLevel.Warning, - "error" => LogLevel.Error, - "none" => LogLevel.None, - _ => LogLevel.Warning, - }; - - var logFilePath = GetSafeEnvironmentVariable("ELASTIC_APM_PROFILER_LOG_DIR"); - if (string.IsNullOrEmpty(logFilePath)) - logFilePath = GetDefaultProfilerLogDirectory(); - var process = Process.GetCurrentProcess(); - var logFileName = Path.Combine(logFilePath, $"{process.ProcessName}_{process.Id}_{Environment.TickCount}.agent.log"); - - var logTargets = ProfilerLogTarget.None; - foreach (var target in GetSafeEnvironmentVariable("ELASTIC_APM_PROFILER_LOG_TARGETS").Split(';')) - { - if (target.Equals("stdout", StringComparison.InvariantCultureIgnoreCase)) - logTargets |= ProfilerLogTarget.StdOut; - else if (target.Equals("file", StringComparison.InvariantCultureIgnoreCase)) - logTargets |= ProfilerLogTarget.File; - } - - if (logTargets == ProfilerLogTarget.None) - logTargets = ProfilerLogTarget.File; - - return new(isActive, logLevel, logTargets, logFileName); - } - } - - [Flags] - internal enum ProfilerLogTarget - { - None = 0, - File = 1, - StdOut = 2 - } -} diff --git a/src/Elastic.Apm/Logging/GlobalLogConfiguration.cs b/src/Elastic.Apm/Logging/GlobalLogConfiguration.cs new file mode 100644 index 000000000..dfd1ac6b3 --- /dev/null +++ b/src/Elastic.Apm/Logging/GlobalLogConfiguration.cs @@ -0,0 +1,206 @@ +// 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; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Linq; + +#if PROFILER_MANAGED_LOADER +using static Elastic.Apm.Profiler.Managed.Loader.LogEnvironmentVariables; +namespace Elastic.Apm.Profiler.Managed.Loader; +#elif PROFILER_MANAGED +using static Elastic.Apm.Profiler.Managed.LogEnvironmentVariables; +namespace Elastic.Apm.Profiler.Managed; +#elif STARTUP_HOOKS +using static ElasticApmStartupHook.LogEnvironmentVariables; +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(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 string GetLogDirectory(params string[] keys) +{ + var path = keys + .Select(k => GetSafeEnvironmentVariable(k)) + .FirstOrDefault(p => !string.IsNullOrEmpty(p)); + + return path; +} + +public bool AnyConfigured(params string[] keys) => + keys + .Select(k => GetSafeEnvironmentVariable(k)) + .Any(p => !string.IsNullOrEmpty(p)); + +public GlobalLogTarget? ParseLogTargets(params string[] keys) +{ + var targets = keys + .Select(k => GetSafeEnvironmentVariable(k)) + .FirstOrDefault(p => !string.IsNullOrEmpty(p)); + if (string.IsNullOrWhiteSpace(targets)) + return null; + + var logTargets = GlobalLogTarget.None; + var found = false; + + 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] +internal enum GlobalLogTarget +{ + None = 0, + File = 1, + StdOut = 2 +} + +public static class LogEnvironmentVariables +{ + // ReSharper disable once InconsistentNaming + public const string OTEL_LOG_LEVEL = nameof(OTEL_LOG_LEVEL); + public const string OTEL_DOTNET_AUTO_LOG_DIRECTORY = nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY); + public const string ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS); + + public const string ELASTIC_APM_LOG_LEVEL = nameof(ELASTIC_APM_LOG_LEVEL); + public const string ELASTIC_APM_LOG_DIRECTORY = nameof(ELASTIC_APM_LOG_DIRECTORY); + + + // profiler logs are deprecated in favor of ELASTIC_OTEL_* + public const string ELASTIC_APM_PROFILER_LOG = nameof(ELASTIC_APM_PROFILER_LOG); + public const string ELASTIC_APM_PROFILER_LOG_DIR = nameof(ELASTIC_APM_PROFILER_LOG_DIR); + public const string ELASTIC_APM_PROFILER_LOG_TARGETS = nameof(ELASTIC_APM_PROFILER_LOG_TARGETS); + + // deprected startup hooks logging configuration, we still listen to it to enable logging + public const string ELASTIC_APM_STARTUP_HOOKS_LOGGING = nameof(ELASTIC_APM_STARTUP_HOOKS_LOGGING); + + // ReSharper enable once InconsistentNaming +} + +internal readonly struct GlobalLogConfiguration +{ + private GlobalLogConfiguration(bool isActive, LogLevel logLevel, GlobalLogTarget logTarget, string logFileDirectory, string logFilePrefix) : + this() + { + IsActive = isActive; + LogLevel = logLevel; + LogTargets = logTarget; + LogFileDirectory = logFileDirectory; + LogFilePrefix = logFilePrefix; + + AgentLogFilePath = CreateLogFileName(); + } + + internal bool IsActive { get; } + internal string AgentLogFilePath { get; } + internal LogLevel LogLevel { get; } + internal GlobalLogTarget LogTargets { get; } + + internal string LogFileDirectory { get; } + internal string LogFilePrefix { get; } + + public override string ToString() => $"IsActive: '{IsActive}', Targets: '{LogTargets}', Level: '{LogLevel}', FilePath: '{AgentLogFilePath}'"; + + internal static GlobalLogConfiguration FromEnvironment(IDictionary environmentVariables = null) + { + var config = new EnvironmentLoggingConfiguration(environmentVariables); + var otelLogLevel = config.GetLogLevel(OTEL_LOG_LEVEL); + var profilerLogLevel = config.GetLogLevel(ELASTIC_APM_PROFILER_LOG); + var apmLogLevel = config.GetLogLevel(ELASTIC_APM_LOG_LEVEL); + + var logLevel = otelLogLevel ?? profilerLogLevel ?? apmLogLevel; + + var logFileDirectory = config.GetLogDirectory(OTEL_DOTNET_AUTO_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); + + //The presence of some variables enable file logging for historical purposes + var isActive = config.AnyConfigured(ELASTIC_APM_STARTUP_HOOKS_LOGGING); + var activeFromLogging = + otelLogLevel is <= LogLevel.Debug + || apmLogLevel is <= LogLevel.Debug + || profilerLogLevel.HasValue; + if (activeFromLogging || !string.IsNullOrWhiteSpace(logFileDirectory) || logTarget.HasValue) + { + isActive = true; + if (logLevel is LogLevel.None) + isActive = false; + else if (logTarget is GlobalLogTarget.None) + isActive = false; + } + + // now that we know what's actively configured, assign defaults + logFileDirectory ??= EnvironmentLoggingConfiguration.GetDefaultLogDirectory(); + var level = logLevel ?? LogLevel.Warning; + + var target = logTarget ?? (isActive ? GlobalLogTarget.File : GlobalLogTarget.None); + return new(isActive, level, target, logFileDirectory, logFilePrefix); + } + + private static string GetLogFilePrefix() + { + var process = Process.GetCurrentProcess(); + return $"{process.ProcessName}_{process.Id}_{Environment.TickCount}"; + } + + public string CreateLogFileName(string applicationName = "agent") + { + var logFileName = Path.Combine(LogFileDirectory, $"{LogFilePrefix}.{applicationName}.log"); + return logFileName; + } +} diff --git a/src/Elastic.Apm/Logging/LogLevel.cs b/src/Elastic.Apm/Logging/LogLevel.cs index f0d012d3b..05bab8a8f 100644 --- a/src/Elastic.Apm/Logging/LogLevel.cs +++ b/src/Elastic.Apm/Logging/LogLevel.cs @@ -2,49 +2,56 @@ // 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.Apm.Logging +#if PROFILER_MANAGED_LOADER +namespace Elastic.Apm.Profiler.Managed.Loader; +#elif PROFILER_MANAGED +namespace Elastic.Apm.Profiler.Managed; +#elif STARTUP_HOOKS +namespace ElasticApmStartupHook; +#else +namespace Elastic.Apm.Logging; +#endif + +// https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=aspnetcore-2.2 +public enum LogLevel { - // https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=aspnetcore-2.2 - public enum LogLevel - { - /// - /// Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are - /// disabled by default and should never be enabled in a production environment. - /// - Trace = 0, - - /// - /// Logs that are used for interactive investigation during development. These logs should primarily contain information - /// useful for debugging and have no long-term value. - /// - Debug = 1, - - /// - /// Logs that track the general flow of the application. These logs should have long-term value. - /// - Information = 2, - - /// - /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application - /// execution to stop. - /// - Warning = 3, - - /// - /// Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure - /// in the current activity, not an application-wide failure. - /// - Error = 4, - - /// - /// Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate - /// attention. - /// - Critical = 5, - - /// - /// Not used for writing log messages. Specifies that a logging category should not write any messages. - /// - None = 6 - } + /// + /// Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are + /// disabled by default and should never be enabled in a production environment. + /// + Trace = 0, + + /// + /// Logs that are used for interactive investigation during development. These logs should primarily contain information + /// useful for debugging and have no long-term value. + /// + Debug = 1, + + /// + /// Logs that track the general flow of the application. These logs should have long-term value. + /// + Information = 2, + + /// + /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application + /// execution to stop. + /// + Warning = 3, + + /// + /// Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure + /// in the current activity, not an application-wide failure. + /// + Error = 4, + + /// + /// Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate + /// attention. + /// + Critical = 5, + + /// + /// Not used for writing log messages. Specifies that a logging category should not write any messages. + /// + None = 6 } diff --git a/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs b/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs index 0a260f0d8..1efbfe841 100644 --- a/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs +++ b/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs @@ -52,7 +52,7 @@ public static IApplicationBuilder UseElasticApm( params IDiagnosticsSubscriber[] subscribers ) { - var logger = NetCoreLogger.GetApmLogger(builder.ApplicationServices); + var logger = ApmExtensionsLogger.GetApmLogger(builder.ApplicationServices); var configReader = configuration == null ? new EnvironmentConfiguration(logger) @@ -70,7 +70,7 @@ params IDiagnosticsSubscriber[] subscribers internal static IApplicationBuilder UseElasticApm( this IApplicationBuilder builder, ApmAgent agent, - IApmLogger logger, + Logging.IApmLogger logger, params IDiagnosticsSubscriber[] subscribers ) { diff --git a/src/integrations/Elastic.Apm.Extensions.Hosting/NetCoreLogger.cs b/src/integrations/Elastic.Apm.Extensions.Hosting/ApmExtensionsLogger.cs similarity index 84% rename from src/integrations/Elastic.Apm.Extensions.Hosting/NetCoreLogger.cs rename to src/integrations/Elastic.Apm.Extensions.Hosting/ApmExtensionsLogger.cs index f806f3499..48d6dd422 100644 --- a/src/integrations/Elastic.Apm.Extensions.Hosting/NetCoreLogger.cs +++ b/src/integrations/Elastic.Apm.Extensions.Hosting/ApmExtensionsLogger.cs @@ -9,11 +9,11 @@ namespace Elastic.Apm.Extensions.Hosting; -internal sealed class NetCoreLogger : IApmLogger +internal sealed class ApmExtensionsLogger : IApmLogger { private readonly ILogger _logger; - public NetCoreLogger(ILoggerFactory loggerFactory) => _logger = loggerFactory?.CreateLogger("Elastic.Apm") ?? throw new ArgumentNullException(nameof(loggerFactory)); + public ApmExtensionsLogger(ILoggerFactory loggerFactory) => _logger = loggerFactory?.CreateLogger("Elastic.Apm") ?? throw new ArgumentNullException(nameof(loggerFactory)); public bool IsEnabled(LogLevel level) => _logger.IsEnabled(Convert(level)); @@ -34,6 +34,6 @@ private static Microsoft.Extensions.Logging.LogLevel Convert(LogLevel logLevel) internal static IApmLogger GetApmLogger(IServiceProvider serviceProvider) => serviceProvider.GetService(typeof(ILoggerFactory)) is ILoggerFactory loggerFactory - ? new NetCoreLogger(loggerFactory) + ? new ApmExtensionsLogger(loggerFactory) : ConsoleLogger.Instance; } diff --git a/src/integrations/Elastic.Apm.Extensions.Hosting/CompositeLogger.cs b/src/integrations/Elastic.Apm.Extensions.Hosting/CompositeLogger.cs new file mode 100644 index 000000000..e7498dc98 --- /dev/null +++ b/src/integrations/Elastic.Apm.Extensions.Hosting/CompositeLogger.cs @@ -0,0 +1,35 @@ +// 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; +using Elastic.Apm.Logging; +using LogLevel = Elastic.Apm.Logging.LogLevel; + +namespace Elastic.Apm.Extensions.Hosting; + +internal sealed class CompositeLogger(TraceLogger traceLogger, IApmLogger logger) : IDisposable , IApmLogger +{ + public TraceLogger TraceLogger { get; } = traceLogger; +public IApmLogger ApmLogger { get; } = logger; + +private bool _isDisposed; + +public void Dispose() => _isDisposed = true; + +public void Log(LogLevel level, TState state, Exception e, Func formatter) +{ + if (_isDisposed) + return; + + if (TraceLogger.IsEnabled(level)) + TraceLogger.Log(level, state, e, formatter); + + if (ApmLogger.IsEnabled(level)) + ApmLogger.Log(level, state, e, formatter); +} + +public bool IsEnabled(LogLevel logLevel) => ApmLogger.IsEnabled(logLevel) || TraceLogger.IsEnabled(logLevel); + + +} diff --git a/src/integrations/Elastic.Apm.Extensions.Hosting/Config/ApmConfiguration.cs b/src/integrations/Elastic.Apm.Extensions.Hosting/Config/ApmConfiguration.cs index b07095571..4105fa587 100644 --- a/src/integrations/Elastic.Apm.Extensions.Hosting/Config/ApmConfiguration.cs +++ b/src/integrations/Elastic.Apm.Extensions.Hosting/Config/ApmConfiguration.cs @@ -33,7 +33,7 @@ internal class ApmConfiguration : FallbackToEnvironmentConfigurationBase { private const string ThisClassName = nameof(ApmConfiguration); - public ApmConfiguration(IConfiguration configuration, IApmLogger logger, string defaultEnvironmentName) + public ApmConfiguration(IConfiguration configuration, Apm.Logging.IApmLogger logger, string defaultEnvironmentName) : base(logger, new ConfigurationDefaults { EnvironmentName = defaultEnvironmentName, DebugName = ThisClassName }, new ConfigurationKeyValueProvider(configuration)) => diff --git a/src/integrations/Elastic.Apm.Extensions.Hosting/HostBuilderExtensions.cs b/src/integrations/Elastic.Apm.Extensions.Hosting/HostBuilderExtensions.cs index d33a62d73..91f97818b 100644 --- a/src/integrations/Elastic.Apm.Extensions.Hosting/HostBuilderExtensions.cs +++ b/src/integrations/Elastic.Apm.Extensions.Hosting/HostBuilderExtensions.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using LogLevel = Elastic.Apm.Logging.LogLevel; namespace Elastic.Apm.Extensions.Hosting { @@ -54,7 +55,13 @@ public static IHostBuilder UseElasticApm(this IHostBuilder builder, params IDiag //If the static agent doesn't exist, we create one here. If there is already 1 agent created, we reuse it. if (!Agent.IsConfigured) { - services.AddSingleton(); + services.AddSingleton(sp => + { + var netCoreLogger = ApmExtensionsLogger.GetApmLogger(sp); + var globalLogger = AgentComponents.GetGlobalLogger(netCoreLogger, LogLevel.Error); + var logger = globalLogger is TraceLogger g ? new CompositeLogger(g, netCoreLogger) : netCoreLogger; + return logger; + }); services.AddSingleton(sp => new ApmConfiguration(ctx.Configuration, sp.GetService(), GetHostingEnvironmentName(ctx, sp.GetService()))); } diff --git a/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs b/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs index 1923e42e2..2931fe1a3 100644 --- a/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs +++ b/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs @@ -12,10 +12,8 @@ using Elastic.Apm.Logging; using Elastic.Apm.NetCoreAll; using Elastic.Apm.Report; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor; namespace Microsoft.Extensions.DependencyInjection; @@ -49,28 +47,25 @@ public static IServiceCollection AddElasticApm(this IServiceCollection services, if (agentConfigured) return Agent.Instance; - var logger = NetCoreLogger.GetApmLogger(sp); - var environmentName = GetEnvironmentName(sp); - - if (environmentName is null) - { - logger?.Warning()?.Log("Failed to retrieve hosting environment name"); - environmentName = "Undetermined"; - } - + var netCoreLogger = ApmExtensionsLogger.GetApmLogger(sp); var configuration = sp.GetService(); + var environmentName = GetDefaultEnvironmentName(sp); IConfigurationReader configurationReader = configuration is null - ? new EnvironmentConfiguration(logger) - : new ApmConfiguration(configuration, logger, environmentName); + ? new EnvironmentConfiguration(netCoreLogger) + : new ApmConfiguration(configuration, netCoreLogger, environmentName ?? "Undetermined"); + + var globalLogger = AgentComponents.GetGlobalLogger(netCoreLogger, configurationReader.LogLevel); + + var logger = globalLogger is TraceLogger g ? new CompositeLogger(g, netCoreLogger) : netCoreLogger; + + if (environmentName is null) + logger?.Warning()?.Log("Failed to retrieve default hosting environment name"); // This may be null, which is fine var payloadSender = sp.GetService(); - var components = agentConfigured - ? Agent.Components - : new AgentComponents(logger, configurationReader, payloadSender); - + var components = new AgentComponents(logger, configurationReader, payloadSender); HostBuilderExtensions.UpdateServiceInformation(components.Service); Agent.Setup(components); @@ -110,7 +105,7 @@ public static IServiceCollection AddElasticApm(this IServiceCollection services, return services; } - private static string GetEnvironmentName(IServiceProvider serviceProvider) => + private static string GetDefaultEnvironmentName(IServiceProvider serviceProvider) => #if NET6_0_OR_GREATER (serviceProvider.GetService(typeof(IHostEnvironment)) as IHostEnvironment)?.EnvironmentName; // This is preferred since 3.0 #else diff --git a/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Elastic.Apm.Profiler.Managed.Loader.csproj b/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Elastic.Apm.Profiler.Managed.Loader.csproj index e826eeee6..373ae5b73 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Elastic.Apm.Profiler.Managed.Loader.csproj +++ b/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Elastic.Apm.Profiler.Managed.Loader.csproj @@ -5,11 +5,16 @@ net462;netcoreapp2.0 false false + $(DefineConstants);PROFILER_MANAGED_LOADER portable + + + + diff --git a/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Logger.cs b/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Logger.cs index 06f351bca..b498caca6 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Logger.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Logger.cs @@ -5,134 +5,92 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Text; -namespace Elastic.Apm.Profiler.Managed.Loader +#if PROFILER_MANAGED +namespace Elastic.Apm.Profiler.Managed; +#else +namespace Elastic.Apm.Profiler.Managed.Loader; +#endif + +internal static class Logger { - // match the log levels of the profiler logger - internal enum LogLevel + static Logger() { - Trace = 0, - Debug = 1, - Info = 2, - Warn = 3, - Error = 4, - Off = 5, + var config = GlobalLogConfiguration.FromEnvironment(Environment.GetEnvironmentVariables()); + + Level = config.LogLevel; + IsActive = config.IsActive; +#if PROFILER_MANAGED + LogFile = config.CreateLogFileName("profiler_managed"); +#else + LogFile = config.CreateLogFileName("profiler_managed_loader"); +#endif + Levels = new Dictionary + { + [LogLevel.None] = "OFF ", + [LogLevel.Error] = "ERROR", + [LogLevel.Warning] = "WARN ", + [LogLevel.Information] = "INFO ", + [LogLevel.Debug] = "DEBUG", + [LogLevel.Critical] = "CRITI", + [LogLevel.Trace] = "TRACE", + }; } - internal static class Logger - { - static Logger() - { - Level = GetLogLevel(LogLevel.Warn); - var logDirectory = GetLogDirectory(); - LogFile = GetLogFile(logDirectory); - Levels = new Dictionary - { - [LogLevel.Off] = "OFF ", - [LogLevel.Error] = "ERROR", - [LogLevel.Warn] = "WARN ", - [LogLevel.Info] = "INFO ", - [LogLevel.Debug] = "DEBUG", - [LogLevel.Trace] = "TRACE", - }; - } + private static readonly bool IsActive; + private static readonly LogLevel Level; + private static readonly string LogFile; + private static readonly Dictionary Levels; - private static readonly LogLevel Level; - private static readonly string LogFile; - private static readonly Dictionary Levels; + public static void Warn(string message, params object[] args) => Log(LogLevel.Warning, message, args); - public static void Log(LogLevel level, Exception exception, string message, params object[] args) - { - if (Level > level) - return; + public static void Debug(string message, params object[] args) => Log(LogLevel.Debug, message, args); - Log(level, $"{message}{Environment.NewLine}{exception}", args); - } + public static void Error(Exception exception, string message, params object[] args) => Log(LogLevel.Error, exception, message, args); - public static void Log(LogLevel level, string message, params object[] args) - { - if (Level > level) - return; + public static void Error(string message, params object[] args) => Log(LogLevel.Error, message, args); - try - { - if (LogFile != null) - { - try - { - using (var stream = File.Open(LogFile, FileMode.Append, FileAccess.Write, FileShare.Read)) - using (var writer = new StreamWriter(stream, new UTF8Encoding(false))) - { - writer.Write($"[{DateTimeOffset.Now:O}] [{Levels[level]}] "); - writer.WriteLine(message, args); - writer.Flush(); - stream.Flush(true); - } - - return; - } - catch - { - // ignore - } - } - - Console.Error.WriteLine($"[{DateTimeOffset.Now:O}] [{Levels[level]}] {message}", args); - } - catch - { - // ignore - } - } - - private static string GetLogFile(string logDirectory) - { - if (logDirectory is null) - return null; + public static void Log(LogLevel level, Exception exception, string message, params object[] args) + { + if (!IsActive || Level > level) + return; - var process = Process.GetCurrentProcess(); - return Path.Combine(logDirectory, $"Elastic.Apm.Profiler.Managed.Loader_{process.ProcessName}_{process.Id}.log"); - } + Log(level, $"{message}{Environment.NewLine}{exception}", args); + } - private static LogLevel GetLogLevel(LogLevel defaultLevel) - { - var level = Environment.GetEnvironmentVariable("ELASTIC_APM_PROFILER_LOG"); - if (string.IsNullOrEmpty(level)) - return defaultLevel; + public static void Log(LogLevel level, string message, params object[] args) + { + if (!IsActive || Level > level) + return; - return Enum.TryParse(level, true, out var parsedLevel) - ? parsedLevel - : defaultLevel; - } + if (string.IsNullOrWhiteSpace(LogFile)) + return; - private static string GetLogDirectory() + try { try { - var logDirectory = Environment.GetEnvironmentVariable("ELASTIC_APM_PROFILER_LOG_DIR"); - if (string.IsNullOrEmpty(logDirectory)) - { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - var programData = Environment.GetEnvironmentVariable("PROGRAMDATA"); - logDirectory = !string.IsNullOrEmpty(programData) - ? Path.Combine(programData, "elastic", "apm-agent-dotnet", "logs") - : "."; - } - else - logDirectory = "/var/log/elastic/apm-agent-dotnet"; - } - - Directory.CreateDirectory(logDirectory); - return logDirectory; + using var stream = File.Open(LogFile, FileMode.Append, FileAccess.Write, FileShare.Read); + using var writer = new StreamWriter(stream, new UTF8Encoding(false)); + writer.Write($"[{DateTimeOffset.Now:O}] [{Levels[level]}] "); + writer.WriteLine(message, args); + writer.Flush(); + stream.Flush(true); + + return; } catch { - return null; + // ignore } + + Console.Error.WriteLine($"[{DateTimeOffset.Now:O}] [{Levels[level]}] {message}", args); + } + catch + { + // ignore } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Startup.cs b/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Startup.cs index e3cf22fff..1c1e47713 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Startup.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed.Loader/Startup.cs @@ -16,7 +16,7 @@ public partial class Startup { static Startup() { - Logger.Log(LogLevel.Info, "Elastic.Apm.Profiler.Managed.Loader.Startup: Invoked "); + Logger.Log(LogLevel.Information, "Elastic.Apm.Profiler.Managed.Loader.Startup: Invoked "); Directory = ResolveDirectory(); try diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/CallTarget/Handlers/IntegrationOptions.cs b/src/profiler/Elastic.Apm.Profiler.Managed/CallTarget/Handlers/IntegrationOptions.cs index f80f0f42f..cea0679aa 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/CallTarget/Handlers/IntegrationOptions.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/CallTarget/Handlers/IntegrationOptions.cs @@ -32,14 +32,14 @@ internal static void LogException(Exception exception, string message = null) if (exception is DuckTypeException) { - Logger.Log(LogLevel.Warn, "DuckTypeException has been detected, the integration <{0}, {1}> will be disabled.", + Logger.Warn("DuckTypeException has been detected, the integration <{0}, {1}> will be disabled.", typeof(TIntegration).FullName, typeof(TTarget).FullName); _disableIntegration = true; } else if (exception is CallTargetInvokerException) { - Logger.Log(LogLevel.Warn, "CallTargetInvokerException has been detected, the integration <{0}, {1}> will be disabled.", + Logger.Warn("CallTargetInvokerException has been detected, the integration <{0}, {1}> will be disabled.", typeof(TIntegration).FullName, typeof(TTarget).FullName); _disableIntegration = true; diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Elastic.Apm.Profiler.Managed.csproj b/src/profiler/Elastic.Apm.Profiler.Managed/Elastic.Apm.Profiler.Managed.csproj index b63d15a15..cbe3fc9e8 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Elastic.Apm.Profiler.Managed.csproj +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Elastic.Apm.Profiler.Managed.csproj @@ -3,8 +3,16 @@ net462;netstandard2.0;netstandard2.1 false + $(DefineConstants);PROFILER_MANAGED + + + + + + + diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Logger.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Logger.cs deleted file mode 100644 index a7f16666d..000000000 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Logger.cs +++ /dev/null @@ -1,144 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -namespace Elastic.Apm.Profiler.Managed -{ - // match the log levels of the profiler logger - internal enum LogLevel - { - Trace = 0, - Debug = 1, - Info = 2, - Warn = 3, - Error = 4, - Off = 5, - } - - internal static class Logger - { - static Logger() - { - Level = GetLogLevel(LogLevel.Warn); - var logDirectory = GetLogDirectory(); - LogFile = GetLogFile(logDirectory); - Levels = new Dictionary - { - [LogLevel.Off] = "OFF ", - [LogLevel.Error] = "ERROR", - [LogLevel.Warn] = "WARN ", - [LogLevel.Info] = "INFO ", - [LogLevel.Debug] = "DEBUG", - [LogLevel.Trace] = "TRACE", - }; - } - - private static readonly LogLevel Level; - private static readonly string LogFile; - private static readonly Dictionary Levels; - - public static void Log(LogLevel level, Exception exception, string message, params object[] args) - { - if (Level > level) - return; - - Log(level, $"{message}{Environment.NewLine}{exception}", args); - } - - public static void Debug(string message, params object[] args) => Log(LogLevel.Debug, message, args); - - public static void Error(Exception exception, string message, params object[] args) => Log(LogLevel.Error, exception, message, args); - - public static void Error(string message, params object[] args) => Log(LogLevel.Error, message, args); - - public static void Log(LogLevel level, string message, params object[] args) - { - if (Level > level) - return; - - try - { - if (LogFile != null) - { - try - { - using (var stream = File.Open(LogFile, FileMode.Append, FileAccess.Write, FileShare.Read)) - using (var writer = new StreamWriter(stream, new UTF8Encoding(false))) - { - writer.Write($"[{DateTimeOffset.Now:O}] [{Levels[level]}] "); - writer.WriteLine(message, args); - writer.Flush(); - stream.Flush(true); - } - - return; - } - catch - { - // ignore - } - } - - Console.Error.WriteLine($"[{DateTimeOffset.Now:O}] [{Levels[level]}] {message}", args); - } - catch - { - // ignore - } - } - - private static string GetLogFile(string logDirectory) - { - if (logDirectory is null) - return null; - - var process = Process.GetCurrentProcess(); - return Path.Combine(logDirectory, $"Elastic.Apm.Profiler.Managed_{process.ProcessName}_{process.Id}.log"); - } - - private static LogLevel GetLogLevel(LogLevel defaultLevel) - { - var level = Environment.GetEnvironmentVariable("ELASTIC_APM_PROFILER_LOG"); - if (string.IsNullOrEmpty(level)) - return defaultLevel; - - return Enum.TryParse(level, true, out var parsedLevel) - ? parsedLevel - : defaultLevel; - } - - private static string GetLogDirectory() - { - try - { - var logDirectory = Environment.GetEnvironmentVariable("ELASTIC_APM_PROFILER_LOG_DIR"); - if (string.IsNullOrEmpty(logDirectory)) - { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - var programData = Environment.GetEnvironmentVariable("PROGRAMDATA"); - logDirectory = !string.IsNullOrEmpty(programData) - ? Path.Combine(programData, "elastic", "apm-agent-dotnet", "logs") - : "."; - } - else - logDirectory = "/var/log/elastic/apm-agent-dotnet"; - } - - Directory.CreateDirectory(logDirectory); - return logDirectory; - } - catch - { - return null; - } - } - } -} diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/MethodBuilder.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/MethodBuilder.cs index 542157c46..13e43b309 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/MethodBuilder.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/MethodBuilder.cs @@ -237,7 +237,7 @@ private TDelegate EmitDelegate() } } else - Logger.Log(LogLevel.Warn, "Unable to resolve module version id {0}. Using method builder fallback.", _moduleVersionId); + Logger.Warn("Unable to resolve module version id {0}. Using method builder fallback.", _moduleVersionId); MethodInfo methodInfo = null; @@ -384,26 +384,25 @@ private MethodInfo VerifyMethodFromToken(MethodInfo methodInfo) if (!string.Equals(_methodName, methodInfo.Name)) { - Logger.Log(LogLevel.Warn, $"Method name mismatch: {detailMessage}"); + Logger.Warn($"Method name mismatch: {detailMessage}"); return null; } if (!GenericsAreViable(methodInfo)) { - Logger.Log(LogLevel.Warn, $"Generics not viable: {detailMessage}"); + Logger.Warn($"Generics not viable: {detailMessage}"); return null; } if (!ParametersAreViable(methodInfo)) { - Logger.Log(LogLevel.Warn, $"Parameters not viable: {detailMessage}"); + Logger.Warn($"Parameters not viable: {detailMessage}"); return null; } if (!methodInfo.IsStatic && !methodInfo.ReflectedType.IsAssignableFrom(_concreteType)) { - Logger.Log(LogLevel.Warn, - $"{_concreteType} cannot be assigned to the type containing the MethodInfo representing the instance method: {detailMessage}"); + Logger.Warn($"{_concreteType} cannot be assigned to the type containing the MethodInfo representing the instance method: {detailMessage}"); return null; } @@ -453,7 +452,7 @@ private MethodInfo TryFindMethod() { var logDetail = $"mdToken {_mdToken} on {_concreteTypeName}.{_methodName} in {_resolutionModule?.FullyQualifiedName ?? "NULL"}, {_resolutionModule?.ModuleVersionId ?? _moduleVersionId}"; - Logger.Log(LogLevel.Warn, $"Using fallback method matching ({logDetail})"); + Logger.Warn($"Using fallback method matching ({logDetail})"); var methods = _concreteType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); @@ -495,7 +494,7 @@ private MethodInfo TryFindMethod() if (methods.Length == 1) { - Logger.Log(LogLevel.Info, $"Resolved by name and namespaceName filters ({logDetail})"); + Logger.Warn($"Resolved by name and namespaceName filters ({logDetail})"); return methods[0]; } @@ -506,7 +505,7 @@ private MethodInfo TryFindMethod() if (methods.Length == 1) { - Logger.Log(LogLevel.Info, $"Resolved by viable parameters ({logDetail})"); + Logger.Warn($"Resolved by viable parameters ({logDetail})"); return methods[0]; } @@ -517,7 +516,7 @@ private MethodInfo TryFindMethod() if (methods.Length == 1) { - Logger.Log(LogLevel.Info, $"Resolved by viable generics ({logDetail})"); + Logger.Warn($"Resolved by viable generics ({logDetail})"); return methods[0]; } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/ModuleLookup.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/ModuleLookup.cs index cb206ba12..32f86659e 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/ModuleLookup.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Reflection/ModuleLookup.cs @@ -55,8 +55,7 @@ public static Module Get(Guid moduleVersionId) { if (_shortCircuitLogicHasLogged) return null; - Logger.Log(LogLevel.Warn, - "Elastic APM is unable to continue attempting module lookups for this AppDomain. Falling back to legacy method lookups."); + Logger.Warn("Elastic APM is unable to continue attempting module lookups for this AppDomain. Falling back to legacy method lookups."); _shortCircuitLogicHasLogged = true; } diff --git a/src/profiler/elastic_apm_profiler/src/profiler/env.rs b/src/profiler/elastic_apm_profiler/src/profiler/env.rs index bb8aea6cd..5538a4db8 100644 --- a/src/profiler/elastic_apm_profiler/src/profiler/env.rs +++ b/src/profiler/elastic_apm_profiler/src/profiler/env.rs @@ -23,28 +23,33 @@ use log4rs::{ use once_cell::sync::Lazy; use std::time::SystemTime; use std::{collections::HashSet, fs::File, io::BufReader, path::PathBuf, str::FromStr}; +use serde::__private; const APP_POOL_ID_ENV_VAR: &str = "APP_POOL_ID"; const DOTNET_CLI_TELEMETRY_PROFILE_ENV_VAR: &str = "DOTNET_CLI_TELEMETRY_PROFILE"; const COMPLUS_LOADEROPTIMIZATION: &str = "COMPLUS_LOADEROPTIMIZATION"; -const ELASTIC_APM_PROFILER_CALLTARGET_ENABLED_ENV_VAR: &str = - "ELASTIC_APM_PROFILER_CALLTARGET_ENABLED"; -const ELASTIC_APM_PROFILER_DISABLE_OPTIMIZATIONS_ENV_VAR: &str = - "ELASTIC_APM_PROFILER_DISABLE_OPTIMIZATIONS"; +const ELASTIC_APM_PROFILER_CALLTARGET_ENABLED_ENV_VAR: &str = "ELASTIC_APM_PROFILER_CALLTARGET_ENABLED"; +const ELASTIC_APM_PROFILER_DISABLE_OPTIMIZATIONS_ENV_VAR: &str = "ELASTIC_APM_PROFILER_DISABLE_OPTIMIZATIONS"; const ELASTIC_APM_PROFILER_ENABLE_INLINING_ENV_VAR: &str = "ELASTIC_APM_PROFILER_ENABLE_INLINING"; -const ELASTIC_APM_PROFILER_EXCLUDE_INTEGRATIONS_ENV_VAR: &str = - "ELASTIC_APM_PROFILER_EXCLUDE_INTEGRATIONS"; -const ELASTIC_APM_PROFILER_EXCLUDE_PROCESSES_ENV_VAR: &str = - "ELASTIC_APM_PROFILER_EXCLUDE_PROCESSES"; -const ELASTIC_APM_PROFILER_EXCLUDE_SERVICE_NAMES_ENV_VAR: &str = - "ELASTIC_APM_PROFILER_EXCLUDE_SERVICE_NAMES"; +const ELASTIC_APM_PROFILER_EXCLUDE_INTEGRATIONS_ENV_VAR: &str = "ELASTIC_APM_PROFILER_EXCLUDE_INTEGRATIONS"; +const ELASTIC_APM_PROFILER_EXCLUDE_PROCESSES_ENV_VAR: &str = "ELASTIC_APM_PROFILER_EXCLUDE_PROCESSES"; +const ELASTIC_APM_PROFILER_EXCLUDE_SERVICE_NAMES_ENV_VAR: &str = "ELASTIC_APM_PROFILER_EXCLUDE_SERVICE_NAMES"; const ELASTIC_APM_PROFILER_HOME_ENV_VAR: &str = "ELASTIC_APM_PROFILER_HOME"; const ELASTIC_APM_PROFILER_INTEGRATIONS_ENV_VAR: &str = "ELASTIC_APM_PROFILER_INTEGRATIONS"; +const ELASTIC_APM_PROFILER_LOG_IL_ENV_VAR: &str = "ELASTIC_APM_PROFILER_LOG_IL"; + +const ELASTIC_APM_PROFILER_LOG_TARGETS_ENV_VAR: &str = "ELASTIC_APM_PROFILER_LOG_TARGETS"; +const ELASTIC_OTEL_LOG_TARGETS_ENV_VAR: &str = "ELASTIC_OTEL_LOG_TARGETS"; + const ELASTIC_APM_PROFILER_LOG_DIR_ENV_VAR: &str = "ELASTIC_APM_PROFILER_LOG_DIR"; const ELASTIC_APM_PROFILER_LOG_ENV_VAR: &str = "ELASTIC_APM_PROFILER_LOG"; -const ELASTIC_APM_PROFILER_LOG_TARGETS_ENV_VAR: &str = "ELASTIC_APM_PROFILER_LOG_TARGETS"; -const ELASTIC_APM_PROFILER_LOG_IL_ENV_VAR: &str = "ELASTIC_APM_PROFILER_LOG_IL"; + +const OTEL_LOG_LEVEL_ENV_VAR: &str = "OTEL_LOG_LEVEL"; +const OTEL_DOTNET_AUTO_LOG_DIRECTORY_ENV_VAR: &str = "OTEL_DOTNET_AUTO_LOG_DIRECTORY"; + +const ELASTIC_APM_LOG_LEVEL_ENV_VAR: &str = "ELASTIC_APM_LOG_LEVEL"; +const ELASTIC_APM_LOG_DIRECTORY_ENV_VAR: &str = "ELASTIC_APM_LOG_DIRECTORY"; const ELASTIC_APM_SERVICE_NAME_ENV_VAR: &str = "ELASTIC_APM_SERVICE_NAME"; @@ -101,6 +106,7 @@ pub fn get_env_vars() -> String { let key = k.to_uppercase(); if key.starts_with("ELASTIC_") || key.starts_with("CORECLR_") + || key.starts_with("OTEL_") || key.starts_with("COR_") || key == APP_POOL_ID_ENV_VAR || key == DOTNET_CLI_TELEMETRY_PROFILE_ENV_VAR @@ -173,16 +179,24 @@ pub fn enable_inlining(default: bool) -> bool { read_bool_env_var(ELASTIC_APM_PROFILER_ENABLE_INLINING_ENV_VAR, default) } +fn to_target(value: String) -> HashSet { + value + .split(';') + .into_iter() + .filter_map(|s| match s.to_lowercase().as_str() { + out if out == "file" || out == "stdout" => Some(out.into()), + _ => None, + }) + .collect() +} + fn read_log_targets_from_env_var() -> HashSet { - let mut set = match std::env::var(ELASTIC_APM_PROFILER_LOG_TARGETS_ENV_VAR) { - Ok(value) => value - .split(';') - .into_iter() - .filter_map(|s| match s.to_lowercase().as_str() { - out if out == "file" || out == "stdout" => Some(out.into()), - _ => None, - }) - .collect(), + let mut set = match ( + std::env::var(ELASTIC_OTEL_LOG_TARGETS_ENV_VAR), + std::env::var(ELASTIC_APM_PROFILER_LOG_TARGETS_ENV_VAR) + ) { + (Ok(value), _) => to_target(value), + (_, Ok(value)) => to_target(value), _ => HashSet::with_capacity(1), }; @@ -193,8 +207,14 @@ fn read_log_targets_from_env_var() -> HashSet { } pub fn read_log_level_from_env_var(default: LevelFilter) -> LevelFilter { - match std::env::var(ELASTIC_APM_PROFILER_LOG_ENV_VAR) { - Ok(value) => LevelFilter::from_str(value.as_str()).unwrap_or(default), + match ( + std::env::var(OTEL_LOG_LEVEL_ENV_VAR), + std::env::var(ELASTIC_APM_PROFILER_LOG_ENV_VAR), + std::env::var(ELASTIC_APM_LOG_DIRECTORY_ENV_VAR) + ) { + (Ok(value), _, _) => LevelFilter::from_str(value.as_str()).unwrap_or(default), + (_, Ok(value), _) => LevelFilter::from_str(value.as_str()).unwrap_or(default), + (_, _, Ok(value)) => LevelFilter::from_str(value.as_str()).unwrap_or(default), _ => default, } } @@ -291,9 +311,15 @@ fn get_home_log_dir() -> PathBuf { } fn get_log_dir() -> PathBuf { - match std::env::var(ELASTIC_APM_PROFILER_LOG_DIR_ENV_VAR) { - Ok(path) => PathBuf::from(path), - Err(_) => get_default_log_dir(), + match ( + std::env::var(OTEL_DOTNET_AUTO_LOG_DIRECTORY_ENV_VAR), + std::env::var(ELASTIC_APM_PROFILER_LOG_DIR_ENV_VAR), + std::env::var(ELASTIC_APM_LOG_DIRECTORY_ENV_VAR), + ) { + (Ok(path), _, _) => PathBuf::from(path), + (_, Ok(path), _) => PathBuf::from(path), + (_, _, Ok(path)) => PathBuf::from(path), + _ => get_default_log_dir(), } } diff --git a/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj b/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj index 19d9afedc..60984f437 100644 --- a/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj +++ b/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj @@ -16,10 +16,4 @@ - - - StartupHookLogger.cs - - - diff --git a/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs b/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs index 2a81154b6..0e3d5677d 100644 --- a/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs +++ b/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs @@ -13,7 +13,8 @@ using Elastic.Apm.Extensions.Hosting; using Elastic.Apm.GrpcClient; using Elastic.Apm.Instrumentations.SqlClient; -using ElasticApmStartupHook; +using Elastic.Apm.Logging; +using IApmLogger = Elastic.Apm.Logging.IApmLogger; namespace Elastic.Apm.StartupHook.Loader { @@ -22,26 +23,15 @@ namespace Elastic.Apm.StartupHook.Loader /// internal class Loader { - /// - /// The directory in which the executing assembly is located - /// - private static string AssemblyDirectory - { - get - { - var location = Assembly.GetExecutingAssembly().Location; - return Path.GetDirectoryName(location); - } - } - /// /// Initializes and starts the agent /// public static void Initialize() { - Agent.Setup(new AgentComponents()); + var agentComponents = new AgentComponents(); + Agent.Setup(agentComponents); - var logger = StartupHookLogger.Create(); + var logger = agentComponents.Logger; LoadDiagnosticSubscriber(new HttpDiagnosticsSubscriber(), logger); LoadDiagnosticSubscriber(new AspNetCoreDiagnosticSubscriber(), logger); LoadDiagnosticSubscriber(new EfCoreDiagnosticsSubscriber(), logger); @@ -51,7 +41,7 @@ public static void Initialize() HostBuilderExtensions.UpdateServiceInformation(Agent.Instance.Service); - static void LoadDiagnosticSubscriber(IDiagnosticsSubscriber diagnosticsSubscriber, StartupHookLogger logger) + static void LoadDiagnosticSubscriber(IDiagnosticsSubscriber diagnosticsSubscriber, IApmLogger logger) { try { @@ -59,8 +49,7 @@ static void LoadDiagnosticSubscriber(IDiagnosticsSubscriber diagnosticsSubscribe } catch (Exception e) { - logger.WriteLine($"Failed subscribing to {diagnosticsSubscriber.GetType().Name}, " + - $"Exception type: {e.GetType().Name}, message: {e.Message}"); + logger.Error()?.LogException(e, $"Failed subscribing to {diagnosticsSubscriber.GetType().Name}"); } } } diff --git a/src/startuphook/ElasticApmAgentStartupHook/ElasticApmAgentStartupHook.csproj b/src/startuphook/ElasticApmAgentStartupHook/ElasticApmAgentStartupHook.csproj index 5c79c06d5..f75da942b 100644 --- a/src/startuphook/ElasticApmAgentStartupHook/ElasticApmAgentStartupHook.csproj +++ b/src/startuphook/ElasticApmAgentStartupHook/ElasticApmAgentStartupHook.csproj @@ -4,7 +4,12 @@ ElasticApmAgentStartupHook false ElasticApmStartupHook + $(DefineConstants);STARTUP_HOOKS + + + + diff --git a/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs b/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs index 7bbf532ef..69ea6fd0a 100644 --- a/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs +++ b/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs @@ -35,13 +35,10 @@ private StartupHookLogger(string logPath, bool enabled) /// public static StartupHookLogger Create() { - var startupHookEnvVar = Environment.GetEnvironmentVariable("DOTNET_STARTUP_HOOKS"); + var config = GlobalLogConfiguration.FromEnvironment(Environment.GetEnvironmentVariables()); + var path = config.CreateLogFileName("startup_hook"); - var startupHookDirectory = Path.GetDirectoryName(startupHookEnvVar); - var startupHookLoggingEnvVar = Environment.GetEnvironmentVariable("ELASTIC_APM_STARTUP_HOOKS_LOGGING"); - - return new StartupHookLogger(Path.Combine(startupHookDirectory, "ElasticApmAgentStartupHook.log"), - !string.IsNullOrEmpty(startupHookLoggingEnvVar)); + return new StartupHookLogger(path, config.IsActive); } public void WriteLine(string message) diff --git a/test/Elastic.Apm.Tests/Config/GlobalLogConfigurationPrecedenceTests.cs b/test/Elastic.Apm.Tests/Config/GlobalLogConfigurationPrecedenceTests.cs new file mode 100644 index 000000000..75c0db47d --- /dev/null +++ b/test/Elastic.Apm.Tests/Config/GlobalLogConfigurationPrecedenceTests.cs @@ -0,0 +1,82 @@ +// 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.Apm.Logging; +using FluentAssertions; +using Xunit; +using static Elastic.Apm.Logging.LogEnvironmentVariables; + +namespace Elastic.Apm.Tests.Config; + +public class GlobalLogConfigurationPrecedenceTests +{ + [Fact] + public void CheckLogLevelPrecedence() + { + var config = CreateConfig([ + (OTEL_LOG_LEVEL, "trace"), + (ELASTIC_APM_PROFILER_LOG, "info"), + (ELASTIC_APM_LOG_LEVEL, "error"), + ]); + config.LogLevel.Should().Be(LogLevel.Trace); + + config = CreateConfig([ + (ELASTIC_APM_PROFILER_LOG, "info"), + (ELASTIC_APM_LOG_LEVEL, "error"), + ]); + config.LogLevel.Should().Be(LogLevel.Information); + + config = CreateConfig([ + (ELASTIC_APM_LOG_LEVEL, "error"), + ]); + config.LogLevel.Should().Be(LogLevel.Error); + } + + [Fact] + public void CheckLogDirPrecedence() + { + var config = CreateConfig([ + (OTEL_DOTNET_AUTO_LOG_DIRECTORY, nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY)), + (ELASTIC_APM_PROFILER_LOG_DIR, nameof(ELASTIC_APM_PROFILER_LOG_DIR)), + (ELASTIC_APM_LOG_DIRECTORY, nameof(ELASTIC_APM_LOG_DIRECTORY)), + ]); + config.LogFileDirectory.Should().Be(nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY)); + + config = CreateConfig([ + (ELASTIC_APM_PROFILER_LOG_DIR, nameof(ELASTIC_APM_PROFILER_LOG_DIR)), + (ELASTIC_APM_LOG_DIRECTORY, nameof(ELASTIC_APM_LOG_DIRECTORY)), + ]); + config.LogFileDirectory.Should().Be(nameof(ELASTIC_APM_PROFILER_LOG_DIR)); + + config = CreateConfig([ + (ELASTIC_APM_LOG_DIRECTORY, nameof(ELASTIC_APM_LOG_DIRECTORY)), + ]); + config.LogFileDirectory.Should().Be(nameof(ELASTIC_APM_LOG_DIRECTORY)); + } + + [Fact] + public void CheckLogTargetsPrecedence() + { + var config = CreateConfig([ + (ELASTIC_OTEL_LOG_TARGETS, "stdout"), + (ELASTIC_APM_PROFILER_LOG_TARGETS, "stdout;file"), + ]); + config.LogTargets.Should().Be(GlobalLogTarget.StdOut); + + config = CreateConfig([ + (ELASTIC_APM_PROFILER_LOG_TARGETS, "stdout;file"), + ]); + config.LogTargets.Should().Be(GlobalLogTarget.StdOut | GlobalLogTarget.File); + } + + private static GlobalLogConfiguration CreateConfig(params (string key, string v)[] values) + { + var environment = new Hashtable(); + foreach (var kv in values) + environment.Add(kv.key, kv.v); + var config = GlobalLogConfiguration.FromEnvironment(environment); + return config; + } +} diff --git a/test/Elastic.Apm.Tests/Config/GlobalLogConfigurationTests.cs b/test/Elastic.Apm.Tests/Config/GlobalLogConfigurationTests.cs new file mode 100644 index 000000000..63cf310f9 --- /dev/null +++ b/test/Elastic.Apm.Tests/Config/GlobalLogConfigurationTests.cs @@ -0,0 +1,176 @@ +// 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.Apm.Config; +using Elastic.Apm.Logging; +using FluentAssertions; +using Xunit; +using static Elastic.Apm.Logging.LogEnvironmentVariables; + +namespace Elastic.Apm.Tests.Config +{ + public class GlobalLogConfigurationTests + { + [Fact] + public void Check_Defaults() + { + var config = GlobalLogConfiguration.FromEnvironment(new Hashtable()); + config.IsActive.Should().BeFalse(); + config.LogLevel.Should().Be(LogLevel.Warning); + config.AgentLogFilePath.Should().StartWith(EnvironmentLoggingConfiguration.GetDefaultLogDirectory()); + config.AgentLogFilePath.Should().EndWith(".agent.log"); + //because is active is false log targets defaults to none; + config.LogTargets.Should().Be(GlobalLogTarget.None); + } + + + // + [Theory] + [InlineData(OTEL_LOG_LEVEL, "Debug")] + [InlineData(ELASTIC_APM_LOG_LEVEL, "Debug")] + [InlineData(ELASTIC_APM_PROFILER_LOG, "Info")] + [InlineData(OTEL_DOTNET_AUTO_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_APM_PROFILER_LOG_TARGETS, "file")] + 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(OTEL_LOG_LEVEL, "none")] + [InlineData(OTEL_LOG_LEVEL, "Info")] + [InlineData(ELASTIC_APM_LOG_LEVEL, "Info")] + [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 + { + { OTEL_DOTNET_AUTO_LOG_DIRECTORY, "" }, + { environmentVariable, value } + }); + config.IsActive.Should().BeFalse(); + config.LogTargets.Should().Be(GlobalLogTarget.None); + } + + [Theory] + //only specifying apm_log_level not sufficient, needs explicit directory configuration + [InlineData(ELASTIC_APM_LOG_LEVEL, "Warning")] + //setting targets to none will result in no global trace logging + [InlineData(ELASTIC_OTEL_LOG_TARGETS, "None")] + [InlineData(ELASTIC_APM_PROFILER_LOG_TARGETS, "None")] + //setting file log level to none will result in no global trace logging + [InlineData(OTEL_LOG_LEVEL, "None")] + //setting profiler log level to none will result in no global trace logging + [InlineData(ELASTIC_APM_PROFILER_LOG, "None")] + public void CheckNonActivation(string environmentVariable, string value) + { + var config = GlobalLogConfiguration.FromEnvironment(new Hashtable { { environmentVariable, value } }); + config.IsActive.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_APM_PROFILER_LOG, envVarValue, logLevel); + Check(ELASTIC_APM_LOG_LEVEL, envVarValue, logLevel); + Check(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_APM_PROFILER_LOG, envVarValue); + Check(ELASTIC_APM_LOG_LEVEL, envVarValue); + Check(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_APM_PROFILER_LOG_DIR, "/foo/bar"); + Check(ELASTIC_APM_LOG_DIRECTORY, "/foo/bar"); + Check(OTEL_DOTNET_AUTO_LOG_DIRECTORY, "/foo/bar"); + return; + + static void Check(string key, string envVarValue) + { + var config = CreateConfig(key, envVarValue); + config.AgentLogFilePath.Should().StartWith("/foo/bar", "{0}", key); + config.AgentLogFilePath.Should().EndWith(".agent.log", "{0}", key); + } + } + + [Theory] + [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)] + [InlineData("StdOut", GlobalLogTarget.StdOut)] + [InlineData("file;stdout", GlobalLogTarget.File | GlobalLogTarget.StdOut)] + [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) + { + 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) + { + var config = CreateConfig(key, envVarValue); + config.LogTargets.Should().Be(targets, "{0}", key); + } + } + + private static GlobalLogConfiguration CreateConfig(string key, string envVarValue) + { + var environment = new Hashtable { { key, envVarValue } }; + var config = GlobalLogConfiguration.FromEnvironment(environment); + return config; + } + } +} diff --git a/test/Elastic.Apm.Tests/Config/ProfilerLogConfigTests.cs b/test/Elastic.Apm.Tests/Config/ProfilerLogConfigTests.cs deleted file mode 100644 index 42420941a..000000000 --- a/test/Elastic.Apm.Tests/Config/ProfilerLogConfigTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// 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.Apm.Config; -using Elastic.Apm.Logging; -using FluentAssertions; -using Xunit; - -namespace Elastic.Apm.Tests.Config -{ - public class ProfilerLogConfigTests - { - [Fact] - public void Check_Defaults() - { - var config = ProfilerLogConfig.Check(new Hashtable()); - config.IsActive.Should().BeFalse(); - config.LogLevel.Should().Be(LogLevel.Warning); - config.LogFilePath.Should().StartWith(ProfilerLogConfig.GetDefaultProfilerLogDirectory()); - config.LogFilePath.Should().EndWith(".agent.log"); - config.LogTargets.Should().Be(ProfilerLogTarget.File); - } - - [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) - { - var environment = new Hashtable { { "ELASTIC_APM_PROFILER_LOG", envVarValue } }; - var config = ProfilerLogConfig.Check(environment); - config.IsActive.Should().BeTrue(); - config.LogLevel.Should().Be(logLevel); - } - - [Theory] - [InlineData(null, false)] - [InlineData("", false)] - [InlineData("foo", true)] - [InlineData("tracing", true)] - public void Check_InvalidLogLevelValues_AreMappedToDefaultWarn(string envVarValue, bool isActive) - { - var environment = new Hashtable { { "ELASTIC_APM_PROFILER_LOG", envVarValue } }; - var config = ProfilerLogConfig.Check(environment); - config.LogLevel.Should().Be(LogLevel.Warning); - config.IsActive.Should().Be(isActive); - } - - [Fact] - public void Check_LogDir_IsEvaluatedCorrectly() - { - var environment = new Hashtable { { "ELASTIC_APM_PROFILER_LOG_DIR", "/foo/bar" } }; - var config = ProfilerLogConfig.Check(environment); - config.LogFilePath.Should().StartWith("/foo/bar"); - config.LogFilePath.Should().EndWith(".agent.log"); - } - - [Theory] - [InlineData(null, ProfilerLogTarget.File)] - [InlineData("", ProfilerLogTarget.File)] - [InlineData("foo", ProfilerLogTarget.File)] - [InlineData("foo,bar", ProfilerLogTarget.File)] - [InlineData("foo;bar", ProfilerLogTarget.File)] - [InlineData("file;foo;bar", ProfilerLogTarget.File)] - [InlineData("file", ProfilerLogTarget.File)] - [InlineData("stdout", ProfilerLogTarget.StdOut)] - [InlineData("StdOut", ProfilerLogTarget.StdOut)] - [InlineData("file;stdout", ProfilerLogTarget.File | ProfilerLogTarget.StdOut)] - [InlineData("FILE;StdOut", ProfilerLogTarget.File | ProfilerLogTarget.StdOut)] - [InlineData("file;stdout;file", ProfilerLogTarget.File | ProfilerLogTarget.StdOut)] - [InlineData("FILE;StdOut;stdout", ProfilerLogTarget.File | ProfilerLogTarget.StdOut)] - internal void Check_LogTargets_AreEvaluatedCorrectly(string envVarValue, ProfilerLogTarget targets) - { - var environment = new Hashtable { { "ELASTIC_APM_PROFILER_LOG_TARGETS", envVarValue } }; - var config = ProfilerLogConfig.Check(environment); - config.LogTargets.Should().Be(targets); - } - } -} diff --git a/test/Elastic.Apm.Tests/LoggerTests.cs b/test/Elastic.Apm.Tests/LoggerTests.cs index 54c05c334..5952a3713 100644 --- a/test/Elastic.Apm.Tests/LoggerTests.cs +++ b/test/Elastic.Apm.Tests/LoggerTests.cs @@ -26,22 +26,22 @@ public void CheckForProfilerLogger_ReturnsExpectedLoggers() { var fallbackLogger = new NoopLogger(); - var logger = AgentComponents.CheckForProfilerLogger(fallbackLogger, LogLevel.Trace, new Hashtable()); + var logger = AgentComponents.GetGlobalLogger(fallbackLogger, LogLevel.Trace, new Hashtable()); logger.Should().NotBeNull(); logger.Should().Be(fallbackLogger); logger.IsEnabled(LogLevel.Trace).Should().BeFalse(); - logger = AgentComponents.CheckForProfilerLogger(fallbackLogger, LogLevel.Trace, new Hashtable { { "ELASTIC_APM_PROFILER_LOG", "trace" } }); + logger = AgentComponents.GetGlobalLogger(fallbackLogger, LogLevel.Trace, new Hashtable { { "ELASTIC_APM_PROFILER_LOG", "trace" } }); logger.Should().NotBeNull(); logger.Should().NotBe(fallbackLogger); logger.IsEnabled(LogLevel.Trace).Should().BeTrue(); - logger = AgentComponents.CheckForProfilerLogger(fallbackLogger, LogLevel.Error, new Hashtable { { "ELASTIC_APM_PROFILER_LOG", "trace" } }); + logger = AgentComponents.GetGlobalLogger(fallbackLogger, LogLevel.Error, new Hashtable { { "ELASTIC_APM_PROFILER_LOG", "trace" } }); logger.Should().NotBeNull(); logger.Should().NotBe(fallbackLogger); logger.IsEnabled(LogLevel.Trace).Should().BeTrue(); - logger = AgentComponents.CheckForProfilerLogger(fallbackLogger, LogLevel.Error, new Hashtable { { "ELASTIC_APM_PROFILER_LOG", "warn" } }); + logger = AgentComponents.GetGlobalLogger(fallbackLogger, LogLevel.Error, new Hashtable { { "ELASTIC_APM_PROFILER_LOG", "warn" } }); logger.Should().NotBeNull(); logger.Should().NotBe(fallbackLogger); logger.IsEnabled(LogLevel.Trace).Should().BeFalse(); diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/ApplicationBuilderExtensionLoggingTest.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/ApplicationBuilderExtensionLoggingTest.cs index 8c0480aea..1a23b5c01 100644 --- a/test/integrations/Elastic.Apm.AspNetCore.Tests/ApplicationBuilderExtensionLoggingTest.cs +++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/ApplicationBuilderExtensionLoggingTest.cs @@ -20,9 +20,9 @@ public void UseElasticApmShouldUseAspNetLoggerWhenLoggingIsConfigured() var services = new ServiceCollection() .AddLogging(); - var logger = NetCoreLogger.GetApmLogger(services.BuildServiceProvider()); + var logger = ApmExtensionsLogger.GetApmLogger(services.BuildServiceProvider()); - Assert.IsType(logger); + Assert.IsType(logger); } [Fact] @@ -30,7 +30,7 @@ public void UseElasticApmShouldUseConsoleLoggerInstanceWhenLoggingIsNotConfigure { var services = new ServiceCollection(); - var logger = NetCoreLogger.GetApmLogger(services.BuildServiceProvider()); + var logger = ApmExtensionsLogger.GetApmLogger(services.BuildServiceProvider()); Assert.IsType(logger); Assert.Same(ConsoleLogger.Instance, logger); diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs index 23edf20e0..10f38113d 100644 --- a/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs +++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs @@ -24,6 +24,7 @@ using SampleAspNetCoreApp; using Xunit; using Xunit.Abstractions; +using IApmLogger = Elastic.Apm.Logging.IApmLogger; namespace Elastic.Apm.AspNetCore.Tests { diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreLoggerTests.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreLoggerTests.cs index a6e4476b1..98e1844ba 100644 --- a/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreLoggerTests.cs +++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreLoggerTests.cs @@ -11,13 +11,13 @@ namespace Elastic.Apm.AspNetCore.Tests { /// - /// Tests the type. + /// Tests the type. /// public class AspNetCoreLoggerTests { [Fact] public void AspNetCoreLoggerShouldThrowExceptionWhenLoggerFactoryIsNull() - => Assert.Throws(() => new NetCoreLogger(null)); + => Assert.Throws(() => new ApmExtensionsLogger(null)); [Fact] public void AspNetCoreLoggerShouldGetLoggerFromFactoryWithProperCategoryName() @@ -27,7 +27,7 @@ public void AspNetCoreLoggerShouldGetLoggerFromFactoryWithProperCategoryName() .Returns(() => Mock.Of()); // ReSharper disable UnusedVariable - var logger = new NetCoreLogger(loggerFactoryMock.Object); + var logger = new ApmExtensionsLogger(loggerFactoryMock.Object); // ReSharper restore UnusedVariable loggerFactoryMock.Verify(x => x.CreateLogger(It.Is(s => s.Equals("Elastic.Apm"))), Times.Once); diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs index 369380440..7cd8c4aab 100644 --- a/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs +++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.Testing; using SampleAspNetCoreApp; using Xunit; +using IApmLogger = Elastic.Apm.Logging.IApmLogger; namespace Elastic.Apm.AspNetCore.Tests { diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/BasicTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/BasicTests.cs index 1a956ecb9..e60c24d16 100644 --- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/BasicTests.cs +++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/BasicTests.cs @@ -12,6 +12,7 @@ using Elastic.Apm.Tests.Utilities.XUnit; using FluentAssertions; using Xunit.Abstractions; +using static Elastic.Apm.Logging.LogEnvironmentVariables; namespace Elastic.Apm.Profiler.Managed.Tests; @@ -38,7 +39,7 @@ public async Task AgentVersionTest() { ["ELASTIC_APM_SERVER_URL"] = $"http://localhost:{port}", ["ELASTIC_APM_DISABLE_METRICS"] = "*", - ["ELASTIC_APM_PROFILER_LOG_TARGETS"] = "file;stdout", + [ELASTIC_OTEL_LOG_TARGETS] = "file;stdout", }; profiledApplication.Start( diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ExcludeTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ExcludeTests.cs index f58479399..0564df106 100644 --- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ExcludeTests.cs +++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ExcludeTests.cs @@ -13,6 +13,7 @@ using FluentAssertions; using Xunit; using Xunit.Abstractions; +using static Elastic.Apm.Logging.LogEnvironmentVariables; namespace Elastic.Apm.Profiler.Managed.Tests; @@ -39,7 +40,7 @@ public async Task ShouldNotInstrumentExcludedIntegrations() ["ELASTIC_APM_SERVER_URL"] = $"http://localhost:{port}", ["ELASTIC_APM_PROFILER_EXCLUDE_INTEGRATIONS"] = "SqliteCommand;AdoNet", ["ELASTIC_APM_DISABLE_METRICS"] = "*", - ["ELASTIC_APM_PROFILER_LOG_TARGETS"] = "file;stdout" + [ELASTIC_OTEL_LOG_TARGETS] = "file;stdout" }; profiledApplication.Start( @@ -95,7 +96,7 @@ public async Task ShouldNotInstrumentExcludedProcess(string targetFramework, str { ["ELASTIC_APM_SERVER_URL"] = $"http://localhost:{port}", ["ELASTIC_APM_DISABLE_METRICS"] = "*", - ["ELASTIC_APM_PROFILER_LOG_TARGETS"] = "file;stdout", + [ELASTIC_OTEL_LOG_TARGETS] = "file;stdout", ["ELASTIC_APM_PROFILER_EXCLUDE_PROCESSES"] = excludeProcess }; @@ -142,7 +143,7 @@ public async Task ShouldNotInstrumentExcludedServiceName() { ["ELASTIC_APM_SERVER_URL"] = $"http://localhost:{port}", ["ELASTIC_APM_DISABLE_METRICS"] = "*", - ["ELASTIC_APM_PROFILER_LOG_TARGETS"] = "file;stdout", + [ELASTIC_OTEL_LOG_TARGETS] = "file;stdout", ["ELASTIC_APM_SERVICE_NAME"] = serviceName, ["ELASTIC_APM_PROFILER_EXCLUDE_SERVICE_NAMES"] = serviceName }; diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ProfiledApplication.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ProfiledApplication.cs index 5e6fa0c81..ab9a523b6 100644 --- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ProfiledApplication.cs +++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/ProfiledApplication.cs @@ -13,6 +13,7 @@ using Elastic.Apm.Tests.Utilities; using ProcNet; using ProcNet.Std; +using static Elastic.Apm.Logging.LogEnvironmentVariables; namespace Elastic.Apm.Profiler.Managed.Tests { @@ -145,11 +146,11 @@ public void Start( environmentVariables["ELASTIC_APM_PROFILER_INTEGRATIONS"] = Path.Combine(SolutionPaths.Root, "src", "profiler", "Elastic.Apm.Profiler.Managed", "integrations.yml"); - environmentVariables["ELASTIC_APM_PROFILER_LOG"] = "trace"; + environmentVariables[OTEL_LOG_LEVEL] = "trace"; // log to relative logs directory for managed loader - environmentVariables["ELASTIC_APM_PROFILER_LOG_DIR"] = Path.Combine(SolutionPaths.Root, "logs"); + environmentVariables[OTEL_DOTNET_AUTO_LOG_DIRECTORY] = Path.Combine(SolutionPaths.Root, "logs"); - environmentVariables["ELASTIC_APM_PROFILER_LOG_TARGETS"] = "file;stdout"; + environmentVariables[ELASTIC_OTEL_LOG_TARGETS] = "file;stdout"; //environmentVariables["ELASTIC_APM_PROFILER_LOG_IL"] = "true"; // use the .exe for net462 diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/SatelliteAssemblyTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/SatelliteAssemblyTests.cs index ca3ba9cc9..ac9a5c01a 100644 --- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/SatelliteAssemblyTests.cs +++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/SatelliteAssemblyTests.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Xunit; using Xunit.Abstractions; +using static Elastic.Apm.Logging.LogEnvironmentVariables; namespace Elastic.Apm.Profiler.Managed.Tests; @@ -36,7 +37,7 @@ public async Task CorrectlyReadSatelliteAssemblyMetadata() { ["ELASTIC_APM_SERVER_URL"] = $"http://localhost:{port}", ["ELASTIC_APM_DISABLE_METRICS"] = "*", - ["ELASTIC_APM_PROFILER_LOG_TARGETS"] = "file;stdout", + [ELASTIC_OTEL_LOG_TARGETS] = "file;stdout", ["ELASTIC_APM_PROFILER_LOG"] = "debug", };