19

I have an application which initializes log4net from one appdomain and needs to use it in another appdomain. Is it supported?

If not, should I initialize log4net from each appdomain? Is there a risk in multiple initializations in the same application? Should I use the same log4net.config?

Yaron Naveh
  • 23,560
  • 32
  • 103
  • 158

5 Answers5

14

The log4net-user mailing list has an answer that works with RollingFileAppender. Add the following line to the appender in log4net.config:

<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
Michael L Perry
  • 7,327
  • 3
  • 35
  • 34
9

Although the question is several years old - maybe it helps someone:

It is possible to use the loggers configured in the parent AppDomain. What needs to be done is route the LoggingEvents from the child AppDomain to the parent AppDomain. For this you need to create a custom Appender that forwards the records out of the Child domain...

/// <summary>
/// Represents an <see cref="IAppender"/> implementation that forwards a <see cref="LoggingEvent"/> to a given Receiver.
/// Instances of this class should be created in the child domain.
/// </summary>
public class CrossDomainOutboundAppender : AppenderSkeleton
{
    private readonly CrossDomainParentAppender crossDomainParentAppender;
    public CrossDomainOutboundAppender(CrossDomainParentAppender crossDomainParentAppender)
    {
        if (crossDomainParentAppender == null)
        {
            throw new ArgumentNullException("crossDomainParentAppender");
        }
        this.crossDomainParentAppender = crossDomainParentAppender;

    }

    protected override void Append(LoggingEvent loggingEvent)
    {
        LoggingEvent copied = new LoggingEvent(loggingEvent.GetLoggingEventData());
        crossDomainParentAppender.Append(copied);
    }
}

, a custom class that receives the forwarded LoggingEvent and appends them to available IAppenders ...

/// <summary>
/// Represents a Receiver that sends Log4Nets <see cref="LoggingEvent"/> to all available <see cref="IAppender"/>s.
/// Instances of this class should be created in the ParentDomain.
/// </summary>
[Serializable]
public class CrossDomainParentAppender : MarshalByRefObject
{
    public void Append(LoggingEvent loggingEvent)
    {
        foreach (IAppender usedAppender in LogManager.GetRepository().GetAppenders())
        {
            usedAppender.DoAppend(loggingEvent);
        }
    }
}

and finally a setup class that ties the two and configures log4net:

public class CrossDomainChildLoggingSetup : MarshalByRefObject
{
    private CrossDomainParentAppender parentAppender;

    public void ConfigureAppender(CrossDomainParentAppender crossDomainParentAppender)
    {
       parentAppender = crossDomainParentAppender;
       CrossDomainOutboundAppender outboundAppender = new CrossDomainOutboundAppender(parentAppender);
       log4net.Config.BasicConfigurator.Configure(outboundAppender);
    }
}

Now - when you setup up your AppDomain you can add the following code...

CrossDomainParentAppender crossDomainParentAppender = new CrossDomainParentAppender();
Type crossDomainType = typeof(CrossDomainChildLoggingSetup);
CrossDomainChildLoggingSetup crossDomainChildLoggingSetup = (CrossDomainChildLoggingSetup)domain.CreateInstanceFrom(crossDomainType.Assembly.Location, crossDomainType.FullName).Unwrap();
crossDomainChildLoggingSetup.ConfigureAppender(crossDomainParentAppender);

...and everything logged in the child domain turns up in the parent domains log. (Please note: I used CreateInstaceFrom(assemblyFilePath,...) - depending on your setup you may not require loading by filePath)

Although I haven't found any bugs or problems: If you see any flaws or problems that could arise please let me know.

Linky
  • 605
  • 8
  • 24
  • Exactly the type of approach I was looking for. Thank you. – Martin Costello Sep 15 '14 at 17:04
  • 2
    If I may suggest an improvement - instead of BasicConfigurator.Configure(outputAppender), changing to the following to pick up the parent domain configuration: var hierarchy = (Hierarchy)LogManager.GetRepository(); hierarchy.Root.AddAppender(outboundAppender); hierarchy.Configured = true; – Peter McEvoy Feb 04 '15 at 15:16
  • Have you gotten a RemotingException when using this? I will get an exception saying "Object '/a0720457_c9e6_4edf_bde5_86d96058cb4e/+mauuadzzsfpiaad3bjv4uss_264.rem' has been disconnected or does not exist at the server." which I believe is because of garbage collection. Any ideas on how to fix this? – Charlie Jun 19 '15 at 17:38
  • I didn't run into it, but it should be possible. To circumvent this problem, it would be possible to implement a keep alive handling and periodically ping the remote object. – Linky Jun 22 '15 at 05:39
  • 1
    I figured it out. I overrode CrossDomainChildLoggingSetup.InitializeLifetimeService to return null since there's only one per app domain and gets cleaned up when the AppDomain is unloaded. I then implemented ISponsor to handle the lifetime of the CrossDomainParentAppender, and used this code to initialize it: var sponsor = new MySponsor(this, k); var lease = (ILease)RemotingServices.GetLifetimeService(crossDomainParentAppender); lease.Register(sponsor); – Charlie Jun 29 '15 at 16:57
  • 1
    This is great but seem to loose the method name in the logs, just shows ? instead – Jack Allan Jul 09 '15 at 11:17
5

The logger should be initialized once per app-domain.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
3

Agree with darin, once per app domain. If you're looking to have these applications use consolidated logging, you'll want to choose a logging target that won't be subject to contention (i.e. not FileAppender or RollingFileAppender).

Thomas Beck
  • 1,022
  • 6
  • 7
1

My answer adds to Linky 's answer.

To answer, Jack Allan 's question. You can solve this by solving the changing the CrossDomainOutboundAppender class:

/// <summary>
/// Represents an <see cref="IAppender"/> implementation that forwards a <see cref="LoggingEvent"/> to a given Receiver.
/// Instances of this class should be created in the child domain.
/// </summary>
public class CrossDomainOutboundAppender : AppenderSkeleton
{
    private readonly CrossDomainParentAppender crossDomainParentAppender;
    public CrossDomainOutboundAppender(CrossDomainParentAppender crossDomainParentAppender)
    {
        if (crossDomainParentAppender == null)
        {
            throw new ArgumentNullException("crossDomainParentAppender");
        }
        this.crossDomainParentAppender = crossDomainParentAppender;

    }

    protected override void Append(LoggingEvent loggingEvent)
    {
        LoggingEvent copied = new LoggingEvent(loggingEvent.GetLoggingEventData(FixFlags.All));
        crossDomainParentAppender.Append(copied);
    }
}

Notice the FixFlags.All

The current version of the .... has a flaw causing all appenders to log the message, thats like defeating the purpose of log4net, since different loggers can log at a different level for example. My improved version of the class:

/// <summary>
/// Represents a Receiver that sends Log4Nets <see cref="LoggingEvent"/> to all available <see cref="IAppender"/>s.
/// Instances of this class should be created in the ParentDomain.
/// </summary>
[Serializable]
public class CrossDomainParentAppender : MarshalByRefObject
{
    public void Append(LoggingEvent loggingEvent)
    {
        LogManager.GetRepository().Log(loggingEvent);
    }
}

This distributes the log to the logmanager, this will find out where to place the log, which logger is responsible etc.

Stormer
  • 76
  • 4