17
votes

I have configured log4net in my app successfully but one thing is a little bit annoying for me.

The log file is created (empty) after my app start even if no error occurs. I would like to log file be created only after some error.

7

7 Answers

15
votes

I actually found a way to do this in this thread:

http://www.l4ndash.com/Log4NetMailArchive/tabid/70/forumid/1/postid/18271/view/topic/Default.aspx

I've tested the first method and it works. Just in case that link is not longer good I'll reproduce the code here. Basically the author states that there are two ways of doing this.

First way:

Create a new locking model that only acquires a lock (and creates the file) if the appropriate threshold for that logger works.

public class MyLock : log4net.Appender.FileAppender.MinimalLock
{
      public override Stream AcquireLock()
      {
            if (CurrentAppender.Threshold == log4net.Core.Level.Off)
                  return null;

            return base.AcquireLock();
      }
}

Now in the config file, set the threshold to start out as:

<threshold value="OFF" />

and make sure you set this new LockingModel as you model:

<lockingModel type="Namespace.MyLock" />

I'm using this with a rolling file appender.

The second method is listed at the link. I haven't tried this technique but it seems to be technically sound.

8
votes

I know this is an old question but I think this can be useful for someone else.

We came across a similar situation where it was required that the application shouldn't leave an empty log file if no errors occurred.

We solved it by creating the following custom LockingModel class:

public class MinimalLockDeleteEmpty : FileAppender.MinimalLock
{
    public override void ReleaseLock()
    {
        base.ReleaseLock();

        var logFile = new FileInfo(CurrentAppender.File);
        if (logFile.Exists && logFile.Length <= 0)
        {
            logFile.Delete();
        }
    }
}

It is derived from the FileAppender.MinimalLock class that will release the lock on the log file after writing each log message.

We added extra functionality that will delete the log file if it is still empty after the lock is released. It prevents the application from leaving empty error log files if the applications runs and exits without any errors.

Pros

  • It will still create an empty log file during the configuration phase of Log4Net, ensuring that logging is working before the rest of the app starts. However, the log file is deleted immediately.
  • It doesn't require you to turn off logging in your config file by setting threshold value to "OFF" and than, later on, turn on logging programmatically before writing your first log event.

Cons

  • This is most likely a slow method of managing your log files because the ReleaseLock method, and the check on the file length, will be called after every log event that is written to the log file. Only use it when you expect to have very few errors and it is a business requirement that the log file shouldn't exist when there are no errors.
  • The log files are created and deleted when empty. This might be a problem if you have other tools monitoring the log directory for file system changes. However, this was not a problem in our situation.
6
votes

The following worked for me.The first call to OpenFile() occurs when the logger is configured. Subsequent calls are when actual log message is generated.

class CustomFileAppender : RollingFileAppender
{
    private bool isFirstTime = true;
    protected override void OpenFile(string fileName, bool append)
    {
        if (isFirstTime)
        {
            isFirstTime = false;
            return;
        }

        base.OpenFile(fileName, append);
    }
}

And in the config file, change the appender

<log4net>
<appender name="RollingFile" type="<your namespace>.CustomFileAppender">
...
</log4net>

The sequence from log4Net source is as below:


  • The first call to OpenFile() is because of ActivateOptions() called from FileAppender's constructor.
  • When log message is generated, AppenderSkeleton's DoAppend() calls PreAppendCheck()
  • PreAppendCheck() is overridden in TextWriterAppender, the base of FileAppender.
  • The overridden PreAppendCheck() calls virtual PrepareWriter if the file is not yet open.
  • PrepareWriter() of FileAppender calls SafeOpenFile() which inturn calls OpenFile()
3
votes

The problem with that approach is that then if the file exists but is read-only, or is in a directory which doesn't exist etc, you won't find out until another error is already causing problems. You really want to be confident that logging is working before the rest of the app starts.

There may be a way of doing this anyway, but if not I suspect that this is the reason.

2
votes

Another method that is quite simple is described in this message of the mailing list archive

Basically, with log4net, the log file is created when the logger is configured. To configure it to do otherwise is a bit hacky. The solution is to defer the execution of the configuration. The message above suggests doing the following when setting up the logger:

private static ILog _log = LogManager.GetLogger(typeof(Program));
public static ILog Log
{
    get
    {
        if(!log4net.LogManager.GetRepository().Configured)
            log4net.Config.XmlConfigurator.Configure(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
        return _log;
    }
}

I usually configure log4net with the assembly attribute, which configures the logger automatically (thus creating the log file), and a simple getter for the log:

[assembly: log4net.Config.XmlConfigurator(Watch = true)]
...
public static log4net.ILog Log { get { return _log; } }
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

But removing that and adding in the above getter with the additional logic instead solved the problem for me.

Note: in general I agree that in most cases it would be best to configure the logger and create the file (and even write to it) on application startup.

0
votes

AcquireLock and ReleaseLock method worked for me, but it bothered me that the file was created/deleted that many times. Here is another similar option that shuts down the logger and deletes the empty logfile when the program completed. Just call RemoveEmptyLogFile when you are done logging.

/// <summary>
/// Sets the logging level for log4net.
/// </summary>
private static void RemoveEmptyLogFile()
{
  //Get the logfilename before we shut it down
  log4net.Appender.FileAppender rootAppender = (log4net.Appender.FileAppender)((log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository()).Root.Appenders[0];
  string filename = rootAppender.File;

  //Shut down all of the repositories to release lock on logfile
  log4net.Repository.ILoggerRepository[] repositories = log4net.LogManager.GetAllRepositories();
  foreach (log4net.Repository.ILoggerRepository repository in repositories)
  {
    repository.Shutdown();
  }

  //Delete log file if it's empty
  var f = new FileInfo(filename);
  if (f.Exists && f.Length <= 0)
  {
    f.Delete();
  }
} // RemoveEmptyLogFile
-2
votes
private static ILog _log = LogManager.GetLogger(typeof(Program));
public static ILog Log
{
    get
    {
        if(!log4net.LogManager.GetRepository().Configured)
        log4net.Config.XmlConfigurator.Configure(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
        return _log;
    }
}