Hi Ian,
That does seem quite complicated, and beyond what I�ve done with log4net. The
only mod I needed was my extra appender class. You might have luck trying to
extend the LoggingEvent class, it may allow you to keep the config nice and
dynamic as in your example. Or maybe you could extend ADONetAppender. Not sure.
My appender is very simple. The drawback is that the info you want to log is
hard-coded inside the appender; it doesn�t have a nice, dynamic config like
ADONetAppender. I don�t find this to be a big deal though, because as long as
you spend enough time on design you should be able to foresee the info you want
to log.
My appender is called WsAppender, for �web service appender�. Basically it uses
log4net to send log events to a web service. This is useful if there is a
firewall between your log event source and destination (eg., a front-end web
server that wants to log to a SQL database behind a firewall).
There are only two config parameters: Async (call the web service
asynchronously), and WebServiceUrl (url of the web service). Let me know if you
have any questions. (sorry for the long post here)�
<log4net debug="false">
<appender name="WsAppender" type="log4net.Appender.WsAppender">
<param name="Async" value="true"/>
<param name="WebServiceUrl"
value="http://localhost/logging/LoggerWs/LoggerWs.asmx"/>
</appender>
<root>
<priority value="DEBUG" />
<appender-ref ref="WsAppender" />
</root>
</log4net>
using System;
using System.Threading;
using log4net.helpers;
using log4net.Layout;
using log4net.spi;
namespace log4net.Appender
{
/// <summary>
/// Sends logging event details to a web service for processing.
/// </summary>
public class WsAppender : BufferingAppenderSkeleton
{
#region Public Instance Constructors
/// <summary>
///
/// </summary>
public WsAppender()
{
}
#endregion // Public Instance Constructors
#region Private Instance Fields
private bool m_async; //set to true to call the web service
asynchronously
private string m_webServiceUrl; //defines where the web service
is located
#endregion // Private Instance Fields
#region Public Instance Properties
/// <summary>
/// Set to true to have appender make asynchronous calls to the
web service.
/// </summary>
public bool Async
{
get { return m_async; }
set { m_async = value; }
}
/// <summary>
/// Full url location of web service to receive log event info.
/// </summary>
public string WebServiceUrl
{
get { return m_webServiceUrl; }
set { m_webServiceUrl = value; }
}
#endregion
#region Override implementation of AppenderSkeleton
/// <summary>
/// Log the requested event.
/// </summary>
/// <param name="loggingEvent"></param>
override protected void Append(LoggingEvent loggingEvent)
{
//create helper object to represent the logging task.
WsAppenderTask wsAppenderTask = new
WsAppenderTask(m_webServiceUrl, loggingEvent);
if (m_async)
{
//do logging asynchronously
Thread thread = new System.Threading.Thread(
new ThreadStart(wsAppenderTask.execute) );
thread.Start();
}
else
{
//do logging synchronously
wsAppenderTask.execute();
}
}
/// <summary>
///
/// </summary>
override protected bool RequiresLayout
{
get { return false; }
}
#endregion
}//class
#region Helper class WsAppenderTask
/// <summary>
/// This helper class contains the *data* needed for making the call to
the web service, and a method to *execute* the task.
/// The reason we need to use a class to store the data is that the
ThreadStart delegate function is not allowed to take
/// any parameters. Therefore we need to setup an object, set the data
into the object, and call an execute method with
/// no parameters.
/// </summary>
public class WsAppenderTask
{
private DateTime dtLogDate;
private string strWebServiceUrl, strLoggerName, strThreadId,
strLogLevel, strMessage, strMachineName, strExceptionDetails, strClassName,
strMethodName, strFileName, strLineNumber, strOtherInfo;
//constructor
public WsAppenderTask(string strWebServiceUrl, LoggingEvent
loggingEvent)
{
this.strWebServiceUrl = strWebServiceUrl;
//get event properties: log_date, thread, log_level,
logger, message, exception, machine name
this.dtLogDate = loggingEvent.TimeStamp;
this.strLoggerName = loggingEvent.LoggerName;
this.strThreadId = loggingEvent.ThreadName;
this.strLogLevel = loggingEvent.Level.Name;
this.strMessage = loggingEvent.MessageObject.ToString();
this.strMachineName = SystemInfo.HostName;
//System.Net.Dns.GetHostName();
this.strExceptionDetails =
loggingEvent.GetExceptionStrRep();
//code location details (not always available in
Release mode, since JIT does optimization and breaks the stack trace)
this.strClassName =
loggingEvent.LocationInformation.ClassName;
this.strMethodName =
loggingEvent.LocationInformation.MethodName;
this.strFileName =
loggingEvent.LocationInformation.FileName;
this.strLineNumber =
loggingEvent.LocationInformation.LineNumber;
this.strOtherInfo = null; //for future use
}
/// <summary>
/// Method to do the work. This method cannot have any
parameters because it is used as a delegate
/// to start a thread, and delegate functions cannot take any
parameters.
/// </summary>
public void execute()
{
//call web service with properties
LoggerWs loggerWs = new LoggerWs(strWebServiceUrl);
loggerWs.PreAuthenticate = true;
loggerWs.Credentials =
System.Net.CredentialCache.DefaultCredentials;
loggerWs.Log(dtLogDate, strLoggerName, strThreadId,
strLogLevel, strMessage, strMachineName, strExceptionDetails, strClassName,
strMethodName, strFileName, strLineNumber, strOtherInfo);
}
}//class WsAppenderTask
#endregion
}//namespace
---------- Original Message ----------------------------------
From: "Ian Bell" <[EMAIL PROTECTED]>
Reply-To: "Log4NET User" <[email protected]>
Date: Mon, 22 Nov 2004 20:40:58 -0000
>Hi Simon
>
>Thanks for that - I was thinking along the lines of pass my XML in as the
>message. The only problem with that was the fact that it didn't seem to fit
>in with the config 'philosophy' of log4net. I'd had hoped that I could
>somehow create my own LoggingEvent to pass in, and then change my app.config
>thus:
>
<!-- snip -->
>
>
>I intended to call the logger thus: Log.Debug(<my xml string>), where the
>xml string would contain my xml data - of course this would also mean I'd
>have to roll my own LoggingEvent class and also do something with Logger.cs
>(to create an instance of my new EventLogger). This all seemed a little
>complicated, but the docs do say that the EventLogger class can be extended
>etc... Does this seem reasonable?
>
>Good point about intercepting the message object in the Append method - I'll
>look into that, but how do I reconcile the fact that I'm not interested in
>logging any of the data the example schema describes, that I have my own
>database schema. Any ideas? Because if I do intercept the message object and
>extract/store the data therein, there's still the issue of the extra fields
>(thead, level, logger etc) that comes as part of the sample ADO appender.
>
>Blimey, sorry to go on and on!
>
>Yes, any example code would be great - please pass it on, and many thanks
>for your email
>
>Regards
>
>Ian
>
>
>
>
>
>
>-----Original Message-----
>From: Simon Wallis [mailto:[EMAIL PROTECTED]
>Sent: 22 November 2004 18:26
>To: [email protected]; [EMAIL PROTECTED]
>Subject: Re: Custom Appender
>
>Hi Ian,
>
>No, you don't need to do anything to the LoggingEvent class. You create your
>own appender and override the Append(LoggingEvent loggingEvent) method. In
>this function you can access loggingEvent.MessageObject, which will give you
>the message you logged from your code -- in your case, an XML message. At
>this point you have your XML and you can do whatever you want with it.
>
>What type of data do you want to "pass in"? Do you mean to pass in as your
>custom appender configuration, or pass in when you call log.Debug(...), etc?
>
>If you need an example of a custom appender with custom config attributes
>let me know, but it sounds like you're almost there.
>
>Simon.
>
>
>---------- Original Message ----------------------------------
>From: "ian" <[EMAIL PROTECTED]>
>Reply-To: <[EMAIL PROTECTED]>
>Date: Mon, 22 Nov 2004 10:38:59 -0500
>
>>Hi
>>
>>I need to send in XML data which I then break up and store in a database -
>any ideas as to the best way to do this?
>>
>>I'm looking to create a custom appender, similar to the current ADONet
>appender by extending the AppenderSkeleton class, and implementing the
>Append method.
>>
>>The trouble is, I'm a little confused when it comes to the LoggingEvent
>class - am I right in thinking that I'll have to create my own LoggingEvent
>class to define the data I want to pass in? If so, then I guess I'll also
>have to override CallAppender in the Logger class to create my new
>loggingevent object.
>>
>>Am I on the right track with this line of thought?
>>
>>Thanks in advance
>>
>>Ian
>>
>
>
>