Changing log4net threshold and levels programmatically during runtime
David | January 10, 2015Log4net supports 2 convenient ways for changing your logging levels during runtime without having to restart your application.
- Edit the log4net.xml file. The ConfigureAndWatch FileSystemWatcher notifies the logger that a change was made
- Write your own custom component
I almost always add a section like the below section to my applications, right above the namespace:
//Here is the once-per-application setup information // 1. Make sure to add the log4net.dll to the project! // 2. Add log4net.xml to the project that starts the application! // 3. Change the project properties to use the full version of the .net framework 4, not the compact version // (i.e. click on the project properties (not the solution) to change the Target framework) // 4. Add the assembly reference ABOVE the Namespace and Class definition as shown below // https://stackoverflow.com/questions/174430/log4net-could-not-find-schema-information-messages [assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.xml", Watch = true)]
Editing the log4net.xml file works fine, but isn’t it more fun to have a component that changes log4net logging levels “on the fly”?
Below is some code to make it happen:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Timers; using log4net; using log4net.Appender; using log4net.Core; using log4net.Repository.Hierarchy; namespace LoggingLevelComponent { /// <summary> /// LoggingLevelComponent provides various methods for changing log4net /// logging levels and thresholds on the fly without having to restart your /// application. /// /// So far, it supports 3 methods for changing the logging levels: /// /// 1. setRootLoggingLevel -- overrides all other settings /// 2. setRepositoryThreshold -- overrides all appender settings /// 3. setAppenderThreshold -- allows individual appender thresholds to be set /// /// It also supplies methods for getting the logging hierarchy, appender names, /// and information about the current logging configuration. /// </summary> public static class LoggingLevelComponent { /// <summary> /// Once-per-class call to initialize the log object /// </summary> private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); /// <summary> /// Constructor /// </summary> static LoggingLevelComponent() { init(); } /// <summary> /// Hierarchy object that organizes and allows access to all the loggers /// </summary> private static Hierarchy hierarchy = null; /// <summary> /// List of appenders /// </summary> private static List<IAppender> appenders = null; /// <summary> /// Startup a timer and register a callback /// </summary> private static void init() { // Get the Hierarchy object hierarchy = LogManager.GetRepository() as Hierarchy; // Get the list of Appenders if (hierarchy != null) { appenders = hierarchy.GetAppenders().ToList(); if (appenders != null) { log.Info(appenders.Count + " appenders loaded"); for (int i = 0; i < appenders.Count; i++) { log.Info("Loaded appender name = " + appenders[i].Name); } } } else { log.Fatal("Log4Net Configuration error: Repository is null"); } // subscribe to be notified when the configuration has been changed hierarchy.ConfigurationChanged += OnConfigurationChanged; // subscribe to be notified when the configuration has been reset hierarchy.ConfigurationReset += OnConfigurationReset; } /// <summary> /// This happens after someone edits the log4net.xml file, or /// when something calls the RaiseConfigurationChanged method. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public static void OnConfigurationChanged(object sender, EventArgs e) { log.Info("Configuration was changed"); } /// <summary> /// Called when the log4net configuration is reset. /// /// Resetting would remove all appenders! That's pretty dangerous /// so, at least generate a warning. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public static void OnConfigurationReset(object sender, EventArgs e) { log.Warn("Configuration was reset! All appenders were removed!"); } /// <summary> /// Gets a list of all the configured appenders. /// </summary> /// <returns>list of IAppenders</returns> public static List<IAppender> getListOfAppenders() { return appenders; } /// <summary> /// Gets the current Root Logging Level. /// </summary> /// <returns>root logging level</returns> public static Level getRootLoggingLevel() { return hierarchy.Root.Level; } /// <summary> /// Sets the current Root Logging Level (overrides all other levels and thresholds). /// </summary> /// <param name="aLevel">log4net logging level</param> public static void setRootLoggingLevel(Level aLevel) { hierarchy.Root.Level = aLevel; } /// <summary> /// Gets the current Repository Threshold. /// </summary> /// <returns></returns> public static Level getRepositoryThreshold() { return log.Logger.Repository.Threshold; } /// <summary> /// Sets the current Repository Threshold. /// </summary> /// <param name="aLevel">log4net logging level</param> public static void setRepositoryThreshold(Level aLevel) { log.Logger.Repository.Threshold = aLevel; } /// <summary> /// Gets the logging Threshold level for the specified Appender. /// </summary> /// <param name="anAppenderName">name of the appender</param> /// <returns>string representation of the logging threshold</returns> public static Level getAppenderThreshold(String anAppenderName) { Level returnLevel = null; try { if (appenders != null) { // Find the Appender by name log4net.Appender.IAppender appender = appenders.Find(x => x.Name == anAppenderName); // If this Appender is an AppenderSkeleton, then update the Threshold if (appender is AppenderSkeleton) { AppenderSkeleton appenderSkeleton = appender as AppenderSkeleton; returnLevel = appenderSkeleton.Threshold; return returnLevel; } else { log.Warn("Appender " + anAppenderName + " is not an AppenderSkeleton type."); } } else { log.Warn("Log4Net Configuration problem: list of appenders is null"); } } catch (Exception e) { log.Error("Could not find appender = " + anAppenderName + ", Exception: " + e.ToString()); } return returnLevel; } /// <summary> /// Sets the logging threshold for the specified Appender. /// </summary> /// <param name="anAppenderName">name of the Appender to modify</param> /// <param name="aLevel">logging Threshold to set</param> public static void setAppenderThreshold(String anAppenderName, Level aLevel) { try { if (appenders != null || aLevel != null) { // Find the Appender by name log4net.Appender.IAppender appender = appenders.Find(x => x.Name == anAppenderName); // If this Appender IS A AppenderSkeleton, then update the Threshold if (appender is AppenderSkeleton) { AppenderSkeleton appenderSkeleton = appender as AppenderSkeleton; appenderSkeleton.Threshold = aLevel; } else { log.Error("Appender " + anAppenderName + " is not an AppenderSkeleton type. Threshold cannot be set"); } } else { log.Fatal("Log4Net Configuration problem: list of appenders is null"); } } catch (Exception e) { log.Error("Could not find appender = " + anAppenderName + ", Exception: " + e.ToString()); } } } }
During testing, I noticed that any changes to the log4net.xml file are observed by the logger during runtime and a ConfigurationChanged event is fired. As long as you set the Watch = true, log4net watches the file for changes.
I also noticed an interesting behavior with the ConfigurationChanged event. When you change a configuration programmatically, the ConfigurationChanged event is not fired. If you wan to be notified of a change, then you would need to write a line of code to specifically fire the event.
What this means: it is possible to know whether or not someone changed the log4net.xml file, simply by subscribing to the ConfigurationChanged event.
hierarchy.ConfigurationChanged += OnConfigurationChanged;
I’m pretty satisfied with this component. It does everything I need it to do.