From c4b0fbc48eaa7de72e5fe7ec9bfa691799806d14 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 22 May 2023 15:55:18 -0500 Subject: [PATCH 01/10] Initial Serilog Logging Implementation (#1655) --- .../Agent/Core/Config/Configuration.cs | 38 -- .../Agent/Core/Config/Configuration.xsd | 13 - .../Agent/Core/Config/ConfigurationLoader.cs | 16 - .../NewRelic/Agent/Core/Config/ILogConfig.cs | 3 - .../Configuration/ConfigurationService.cs | 11 +- src/Agent/NewRelic/Agent/Core/Core.csproj | 29 +- .../Agent/Core/Labels/LabelsService.cs | 12 +- .../NewRelic/Agent/Core/Logging/AuditLog.cs | 6 +- .../Logging/CustomAuditLogTextFormatter.cs | 19 + .../Agent/Core/Logging/CustomTextFormatter.cs | 24 + .../Agent/Core/Logging/LogLevelExtensions.cs | 63 +++ .../NewRelic/Agent/Core/Logging/Logger.cs | 75 +-- .../Agent/Core/Logging/LoggerBootstrapper.cs | 479 +++++++----------- .../Core/Logging/ProcessIdEnricher - Copy.cs | 18 + .../Agent/Core/Logging/ProcessIdEnricher.cs | 18 + .../Core/NewRelic.Agent.Core/AgentManager.cs | 2 +- .../Core/NewRelic.Agent.Core/AgentShim.cs | 11 +- .../RuntimeEnvironmentInfo.cs | 7 +- .../NewRelic/Agent/Core/Utilities/EventBus.cs | 3 +- .../Agent/Core/Utilities/RequestBus.cs | 4 +- src/Agent/Shared/SharedLog4NetRepository.cs | 5 - .../Core.UnitTest/Core.UnitTest.csproj | 5 - 22 files changed, 385 insertions(+), 476 deletions(-) create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/CustomAuditLogTextFormatter.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/CustomTextFormatter.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher - Copy.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs delete mode 100644 src/Agent/Shared/SharedLog4NetRepository.cs diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs index 2ad62e7633..24f6de5a6b 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs @@ -1301,8 +1301,6 @@ public partial class configurationLog private bool auditLogField; - private System.Nullable fileLockingModelField; - /// /// configurationLog class constructor /// @@ -1381,42 +1379,6 @@ public bool auditLog } } - [System.Xml.Serialization.XmlAttributeAttribute()] - public configurationLogFileLockingModel fileLockingModel - { - get - { - if (this.fileLockingModelField.HasValue) - { - return this.fileLockingModelField.Value; - } - else - { - return default(configurationLogFileLockingModel); - } - } - set - { - this.fileLockingModelField = value; - } - } - - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool fileLockingModelSpecified - { - get - { - return this.fileLockingModelField.HasValue; - } - set - { - if (value==false) - { - this.fileLockingModelField = null; - } - } - } - #region Clone method /// /// Create a clone of this configurationLog object diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd b/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd index 237943159b..5e1a5b6f76 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd @@ -347,19 +347,6 @@ - - - - Controls how agent-produced log files are to be locked. Experimental. - - - - - - - - - diff --git a/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs b/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs index 324189c74b..e4226ab8fd 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs @@ -586,22 +586,6 @@ private string GetLogFileName() return "newrelic_agent_" + Strings.SafeFileName(name) + ".log"; } - public bool FileLockingModelSpecified - { - get - { - return fileLockingModelSpecified; - } - } - - public configurationLogFileLockingModel FileLockingModel - { - get - { - return fileLockingModel; - } - } - public bool Console { get diff --git a/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs b/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs index 63762c69b0..8b072cbdd8 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs @@ -9,9 +9,6 @@ public interface ILogConfig string GetFullLogFileName(); - bool FileLockingModelSpecified { get; } - configurationLogFileLockingModel FileLockingModel { get; } - bool Console { get; } bool IsAuditLogEnabled { get; } diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs index ed2abb61d5..28fb62029e 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs @@ -63,15 +63,8 @@ private void OnConfigurationDeserialized(ConfigurationDeserializedEvent configur private static void UpdateLogLevel(configuration localConfiguration) { - var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - - var logLevel = logger.Hierarchy.LevelMap[localConfiguration.LogConfig.LogLevel]; - if (logLevel != null && logLevel != logger.Level) - { - Log.InfoFormat("The log level was updated to {0}", logLevel); - logger.Level = logLevel; - } + Log.InfoFormat("The log level was updated to {0}", localConfiguration.LogConfig.LogLevel); + LoggerBootstrapper.UpdateLoggingLevel(localConfiguration.LogConfig.LogLevel); } private void OnServerConfigurationUpdated(ServerConfigurationUpdatedEvent serverConfigurationUpdatedEvent) diff --git a/src/Agent/NewRelic/Agent/Core/Core.csproj b/src/Agent/NewRelic/Agent/Core/Core.csproj index cf61c9713d..52ab082e16 100644 --- a/src/Agent/NewRelic/Agent/Core/Core.csproj +++ b/src/Agent/NewRelic/Agent/Core/Core.csproj @@ -37,9 +37,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + - @@ -79,11 +83,6 @@ - - - Properties\SharedLog4NetRepository.cs - - @@ -105,8 +104,12 @@ - + + + + + @@ -120,14 +123,20 @@ - + + + + + + + - 15 - 11 + 18 + 14 diff --git a/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs b/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs index f7e91f3c76..07345ad0a3 100644 --- a/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs +++ b/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs @@ -12,7 +12,7 @@ namespace NewRelic.Agent.Core.Labels { public class LabelsService : ILabelsService { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(LabelsService)); + private readonly Serilog.ILogger Log = Serilog.Log.Logger; private const int MaxLabels = 64; private const int MaxLength = 255; @@ -45,18 +45,18 @@ private IEnumerable public void Error(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Error(exception.ToString()); + _logger.Error(exception, ""); } /// @@ -96,7 +88,6 @@ public void ErrorFormat(string format, params object[] args) { if (IsErrorEnabled) { - EnsureThreadIdPropertyExistsInContext(); _logger.Error(string.Format(format, args)); } } @@ -108,15 +99,14 @@ public void ErrorFormat(string format, params object[] args) /// /// True iff logging has been configured to include WARN level logs. /// - public bool IsWarnEnabled => _logger.IsWarnEnabled; + public bool IsWarnEnabled => _logger.IsEnabled(LogEventLevel.Warning); /// /// Logs at the WARN level. This log level should be used for information regarding *possible* problems in the agent that *might* adversely affect the user in some way (data loss, performance problems, reduced agent functionality, etc). Do not use if logging that information will create a performance problem (say, due to excessive logging). /// public void Warn(string message) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Warn(message); + _logger.Warning(message); } /// @@ -124,8 +114,7 @@ public void Warn(string message) /// public void Warn(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Warn(exception.ToString()); + _logger.Warning(exception, ""); } /// @@ -135,8 +124,7 @@ public void WarnFormat(string format, params object[] args) { if (IsWarnEnabled) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Warn(string.Format(format, args)); + _logger.Warning(string.Format(format, args)); } } @@ -147,15 +135,14 @@ public void WarnFormat(string format, params object[] args) /// /// True iff logging has been configured to include INFO level logs. /// - public bool IsInfoEnabled => _logger.IsInfoEnabled; + public bool IsInfoEnabled => _logger.IsEnabled(LogEventLevel.Information); /// /// Logs at the INFO level. This log level should be used for information for non-error information that may be of interest to the user, such as a the agent noticing a configuration change, or an infrequent "heartbeat". Do not use if logging that information will create a performance problem (say, due to excessive logging). /// public void Info(string message) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Info(message); + _logger.Information(message); } /// @@ -163,8 +150,7 @@ public void Info(string message) /// public void Info(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Info(exception.ToString()); + _logger.Information(exception, ""); } /// @@ -174,8 +160,7 @@ public void InfoFormat(string format, params object[] args) { if (IsInfoEnabled) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Info(string.Format(format, args)); + _logger.Information(string.Format(format, args)); } } @@ -186,14 +171,13 @@ public void InfoFormat(string format, params object[] args) /// /// True iff logging has been configured to include DEBUG level logs. /// - public bool IsDebugEnabled => _logger.IsDebugEnabled; + public bool IsDebugEnabled => _logger.IsEnabled(LogEventLevel.Debug); /// /// Logs at the DEBUG level. This log level should be used for information that is non-critical and used mainly for troubleshooting common problems such as RUM injection or SQL explain plans. This level is not enabled by default so there is less concern about performance, but this level still should not be used for any logging that would cause significant performance, such as logging every transaction name for every transaction. /// public void Debug(string message) { - EnsureThreadIdPropertyExistsInContext(); _logger.Debug(message); } @@ -202,8 +186,7 @@ public void Debug(string message) /// public void Debug(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Debug(exception.ToString()); + _logger.Debug(exception, ""); } /// @@ -211,9 +194,8 @@ public void Debug(Exception exception) /// public void DebugFormat(string format, params object[] args) { - if (_logger.IsDebugEnabled) + if (IsDebugEnabled) { - EnsureThreadIdPropertyExistsInContext(); _logger.Debug(string.Format(format, args)); } } @@ -225,15 +207,14 @@ public void DebugFormat(string format, params object[] args) /// /// True iff logging has been configured to include FINEST level logs. /// - public bool IsFinestEnabled => _logger.Logger.IsEnabledFor(log4net.Core.Level.Finest); + public bool IsFinestEnabled => _logger.IsEnabled(LogEventLevel.Verbose); /// /// Logs at the FINEST level. This log level should be used as a last resort for information that would otherwise be too expensive or too noisy to log at DEBUG level, such as logging every transaction name for every transaction. Useful for troubleshooting subtle problems like WCF's dual transactions. /// public void Finest(string message) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Logger.Log(typeof(Logger), log4net.Core.Level.Finest, message, null); + _logger.Verbose(message); } /// @@ -241,8 +222,7 @@ public void Finest(string message) /// public void Finest(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Logger.Log(typeof(Logger), log4net.Core.Level.Finest, exception.ToString(), null); + _logger.Verbose(exception, ""); } /// @@ -252,9 +232,8 @@ public void FinestFormat(string format, params object[] args) { if (IsFinestEnabled) { - EnsureThreadIdPropertyExistsInContext(); var formattedMessage = string.Format(format, args); - _logger.Logger.Log(typeof(Logger), log4net.Core.Level.Finest, formattedMessage, null); + _logger.Verbose(formattedMessage); } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs index 5c5000e1ce..5d0656344c 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs @@ -1,20 +1,15 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -using log4net; -using log4net.Appender; -using log4net.Core; -using log4net.Filter; -using log4net.Layout; using NewRelic.Agent.Core.Config; -using NewRelic.Agent.Core.Logging; -using NewRelic.Core.Logging; -using NewRelic.SystemInterfaces; using System; -using System.Collections.Generic; using System.IO; -using System.Reflection; -using log4netLogger = log4net.Repository.Hierarchy.Logger; +using System.Runtime.InteropServices; +using System.Text; +using Serilog; +using Serilog.Core; +using Serilog.Formatting; +using Logger = NewRelic.Agent.Core.Logging.Logger; namespace NewRelic.Agent.Core { @@ -22,372 +17,243 @@ public static class LoggerBootstrapper { /// - /// The name of the Audit log appender. + /// The name of the event log to log to. /// - private static readonly string AuditLogAppenderName = "AuditLog"; +#pragma warning disable CS0414 + private static readonly string EventLogName = "Application"; +#pragma warning restore CS0414 /// - /// The name of the Console log appender. + /// The event source name. /// - private static readonly string ConsoleLogAppenderName = "ConsoleLog"; +#pragma warning disable CS0414 + private static readonly string EventLogSourceName = "New Relic .NET Agent"; +#pragma warning restore CS0414 /// - /// The name of the temporary event log appender. + /// The string name of the Audit log level. /// - private static readonly string TemporaryEventLogAppenderName = "TemporaryEventLog"; - -#if NETFRAMEWORK - /// - /// The name of the event log appender. - /// - private static readonly string EventLogAppenderName = "EventLog"; - - /// - /// The name of the event log to log to. - /// - private static readonly string EventLogName = "Application"; - - /// - /// The event source name. - /// - private static readonly string EventLogSourceName = "New Relic .NET Agent"; -#endif - - /// - /// The numeric level of the Audit log. - /// - private static int AuditLogLevel = 150000; - - /// - /// The string name of the Audit log. - /// - private static string AuditLogName = "Audit"; + private static string AuditLogLevelName = "Audit"; // Watch out! If you change the time format that the agent puts into its log files, other log parsers may fail. - private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); - private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); + //private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); + //private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); - private static ILayout eventLoggerLayout = new PatternLayout("%level: %message"); - private static string STARTUP_APPENDER_NAME = "NEWRELIC_DOTNET_AGENT_STARTUP_APPENDER"; + private static LoggingLevelSwitch _loggingLevelSwitch = new LoggingLevelSwitch(); - private static List DeprecatedLogLevels = new List() { Level.Alert, Level.Critical, Level.Emergency, Level.Fatal, Level.Finer, Level.Trace, Level.Notice, Level.Severe, Level.Verbose, Level.Fine }; + public static void UpdateLoggingLevel(string newLogLevel) + { + _loggingLevelSwitch.MinimumLevel = newLogLevel.MapToSerilogLogLevel(); + } public static void Initialize() { - CreateAuditLogLevel(); - var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - - // initially we will log to console and event log so it should only log items that need action - logger.Level = Level.Info; - - GlobalContext.Properties["pid"] = new ProcessStatic().GetCurrentProcess().Id; - - SetupStartupLogAppender(logger); - SetupConsoleLogAppender(logger); - SetupTemporaryEventLogAppender(logger); + var startupLoggerConfig = new LoggerConfiguration() + .Enrich.With(new ThreadIdEnricher()) + .Enrich.With(new ProcessIdEnricher()) + .MinimumLevel.Information() + .ConfigureInMemoryLogSink() + // TODO: implement event log sink + //.ConfigureEventLogSink() + // TODO: Remove console log sink when in-memory sink is implemented + .ConfigureConsoleSink(); + + // set the global Serilog logger to our startup logger instance, this gets replaced when ConfigureLogger() is called + Serilog.Log.Logger = startupLoggerConfig.CreateLogger(); } /// /// Configures the agent logger. /// - /// - /// A - /// - /// - /// A - /// - /// - /// A - /// /// This should only be called once, as soon as you have a valid config. public static void ConfigureLogger(ILogConfig config) { - var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; + SetupLogLevel(config); - SetupLogLevel(logger, config); + var loggerConfig = new LoggerConfiguration() + .Enrich.With(new ThreadIdEnricher()) + .Enrich.With(new ProcessIdEnricher()) + .MinimumLevel.ControlledBy(_loggingLevelSwitch) + .ConfigureFileSink(config) + .ConfigureAuditLogSink(config) + .ConfigureDebugSink(); - SetupFileLogAppender(logger, config); - SetupAuditLogger(logger, config); - SetupDebugLogAppender(logger); - logger.RemoveAppender(TemporaryEventLogAppenderName); - - if (!config.Console) + if (config.Console) { - logger.RemoveAppender(ConsoleLogAppenderName); + loggerConfig = loggerConfig.ConfigureConsoleSink(); } - logger.Repository.Configured = true; + var startupLogger = Serilog.Log.Logger; - // We have now bootstrapped the agent logger, so - // remove the startup appender, then send its messages - // to the agent logger, which will get picked up by - // the other appenders. - ShutdownStartupLogAppender(logger); + // configure the global singleton logger instance (which remains specific to the Agent by way of ILRepack) + var configuredLogger = loggerConfig.CreateLogger(); - Log.Initialize(new Logger()); - } + EchoInMemoryLogsToConfiguredLogger(configuredLogger); - /// - /// Gets the log4net Level of the "Audit" log level. - /// - /// The "Audit" log4net Level. - public static Level GetAuditLevel() - { - return LogManager.GetRepository(Assembly.GetCallingAssembly()).LevelMap[AuditLogName]; - } + Serilog.Log.Logger = configuredLogger; - private static void ShutdownStartupLogAppender(log4netLogger logger) - { - var startupAppender = logger.GetAppender(STARTUP_APPENDER_NAME) as MemoryAppender; - if (startupAppender != null) - { - LoggingEvent[] events = startupAppender.GetEvents(); - logger.RemoveAppender(startupAppender); - - if (events != null) - { - foreach (LoggingEvent logEvent in events) - { - logger.Log(logEvent.Level, logEvent.MessageObject, null); - } - } - } - } - - private static FileAppender.LockingModelBase GetFileLockingModel(ILogConfig config) - { - if (config.FileLockingModelSpecified) - { - if (config.FileLockingModel.Equals(configurationLogFileLockingModel.minimal)) - { - return (FileAppender.LockingModelBase)new FileAppender.MinimalLock(); - } - else - { - return new FileAppender.ExclusiveLock(); - } - - } - return new FileAppender.MinimalLock(); - } - - /// - /// Creates a new AuditLogName log level at level AuditLogLevel (higher than Emergency log level) and registers it as a log4net level. - /// - private static void CreateAuditLogLevel() - { - Level auditLevel = new Level(AuditLogLevel, AuditLogName); - LogManager.GetRepository(Assembly.GetCallingAssembly()).LevelMap.Add(auditLevel); + NewRelic.Core.Logging.Log.Initialize(new Logger()); } /// - /// Returns a filter set to immediately deny any log events that are "Audit" level. + /// Gets a string identifying the Audit log level /// - /// A filter set to imediately deny any log events that are "Audit" level. - private static IFilter GetNoAuditFilter() - { - LevelMatchFilter filter = new LevelMatchFilter(); - filter.LevelToMatch = GetAuditLevel(); - filter.AcceptOnMatch = false; - return filter; - } + public static string GetAuditLevel() => AuditLogLevelName; - /// - /// Returns a filter set to immediately accept any log events that are "Audit" level. - /// - /// A filter set to immediately accept any log events that are "Audit" level. - private static IFilter GetAuditFilter() + private static void EchoInMemoryLogsToConfiguredLogger(Serilog.ILogger configuredLogger) { - LevelMatchFilter filter = new LevelMatchFilter(); - filter.LevelToMatch = GetAuditLevel(); - filter.AcceptOnMatch = true; - return filter; + // TODO: copy logs from inMemory logger and emit them to Serilog.Log.Logger + // possible example: + //foreach (LogEvent logEvent in InMemorySink.Instance.LogEvents) + //{ + // configuredLogger.Write(logEvent.Level, logEvent.Exception, logEvent.MessageTemplate.Render(logEvent.Properties)); + //} + //InMemorySink.Instance.Dispose(); } /// /// Sets the log level for logger to either the level provided by the config or an public default. /// - /// The logger to set the level of. /// The LogConfig to look for the level setting in. - private static void SetupLogLevel(log4netLogger logger, ILogConfig config) + private static void SetupLogLevel(ILogConfig config) { - logger.Level = logger.Hierarchy.LevelMap[config.LogLevel]; - - if (logger.Level == null) - { - logger.Level = log4net.Core.Level.Info; - } - - if (logger.Level == GetAuditLevel()) - { - logger.Level = Level.Info; - logger.Log(Level.Warn, $"Log level was set to {AuditLogName} which is not a valid log level. To enable audit logging, set the auditLog configuration option to true. Log level will be treated as INFO for this run.", null); - } - - if (IsLogLevelDeprecated(logger.Level)) - { - logger.Log(Level.Warn, string.Format( - "The log level, {0}, set in your configuration file has been deprecated. The agent will still log correctly, but you should change to a supported logging level as described in newrelic.config or the online documentation.", - logger.Level.ToString()), null); - } + _loggingLevelSwitch.MinimumLevel = config.LogLevel.MapToSerilogLogLevel(); } - private static bool IsLogLevelDeprecated(Level level) - { - foreach (var l in DeprecatedLogLevels) - { - if (l.Name.Equals(level.Name, StringComparison.InvariantCultureIgnoreCase)) return true; - } - return false; - } - - /// - /// A memory appender for logging to memory during startup. Log messages will be re-logged after configuration is loaded. - /// - /// - private static void SetupStartupLogAppender(log4netLogger logger) + // TODO: implement but don't use log4net log levels enum + //private static bool IsLogLevelDeprecated(Level level) + //{ + // foreach (var l in DeprecatedLogLevels) + // { + // if (l.Name.Equals(level.Name, StringComparison.InvariantCultureIgnoreCase)) return true; + // } + // return false; + //} + + private static LoggerConfiguration ConfigureInMemoryLogSink(this LoggerConfiguration loggerConfiguration) { - var startupAppender = new MemoryAppender(); - startupAppender.Name = STARTUP_APPENDER_NAME; - startupAppender.Layout = LoggerBootstrapper.FileLogLayout; - startupAppender.ActivateOptions(); + // TODO Configure the (yet-to-be-implemented) in-memory sink - logger.AddAppender(startupAppender); - logger.Repository.Configured = true; + return loggerConfiguration; } - /// - /// A temporary event log appender for logging during startup (before config is loaded) - /// - /// - private static void SetupTemporaryEventLogAppender(log4netLogger logger) - { -#if NETFRAMEWORK - var appender = new EventLogAppender(); - appender.Layout = eventLoggerLayout; - appender.Name = TemporaryEventLogAppenderName; - appender.LogName = EventLogName; - appender.ApplicationName = EventLogSourceName; - appender.Threshold = Level.Warn; - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - - logger.AddAppender(appender); -#endif - } - - /// - /// Setup the event log appender and attach it to a logger. - /// - /// The logger you want to attach the event log appender to. - /// The configuration for the appender. - private static void SetupEventLogAppender(log4netLogger logger, ILogConfig config) - { -#if NETFRAMEWORK - var appender = new EventLogAppender(); - appender.Layout = eventLoggerLayout; - appender.Threshold = Level.Warn; - appender.Name = EventLogAppenderName; - appender.LogName = EventLogName; - appender.ApplicationName = EventLogSourceName; - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - - logger.AddAppender(appender); -#endif - } + // TODO: Implement EventLog support, see commented package reference in Core.csproj + ///// + ///// Add the Event Log sink if running on Windows + ///// + ///// + //private static LoggerConfiguration ConfigureEventLogSink(this LoggerConfiguration loggerConfiguration) + //{ + // if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // return loggerConfiguration; + + // loggerConfiguration + // .WriteTo.Logger(configuration => + // { + // configuration + // .ExcludeAuditLog() + // .WriteTo.EventLog( + // source: EventLogSourceName, + // logName: EventLogName, + // restrictedToMinimumLevel: LogEventLevel.Warning + // ); + // }); + + // return loggerConfiguration; + //} /// - /// Setup the debug log appender and attach it to a logger. + /// Configure the debug sink /// - /// The logger you want to attach the event log appender to. - private static void SetupDebugLogAppender(log4netLogger logger) + private static LoggerConfiguration ConfigureDebugSink(this LoggerConfiguration loggerConfiguration) { #if DEBUG - // Create the debug appender and connect it to our logger. - var debugAppender = new DebugAppender(); - debugAppender.Layout = FileLogLayout; - debugAppender.AddFilter(GetNoAuditFilter()); - logger.AddAppender(debugAppender); + loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Debug(formatter: new CustomTextFormatter()); + }); #endif + return loggerConfiguration; } /// - /// Setup the console log appender and attach it to a logger. + /// Configure the console sink /// - /// The logger you want to attach the console log appender to. - private static void SetupConsoleLogAppender(log4netLogger logger) + private static LoggerConfiguration ConfigureConsoleSink(this LoggerConfiguration loggerConfiguration) { - var appender = new ConsoleAppender(); - appender.Name = ConsoleLogAppenderName; - appender.Layout = FileLogLayout; - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - logger.AddAppender(appender); + return loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Console(formatter: new CustomTextFormatter()); + }); } /// - /// Setup the file log appender and attach it to a logger. + /// Configure the file log sink /// - /// The logger you want to attach the file appender to. + /// /// The configuration for the appender. - /// If an exception occurs, the Event Log Appender is setup - /// to handle output. - private static void SetupFileLogAppender(log4netLogger logger, ILogConfig config) + private static LoggerConfiguration ConfigureFileSink(this LoggerConfiguration loggerConfiguration, ILogConfig config) { string logFileName = config.GetFullLogFileName(); try { - var appender = SetupRollingFileAppender(config, logFileName, "FileLog", FileLogLayout); - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - logger.AddAppender(appender); + loggerConfiguration + .WriteTo + .Logger(configuration => + { + configuration + .ExcludeAuditLog() + .ConfigureRollingLogSink(logFileName, new CustomTextFormatter()); + }); } - catch (Exception) + catch (Exception ex) { - // Fallback to the event logger if we cannot setup a file logger. - SetupEventLogAppender(logger, config); + Serilog.Log.Logger.Warning(ex, "Unexpected exception when configuring file sink. Falling back to EventLog sink."); + // TODO uncomment when EventLogSink is supported + //// Fallback to the event log sink if we cannot setup a file logger. + //loggerConfiguration.ConfigureEventLogSink(); } + + return loggerConfiguration; } /// /// Setup the audit log file appender and attach it to a logger. /// - /// The logger you want to attach the audit log appender to. - /// The configuration for the appender. - private static void SetupAuditLogger(log4netLogger logger, ILogConfig config) + private static LoggerConfiguration ConfigureAuditLogSink(this LoggerConfiguration loggerConfiguration, ILogConfig config) { - if (!config.IsAuditLogEnabled) return; + if (!config.IsAuditLogEnabled) return loggerConfiguration; string logFileName = config.GetFullLogFileName().Replace(".log", "_audit.log"); - try - { - var appender = SetupRollingFileAppender(config, logFileName, AuditLogAppenderName, AuditLogLayout); - appender.AddFilter(GetAuditFilter()); - appender.AddFilter(new DenyAllFilter()); - appender.ActivateOptions(); - logger.AddAppender(appender); - } - catch (Exception) - { } + return loggerConfiguration + .WriteTo + .Logger(configuration => + { + configuration + .MinimumLevel.Fatal() // We've hijacked Fatal log level as the level to use when writing an audit log + .IncludeOnlyAuditLog() + .ConfigureRollingLogSink(logFileName, new CustomAuditLogTextFormatter()); + }); } /// /// Sets up a rolling file appender using defaults shared for all our rolling file appenders. /// - /// The configuration for the appender. + /// /// The name of the file this appender will write to. - /// The name of this appender. + /// /// This does not call appender.ActivateOptions or add the appender to the logger. - private static RollingFileAppender SetupRollingFileAppender(ILogConfig config, string fileName, string appenderName, ILayout layout) + private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfiguration loggerConfiguration, string fileName, ITextFormatter textFormatter) { - var log = log4net.LogManager.GetLogger(typeof(AgentManager)); - // check that the log file is accessible try { @@ -395,37 +261,46 @@ private static RollingFileAppender SetupRollingFileAppender(ILogConfig config, s var directory = Path.GetDirectoryName(fileName); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); - using (File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write)) { } } catch (Exception exception) { - log.ErrorFormat("Unable to write the {0} log to \"{1}\": {2}", appenderName, fileName, exception.Message); + Serilog.Log.Logger.Warning(exception, $"Unable to write logfile at \"{fileName}\""); throw; } try { - var appender = new RollingFileAppender(); - - appender.LockingModel = GetFileLockingModel(config); - appender.Layout = layout; - appender.File = fileName; - appender.Encoding = System.Text.Encoding.UTF8; - appender.AppendToFile = true; - appender.RollingStyle = RollingFileAppender.RollingMode.Size; - appender.MaxSizeRollBackups = 4; - appender.MaxFileSize = 50 * 1024 * 1024; // 50MB - appender.StaticLogFileName = true; - appender.ImmediateFlush = true; - - return appender; + return loggerConfiguration + .WriteTo.File( + path: fileName, + formatter: textFormatter, + fileSizeLimitBytes: 50 * 1024 * 1024, + encoding: Encoding.UTF8, + rollOnFileSizeLimit: true, + retainedFileCountLimit: 4, + buffered: false + ); } catch (Exception exception) { - log.ErrorFormat("Unable to configure the {0} log file appender for \"{1}\": {2}", appenderName, fileName, exception.Message); + Serilog.Log.Logger.Warning(exception, $"Unexpected exception while configuring file logging for \"{fileName}\""); throw; } } + + private static LoggerConfiguration IncludeOnlyAuditLog(this LoggerConfiguration loggerConfiguration) + { + return loggerConfiguration.Filter.ByIncludingOnly(logEvent => + logEvent.Properties.ContainsKey(AuditLogLevelName)); + + } + private static LoggerConfiguration ExcludeAuditLog(this LoggerConfiguration loggerConfiguration) + { + return loggerConfiguration.Filter.ByExcluding(logEvent => + logEvent.Properties.ContainsKey(AuditLogLevelName)); + + } + } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher - Copy.cs b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher - Copy.cs new file mode 100644 index 0000000000..f21a6b9fa1 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher - Copy.cs @@ -0,0 +1,18 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Threading; +using Serilog.Core; +using Serilog.Events; + +namespace NewRelic.Agent.Core +{ + class ThreadIdEnricher : ILogEventEnricher + { + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( + "tid", Thread.CurrentThread.ManagedThreadId)); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs new file mode 100644 index 0000000000..d8f181045f --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs @@ -0,0 +1,18 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NewRelic.SystemInterfaces; +using Serilog.Core; +using Serilog.Events; + +namespace NewRelic.Agent.Core +{ + class ProcessIdEnricher : ILogEventEnricher + { + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( + "pid", new ProcessStatic().GetCurrentProcess().Id)); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs index e4bff496a6..5a765c329e 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs @@ -373,7 +373,7 @@ private void Shutdown(bool cleanShutdown) finally { Dispose(); - log4net.LogManager.Shutdown(); + Serilog.Log.CloseAndFlush(); } } diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs index 950fe13106..eb6ff4c614 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs @@ -15,12 +15,9 @@ namespace NewRelic.Agent.Core /// public class AgentShim { - private static log4net.ILog Log; - static void Initialize() { AgentInitializer.InitializeAgent(); - Log = log4net.LogManager.GetLogger(typeof(AgentShim)); } #if NETSTANDARD2_0 @@ -229,7 +226,7 @@ public static ITracer GetTracer( { try { - Log.Debug("Exception occurred in AgentShim.GetTracer", exception); + Serilog.Log.Logger.Debug(exception, "Exception occurred in AgentShim.GetTracer"); } catch { @@ -263,7 +260,7 @@ public static void FinishTracer(object tracerObject, object returnValue, object ITracer tracer = tracerObject as ITracer; if (tracer == null) { - Log.ErrorFormat("AgentShim.FinishTracer received a tracer object but it was not an ITracer. {0}", tracerObject); + Serilog.Log.Logger.Error($"AgentShim.FinishTracer received a tracer object but it was not an ITracer. {tracerObject}"); return; } @@ -271,7 +268,7 @@ public static void FinishTracer(object tracerObject, object returnValue, object Exception exception = exceptionObject as Exception; if (exception == null && exceptionObject != null) { - Log.ErrorFormat("AgentShim.FinishTracer received an exception object but it was not an Exception. {0}", exceptionObject); + Serilog.Log.Logger.Error($"AgentShim.FinishTracer received an exception object but it was not an Exception. {exceptionObject}"); return; } @@ -291,7 +288,7 @@ public static void FinishTracer(object tracerObject, object returnValue, object { try { - Log.Debug("Exception occurred in AgentShim.FinishTracer", exception); + Serilog.Log.Logger.Debug(exception,"Exception occurred in AgentShim.FinishTracer"); } catch { diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs index 067e36c89d..b3fa14f30f 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs @@ -4,6 +4,7 @@ using System; using System.IO; using NewRelic.Core.CodeAttributes; +using NewRelic.Core.Logging; namespace NewRelic.Agent.Core { @@ -88,8 +89,7 @@ private static string GetFreeBSDVersion() } catch (Exception ex) { - log4net.ILog logger = log4net.LogManager.GetLogger(typeof(RuntimeEnvironmentInfo)); - logger.Debug($"Unable to report Operating System: Unexpected exception in GetFreeBSDVersion: {ex}"); + Serilog.Log.Logger.Debug(ex, "Unable to report Operating System: Unexpected exception in GetFreeBSDVersion."); } #endif return string.Empty; @@ -152,8 +152,7 @@ private static DistroInfo LoadDistroInfo() } catch (Exception ex) { - log4net.ILog logger = log4net.LogManager.GetLogger(typeof(RuntimeEnvironmentInfo)); - logger.Debug($"Unable to report Operating System: Unexpected exception in LoadDistroInfo: {ex}"); + Serilog.Log.Logger.Debug(ex, $"Unable to report Operating System: Unexpected exception in LoadDistroInfo."); } return result; diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs b/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs index c2ca77c9eb..bddb6a9545 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs @@ -8,7 +8,6 @@ namespace NewRelic.Agent.Core.Utilities { public static class EventBus { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(EventBus)); private static event Action Events = T => { }; private static readonly ReaderWriterLock Lock = new ReaderWriterLock(); private static readonly ReaderLockGuard ReaderLockGuard = new ReaderLockGuard(Lock); @@ -63,7 +62,7 @@ public static void Publish(T message) } catch (Exception exception) { - Log.Error($"Exception thrown from event handler. Event handlers should not let exceptions bubble out of them: {exception}"); + Serilog.Log.Logger.Error(exception, "Exception thrown from event handler. Event handlers should not let exceptions bubble out of them."); } } } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs b/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs index 7dad1e56ff..1cf3d03717 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs @@ -15,8 +15,6 @@ namespace NewRelic.Agent.Core.Utilities /// Responders are not required to answer and there may not be a responder setup for any given request so you must be prepared to handle either no callback, an empty enumeration or default(TResponse), depending on which Post overload you use. public static class RequestBus { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(RequestBus)); - public delegate void ResponsesCallback(IEnumerable responses); public delegate void ResponseCallback(TResponse response); @@ -73,7 +71,7 @@ public static void Post(TRequest request, ResponsesCallback responsesCallback) } catch (Exception exception) { - Log.Error($"Exception thrown from request handler. Request handlers should not let exceptions bubble out of them: {exception}"); + Serilog.Log.Logger.Error(exception, "Exception thrown from request handler. Request handlers should not let exceptions bubble out of them."); } } diff --git a/src/Agent/Shared/SharedLog4NetRepository.cs b/src/Agent/Shared/SharedLog4NetRepository.cs deleted file mode 100644 index a1d12a3e1c..0000000000 --- a/src/Agent/Shared/SharedLog4NetRepository.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// This ensures that we use a different logging repository from the rest of the process that we end up in. -[assembly: log4net.Config.Repository("NewRelic Log4Net Repository")] diff --git a/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj b/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj index edadb1f512..23726512af 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj +++ b/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj @@ -43,11 +43,6 @@ - - - Properties\SharedLog4NetRepository.cs - - PreserveNewest From 29d5c82a1cb464e20323395de8c0aec9abf4560d Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Tue, 23 May 2023 10:47:43 -0700 Subject: [PATCH 02/10] feat: Implement simple in-memory Serilog sink. (#1657) --- .../Agent/Core/Logging/InMemorySink.cs | 45 +++++++++++++++++++ .../Agent/Core/Logging/LoggerBootstrapper.cs | 33 ++++++++------ .../AgentLoggerTest.cs | 3 +- 3 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/InMemorySink.cs diff --git a/src/Agent/NewRelic/Agent/Core/Logging/InMemorySink.cs b/src/Agent/NewRelic/Agent/Core/Logging/InMemorySink.cs new file mode 100644 index 0000000000..a1897c22da --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/InMemorySink.cs @@ -0,0 +1,45 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using Serilog.Core; +using Serilog.Events; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace NewRelic.Agent.Core.Logging +{ + public class InMemorySink : ILogEventSink, IDisposable + { + private readonly ConcurrentQueue _logEvents; + + public InMemorySink() + { + _logEvents = new ConcurrentQueue(); + } + + public void Emit(LogEvent logEvent) + { + _logEvents.Enqueue(logEvent); + } + + public IEnumerable LogEvents + { + get + { + return _logEvents; + } + } + + public void Clear() + { + while (_logEvents.TryDequeue(out _)) + { } + } + + public void Dispose() + { + Clear(); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs index 5d0656344c..64d45f35a6 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs @@ -10,6 +10,7 @@ using Serilog.Core; using Serilog.Formatting; using Logger = NewRelic.Agent.Core.Logging.Logger; +using NewRelic.Agent.Core.Logging; namespace NewRelic.Agent.Core { @@ -42,6 +43,8 @@ public static class LoggerBootstrapper private static LoggingLevelSwitch _loggingLevelSwitch = new LoggingLevelSwitch(); + private static InMemorySink _inMemorySink = new InMemorySink(); + public static void UpdateLoggingLevel(string newLogLevel) { _loggingLevelSwitch.MinimumLevel = newLogLevel.MapToSerilogLogLevel(); @@ -53,11 +56,9 @@ public static void Initialize() .Enrich.With(new ThreadIdEnricher()) .Enrich.With(new ProcessIdEnricher()) .MinimumLevel.Information() - .ConfigureInMemoryLogSink() + .ConfigureInMemoryLogSink(); // TODO: implement event log sink - //.ConfigureEventLogSink() - // TODO: Remove console log sink when in-memory sink is implemented - .ConfigureConsoleSink(); + //.ConfigureEventLogSink(); // set the global Serilog logger to our startup logger instance, this gets replaced when ConfigureLogger() is called Serilog.Log.Logger = startupLoggerConfig.CreateLogger(); @@ -103,13 +104,12 @@ public static void ConfigureLogger(ILogConfig config) private static void EchoInMemoryLogsToConfiguredLogger(Serilog.ILogger configuredLogger) { - // TODO: copy logs from inMemory logger and emit them to Serilog.Log.Logger - // possible example: - //foreach (LogEvent logEvent in InMemorySink.Instance.LogEvents) - //{ - // configuredLogger.Write(logEvent.Level, logEvent.Exception, logEvent.MessageTemplate.Render(logEvent.Properties)); - //} - //InMemorySink.Instance.Dispose(); + foreach (var logEvent in _inMemorySink.LogEvents) + { + configuredLogger.Write(logEvent); + } + + _inMemorySink.Dispose(); } /// @@ -133,9 +133,14 @@ private static void SetupLogLevel(ILogConfig config) private static LoggerConfiguration ConfigureInMemoryLogSink(this LoggerConfiguration loggerConfiguration) { - // TODO Configure the (yet-to-be-implemented) in-memory sink - - return loggerConfiguration; + // formatter not needed since this will be pushed to other sinks for output. + return loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Sink(_inMemorySink); + }); } // TODO: Implement EventLog support, see commented package reference in Core.csproj diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs index 3c80ac7242..9b97ed3c34 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs @@ -11,6 +11,7 @@ using log4net.Appender; using log4net.Core; using System.IO; +using NewRelic.Agent.Core.Logging; namespace NewRelic.Agent.Core { @@ -368,8 +369,6 @@ public static void Logging_sets_threadid_property_for_LogException() Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); } - - static private ILogConfig GetLogConfig(string logLevel) { var xml = string.Format( From 2a857f5583a4d1501a06d31ab14726b95771bbf5 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed, 24 May 2023 08:48:21 -0500 Subject: [PATCH 03/10] Refactor Unit tests to use Serilog instead of log4net --- .../NewRelic/Agent/Core/AgentInitializer.cs | 2 +- .../NewRelic/Agent/Core/Logging/AuditLog.cs | 4 +- .../Agent/Core/Logging/LogLevelExtensions.cs | 43 ++- .../Agent/Core/Logging/LoggerBootstrapper.cs | 46 +-- ...Enricher - Copy.cs => ThreadIdEnricher.cs} | 0 .../Core.UnitTest/Core.UnitTest.csproj | 1 - .../Labels/LabelsServiceTests.cs | 40 ++- .../AgentLoggerTest.cs | 305 ++---------------- .../NewRelic.Agent.TestUtilities/Logging.cs | 130 ++------ .../NewRelic.Agent.TestUtilities.csproj | 4 +- 10 files changed, 120 insertions(+), 455 deletions(-) rename src/Agent/NewRelic/Agent/Core/Logging/{ProcessIdEnricher - Copy.cs => ThreadIdEnricher.cs} (100%) diff --git a/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs b/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs index e58dceb31d..609da830c3 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs @@ -26,7 +26,7 @@ private static class CallOnce { static CallOnce() { - // we must ensure that we hook up to ProcessExit and DomainUnload *before* log4net. Otherwise we can't log anything during OnExit. + // we must ensure that we hook up to ProcessExit and DomainUnload *before* log initialization. Otherwise we can't log anything during OnExit. AppDomain.CurrentDomain.ProcessExit += (sender, args) => OnExit(sender, args); AppDomain.CurrentDomain.DomainUnload += (sender, args) => OnExit(sender, args); LoggerBootstrapper.Initialize(); diff --git a/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs b/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs index ed301312d6..5b08719c31 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs @@ -10,8 +10,10 @@ public static class AuditLog /// public static void Log(string message) { + // create a logger that injects an Audit property that we can later filter on (see LoggerBootstrapper.IncludeOnlyAuditLog) + var auditLogger = Serilog.Log.ForContext(LogLevelExtensions.AuditLevel, LogLevelExtensions.AuditLevel); // use Fatal log level to ensure audit log messages never get filtered due to level restrictions - Serilog.Log.Logger.Fatal("{Message} {Audit}", message, LoggerBootstrapper.GetAuditLevel()); + auditLogger.Fatal(message); } } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs index e98fa43dc1..1f74618ba1 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs @@ -2,40 +2,71 @@ // SPDX-License-Identifier: Apache-2.0 using System; +using System.Collections.Generic; +using System.Linq; using Serilog.Events; namespace NewRelic.Agent.Core { internal static class LogLevelExtensions { + private static readonly List DeprecatedLogLevels = new List() { "Alert", "Critical", "Emergency", "Fatal", "Finer", "Trace", "Notice", "Severe", "Verbose", "Fine" }; + public static bool IsLogLevelDeprecated(this string level) => DeprecatedLogLevels.Any(l => l.Equals(level, StringComparison.InvariantCultureIgnoreCase)); + + /// + /// Gets a string identifying the Audit log level + /// + public const string AuditLevel = "Audit"; + /// - /// Map a configfile loglevel to the equivalent Serilog loglevel + /// Map a configfile loglevel to the equivalent Serilog loglevel. Includes mappings + /// for all of the deprecated loglevels as well. /// /// public static LogEventLevel MapToSerilogLogLevel(this string configLogLevel) { - switch (configLogLevel.ToUpper()) + if (configLogLevel?.IsLogLevelDeprecated() ?? false) { + Serilog.Log.Logger.Warning($"The log level, {configLogLevel}, set in your configuration file has been deprecated. The agent will still log correctly, but you should change to a supported logging level as described in newrelic.config or the online documentation."); + } + + switch (configLogLevel?.ToUpper()) + { + case "VERBOSE": + case "FINE": + case "FINER": case "FINEST": + case "TRACE": + case "ALL": return LogEventLevel.Verbose; case "DEBUG": return LogEventLevel.Debug; case "INFO": + case "NOTICE": return LogEventLevel.Information; - case "WARN": + case "WARN": + case "ALERT": return LogEventLevel.Warning; + case "ERROR": + case "CRITICAL": + case "EMERGENCY": + case "FATAL": + case "SEVERE": + return LogEventLevel.Error; case "OFF": // moderately hack-ish, but setting the level to something higher than Fatal disables logs as per https://stackoverflow.com/a/30864356/2078975 - return (LogEventLevel)1 + (int)LogEventLevel.Fatal; + return (LogEventLevel)1 + (int)LogEventLevel.Fatal; + case AuditLevel: + Serilog.Log.Logger.Warning("Log level was set to \"Audit\" which is not a valid log level. To enable audit logging, set the auditLog configuration option to true. Log level will be treated as INFO for this run."); + return LogEventLevel.Information; default: - // TODO: Add checking for deprecated log levels ?? Serilog.Log.Logger.Warning($"Invalid log level '{configLogLevel}' specified. Using log level 'Info' by default."); return LogEventLevel.Information; } } /// - /// Translates Serilog log level to the "legacy" Log4Net levels to ensure log file consistency + /// Translates Serilog log level to the "legacy" Log4Net levels to ensure log file format consistency /// /// /// diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs index 64d45f35a6..0208fcbc7f 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs @@ -4,7 +4,6 @@ using NewRelic.Agent.Core.Config; using System; using System.IO; -using System.Runtime.InteropServices; using System.Text; using Serilog; using Serilog.Core; @@ -31,16 +30,10 @@ public static class LoggerBootstrapper private static readonly string EventLogSourceName = "New Relic .NET Agent"; #pragma warning restore CS0414 - /// - /// The string name of the Audit log level. - /// - private static string AuditLogLevelName = "Audit"; - // Watch out! If you change the time format that the agent puts into its log files, other log parsers may fail. //private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); //private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); - private static LoggingLevelSwitch _loggingLevelSwitch = new LoggingLevelSwitch(); private static InMemorySink _inMemorySink = new InMemorySink(); @@ -61,7 +54,7 @@ public static void Initialize() //.ConfigureEventLogSink(); // set the global Serilog logger to our startup logger instance, this gets replaced when ConfigureLogger() is called - Serilog.Log.Logger = startupLoggerConfig.CreateLogger(); + Log.Logger = startupLoggerConfig.CreateLogger(); } /// @@ -85,24 +78,17 @@ public static void ConfigureLogger(ILogConfig config) loggerConfig = loggerConfig.ConfigureConsoleSink(); } - var startupLogger = Serilog.Log.Logger; - // configure the global singleton logger instance (which remains specific to the Agent by way of ILRepack) var configuredLogger = loggerConfig.CreateLogger(); EchoInMemoryLogsToConfiguredLogger(configuredLogger); - Serilog.Log.Logger = configuredLogger; + Log.Logger = configuredLogger; NewRelic.Core.Logging.Log.Initialize(new Logger()); } - /// - /// Gets a string identifying the Audit log level - /// - public static string GetAuditLevel() => AuditLogLevelName; - - private static void EchoInMemoryLogsToConfiguredLogger(Serilog.ILogger configuredLogger) + private static void EchoInMemoryLogsToConfiguredLogger(ILogger configuredLogger) { foreach (var logEvent in _inMemorySink.LogEvents) { @@ -116,20 +102,7 @@ private static void EchoInMemoryLogsToConfiguredLogger(Serilog.ILogger configure /// Sets the log level for logger to either the level provided by the config or an public default. /// /// The LogConfig to look for the level setting in. - private static void SetupLogLevel(ILogConfig config) - { - _loggingLevelSwitch.MinimumLevel = config.LogLevel.MapToSerilogLogLevel(); - } - - // TODO: implement but don't use log4net log levels enum - //private static bool IsLogLevelDeprecated(Level level) - //{ - // foreach (var l in DeprecatedLogLevels) - // { - // if (l.Name.Equals(level.Name, StringComparison.InvariantCultureIgnoreCase)) return true; - // } - // return false; - //} + private static void SetupLogLevel(ILogConfig config) => _loggingLevelSwitch.MinimumLevel = config.LogLevel.MapToSerilogLogLevel(); private static LoggerConfiguration ConfigureInMemoryLogSink(this LoggerConfiguration loggerConfiguration) { @@ -221,7 +194,7 @@ private static LoggerConfiguration ConfigureFileSink(this LoggerConfiguration lo } catch (Exception ex) { - Serilog.Log.Logger.Warning(ex, "Unexpected exception when configuring file sink. Falling back to EventLog sink."); + Log.Logger.Warning(ex, "Unexpected exception when configuring file sink. Falling back to EventLog sink."); // TODO uncomment when EventLogSink is supported //// Fallback to the event log sink if we cannot setup a file logger. //loggerConfiguration.ConfigureEventLogSink(); @@ -270,7 +243,7 @@ private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfigurat } catch (Exception exception) { - Serilog.Log.Logger.Warning(exception, $"Unable to write logfile at \"{fileName}\""); + Log.Logger.Warning(exception, $"Unable to write logfile at \"{fileName}\""); throw; } @@ -289,7 +262,7 @@ private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfigurat } catch (Exception exception) { - Serilog.Log.Logger.Warning(exception, $"Unexpected exception while configuring file logging for \"{fileName}\""); + Log.Logger.Warning(exception, $"Unexpected exception while configuring file logging for \"{fileName}\""); throw; } } @@ -297,14 +270,13 @@ private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfigurat private static LoggerConfiguration IncludeOnlyAuditLog(this LoggerConfiguration loggerConfiguration) { return loggerConfiguration.Filter.ByIncludingOnly(logEvent => - logEvent.Properties.ContainsKey(AuditLogLevelName)); + logEvent.Properties.ContainsKey(LogLevelExtensions.AuditLevel)); } private static LoggerConfiguration ExcludeAuditLog(this LoggerConfiguration loggerConfiguration) { return loggerConfiguration.Filter.ByExcluding(logEvent => - logEvent.Properties.ContainsKey(AuditLogLevelName)); - + logEvent.Properties.ContainsKey(LogLevelExtensions.AuditLevel)); } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher - Copy.cs b/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs similarity index 100% rename from src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher - Copy.cs rename to src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs diff --git a/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj b/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj index 23726512af..19524021b2 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj +++ b/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj @@ -12,7 +12,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs index ef84692707..8fc327ccc7 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs @@ -264,33 +264,29 @@ public static IEnumerable CrossAgentTestCases } } - [TestCaseSource(nameof(CrossAgentTestCases))] + [TestCaseSource(nameof(CrossAgentTestCases))] public void cross_agent_tests(TestCase testCase) { - var logAppender = new log4net.Appender.MemoryAppender(); - var logFilter = new log4net.Filter.LevelMatchFilter(); - logFilter.LevelToMatch = log4net.Core.Level.Warn; - logAppender.AddFilter(logFilter); - var logger = (log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy).Root; - logger.AddAppender(logAppender); - logger.Level = log4net.Core.Level.Warn; - logger.Repository.Configured = true; + using (var logger = new TestUtilities.Logging()) + { - // arrange - var configurationService = Mock.Create(); - Mock.Arrange(() => configurationService.Configuration.Labels).Returns(testCase.LabelConfigurationString); + // arrange + var configurationService = Mock.Create(); + Mock.Arrange(() => configurationService.Configuration.Labels) + .Returns(testCase.LabelConfigurationString); - // act - var labelsService = new LabelsService(configurationService); - var actualResults = JsonConvert.SerializeObject(labelsService.Labels); - var expectedResults = JsonConvert.SerializeObject(testCase.Expected); + // act + var labelsService = new LabelsService(configurationService); + var actualResults = JsonConvert.SerializeObject(labelsService.Labels); + var expectedResults = JsonConvert.SerializeObject(testCase.Expected); - // assert - Assert.AreEqual(expectedResults, actualResults); - if (testCase.Warning) - Assert.AreNotEqual(0, logAppender.GetEvents().Length); - else - Assert.AreEqual(0, logAppender.GetEvents().Length); + // assert + Assert.AreEqual(expectedResults, actualResults); + if (testCase.Warning) + Assert.AreNotEqual(0, logger.WarnCount); + else + Assert.AreEqual(0, logger.MessageCount); + } } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs index 9b97ed3c34..6da281e414 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs @@ -2,16 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 using System; -using System.Reflection; -using System.Linq; -using System.Threading; using NewRelic.Agent.Core.Config; using NewRelic.Core.Logging; using NUnit.Framework; -using log4net.Appender; -using log4net.Core; using System.IO; -using NewRelic.Agent.Core.Logging; +using NewRelic.Testing.Assertions; namespace NewRelic.Agent.Core { @@ -20,21 +15,34 @@ namespace NewRelic.Agent.Core public class LoggerBootstrapperTest { [Test] - public static void IsDebugEnabled_is_false_when_config_log_is_off() + public static void No_log_levels_are_enabled_when_config_log_is_off() { ILogConfig config = GetLogConfig("off"); LoggerBootstrapper.Initialize(); LoggerBootstrapper.ConfigureLogger(config); - Assert.IsFalse(Log.IsDebugEnabled); + NrAssert.Multiple( + () => Assert.IsFalse(Log.IsFinestEnabled), + () => Assert.IsFalse(Log.IsDebugEnabled), + () => Assert.IsFalse(Log.IsInfoEnabled), + () => Assert.IsFalse(Log.IsWarnEnabled), + () => Assert.IsFalse(Log.IsErrorEnabled) + ); } [Test] - public static void IsDebugEnabled_is_true_when_config_log_is_all() + public static void All_log_levels_are_enabled_when_config_log_is_all() { ILogConfig config = GetLogConfig("all"); LoggerBootstrapper.Initialize(); LoggerBootstrapper.ConfigureLogger(config); - Assert.That(Log.IsDebugEnabled); + + NrAssert.Multiple( + () => Assert.IsTrue(Log.IsFinestEnabled), + () => Assert.IsTrue(Log.IsDebugEnabled), + () => Assert.IsTrue(Log.IsInfoEnabled), + () => Assert.IsTrue(Log.IsWarnEnabled), + () => Assert.IsTrue(Log.IsErrorEnabled) + ); } [Test] @@ -43,6 +51,7 @@ public static void IsInfoEnabled_is_true_when_config_log_is_info() ILogConfig config = GetLogConfig("info"); LoggerBootstrapper.Initialize(); LoggerBootstrapper.ConfigureLogger(config); + Assert.That(Log.IsInfoEnabled); } @@ -74,35 +83,6 @@ public static void IsEnabledFor_finest_is_false_when_config_log_is_debug() } - [Test] - public static void ConsoleAppender_exists_and_has_correct_level_when_console_true_in_config() - { - ILogConfig config = LogConfigFixtureWithConsoleLogEnabled("debug"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - var logger = GetLogger(); - var consoleAppender = logger.Appenders.OfType().First(); - - Assert.IsFalse(Log.IsFinestEnabled); - Assert.That(logger.Level == Level.Debug); - // If the appender's threshold is null, it basically - // inherits the parent logger's level. - Assert.That(consoleAppender.Threshold == null); - } - - [Test] - public static void ConsoleAppender_does_not_exist_when_console_false_in_config() - { - ILogConfig config = GetLogConfig("debug"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - var logger = GetLogger(); - var consoleAppenders = logger.Appenders.OfType(); - - Assert.IsFalse(Log.IsFinestEnabled); - Assert.IsEmpty(consoleAppenders); - } - [Test] public static void Config_IsAuditEnabled_for_config_is_true_when_auditLog_true_in_config() { @@ -131,244 +111,6 @@ public static void Config_IsConsoleEnabled_for_config_is_false_when_not_added_to Assert.IsFalse(config.Console); } - [Test] - public static void Logging_sets_threadid_property_for_Info() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Info("Please set my thread id."); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Info_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Info(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_InfoFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.InfoFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Debug() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Debug("debug mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Debug_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Debug(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_DebugFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.DebugFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_ErrorFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.ErrorFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Error() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Error("debug mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Error_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Error(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_FinestFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.FinestFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Finest() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Finest("debug mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Finest_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Finest(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_WarnFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.WarnFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Warn() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Warn("warn mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Warn_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Warn(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_LogMessage() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.LogMessage(LogLevel.Info, "Test Message"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_LogException() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.LogException(LogLevel.Info, new Exception("Test exception")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - static private ILogConfig GetLogConfig(string logLevel) { var xml = string.Format( @@ -425,14 +167,5 @@ static private ILogConfig LogConfigFixtureWithConsoleLogEnabled(string logLevel) var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); return configuration.LogConfig; } - - private static log4net.Repository.Hierarchy.Logger GetLogger() - { - var hierarchy = - log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as - log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - return logger; - } } } diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs index a261bf0287..86a7780a4a 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs @@ -1,72 +1,54 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -using NewRelic.Agent.Core.Logging; -using NewRelic.Core.Logging; -using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; - -// This ensures that we use a different logging repository from the rest of the process that we end up in. -[assembly: log4net.Config.Repository("NewRelic Log4Net Repository")] +using NewRelic.Agent.Core.Logging; +using Serilog.Events; +using Serilog; +using Log = NewRelic.Core.Logging.Log; +using Logger = NewRelic.Agent.Core.Logging.Logger; namespace NewRelic.Agent.TestUtilities { /// - /// While this object is in scope, log4net will log to a memory appender. + /// While this object is in scope, serilog will log to an in-memory sink /// public class Logging : IDisposable { - public readonly log4net.Appender.MemoryAppender MemoryAppender = new log4net.Appender.MemoryAppender(); - public readonly log4net.Repository.Hierarchy.Logger Logger = (log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy).Root; - private readonly log4net.Appender.AppenderCollection _previousAppenders = new log4net.Appender.AppenderCollection(); + private readonly InMemorySink _inMemorySink = new InMemorySink(); /// - /// Initializes log4net to log to a memory appender which can then be referenced + /// Initializes serilog to log to an in-memory sink which can then be queried /// - public Logging(log4net.Core.Level level = null) + public Logging(LogEventLevel logLevel = LogEventLevel.Information) { - Logger.Level = level ?? log4net.Core.Level.All; + var loggerConfig = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(_inMemorySink); - Logger.RemoveAllAppenders(); - Logger.AddAppender(MemoryAppender); - - Logger.Repository.Configured = true; + Serilog.Log.Logger = loggerConfig.CreateLogger(); Log.Initialize(new Logger()); } - /// - /// When you dispose of this object the memory appender will be removed from the logging system. - /// public void Dispose() { - Logger.Repository.Configured = false; - - if (Logger.Appenders == null) - Assert.Fail("We somehow ended up with no log appenders, test is invalid."); - - if (Logger.Appenders.Count != 1) - Assert.Fail("Someone added or removed log appenders during the execution of this test potentially invalidating it."); - - Logger.RemoveAllAppenders(); } public override string ToString() { var builder = new StringBuilder(); - var logEvents = MemoryAppender.GetEvents(); + var logEvents = _inMemorySink.LogEvents; if (logEvents == null) return "Nothing was logged."; foreach (var logEvent in logEvents) { - if (logEvent == null) - continue; - - builder.AppendLine(logEvent.RenderedMessage); + builder.AppendLine(logEvent.RenderMessage()); } return builder.ToString(); @@ -77,118 +59,66 @@ public override string ToString() /// /// The message you want to check for. /// True if the message was logged, false otherwise. - public bool HasMessage(string message) - { - var events = MemoryAppender.GetEvents(); - foreach (var item in events) - { - if (item.MessageObject.ToString() == message) - return true; - } - return false; - } + public bool HasMessage(string message) => _inMemorySink.LogEvents.Any(e => e.RenderMessage() == message); /// /// checks for messages that begins with a segment /// /// /// - public bool HasMessageBeginingWith(string segment) - { - var events = MemoryAppender.GetEvents(); - return events.Any(item => item.MessageObject.ToString().StartsWith(segment)); - } + public bool HasMessageBeginingWith(string segment) => _inMemorySink.LogEvents.Any(item => item.RenderMessage().StartsWith(segment)); /// /// checks for messages that begins with a segment /// /// /// - public bool HasMessageThatContains(string segment) - { - var events = MemoryAppender.GetEvents(); - return events.Any(item => item.MessageObject.ToString().Contains(segment)); - } - - /// - /// Returns the exception associated with the message if it exists. - /// - /// The message to look for in the message collection. - /// The exception associated with the given message or null if either the message wasn't found or no exception was associated with the message. - public Exception TryGetExceptionForMessage(string message) - { - var events = MemoryAppender.GetEvents(); - foreach (var item in events) - { - if (item.MessageObject.ToString() == message) - return item.ExceptionObject; - } - return null; - } + public bool HasMessageThatContains(string segment) => _inMemorySink.LogEvents.Any(item => item.RenderMessage().Contains(segment)); /// /// Counts the number of messages that were logged since the construction of this object. /// - public int MessageCount { get { return MemoryAppender.GetEvents().Length; } } + public int MessageCount => _inMemorySink.LogEvents.Count(); /// /// Counts the number of [level] messages that were logged since the construction of this object. /// /// The number of messages logged at [level] level. - private int LevelCount(log4net.Core.Level level) - { - var events = MemoryAppender.GetEvents(); - int count = 0; - foreach (var item in events) - { - if (item.Level == level) - { - ++count; - } - } - - return count; - - } + private int LevelCount(LogEventLevel level) => _inMemorySink.LogEvents.Count(e => e.Level == level); - public IEnumerable ErrorMessages - { - get - { - return MemoryAppender.GetEvents() - .Where(@event => @event.Level == log4net.Core.Level.Error) - .Select(@event => @event.RenderedMessage); - } - } + public IEnumerable ErrorMessages => + _inMemorySink.LogEvents + .Where(@event => @event.Level == LogEventLevel.Error) + .Select(@event => @event.RenderMessage()); /// /// Counts the number of error level messages that were logged since construction of this object. /// /// - public int ErrorCount { get { return LevelCount(log4net.Core.Level.Error); } } + public int ErrorCount => LevelCount(LogEventLevel.Error); /// /// Counts the number of warn level messages that were logged since construction of this object. /// /// - public int WarnCount { get { return LevelCount(log4net.Core.Level.Warn); } } + public int WarnCount => LevelCount(LogEventLevel.Warning); /// /// Counts the number of info level messages that were logged since construction of this object. /// /// - public int InfoCount { get { return LevelCount(log4net.Core.Level.Info); } } + public int InfoCount => LevelCount(LogEventLevel.Information); /// /// Counts the number of debug level messages that were logged since construction of this object. /// /// - public int DebugCount { get { return LevelCount(log4net.Core.Level.Debug); } } + public int DebugCount => LevelCount(LogEventLevel.Debug); /// /// Counts the number of finest level messages that were logged since construction of this object. /// /// - public int FinestCount { get { return LevelCount(log4net.Core.Level.Finest); } } + public int FinestCount => LevelCount(LogEventLevel.Verbose); } } diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj index 4505205c09..4505ab92ce 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj @@ -9,8 +9,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + From d7450fc8196ccc427589058374189b1f1373fe5f Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed, 24 May 2023 15:24:12 -0500 Subject: [PATCH 04/10] Add Serilog.Async sink, wrap Console and File sinks with Async sink. (#1662) * Add Serilog.Async sink, wrap Console and File sinks with Async sink. * Enable EventLog output for .NET Framework applications. --- src/Agent/NewRelic/Agent/Core/Core.csproj | 14 +- .../Agent/Core/Logging/LogLevelExtensions.cs | 2 +- .../Agent/Core/Logging/LoggerBootstrapper.cs | 127 +++++++++--------- 3 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Core.csproj b/src/Agent/NewRelic/Agent/Core/Core.csproj index 52ab082e16..f5d6bbd0c8 100644 --- a/src/Agent/NewRelic/Agent/Core/Core.csproj +++ b/src/Agent/NewRelic/Agent/Core/Core.csproj @@ -38,9 +38,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + - @@ -60,6 +60,7 @@ + @@ -106,9 +107,10 @@ + - + @@ -125,18 +127,16 @@ + - - - - 18 - 14 + 20 + 15 diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs index 1f74618ba1..5105411fdb 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs @@ -84,7 +84,7 @@ public static string TranslateLogLevel(this LogEventLevel logEventLevel) case LogEventLevel.Warning: return "WARN"; case LogEventLevel.Error: - return "ERR"; + return "ERROR"; default: throw new ArgumentOutOfRangeException(nameof(logEventLevel), logEventLevel, null); } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs index 0208fcbc7f..bad0b265ae 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs @@ -10,26 +10,15 @@ using Serilog.Formatting; using Logger = NewRelic.Agent.Core.Logging.Logger; using NewRelic.Agent.Core.Logging; +#if NETFRAMEWORK +using Serilog.Events; +#endif namespace NewRelic.Agent.Core { public static class LoggerBootstrapper { - /// - /// The name of the event log to log to. - /// -#pragma warning disable CS0414 - private static readonly string EventLogName = "Application"; -#pragma warning restore CS0414 - - /// - /// The event source name. - /// -#pragma warning disable CS0414 - private static readonly string EventLogSourceName = "New Relic .NET Agent"; -#pragma warning restore CS0414 - // Watch out! If you change the time format that the agent puts into its log files, other log parsers may fail. //private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); //private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); @@ -49,9 +38,8 @@ public static void Initialize() .Enrich.With(new ThreadIdEnricher()) .Enrich.With(new ProcessIdEnricher()) .MinimumLevel.Information() - .ConfigureInMemoryLogSink(); - // TODO: implement event log sink - //.ConfigureEventLogSink(); + .ConfigureInMemoryLogSink() + .ConfigureEventLogSink(); // set the global Serilog logger to our startup logger instance, this gets replaced when ConfigureLogger() is called Log.Logger = startupLoggerConfig.CreateLogger(); @@ -116,30 +104,30 @@ private static LoggerConfiguration ConfigureInMemoryLogSink(this LoggerConfigura }); } - // TODO: Implement EventLog support, see commented package reference in Core.csproj - ///// - ///// Add the Event Log sink if running on Windows - ///// - ///// - //private static LoggerConfiguration ConfigureEventLogSink(this LoggerConfiguration loggerConfiguration) - //{ - // if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - // return loggerConfiguration; - - // loggerConfiguration - // .WriteTo.Logger(configuration => - // { - // configuration - // .ExcludeAuditLog() - // .WriteTo.EventLog( - // source: EventLogSourceName, - // logName: EventLogName, - // restrictedToMinimumLevel: LogEventLevel.Warning - // ); - // }); - - // return loggerConfiguration; - //} + /// + /// Add the Event Log sink if running on .NET Framework + /// + /// + private static LoggerConfiguration ConfigureEventLogSink(this LoggerConfiguration loggerConfiguration) + { +#if NETFRAMEWORK + const string eventLogName = "Application"; + const string eventLogSourceName = "New Relic .NET Agent"; + + loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.EventLog( + source: eventLogSourceName, + logName: eventLogName, + restrictedToMinimumLevel: LogEventLevel.Warning + ); + }); +#endif + return loggerConfiguration; + } /// /// Configure the debug sink @@ -164,12 +152,14 @@ private static LoggerConfiguration ConfigureDebugSink(this LoggerConfiguration l private static LoggerConfiguration ConfigureConsoleSink(this LoggerConfiguration loggerConfiguration) { return loggerConfiguration - .WriteTo.Logger(configuration => - { - configuration - .ExcludeAuditLog() - .WriteTo.Console(formatter: new CustomTextFormatter()); - }); + .WriteTo.Async(a => + a.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Console(formatter: new CustomTextFormatter()); + }) + ); } /// @@ -185,19 +175,23 @@ private static LoggerConfiguration ConfigureFileSink(this LoggerConfiguration lo { loggerConfiguration .WriteTo - .Logger(configuration => - { - configuration - .ExcludeAuditLog() - .ConfigureRollingLogSink(logFileName, new CustomTextFormatter()); - }); + .Async(a => + a.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .ConfigureRollingLogSink(logFileName, new CustomTextFormatter()); + }) + ); } catch (Exception ex) { - Log.Logger.Warning(ex, "Unexpected exception when configuring file sink. Falling back to EventLog sink."); - // TODO uncomment when EventLogSink is supported - //// Fallback to the event log sink if we cannot setup a file logger. - //loggerConfiguration.ConfigureEventLogSink(); + Log.Logger.Warning(ex, "Unexpected exception when configuring file sink."); +#if NETFRAMEWORK + // Fallback to the event log sink if we cannot setup a file logger. + Log.Logger.Warning("Falling back to EventLog sink."); + loggerConfiguration.ConfigureEventLogSink(); +#endif } return loggerConfiguration; @@ -250,15 +244,16 @@ private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfigurat try { return loggerConfiguration - .WriteTo.File( - path: fileName, - formatter: textFormatter, - fileSizeLimitBytes: 50 * 1024 * 1024, - encoding: Encoding.UTF8, - rollOnFileSizeLimit: true, - retainedFileCountLimit: 4, - buffered: false - ); + .WriteTo + .File(path: fileName, + formatter: textFormatter, + fileSizeLimitBytes: 50 * 1024 * 1024, + encoding: Encoding.UTF8, + rollOnFileSizeLimit: true, + retainedFileCountLimit: 4, + shared: true, + buffered: false + ); } catch (Exception exception) { From 98dfc0dd985912dc7e7e5e3d0907ff3b418aa502 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 25 May 2023 11:02:30 -0500 Subject: [PATCH 05/10] log4net to Serilog: Unit tests plus code cleanup for serilog implementation (#1663) --- .../Agent/Core/Logging/LogLevelExtensions.cs | 4 +- .../NewRelic/Agent/Core/Logging/Logger.cs | 28 +- .../Logging/InMemorySinkTests.cs | 58 +++++ .../Logging/LogLevelExtensionsTests.cs | 113 +++++++++ .../Core.UnitTest/Logging/LoggerTests.cs | 240 ++++++++++++++++++ 5 files changed, 418 insertions(+), 25 deletions(-) create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs index 5105411fdb..af195116f8 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs @@ -8,7 +8,7 @@ namespace NewRelic.Agent.Core { - internal static class LogLevelExtensions + public static class LogLevelExtensions { private static readonly List DeprecatedLogLevels = new List() { "Alert", "Critical", "Emergency", "Fatal", "Finer", "Trace", "Notice", "Severe", "Verbose", "Fine" }; public static bool IsLogLevelDeprecated(this string level) => DeprecatedLogLevels.Any(l => l.Equals(level, StringComparison.InvariantCultureIgnoreCase)); @@ -56,7 +56,7 @@ public static LogEventLevel MapToSerilogLogLevel(this string configLogLevel) case "OFF": // moderately hack-ish, but setting the level to something higher than Fatal disables logs as per https://stackoverflow.com/a/30864356/2078975 return (LogEventLevel)1 + (int)LogEventLevel.Fatal; - case AuditLevel: + case "AUDIT": Serilog.Log.Logger.Warning("Log level was set to \"Audit\" which is not a valid log level. To enable audit logging, set the auditLog configuration option to true. Log level will be treated as INFO for this run."); return LogEventLevel.Information; default: diff --git a/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs b/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs index fb057db18c..8a61d05a80 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs @@ -53,8 +53,6 @@ public void Log(Level level, object message) case Level.Error: _logger.Error(messageString); break; - default: - break; } } @@ -86,10 +84,7 @@ public void Error(Exception exception) /// public void ErrorFormat(string format, params object[] args) { - if (IsErrorEnabled) - { - _logger.Error(string.Format(format, args)); - } + _logger.Error(string.Format(format, args)); } #endregion Error @@ -122,10 +117,7 @@ public void Warn(Exception exception) /// public void WarnFormat(string format, params object[] args) { - if (IsWarnEnabled) - { - _logger.Warning(string.Format(format, args)); - } + _logger.Warning(string.Format(format, args)); } #endregion Warn @@ -158,10 +150,7 @@ public void Info(Exception exception) /// public void InfoFormat(string format, params object[] args) { - if (IsInfoEnabled) - { - _logger.Information(string.Format(format, args)); - } + _logger.Information(string.Format(format, args)); } #endregion Info @@ -194,10 +183,7 @@ public void Debug(Exception exception) /// public void DebugFormat(string format, params object[] args) { - if (IsDebugEnabled) - { - _logger.Debug(string.Format(format, args)); - } + _logger.Debug(string.Format(format, args)); } #endregion Debug @@ -230,11 +216,7 @@ public void Finest(Exception exception) /// public void FinestFormat(string format, params object[] args) { - if (IsFinestEnabled) - { - var formattedMessage = string.Format(format, args); - _logger.Verbose(formattedMessage); - } + _logger.Verbose(string.Format(format, args)); } #endregion Finest diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs new file mode 100644 index 0000000000..3dc9b1c98a --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs @@ -0,0 +1,58 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NUnit.Framework; +using Serilog.Events; +using System; +using System.Linq; +using Serilog.Parsing; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class InMemorySinkTests + { + private InMemorySink _sink; + private LogEvent _logEvent; + + [SetUp] + public void SetUp() + { + _sink = new InMemorySink(); + _logEvent = new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate("Test", Enumerable.Empty()), Enumerable.Empty()); + } + + [Test] + public void Emit_Enqueues_LogEvent() + { + _sink.Emit(_logEvent); + Assert.AreEqual(1, _sink.LogEvents.Count()); + } + + [Test] + public void LogEvents_ReturnsCorrectEvents() + { + _sink.Emit(_logEvent); + var logEvents = _sink.LogEvents; + + Assert.AreEqual(1, logEvents.Count()); + Assert.AreEqual(_logEvent, logEvents.First()); + } + + [Test] + public void Clear_LogEvents_EmptiesQueue() + { + _sink.Emit(_logEvent); + _sink.Clear(); + Assert.AreEqual(0, _sink.LogEvents.Count()); + } + + [Test] + public void Dispose_ClearsLogEvents() + { + _sink.Emit(_logEvent); + _sink.Dispose(); + Assert.AreEqual(0, _sink.LogEvents.Count()); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs new file mode 100644 index 0000000000..5450103c67 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs @@ -0,0 +1,113 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NUnit.Framework; +using Serilog.Events; +using Telerik.JustMock; +using ILogger = Serilog.ILogger; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class LogLevelExtensionsTests + { + private ILogger _serilogLogger; + + [SetUp] + public void Setup() + { + _serilogLogger = Mock.Create(); + Serilog.Log.Logger = _serilogLogger; + } + [Test] + [TestCase("Alert", ExpectedResult = true)] + [TestCase("Critical", ExpectedResult = true)] + [TestCase("Emergency", ExpectedResult = true)] + [TestCase("Fatal", ExpectedResult = true)] + [TestCase("Finer", ExpectedResult = true)] + [TestCase("Trace", ExpectedResult = true)] + [TestCase("Notice", ExpectedResult = true)] + [TestCase("Severe", ExpectedResult = true)] + [TestCase("Verbose", ExpectedResult = true)] + [TestCase("Fine", ExpectedResult = true)] + [TestCase("Other", ExpectedResult = false)] + [TestCase("", ExpectedResult = false)] + [TestCase(null, ExpectedResult = false)] + public bool IsLogLevelDeprecated_ReturnsCorrectResult(string logLevel) + { + return logLevel.IsLogLevelDeprecated(); + } + + [Test] + [TestCase("Verbose", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Fine", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Finer", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Finest", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Trace", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("All", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Debug", ExpectedResult = LogEventLevel.Debug)] + [TestCase("Info", ExpectedResult = LogEventLevel.Information)] + [TestCase("Notice", ExpectedResult = LogEventLevel.Information)] + [TestCase("Warn", ExpectedResult = LogEventLevel.Warning)] + [TestCase("Alert", ExpectedResult = LogEventLevel.Warning)] + [TestCase("Error", ExpectedResult = LogEventLevel.Error)] + [TestCase("Critical", ExpectedResult = LogEventLevel.Error)] + [TestCase("Emergency", ExpectedResult = LogEventLevel.Error)] + [TestCase("Fatal", ExpectedResult = LogEventLevel.Error)] + [TestCase("Severe", ExpectedResult = LogEventLevel.Error)] + [TestCase("Off", ExpectedResult = (LogEventLevel)6)] + [TestCase(LogLevelExtensions.AuditLevel, ExpectedResult = LogEventLevel.Information)] + [TestCase("NonExistent", ExpectedResult = LogEventLevel.Information)] + public LogEventLevel MapToSerilogLogLevel_ReturnsCorrectResult(string configLogLevel) + { + return configLogLevel.MapToSerilogLogLevel(); + } + + [Test] + public void MapToSerilogLogLevel_LogsDeprecationWarning_IfLogLevelIsDeprecated() + { + string deprecatedLogLevel = "Severe"; + deprecatedLogLevel.MapToSerilogLogLevel(); + + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + [Test] + public void MapToSerilogLogLevel_LogsWarning_IfLogLevelIsAudit() + { + string auditLevel = LogLevelExtensions.AuditLevel; + auditLevel.MapToSerilogLogLevel(); + + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + [Test] + public void MapToSerilogLogLevel_LogsWarning_IfLogLevelIsInvalid() + { + string deprecatedLogLevel = "FOOBAR"; + deprecatedLogLevel.MapToSerilogLogLevel(); + + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + [Test] + [TestCase(LogEventLevel.Verbose, ExpectedResult = "FINEST")] + [TestCase(LogEventLevel.Debug, ExpectedResult = "DEBUG")] + [TestCase(LogEventLevel.Information, ExpectedResult = "INFO")] + [TestCase(LogEventLevel.Warning, ExpectedResult = "WARN")] + [TestCase(LogEventLevel.Error, ExpectedResult = "ERROR")] + public string TranslateLogLevel_ReturnsCorrectResult(LogEventLevel logEventLevel) + { + return logEventLevel.TranslateLogLevel(); + } + + [Test] + public void TranslateLogLevel_Throws_IfLogEventLevelIsInvalid() + { + var invalidLogLevel = (LogEventLevel)9999; + + Assert.Throws(() => invalidLogLevel.TranslateLogLevel()); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs new file mode 100644 index 0000000000..d499721f1e --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs @@ -0,0 +1,240 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NewRelic.Agent.Extensions.Logging; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using Serilog; +using Serilog.Events; +using Telerik.JustMock; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class LoggerTests + { + private Logger _logger; + private Serilog.ILogger _serilogLogger; + private string _testMessage; + private Exception _testException; + private string _testFormat; + private object[] _testArgs; + + [SetUp] + public void SetUp() + { + _serilogLogger = Mock.Create(); + Log.Logger = _serilogLogger; + _logger = new Logger(); + + _testMessage = "Test message"; + _testException = new Exception("Test exception"); + _testFormat = "Test format {0}"; + _testArgs = new object[] { "arg1" }; + } + + [Test] + [TestCase(Level.Finest, LogEventLevel.Verbose)] + [TestCase(Level.Debug, LogEventLevel.Debug)] + [TestCase(Level.Info, LogEventLevel.Information)] + [TestCase(Level.Warn, LogEventLevel.Warning)] + [TestCase(Level.Error, LogEventLevel.Error)] + public void IsEnabledFor_Level_ReturnsCorrectValue(Level level, LogEventLevel logEventLevel) + { + Mock.Arrange(() => _serilogLogger.IsEnabled(logEventLevel)).Returns(true); + + var result = _logger.IsEnabledFor(level); + + Assert.IsTrue(result); + } + + [Test] + public void IsEnabledFor_UnsupportedLevel_ReturnsFalse() + { + var result = _logger.IsEnabledFor((Level)9999); + + Assert.IsFalse(result); + } + + [Test] + [TestCase(Level.Finest, LogEventLevel.Verbose)] + [TestCase(Level.Debug, LogEventLevel.Debug)] + [TestCase(Level.Info, LogEventLevel.Information)] + [TestCase(Level.Warn, LogEventLevel.Warning)] + [TestCase(Level.Error, LogEventLevel.Error)] + public void Log_ValidLevel_CallsSerilogLogger(Level level, LogEventLevel logEventLevel) + { + string message = "Test message"; + Mock.Arrange(() => _serilogLogger.IsEnabled(logEventLevel)).Returns(true); + _logger.Log(level, message); + + switch (level) + { + case Level.Finest: + Mock.Assert(() => _serilogLogger.Verbose(message), Occurs.Once()); + break; + case Level.Debug: + Mock.Assert(() => _serilogLogger.Debug(message), Occurs.Once()); + break; + case Level.Info: + Mock.Assert(() => _serilogLogger.Information(message), Occurs.Once()); + break; + case Level.Warn: + Mock.Assert(() => _serilogLogger.Warning(message), Occurs.Once()); + break; + case Level.Error: + Mock.Assert(() => _serilogLogger.Error(message), Occurs.Once()); + break; + } + } + + [Test] + public void Log_UnsupportedLevel_NoSerilogCalls() + { + _logger.Log((Level)9999, _testMessage); + Mock.Assert(() => _serilogLogger.Verbose(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Debug(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Information(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Error(Arg.AnyString), Occurs.Never()); + } + + + [Test] + public void IsErrorEnabled_ReturnsCorrectValue() + { + Mock.Arrange(() => _serilogLogger.IsEnabled(LogEventLevel.Error)).Returns(true); + + var result = _logger.IsErrorEnabled; + + Assert.IsTrue(result); + } + + [Test] + public void Error_LogsError() + { + string message = "Test Error"; + _logger.Error(message); + + Mock.Assert(() => _serilogLogger.Error(message), Occurs.Once()); + } + + [Test] + public void Error_Exception_LogsError() + { + var exception = new Exception("Test Exception"); + _logger.Error(exception); + + Mock.Assert(() => _serilogLogger.Error(exception, ""), Occurs.Once()); + } + + // Level methods + + [Test] + public void IsEnabledFor_ShouldCallSerilogLoggerIsEnabled() + { + _logger.IsEnabledFor(Level.Debug); + Mock.Assert(() => _serilogLogger.IsEnabled(Arg.IsAny()), Occurs.Once()); + } + + // Error methods + + [Test] + public void Error_Message_CallsSerilogLoggerError() + { + _logger.Error(_testMessage); + Mock.Assert(() => _serilogLogger.Error(_testMessage), Occurs.Once()); + } + + [Test] + public void Error_Exception_CallsSerilogLoggerError() + { + _logger.Error(_testException); + Mock.Assert(() => _serilogLogger.Error(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void ErrorFormat_CallsSerilogLoggerError() + { + _logger.ErrorFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Error(Arg.AnyString), Occurs.Once()); + } + + // Warn methods + + [Test] + public void Warn_Message_CallsSerilogLoggerWarning() + { + _logger.Warn(_testMessage); + Mock.Assert(() => _serilogLogger.Warning(_testMessage), Occurs.Once()); + } + + [Test] + public void Warn_Exception_CallsSerilogLoggerWarning() + { + _logger.Warn(_testException); + Mock.Assert(() => _serilogLogger.Warning(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void WarnFormat_CallsSerilogLoggerWarning() + { + _logger.WarnFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + // Info methods + + [Test] + public void Info_Message_CallsSerilogLoggerInformation() + { + _logger.Info(_testMessage); + Mock.Assert(() => _serilogLogger.Information(_testMessage), Occurs.Once()); + } + + [Test] + public void Info_Exception_CallsSerilogLoggerInformation() + { + _logger.Info(_testException); + Mock.Assert(() => _serilogLogger.Information(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void InfoFormat_CallsSerilogLoggerInformation() + { + + _logger.InfoFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Information(Arg.AnyString), Occurs.Once()); + } + + // Debug methods + + [Test] + public void Debug_Message_CallsSerilogLoggerDebug() + { + _logger.Debug(_testMessage); + Mock.Assert(() => _serilogLogger.Debug(_testMessage), Occurs.Once()); + } + + [Test] + public void Debug_Exception_CallsSerilogLoggerDebug() + { + _logger.Debug(_testException); + Mock.Assert(() => _serilogLogger.Debug(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void DebugFormat_CallsSerilogLoggerDebug() + { + _logger.DebugFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Debug(Arg.AnyString), Occurs.Once()); + } + [TearDown] + public void TearDown() + { + _logger = null; + _serilogLogger = null; + } + } +} From 2cddfb10d83bf67a85483477f41db17ee08a0d8b Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue, 30 May 2023 09:20:16 -0500 Subject: [PATCH 06/10] log4net to Serilog: Log format cleanup / rework (#1672) * Fixed some issues with Agent log and audit log formatting, adjusted a unit test to slightly increase code coverage. * More log formatting cleanup, better handling for embedded json in log messages * Unit test fixes --- src/Agent/NewRelic/Agent/Core/Core.csproj | 7 ++-- .../NewRelic/Agent/Core/Logging/AuditLog.cs | 28 ++++++++++++--- .../Logging/CustomAuditLogTextFormatter.cs | 19 ---------- .../Agent/Core/Logging/CustomTextFormatter.cs | 24 ------------- .../Agent/Core/Logging/LogLevelExtensions.cs | 2 ++ .../NewRelic/Agent/Core/Logging/Logger.cs | 15 +++++--- .../Agent/Core/Logging/LoggerBootstrapper.cs | 36 +++++++------------ .../Agent/Core/Logging/NrLogLevelEnricher.cs | 21 +++++++++++ .../Agent/Core/Logging/ProcessIdEnricher.cs | 7 ++-- .../Agent/Core/Logging/ThreadIdEnricher.cs | 4 ++- src/NewRelic.Core/Logging/Log.cs | 32 ++++------------- .../Core.UnitTest/Logging/LoggerTests.cs | 15 +++++--- .../BootstrapConfigTest.cs | 8 ++--- ...oggerTest.cs => LoggerBootstrapperTest.cs} | 8 ++--- .../ThreadProfilingBucketTest.cs | 2 +- .../AgentWrapperApi/AgentWrapperApiTests.cs | 2 +- .../NewRelic.Agent.TestUtilities/Logging.cs | 3 +- 17 files changed, 108 insertions(+), 125 deletions(-) delete mode 100644 src/Agent/NewRelic/Agent/Core/Logging/CustomAuditLogTextFormatter.cs delete mode 100644 src/Agent/NewRelic/Agent/Core/Logging/CustomTextFormatter.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs rename tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/{AgentLoggerTest.cs => LoggerBootstrapperTest.cs} (95%) diff --git a/src/Agent/NewRelic/Agent/Core/Core.csproj b/src/Agent/NewRelic/Agent/Core/Core.csproj index f5d6bbd0c8..4e7e399494 100644 --- a/src/Agent/NewRelic/Agent/Core/Core.csproj +++ b/src/Agent/NewRelic/Agent/Core/Core.csproj @@ -38,6 +38,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -107,6 +108,7 @@ + @@ -127,6 +129,7 @@ + @@ -135,8 +138,8 @@ - 20 - 15 + 21 + 16 diff --git a/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs b/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs index 5b08719c31..fae1250010 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs @@ -1,19 +1,37 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +using System; +using Serilog; +using ILogger = Serilog.ILogger; + namespace NewRelic.Agent.Core.Logging { public static class AuditLog { + // a lazy ILogger instance that injects an "Audit" property + private static Lazy _lazyAuditLogger = new Lazy(() => + Serilog.Log.Logger.ForContext(LogLevelExtensions.AuditLevel, LogLevelExtensions.AuditLevel)); + /// - /// Logs at the AUDIT level, a custom log level that is not well-defined in popular logging providers like log4net. This log level should be used only as dictated by the security team to satisfy auditing requirements. + /// Logs at the AUDIT level. This log level should be used only as dictated by the security team to satisfy auditing requirements. /// public static void Log(string message) { - // create a logger that injects an Audit property that we can later filter on (see LoggerBootstrapper.IncludeOnlyAuditLog) - var auditLogger = Serilog.Log.ForContext(LogLevelExtensions.AuditLevel, LogLevelExtensions.AuditLevel); - // use Fatal log level to ensure audit log messages never get filtered due to level restrictions - auditLogger.Fatal(message); + // use Fatal log level to ensure audit log messages never get filtered due to level restrictions + _lazyAuditLogger.Value.Fatal(message); + } + + internal static LoggerConfiguration IncludeOnlyAuditLog(this LoggerConfiguration loggerConfiguration) + { + return loggerConfiguration.Filter.ByIncludingOnly($"{LogLevelExtensions.AuditLevel} is not null"); + + //return loggerConfiguration.Filter.ByIncludingOnly(logEvent => + // logEvent.Properties.ContainsKey(LogLevelExtensions.AuditLevel)); + } + internal static LoggerConfiguration ExcludeAuditLog(this LoggerConfiguration loggerConfiguration) + { + return loggerConfiguration.Filter.ByIncludingOnly($"{LogLevelExtensions.AuditLevel} is null"); } } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/CustomAuditLogTextFormatter.cs b/src/Agent/NewRelic/Agent/Core/Logging/CustomAuditLogTextFormatter.cs deleted file mode 100644 index d464c6f282..0000000000 --- a/src/Agent/NewRelic/Agent/Core/Logging/CustomAuditLogTextFormatter.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.IO; -using Serilog.Events; -using Serilog.Formatting; - -namespace NewRelic.Agent.Core -{ - class CustomAuditLogTextFormatter : ITextFormatter - { - public void Format(LogEvent logEvent, TextWriter output) - { - logEvent.Properties.TryGetValue("Message", out var message); - - output.Write($"{logEvent.Timestamp.ToUniversalTime():yyyy-MM-dd HH:mm:ss,fff} NewRelic AUDIT: {message}{output.NewLine}"); - } - } -} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/CustomTextFormatter.cs b/src/Agent/NewRelic/Agent/Core/Logging/CustomTextFormatter.cs deleted file mode 100644 index b2e2be2daa..0000000000 --- a/src/Agent/NewRelic/Agent/Core/Logging/CustomTextFormatter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.IO; -using Serilog.Events; -using Serilog.Formatting; - -namespace NewRelic.Agent.Core -{ - class CustomTextFormatter : ITextFormatter - { - public void Format(LogEvent logEvent, TextWriter output) - { - // try to get process and thread id - logEvent.Properties.TryGetValue("pid", out var pid); - logEvent.Properties.TryGetValue("tid", out var tid); - - // format matches the log4net format, but adds the Exception property (if present in the message as a separate property) to the end, after the newline - // if that's not desired, take the Exception property out and refactor calls to `Serilog.Log.Logger.LogXxxx(exception, message)` so the exception is - // formatted into the message - output.Write($"{logEvent.Timestamp.ToUniversalTime():yyyy-MM-dd HH:mm:ss,fff} NewRelic {logEvent.Level.TranslateLogLevel(),6}: [pid: {pid?.ToString()}, tid: {tid?.ToString()}] {logEvent.MessageTemplate}{output.NewLine}{logEvent.Exception}"); - } - } -} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs index af195116f8..4f4ea03e27 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs @@ -85,6 +85,8 @@ public static string TranslateLogLevel(this LogEventLevel logEventLevel) return "WARN"; case LogEventLevel.Error: return "ERROR"; + case LogEventLevel.Fatal: // Fatal is the level we use for Audit log messages + return AuditLevel; default: throw new ArgumentOutOfRangeException(nameof(logEventLevel), logEventLevel, null); } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs b/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs index 8a61d05a80..a202bd61c3 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/Logger.cs @@ -84,7 +84,8 @@ public void Error(Exception exception) /// public void ErrorFormat(string format, params object[] args) { - _logger.Error(string.Format(format, args)); + if (IsErrorEnabled) + _logger.Error(format, args); } #endregion Error @@ -117,7 +118,8 @@ public void Warn(Exception exception) /// public void WarnFormat(string format, params object[] args) { - _logger.Warning(string.Format(format, args)); + if (IsWarnEnabled) + _logger.Warning(format, args); } #endregion Warn @@ -150,7 +152,8 @@ public void Info(Exception exception) /// public void InfoFormat(string format, params object[] args) { - _logger.Information(string.Format(format, args)); + if (IsInfoEnabled) + _logger.Information(format, args); } #endregion Info @@ -183,7 +186,8 @@ public void Debug(Exception exception) /// public void DebugFormat(string format, params object[] args) { - _logger.Debug(string.Format(format, args)); + if (IsDebugEnabled) + _logger.Debug(format, args); } #endregion Debug @@ -216,7 +220,8 @@ public void Finest(Exception exception) /// public void FinestFormat(string format, params object[] args) { - _logger.Verbose(string.Format(format, args)); + if (IsFinestEnabled) + _logger.Verbose(format, args); } #endregion Finest diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs index bad0b265ae..50fcab3436 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs @@ -10,6 +10,7 @@ using Serilog.Formatting; using Logger = NewRelic.Agent.Core.Logging.Logger; using NewRelic.Agent.Core.Logging; +using Serilog.Templates; #if NETFRAMEWORK using Serilog.Events; #endif @@ -23,6 +24,9 @@ public static class LoggerBootstrapper //private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); //private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); + private static ExpressionTemplate AuditLogLayout = new ExpressionTemplate("{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss,fff} NewRelic Audit: {@m}\n"); + private static ExpressionTemplate FileLogLayout = new ExpressionTemplate("{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss,fff} NewRelic {NRLogLevel,6}: [pid: {pid}, tid: {tid}] {@m}\n{@x}"); + private static LoggingLevelSwitch _loggingLevelSwitch = new LoggingLevelSwitch(); private static InMemorySink _inMemorySink = new InMemorySink(); @@ -35,8 +39,7 @@ public static void UpdateLoggingLevel(string newLogLevel) public static void Initialize() { var startupLoggerConfig = new LoggerConfiguration() - .Enrich.With(new ThreadIdEnricher()) - .Enrich.With(new ProcessIdEnricher()) + .Enrich.With(new ThreadIdEnricher(), new ProcessIdEnricher(), new NrLogLevelEnricher()) .MinimumLevel.Information() .ConfigureInMemoryLogSink() .ConfigureEventLogSink(); @@ -54,11 +57,10 @@ public static void ConfigureLogger(ILogConfig config) SetupLogLevel(config); var loggerConfig = new LoggerConfiguration() - .Enrich.With(new ThreadIdEnricher()) - .Enrich.With(new ProcessIdEnricher()) .MinimumLevel.ControlledBy(_loggingLevelSwitch) - .ConfigureFileSink(config) .ConfigureAuditLogSink(config) + .Enrich.With(new ThreadIdEnricher(), new ProcessIdEnricher(), new NrLogLevelEnricher()) + .ConfigureFileSink(config) .ConfigureDebugSink(); if (config.Console) @@ -122,7 +124,8 @@ private static LoggerConfiguration ConfigureEventLogSink(this LoggerConfiguratio .WriteTo.EventLog( source: eventLogSourceName, logName: eventLogName, - restrictedToMinimumLevel: LogEventLevel.Warning + restrictedToMinimumLevel: LogEventLevel.Warning, + outputTemplate: "{Level}: {Message}{NewLine}{Exception}" ); }); #endif @@ -140,7 +143,7 @@ private static LoggerConfiguration ConfigureDebugSink(this LoggerConfiguration l { configuration .ExcludeAuditLog() - .WriteTo.Debug(formatter: new CustomTextFormatter()); + .WriteTo.Debug(FileLogLayout); }); #endif return loggerConfiguration; @@ -157,7 +160,7 @@ private static LoggerConfiguration ConfigureConsoleSink(this LoggerConfiguration { configuration .ExcludeAuditLog() - .WriteTo.Console(formatter: new CustomTextFormatter()); + .WriteTo.Console(FileLogLayout); }) ); } @@ -180,7 +183,7 @@ private static LoggerConfiguration ConfigureFileSink(this LoggerConfiguration lo { configuration .ExcludeAuditLog() - .ConfigureRollingLogSink(logFileName, new CustomTextFormatter()); + .ConfigureRollingLogSink(logFileName, FileLogLayout); }) ); } @@ -213,7 +216,7 @@ private static LoggerConfiguration ConfigureAuditLogSink(this LoggerConfiguratio configuration .MinimumLevel.Fatal() // We've hijacked Fatal log level as the level to use when writing an audit log .IncludeOnlyAuditLog() - .ConfigureRollingLogSink(logFileName, new CustomAuditLogTextFormatter()); + .ConfigureRollingLogSink(logFileName, AuditLogLayout); }); } @@ -261,18 +264,5 @@ private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfigurat throw; } } - - private static LoggerConfiguration IncludeOnlyAuditLog(this LoggerConfiguration loggerConfiguration) - { - return loggerConfiguration.Filter.ByIncludingOnly(logEvent => - logEvent.Properties.ContainsKey(LogLevelExtensions.AuditLevel)); - - } - private static LoggerConfiguration ExcludeAuditLog(this LoggerConfiguration loggerConfiguration) - { - return loggerConfiguration.Filter.ByExcluding(logEvent => - logEvent.Properties.ContainsKey(LogLevelExtensions.AuditLevel)); - } - } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs new file mode 100644 index 0000000000..ae48b788fb --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs @@ -0,0 +1,21 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NewRelic.Core.CodeAttributes; +using Serilog.Core; +using Serilog.Events; + +namespace NewRelic.Agent.Core +{ + /// + /// Maps serilog log level to corresponding "legacy" log4net loglevel and adds the mapped value as a property named NRLogLevel + /// + internal class NrLogLevelEnricher : ILogEventEnricher + { + [NrExcludeFromCodeCoverage] + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("NRLogLevel", logEvent.Level.TranslateLogLevel())); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs index d8f181045f..0d761d1ed7 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs @@ -1,18 +1,19 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +using NewRelic.Core.CodeAttributes; using NewRelic.SystemInterfaces; using Serilog.Core; using Serilog.Events; namespace NewRelic.Agent.Core { - class ProcessIdEnricher : ILogEventEnricher + [NrExcludeFromCodeCoverage] + internal class ProcessIdEnricher : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { - logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( - "pid", new ProcessStatic().GetCurrentProcess().Id)); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("pid", new ProcessStatic().GetCurrentProcess().Id)); } } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs index f21a6b9fa1..5fbc34b31e 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 using System.Threading; +using NewRelic.Core.CodeAttributes; using Serilog.Core; using Serilog.Events; namespace NewRelic.Agent.Core { - class ThreadIdEnricher : ILogEventEnricher + [NrExcludeFromCodeCoverage] + internal class ThreadIdEnricher : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { diff --git a/src/NewRelic.Core/Logging/Log.cs b/src/NewRelic.Core/Logging/Log.cs index 90502d279c..d7b0a94182 100644 --- a/src/NewRelic.Core/Logging/Log.cs +++ b/src/NewRelic.Core/Logging/Log.cs @@ -51,10 +51,7 @@ public static void Error(Exception exception) /// public static void ErrorFormat(string format, params object[] args) { - if (IsErrorEnabled) - { - Logger.Error(string.Format(format, args)); - } + Logger.ErrorFormat(format, args); } #endregion Error @@ -87,10 +84,7 @@ public static void Warn(Exception exception) /// public static void WarnFormat(string format, params object[] args) { - if (IsWarnEnabled) - { - Logger.Warn(string.Format(format, args)); - } + Logger.WarnFormat(format, args); } #endregion Warn @@ -123,10 +117,7 @@ public static void Info(Exception exception) /// public static void InfoFormat(string format, params object[] args) { - if (IsInfoEnabled) - { - Logger.Info(string.Format(format, args)); - } + Logger.InfoFormat(format, args); } #endregion Info @@ -151,7 +142,7 @@ public static void Debug(string message) /// public static void Debug(Exception exception) { - Logger.Debug(exception.ToString()); + Logger.Debug(exception); } /// @@ -159,10 +150,7 @@ public static void Debug(Exception exception) /// public static void DebugFormat(string format, params object[] args) { - if (Logger.IsDebugEnabled) - { - Logger.Debug(string.Format(format, args)); - } + Logger.DebugFormat(format, args); } #endregion Debug @@ -195,10 +183,7 @@ public static void Finest(Exception exception) /// public static void FinestFormat(string format, params object[] args) { - if (IsFinestEnabled) - { - Logger.FinestFormat(format, args); - } + Logger.FinestFormat(format, args); } #endregion Finest @@ -251,10 +236,5 @@ public static void LogMessage(LogLevel level, string message) break; } } - - public static void LogException(LogLevel level, Exception ex) - { - LogMessage(level, ex.ToString()); - } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs index d499721f1e..66bdee6ccb 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs @@ -157,8 +157,10 @@ public void Error_Exception_CallsSerilogLoggerError() [Test] public void ErrorFormat_CallsSerilogLoggerError() { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + _logger.ErrorFormat(_testFormat, _testArgs); - Mock.Assert(() => _serilogLogger.Error(Arg.AnyString), Occurs.Once()); + Mock.Assert(() => _serilogLogger.Error(Arg.AnyString, Arg.IsAny()), Occurs.Once()); } // Warn methods @@ -180,8 +182,10 @@ public void Warn_Exception_CallsSerilogLoggerWarning() [Test] public void WarnFormat_CallsSerilogLoggerWarning() { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + _logger.WarnFormat(_testFormat, _testArgs); - Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString, Arg.IsAny()), Occurs.Once()); } // Info methods @@ -203,9 +207,10 @@ public void Info_Exception_CallsSerilogLoggerInformation() [Test] public void InfoFormat_CallsSerilogLoggerInformation() { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); _logger.InfoFormat(_testFormat, _testArgs); - Mock.Assert(() => _serilogLogger.Information(Arg.AnyString), Occurs.Once()); + Mock.Assert(() => _serilogLogger.Information(Arg.AnyString, Arg.IsAny()), Occurs.Once()); } // Debug methods @@ -227,8 +232,10 @@ public void Debug_Exception_CallsSerilogLoggerDebug() [Test] public void DebugFormat_CallsSerilogLoggerDebug() { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + _logger.DebugFormat(_testFormat, _testArgs); - Mock.Assert(() => _serilogLogger.Debug(Arg.AnyString), Occurs.Once()); + Mock.Assert(() => _serilogLogger.Debug(Arg.AnyString, Arg.IsAny()), Occurs.Once()); } [TearDown] public void TearDown() diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs index 5da055b3a2..3fa33e2cb6 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs @@ -27,8 +27,8 @@ public void TestInvalidServiceAttribute() ConfigurationLoader.InitializeFromXml(bogusConfigXml, configSchemaSource); var errorMessage = Type.GetType("Mono.Runtime") == null ? - "An error occurred parsing newrelic.config - The 'bogus' attribute is not declared." : - "An error occurred parsing newrelic.config - XmlSchema error: Attribute declaration was not found for bogus"; + "The 'bogus' attribute is not declared" : + "XmlSchema error: Attribute declaration was not found for bogus"; Assert.IsTrue(logging.HasMessageThatContains(errorMessage)); } } @@ -53,9 +53,7 @@ public void TestMissingOrEmptyConfigXsd() // While this error message is somewhat cryptic, in an actual agent run it would be // preceeded by a warning message regarding failure to read the schema file contents from disk - var errorMessage = Type.GetType("Mono.Runtime") == null ? - "An error occurred parsing newrelic.config - Root element is missing." : - "An error occurred parsing newrelic.config - Root element is missing."; + var errorMessage = "Root element is missing"; Assert.IsTrue(logging.HasMessageThatContains(errorMessage)); } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs similarity index 95% rename from tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs rename to tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs index 6da281e414..95dc4f1fd0 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs @@ -67,7 +67,7 @@ public static void IsDebugEnabled_is_false_when_config_log_is_info() [Test] public static void IsDebugEnabled_is_true_when_config_log_is_debug() { - ILogConfig config = GetLogConfig("debug"); + ILogConfig config = LogConfigFixtureWithConsoleLogEnabled("debug"); // just to increase code coverage LoggerBootstrapper.Initialize(); LoggerBootstrapper.ConfigureLogger(config); Assert.That(Log.IsDebugEnabled); @@ -111,7 +111,7 @@ public static void Config_IsConsoleEnabled_for_config_is_false_when_not_added_to Assert.IsFalse(config.Console); } - static private ILogConfig GetLogConfig(string logLevel) + private static ILogConfig GetLogConfig(string logLevel) { var xml = string.Format( "" + @@ -130,7 +130,7 @@ static private ILogConfig GetLogConfig(string logLevel) return configuration.LogConfig; } - static private ILogConfig LogConfigFixtureWithAuditLogEnabled(string logLevel) + private static ILogConfig LogConfigFixtureWithAuditLogEnabled(string logLevel) { var xml = string.Format( "" + @@ -149,7 +149,7 @@ static private ILogConfig LogConfigFixtureWithAuditLogEnabled(string logLevel) return configuration.LogConfig; } - static private ILogConfig LogConfigFixtureWithConsoleLogEnabled(string logLevel) + private static ILogConfig LogConfigFixtureWithConsoleLogEnabled(string logLevel) { var xml = string.Format( "" + diff --git a/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs b/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs index 705f7bd4b4..1cf456b85a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs @@ -61,7 +61,7 @@ public void verify_UpdateTree_handles_null_StackInfo_argument() { ThreadProfilingBucket bucket = new ThreadProfilingBucket(new MockThreadProfilingService()); bucket.UpdateTree(null); - Assert.IsTrue(logging.HasMessageBeginingWith("fids passed to UpdateTree is null")); + Assert.IsTrue(logging.HasMessageBeginningWith("fids passed to UpdateTree is null")); } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs index 3864bd3826..5625ffcf69 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs @@ -225,7 +225,7 @@ public void EndTransaction_ShouldNotLogResponseTimeAlreadyCaptured() { _agent.CurrentTransaction.End(); - var foundResponseTimeAlreadyCapturedMessage = logging.HasMessageBeginingWith("Transaction has already captured the response time."); + var foundResponseTimeAlreadyCapturedMessage = logging.HasMessageBeginningWith("Transaction has already captured the response time."); Assert.False(foundResponseTimeAlreadyCapturedMessage); } } diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs index 86a7780a4a..99a74f819a 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using NewRelic.Agent.Core.Logging; using Serilog.Events; @@ -66,7 +65,7 @@ public override string ToString() /// /// /// - public bool HasMessageBeginingWith(string segment) => _inMemorySink.LogEvents.Any(item => item.RenderMessage().StartsWith(segment)); + public bool HasMessageBeginningWith(string segment) => _inMemorySink.LogEvents.Any(item => item.RenderMessage().StartsWith(segment)); /// /// checks for messages that begins with a segment From 2bbbead11390b89329035110930eac9d3089fcee Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue, 30 May 2023 10:21:25 -0500 Subject: [PATCH 07/10] Removed an unused enum from configuration.cs --- .../NewRelic/Agent/Core/Config/Configuration.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs index 24f6de5a6b..45ac856540 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs @@ -1390,19 +1390,6 @@ public virtual configurationLog Clone() #endregion } - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="urn:newrelic-config")] - public enum configurationLogFileLockingModel - { - - /// - exclusive, - - /// - minimal, - } - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")] [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] From cf7846c81c5c85c9b57a45a108962155d42e9796 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue, 30 May 2023 13:20:32 -0500 Subject: [PATCH 08/10] Re-generated Configuration.cs using xsd2code, looks like only whitespace differences --- src/Agent/NewRelic/Agent/Core/Config/Configuration.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs index 45ac856540..a2f7f7fdcc 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs @@ -1,6 +1,3 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - // ------------------------------------------------------------------------------ // // Generated by Xsd2Code. Version 3.6.0.0 From 75cef9d0d2673a308b8a9e948974ed78e649da4d Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue, 30 May 2023 13:22:27 -0500 Subject: [PATCH 09/10] Stupid file headers. Grr. --- src/Agent/NewRelic/Agent/Core/Config/Configuration.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs index a2f7f7fdcc..95ec2127f7 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs @@ -1,3 +1,6 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + // ------------------------------------------------------------------------------ // // Generated by Xsd2Code. Version 3.6.0.0 @@ -6,12 +9,6 @@ // ------------------------------------------------------------------------------ namespace NewRelic.Agent.Core.Config { - using System; - using System.Diagnostics; - using System.Xml.Serialization; - using System.Collections; - using System.Xml.Schema; - using System.ComponentModel; using System.Collections.Generic; From 6cbcaad5e6cde88d0bd428c90d1fe178e3b22957 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue, 30 May 2023 13:59:40 -0500 Subject: [PATCH 10/10] Minor tweak in ProcessIdEnricher --- src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs index 0d761d1ed7..c5171e9c7f 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs @@ -11,9 +11,11 @@ namespace NewRelic.Agent.Core [NrExcludeFromCodeCoverage] internal class ProcessIdEnricher : ILogEventEnricher { + private static int _pid = new ProcessStatic().GetCurrentProcess().Id; + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { - logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("pid", new ProcessStatic().GetCurrentProcess().Id)); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("pid", _pid)); } } }