Search code examples
.netlogginglog4netapp-configsmtpappender

Multiple appenders log4net configuration


Sorry for the log4net newb quesiton, this looks like a common setup, but I cannot find an example.

I am trying to get the following setup with my log4net config:

Two appenders:

  • File_Appender -> log4net.Appender.RollingFileAppender
  • Smtp_Appender -> log4net.Appender.SmtpAppender
    (evaluator threshold set to ERROR)

Many loggers:

  • All loggers (root) -> INFO,WARN,ERROR -> File_Appender
  • All loggers (root) -> ERROR -> Smtp_Appender
  • Specific loggers -> DEBUG -> File_Appender
  • Specific loggers -> INFO -> Smtp_Appender

I am not sure how to configure the "root" logger to log different levels to different appenders (I don't want to explicitly configure each logger to log errors to a Smtp_Appender).


Solution

  • I ended up doing my own appender filter, which allows setting a lower matching level for explicitly specified loggers:

    public class MultipleLevelFilter : LevelMatchFilter
    {
        public MultipleLevelFilter()
        {
            LevelToMatchForSpecificLoggers = Level.Fatal;
            _specificLoggers = new string[0];
        }
    
        /// <summary>
        /// Gets or sets the level to match for loggers
        /// specified in the <see cref="SpecificLoggers"/> property.
        /// </summary>
        /// <value>The level to match for specific loggers.</value>
        public Level LevelToMatchForSpecificLoggers { get; set; }
    
        private string[] _specificLoggers;
        /// <summary>
        /// Gets or sets a comma separated list of "specific" loggers.
        /// For these loggers, <see cref="LevelToMatchForSpecificLoggers"/>
        /// property is checked to see whether they should be logged.
        /// </summary>
        /// <value>The specific loggers.</value>
        public string SpecificLoggers 
        {
            get { return string.Join(",", _specificLoggers); }
            set
            { 
                _specificLoggers = value.Split(',').Select(s=>s.Trim()).ToArray();
            }
        }
    
        /// <summary>
        /// Tests if filter matches the specified logging event.
        /// </summary>
        /// <param name="loggingEvent">the event to filter</param>
        public override FilterDecision Decide(LoggingEvent loggingEvent)
        {
            // if base.LevelToMatch is matched, always match
            if (loggingEvent.Level >= LevelToMatch)
               return AcceptOnMatch ? FilterDecision.Accept : FilterDecision.Deny; 
    
            // if specific level is matched, check if logger name matches
            if (loggingEvent.Level >= LevelToMatchForSpecificLoggers)
            {
                string name = loggingEvent.LoggerName;
                for (int i = 0; i < _specificLoggers.Length; i++)
                    if (name == _specificLoggers[i])
                        return AcceptOnMatch ?
                            FilterDecision.Accept : FilterDecision.Deny;
            }
    
            // continue with next filter
            return FilterDecision.Neutral;
        }
    }
    

    Usage:

    <appender name="Smtp_Appender" 
         type="Ster.Log4Net.DelayedSendSmtpAppender">
    
      <!-- ... common properties (from, to, etc) -->
    
      <!-- our custom filter -->      
      <filter type="MyNamespace.MultipleLevelFilter">
    
        <!-- this level is ALWAYS matched -->
        <levelToMatch value="ERROR" />
    
        <!-- this level is matched only for specific loggers -->
        <levelToMatchForSpecificLoggers value="INFO" />
    
        <!-- these are the "specific" loggers (comma separated) -->
        <specificLoggers value="SomeLogger, SomeOtherLogger" />
    
      </filter>
      <!-- always end the filter chain with DenyAll -->
      <filter type="log4net.Filter.DenyAllFilter" />
    
    </appender>
    
    <root>
      <priority value="INFO" />
      <appender-ref ref="File_Appender" />
      <appender-ref ref="Smtp_Appender" />
    </root>