7

I use log4net in my console application, I use log.Error("Message to log"); multiple times in if/else structures.
My application has to return a code which specifies if an error has occurred during the run of the application (0 → ok, 1 → at least 1 error occurred).
Is there a way to ask log4net if it logged an error, for instance:

bool b = LogManager.AtLeastOneErrorHasBeenLogged;
Nicolas
  • 6,289
  • 4
  • 36
  • 51
  • 1
    Why not check to log to see if an error has been thrown? – Mason T. Mar 10 '14 at 17:17
  • 1
    @Nappa, what do you mean? Open the log file (for instance) and check if an error has been logged? – Nicolas Mar 10 '14 at 17:29
  • First check you have it properly set up. Have you set your log4net configuration? Did you do it in your web.config file, or did you create a configuration file? – Ricardo Appleton Mar 10 '14 at 17:49
  • 1
    @RicardoAppleton, I set up properly. I set my log4net configuration in my app.config file. Do you have a solution to my problem? I don't see the point of your comment. – Nicolas Mar 10 '14 at 18:01
  • That was just to make sure that was covered. Then I guess that if all you want is to have a flag indicating an error has been logged, you could take on Nicholas Carey's solution, only simpler: You make a derived class with a boolean flag that'd be set to true when .Error(...) was called. You might also want to have a generic superclass for your application, to have your log4net set up there, hence accessible throughout your application – Ricardo Appleton Mar 11 '14 at 09:37

3 Answers3

6

This seems like it would be straightforward if you use a custom appender whose only purpose is to track if an error occurred.

This (untested) example uses the technique of only calling the appender if the log level is Error by using a LevelRangeFilter but you could just as easily check the log level (or other criteria) in the Append method, or in the root logger config. The example assumes you are using XML config rather than programmatic config, but it is possible to add appenders programatically so they are used by all loggers.

The appender:

public namespace MyApp
{
    public class ErrorFlagAppender : AppenderSkeleton
    {
        public bool ErrorOccurred { get; set; }

        protected override void Append(LoggingEvent loggingEvent)
        {
            ErrorOccurred = true;
        }
    }
} 

The config:

<appender name="ErrorFlagAppender" type="MyApp.ErrorFlagAppender,MyApp" >
    <filter type="log4net.Filter.LevelRangeFilter">
        <levelMin value="ERROR"/>
        <levelMax value="ERROR"/>
    </filter>
</appender>

<root>
  <appender-ref ref="[your existingappender]" />
  <appender-ref ref="ErrorFlagAppender" />
</root>

Then you can have an extension method to see if an error occurred:

public static class LogManagerExtensions
{
    public static bool AtLeastOneErrorHasBeenLogged(this LogManager logManager)
    {
        var flagAppenders = logManager.GetRepository()
                                      .GetAppenders()
                                      .OfType<ErrorFlagAppender>();

        return flagAppenders.Any(f => f. ErrorOccurred);
    }
}
stuartd
  • 70,509
  • 14
  • 132
  • 163
  • I really like your solution. I thought to create custom appender to add code to specify if an error was logged. But creating a specific appender to do it is even better. – Nicolas Mar 12 '14 at 14:26
4

I think that most people would consider the idea of asking log4net (or whatever logging framework one might use) if an error was logged to be very odd. You are in control of calling log4net, so why not keep track yourself if an error occurred. What if the user of your program turns off logging in the config file? If an error happens when logging is turned off and you are depending on whether or not an error was logged, then you have no way of knowing that an error occurred. Nicholas Carey suggested to add logic inside your catch blocks to keep track of whether or not an exception occurred. Even if you don't use exceptions, you could do something similar inside your if/then/else logic:

(Note that I am borrowing Nicholas Carey's sample as a basis for my answer)

static int Main( string[] argv )
{
  static int errorOccurred = 0;
  try
  {
    log.Info("Begin");
    if (SomeImportantPreconditionExists())
    {
      DoSomeStuff();
      if (DoingSomeStuffWorkedRight())
      {
        Hooray();
      }
      else
      {
        log.Error("DoSomeStuff seems to have failed");
        ErrorOccurred();
      }
    }
    else
    {
      log.Error("SomeImportantPrecondition does not exist");
      ErrorOccurred();
    }
  }
  catch( Exception e )
  {
     log.Error("Exception of some sort", e);
     ErrorOccurred();
  }
  return ErrorStatus();
}

private static void InitializeErrorOccurred()
{
  errorOccurred = 0;
}

private static void ErrorOccurred()
{
  errorOccurred = 1;
}

private static int ErrorStatus()
{
  return errorOccurred;
}

Having posted that code, I have to say that I think that it likely you could come up with a more robust solution than what I posted.

My final advice is to NOT make your program logic depend on something that the logging framework does (or does not). Generally speaking, your program should run the same way whether logging is enabled or not (notwithstanding that when logging is enabled, messages are logged).

wageoghe
  • 27,390
  • 13
  • 88
  • 116
  • Thanks for pointing me that I have to separate the log from the error system. But it seems to be a waste of time to each time log for errors and also implement the error system. Maybe I should group them and make the log optional. – Nicolas Mar 11 '14 at 09:03
2

I don't believe so...but it would be easy enough to use the decorator pattern and decorate the ILog interface to gather that information. The problem is that the log4net standard way of instantiating a logger is to have every class instantiate its own class-specific logger instance via LogManager.GetLogger(Type t).

However, shouldn't you just be catching the exception in your Main() method, logging it and exiting with the condition code set to zero or one as appropriate?

static int Main( string[] argv )
{
  int cc ;
  try
  {
     ExecApplicationCore( argv ) ;
     cc = 0 ;
  }
  catch( Exception e )
  {
     ILog log = LogManager.GetLogger(this.GetType()) ;
     log.Fatal(e) ;
     cc = 1 ;
  }
  return cc ;
}

That's the point of exceptions. If you can't actually handle it, you shouldn't be catching it: just let it bubble up to the top.

Edited to note: a sample ILog decorator and factory:

using log4net;
using log4net.Core;
using log4net.Repository;

public class CountingLogger : ILog , ILogger
{
  public IDictionary<Level , int> Counts { get; private set; }
  private ILog Log4Net { get; set; }

  public CountingLogger( ILog logger )
  {
    this.Log4Net = logger ;
    this.Counts  = this.Log4Net.Logger.Repository.LevelMap.AllLevels.Cast<Level>().ToDictionary( x => x , x => 0 ) ;
    return;
  }
  private void Count( Level level )
  {
    int count;
    bool success = this.Counts.TryGetValue( level , out count );
    this.Counts[level] = ++count;
    return;
  }


  public bool IsDebugEnabled { get { return Log4Net.IsDebugEnabled ; } }
  public bool IsErrorEnabled { get { return Log4Net.IsErrorEnabled ; } }
  public bool IsFatalEnabled { get { return Log4Net.IsFatalEnabled ; } }
  public bool IsInfoEnabled  { get { return Log4Net.IsInfoEnabled  ; } }
  public bool IsWarnEnabled  { get { return Log4Net.IsWarnEnabled  ; } }

  public ILogger Logger { get { return this ; } }

  public void Debug( object message , Exception exception ) { Count( Level.Debug ) ; Log4Net.Debug( message , exception ) ; }
  public void Info(  object message , Exception exception ) { Count( Level.Info  ) ; Log4Net.Info(  message , exception ) ; }
  public void Warn(  object message , Exception exception ) { Count( Level.Warn  ) ; Log4Net.Warn(  message , exception ) ; }
  public void Error( object message , Exception exception ) { Count( Level.Error ) ; Log4Net.Error( message , exception ) ; }
  public void Fatal( object message , Exception exception ) { Count( Level.Fatal ) ; Log4Net.Fatal( message , exception ) ; }

  public void Debug( object message ) { Count( Level.Debug ) ; Log4Net.Debug( message ) ; }
  public void Info(  object message ) { Count( Level.Info  ) ; Log4Net.Info(  message ) ; }
  public void Warn(  object message ) { Count( Level.Warn  ) ; Log4Net.Warn(  message ) ; }
  public void Error( object message ) { Count( Level.Error ) ; Log4Net.Error( message ) ; }
  public void Fatal( object message ) { Count( Level.Fatal ) ; Log4Net.Fatal( message ) ; }

  public void DebugFormat( IFormatProvider provider , string format , params object[] args ) { Count( Level.Debug ) ; Log4Net.DebugFormat( provider , format , args ) ; }
  public void InfoFormat(  IFormatProvider provider , string format , params object[] args ) { Count( Level.Info  ) ; Log4Net.InfoFormat(  provider , format , args ) ; }
  public void WarnFormat(  IFormatProvider provider , string format , params object[] args ) { Count( Level.Warn  ) ; Log4Net.WarnFormat(  provider , format , args ) ; }
  public void ErrorFormat( IFormatProvider provider , string format , params object[] args ) { Count( Level.Error ) ; Log4Net.ErrorFormat( provider , format , args ) ; }
  public void FatalFormat( IFormatProvider provider , string format , params object[] args ) { Count( Level.Fatal ) ; Log4Net.FatalFormat( provider , format , args ) ; }

  public void DebugFormat( string format , object arg0 , object arg1 , object arg2 ) { Count( Level.Debug ) ; Log4Net.DebugFormat( format , arg0 , arg1 , arg2 ) ; }
  public void InfoFormat(  string format , object arg0 , object arg1 , object arg2 ) { Count( Level.Info  ) ; Log4Net.InfoFormat(  format , arg0 , arg1 , arg2 ) ; }
  public void WarnFormat(  string format , object arg0 , object arg1 , object arg2 ) { Count( Level.Warn  ) ; Log4Net.WarnFormat(  format , arg0 , arg1 , arg2 ) ; }
  public void ErrorFormat( string format , object arg0 , object arg1 , object arg2 ) { Count( Level.Error ) ; Log4Net.ErrorFormat( format , arg0 , arg1 , arg2 ) ; }
  public void FatalFormat( string format , object arg0 , object arg1 , object arg2 ) { Count( Level.Fatal ) ; Log4Net.FatalFormat( format , arg0 , arg1 , arg2 ) ; }

  public void DebugFormat( string format , object arg0 , object arg1 ) { Count( Level.Debug ) ; Log4Net.DebugFormat( format , arg0 , arg1 ) ; }
  public void InfoFormat(  string format , object arg0 , object arg1 ) { Count( Level.Info  ) ; Log4Net.InfoFormat(  format , arg0 , arg1 ) ; }
  public void WarnFormat(  string format , object arg0 , object arg1 ) { Count( Level.Warn  ) ; Log4Net.WarnFormat(  format , arg0 , arg1 ) ; }
  public void ErrorFormat( string format , object arg0 , object arg1 ) { Count( Level.Error ) ; Log4Net.ErrorFormat( format , arg0 , arg1 ) ; }
  public void FatalFormat( string format , object arg0 , object arg1 ) { Count( Level.Fatal ) ; Log4Net.FatalFormat( format , arg0 , arg1 ) ; }

  public void DebugFormat( string format , object arg0 ) { Count( Level.Debug ) ; Log4Net.DebugFormat( format , arg0 ) ; }
  public void InfoFormat(  string format , object arg0 ) { Count( Level.Info  ) ; Log4Net.InfoFormat(  format , arg0 ) ; }
  public void WarnFormat(  string format , object arg0 ) { Count( Level.Warn  ) ; Log4Net.WarnFormat(  format , arg0 ) ; }
  public void ErrorFormat( string format , object arg0 ) { Count( Level.Error ) ; Log4Net.ErrorFormat( format , arg0 ) ; }
  public void FatalFormat( string format , object arg0 ) { Count( Level.Fatal ) ; Log4Net.FatalFormat( format , arg0 ) ; }

  public void DebugFormat( string format , params object[] args ) { Count( Level.Debug ) ; Log4Net.DebugFormat( format , args ) ; }
  public void InfoFormat(  string format , params object[] args ) { Count( Level.Info  ) ; Log4Net.InfoFormat(  format , args ) ; }
  public void WarnFormat(  string format , params object[] args ) { Count( Level.Warn  ) ; Log4Net.WarnFormat(  format , args ) ; }
  public void ErrorFormat( string format , params object[] args ) { Count( Level.Error ) ; Log4Net.ErrorFormat( format , args ) ; }
  public void FatalFormat( string format , params object[] args ) { Count( Level.Fatal ) ; Log4Net.FatalFormat( format , args ) ; }

  #region ILogger implementation

  bool ILogger.IsEnabledFor( Level level )
  {
    return this.Log4Net.Logger.IsEnabledFor( level ) ;
  }

  void ILogger.Log( LoggingEvent logEvent )
  {
    Count( logEvent.Level ) ;
    this.Log4Net.Logger.Log( logEvent ) ;
    return ;
  }

  void ILogger.Log( Type callerStackBoundaryDeclaringType , Level level , object message , Exception exception )
  {
    Count( level ) ;
    this.Log4Net.Logger.Log( callerStackBoundaryDeclaringType , level , message , exception ) ;
    return ;
  }

  string            ILogger.Name       { get { return this.Log4Net.Logger.Name       ; } }
  ILoggerRepository ILogger.Repository { get { return this.Log4Net.Logger.Repository ; } }

  #endregion

}

static class LoggerFactory
{

  public static ILog GetLogger( string name )
  {
    ILog log             = LogManager.GetLogger( name );
    ILog decoratedLogger = new CountingLogger( log );
    return decoratedLogger;
  }
  public static ILog GetLogger( Type type )
  {
    ILog log             = LogManager.GetLogger( type );
    ILog decoratedLogger = new CountingLogger( log );
    return decoratedLogger;
  }

  public static ILog GetLogger( string repository , string name )
  {
    ILog log             = LogManager.GetLogger( repository , name );
    ILog decoratedLogger = new CountingLogger( log );
    return decoratedLogger;
  }
  public static ILog GetLogger( string repository , Type type )
  {
    ILog log             = LogManager.GetLogger( repository , type );
    ILog decoratedLogger = new CountingLogger( log );
    return decoratedLogger;
  }

  public static ILog GetLogger( Assembly repositoryAssembly , string name )
  {
    ILog log             = LogManager.GetLogger( repositoryAssembly , name );
    ILog decoratedLogger = new CountingLogger( log );
    return decoratedLogger;
  }
  public static ILog GetLogger( Assembly repositoryAssembly , Type type )
  {
    ILog log             = LogManager.GetLogger( repositoryAssembly , type );
    ILog decoratedLogger = new CountingLogger( log );
    return decoratedLogger;
  }

}
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • The decorator pattern seems to be a solution. Do you have a clear example of it with log4net? – Nicolas Mar 10 '14 at 17:32
  • About your try/catch solution, it won't work in my application because I don't throw exception. I don't want to stop the process if an error occurred, I just want to log it. – Nicolas Mar 10 '14 at 17:34
  • 1
    Nice idea (although I still think the OP would be better served not using his logger to keep track of whether not not an error happened). I will note that your approach of wrapping log4net will break log4net's ability to log the call site. If call site logging is turned on, you will get the call site from within the wrapper rather than the call site from within the program code. See my answer here for a brief example of how to wrap log4net such that call site is preserved http://stackoverflow.com/questions/1149844/how-to-log-method-name-when-using-wrapper-class-with-log4net/4391860#4391860 – wageoghe Mar 10 '14 at 22:17
  • @wageoghe: I agree with you that this subverts (if not outright violates) the Single Responsibility Principle, but it's a means to an end. A lot of people don't think about decorators, though: they can be very useful in making things flexible and configurable, especially when coupled with dependency injection. We use the MSEL logging block; I'd like to get us using Log4Net, but get resistance due to things like the static method call needed to get a logger making dependency injection and mocking difficult. A good wrapper and factory might reduce the resistance there. Thanks for the suggestion! – Nicholas Carey Mar 10 '14 at 23:17
  • My personal opinion is that that decorator pattern is extremely ugly. – stuartd Mar 11 '14 at 23:45