1

I have a DLL file which does Log4Net logging to a a file. There is a process which loads the DLL and can create multiple instances of the DLL.

Each instance of a DLL has to create a separate log file. Therefore I do all the Log4Net configuration programatically.

I've used some help from here.

Here is my code:

public class LogHelper
{
    private PatternLayout _layout = new PatternLayout();
    private const string LOG_PATTERN = "%date %-5level - %message%newline";
    private String Configuration;

    public static string DefaultPattern
    {
        get { return LOG_PATTERN; }
    }

    public ILog log = null;

    public LogHelper(String configuration)
    {
        Configuration = configuration;

        InitialiseLogger();

        _layout.ConversionPattern = DefaultPattern;
        _layout.ActivateOptions();

        Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();

        hierarchy.Configured = true;            
        hierarchy.LevelMap.Add(log4net.Core.Level.Debug);
        hierarchy.LevelMap.Add(log4net.Core.Level.Critical);
        hierarchy.LevelMap.Add(log4net.Core.Level.Info);
        hierarchy.LevelMap.Add(log4net.Core.Level.Warn);
        hierarchy.LevelMap.Add(log4net.Core.Level.Error);
        hierarchy.LevelMap.Add(log4net.Core.Level.Fatal);
    }

    ~LogHelper()
    {            
        log.Debug("Closing myself down");
        IAppender[] appenders = log.Logger.Repository.GetAppenders();
        //appenders are empty
        log.Logger.Repository.Shutdown();
    }

    public void InitialiseLogger()
    {            
        Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
        Logger newLogger = hierarchy.GetLogger(Configuration) as Logger;

        PatternLayout patternLayout = new PatternLayout();
        patternLayout.ConversionPattern = LOG_PATTERN;
        patternLayout.ActivateOptions();

        RollingFileAppender roller = new RollingFileAppender();            
        roller.Layout = patternLayout;
        roller.AppendToFile = true;
        roller.RollingStyle = RollingFileAppender.RollingMode.Size;
        roller.MaxSizeRollBackups = 4;
        roller.MaximumFileSize = "10MB";
        String name = String.Format("-{0:yyyy-MM-dd_HH-mm-ss}", DateTime.Now);
        roller.File = "C:\\Logs\\" + Configuration + name + ".log";
        roller.ImmediateFlush = true;
        roller.ActivateOptions();

        newLogger.AddAppender(roller);

        log = LogManager.GetLogger(Configuration);
    }

The problem is that log.Debug("Closing myself down"); is not logged to a log file; I know it is being called. And the log files never get released, unless I stop the process that loads my DLL0, and I do not want to stop it.

A link from here explains how to shutdown appenders. But the problem is that in my destructor a call to log.Logger.Repository.GetAppenders(); returns an empty array.

How should I solve it?

Just a note: the process that loads my DLL is from 3rd party and I don't know the internals of it.

Community
  • 1
  • 1
Tadzys
  • 1,044
  • 3
  • 16
  • 22
  • 1
    What do you mean by an instance of a DLL? – Emond Mar 13 '13 at 10:02
  • Sorry, expressed myself badly, the process loads dll and then instantiates multiple instances of a "main class" in the dll, so I have a separate log file per instance of that class. Once the instance of the class is destroyed my files still remain locked by the main process. – Tadzys Mar 13 '13 at 10:07
  • "the instance of the class is destroyed" is also badly expressed :) Read a bit more on object lifetime and garbage collection in .NET. That will help you understand more about this process and why it is causing you issues. See: http://stackoverflow.com/questions/12368/how-to-dispose-a-class-in-net/12394#12394 – Emond Mar 13 '13 at 10:18

2 Answers2

4

You are using the destructor of the LogHelper to release the file(s)

According to 1.6.7.6 Destructors in the language specification the destructor will be called but you can not know when. You just know it will be called before the process terminates.

The most obvious thing to do is to move the logic of the destructor to a method that will be called explicitly (e.g. Dispose)

That way you will be able to call the method and thus release the files.

Emond
  • 50,210
  • 11
  • 84
  • 115
2

What you call a "Destructor" is actually a Finalizer. They should only be used to release unmanaged resources, so it looks to me like you are abusing it. Also note that the Finalizer is likely to be called on a separate thread, it may be called at effectively a random time, and it may not even be called at all.

You should make LogHelper implement IDisposable and implement Dispose() (which will contain the logic that is currently in your Finalizer).

Then you need to managed the lifetime of your LogHelper by calling Dispose() at the appropriate time.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276