简体   繁体   中英

How can I use log4net to log to a RichTextBox?

I would like to see the log4net entries shown in a Windows.Forms.RichTextBox. I was thinking of using a MemoryAppender, but I am not sure how to get the entry every time it is added as an event.

log4net is inherently a push model, which is not obvious (most people associate method calls with pull models), but we can change it to another push model that most .NET developers are more familiar with (events) and build another push model on top of that which makes it easier for us to subscribe/unsubscribe to these events (observables).

What you need to do is create an implementation of the IAppender interface and translate the calls to the interface implementation into events.

Let's define the EventArgs class that you'll use to indicate that an event has taken place:

public class LogEventArgs : EventArgs
{
    public LogEventArgs(IEnumerable<LoggingEvent> loggingEvents)
    {
        // Validate parameters.
        if (loggingEvents == null) 
            throw new ArgumentNullException("loggingEvents");

        // Assign values.
        LoggingEvents = loggingEvents;
    }

    // Poor-man's immutability.
    public IEnumerable<LoggingEvent> LoggingEvents { get; private set; }
}

Of note, this is exposing a sequence of LoggingEvent instances, as we want to support the IBulkAppender interface as well.

With that out of the way, we can create an implementation of IAppender . Note that there are only two methods that you have to implement. The more important of the two is DoAppend which is where you'll translate the call into an event:

public class EventAppender : AppenderSkeleton
{
    // Note, you will probably have to override other things here.

    // The lock for the event.
    private readonly object _eventLock = new object();

    // The backing field for the event.
    private EventHandler<LogEventArgs> _loggedEventHandlers;

    // Add and remove methods.
    public event Logged
    {
        add { lock(_eventLock) _loggedEventHandlers += value; }
        remove { lock(_eventLock) _loggedEventHandlers -= value; }
    }

    // Singular case.
    protected override void Append(LoggingEvent loggingEvent)
    {
        // Validate parameters.
        if (loggingEvent == null) 
            throw new ArgumentNullException("loggingEvent");

        // Call the override that processes these in bulk.
        Append(new [] { loggingEvent });
    }

    // Multiple case.
    protected override void Append(LoggingEvent[] loggingEvents)
    {
        // Validate parameters.
        if (loggingEvents == null)
            throw new ArgumentNullException("loggingEvents");

        // The event handlers.
        EventHandler<LogEventArgs> handlers;

        // Get the handlers.
        lock (_eventLock) handlers = _loggedEventHandlers;

        // Fire if not null.
        if (handlers != null) handlers(new LogEventArgs(loggingEvents);
    }
}

Once you have this, you can programmatically append the appender . It's probably easier to do this programmatically as it facilitates finding the instance to attach the event to. However, you can walk the list of appenders to find it if that's your preference.

Then, it's just a matter of wiring up the event from this instance to a handler that will write to a RichTextBox .

Once you have this, you can easily turn this into an IObservable<T> implementation, and flatten all of the LoggingEvent instances.

First, you'd take the class above, and create an IObservable<LogEventArgs> using the Observable.FromEvent method :

// The appender that was added programmatically.
EventAppender appender = ...;

// Get the observable of LogEventArgs first.
IObservable<LogEventArgs> logEventObservable = 
    Observable.FromEvent<LogEventArgs>(
        a => appender.Logged += a, a => appender.Logged -= a);

At this point though, we want to make it a little easier to process individual instances of the LoggingEvent class. There's no reason your code should have to do that. That can easily be done with the SelectMany extension method :

// The observable of the individual LoggingEvent instances.
IObservable<LoggingEvent> loggingEvents = logEventObservable.
    SelectMany(e => e.LoggingEvents);

From there, you can then easily subscribe to the IObservable<LoggingEvent> instance to publish to the UI thread when new events come in:

// The RichTextBox.
RichTextBox rtb = ...;

// This code goes where you want to start subscribing.  This needs to be
// called on the UI thread, because you need the proper
// SynchronizationContext:
IObservable<LoggingEvent> synchronized = loggingEvents.ObserveOn(
    SynchronizationContext.Current);

// Subscribe to the event.  Store the result of this.
IDisposable unsubscribe = synchronized.
    // If you need to do anything fancier, then use the
    // LoggingEvent represented in e and update the
    // RichTextBox accordingly.
    Subscribe(e => rtb.AppendText(e.RenderedMessage));

Then, if you want to unsubscribe (stop receiving updates), you simply call the Dispose method on the IDisposable implementation returned from the call to Subscribe .

Some of the advantages of using an IObservable<T> here are:

  • The marshalling of the call back to the UI thread is handled for you by the ObserveOn method.
  • You can filter/transform/etc. the sequences by using the extension methods in the System.Reactive.Linq namespace , particularly the extension methods on the Observable class which allow for using query expressions in C# .
  • The extensions for IObservable<T> implementations allow you to do things like buffering (in case you're getting way too many messages at once), sliding windows, etc.
  • You can combine this event stream with other event streams easily.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM