Unique log file for each instance of class - c#

I am currently running a windows service that creates multiple instances of a class.
At the top of the service class and every other class in my solution, I have something like this:
private static readonly ILog _log = LogManager.GetLogger(typeof(SomeClassTypeHere));
In my App.config, I have Log4Net configured for a single file:
<log4net debug="true">
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs\SomeLogFileName.xml" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<countDirection value="1" />
<maxSizeRollBackups value="30" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.XmlLayoutSchemaLog4j">
<locationInfo value="true" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="RollingFileAppender" />
</root>
</log4net>
This works great in most respects, and everything logs to a single file. However, I'd really like to create a separate log file for each instance of a particular class that my service creates.
This is a class that we often need to monitor for support and we can have a handful of instances running at the same time.
We don't know which instances will be running at a given time, so it makes creating static files in the configuration kinda painful.
I tried taking off the readonly modifier and setting the following in the class constructor:
_log = LogManager.GetLogger("DataCollectionClass_" + deviceName + "_" + DateTime.Now.ToString("MMddyyyy"), typeof(SomeClassTypeHere));
But that requires that I define an appender manually in the configuration, which would be cumbersome and tough to keep up with.
Any thoughts on doing this in L4N? I have seen links here but don't really know if that much frameworking is necessary.

The code below shows how you can programatically configure log4Net without using a configuration file to achieve the effect you're looking for. Basically, it just involves creating a named logger and adding to the hierarchy.
I used as a starting point one of the answers from here.
using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;
namespace LoggerTest
{
class Program
{
static void Main(string[] args)
{
DeviceConnection dev1 = new DeviceConnection("Device1");
DeviceConnection dev2 = new DeviceConnection("Device2");
dev1.DoSomething();
dev2.DoSomething();
}
}
public class DeviceConnection
{
private string name;
private readonly ILog logger;
public DeviceConnection(string _name)
{
name = _name;
logger = TestLogger.AddNamedLogger(name);
logger.Info("---- Begin Logging for DeviceConnection: " + name);
}
public void DoSomething()
{
logger.Info("Doing something for device connection " + name);
}
}
public static class TestLogger
{
private static PatternLayout _layout = new PatternLayout();
private const string LOG_PATTERN = "%d [%t] %-5p %m%n";
public static string DefaultPattern
{
get { return LOG_PATTERN; }
}
static TestLogger()
{
_layout.ConversionPattern = DefaultPattern;
_layout.ActivateOptions();
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Configured = true;
}
public static PatternLayout DefaultLayout
{
get { return _layout; }
}
public static ILog AddNamedLogger(string name)
{
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
Logger newLogger = hierarchy.GetLogger(name) 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 = "100KB";
roller.StaticLogFileName = true;
roller.File = name + ".log";
roller.ActivateOptions();
newLogger.AddAppender(roller);
return LogManager.GetLogger(name);
}
}
}

Use the ADO.Net appender and log to a SQL Server database and just query for the information you need.
Another alternative is the log4net Dashboard: http://www.l4ndash.com/. It does a pretty decent job of integrating logs from various sources, and then slicing and dicing them in different ways. Reasonbly priced, too.

log4net has a concept called logger hierarchy.
A logger is said to be
an ancestor of another logger if its
name followed by a dot is a prefix of
the descendant logger name. A logger
is said to be a parent of a child
logger if there are no ancestors
between itself and the descendant
logger. The hierarchy works very much
in the same way as the namespace and
class hierarchy in .NET. This is very
convenient as we shall soon see.
So you really should be creating your instance specific loggers with . characters instead of _ characters.
_log = LogManager.GetLogger("DataCollectionClass." + deviceName + "." + DateTime.Now.ToString("MMddyyyy"), typeof(SomeClassTypeHere));
Then in the configuration file reference the logger hierarchy like the following.
<log4net>
<!-- Other log4net configuration omitted for brevity -->
<logger name="DataCollectionClass">
<!-- Put your appender-ref entries here -->
</logger>
</log4net>
Notice how the logger name reference does not contain the fully qualified name used in code. It only references the root of the name. Think of it in the same way you think of namespaces.

I have an article that might help:
https://web.archive.org/web/20110502044414/http://horth.com/blog/?p=165
This is about changing a logfile at runtime. What you could do is pass in the file name for each instance into your log4net file. That way you could create a log file for each instance of your class. This way your config file is simple and yet you have the flexibility to create a new log file for each class instance.
It was mentioned above that you could log to a database as well with indicators for each instance. If you don't wan to buy anything, use SQL Express which allows 10GB databases. You can even write directly to the MDF file instead of installing SQL.

Related

log4net config file for net.core with UDP Append remote address dynamically

I got a log4net file in which I added the following :
I want to be able to inject the RemoteAddress variable from my appSetting.json file via the startup.cs page.
But I am getting the following error :
log4net:ERROR Could not create Appender [UdpAppender] of type [log4net.Appender.UdpAppender]. Reported error follows.
log4net.Util.TypeConverters.ConversionNotSupportedException: Cannot convert from type [System.String] value [%propery{RemoteAddress}] to type [System.Net.IPAddress] ---> System.Net.Sockets.SocketException: No such host is known
at System.Net.Dns.InternalGetHostByName(String hostName, Boolean includeIPv6)
at System.Net.Dns.ResolveCallback(Object context)
The code in the log4net file -
<appender name="UdpAppender" type="log4net.Appender.UdpAppender">
<RemoteAddress value="%propery{RemoteAddress}" />
<RemotePort value="5005" />
<encoding value="utf-8"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level - %property{log4net:HostName} - %message%newline" />
</layout>
</appender>
I added in the startup file the following code -
//logging
XmlDocument log4NetConfig = new XmlDocument();
log4NetConfig.Load(File.OpenRead("log4net.config"));
var repo = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy));
log4net.GlobalContext.Properties["LogFileName"] = env.ContentRootPath + Configuration["AppSettings:Logging:LoggerPath"];
log4net.GlobalContext.Properties["RemoteAddress"] = System.Net.IPAddress.Parse(Configuration["AppSettings:Logging:RemoteAddress"]);
log4net.Config.XmlConfigurator.Configure(repo, log4NetConfig["log4net"]);
log4net.LogManager.GetLogger(typeof(Startup)).Info($"Invoice service started. environment={env.EnvironmentName}");
Is it not possible to add the remote Address dynamically? When I use the actual IP address in the log4net file instead of using "%propery{RemoteAddress}" and log4net.GlobalContext.Properties["RemoteAddress"] = System.Net.IPAddress.Parse(Configuration["AppSettings:Logging:RemoteAddress"]); it is working.
This can be achieved via a custom type converter (via IConvertFrom).
You can reuse the implementation of Log4net's PatternString for the parsing of the context properties (here: %property{RemoteAddress}).
The type converter looks like here below, parsing and converting the configured value to an IPAddress.
public class IPAddressPatternConverter : IConvertFrom
{
public IPAddressPatternConverter()
{}
public Boolean CanConvertFrom(Type sourceType)
{
return typeof(String) == sourceType;
}
public Object ConvertFrom(Object source)
{
String pattern = (String)source;
PatternString patternString = new PatternString(pattern);
String value = patternString.Format();
return IPAddress.Parse(value);
}
}
At the start of your application you register it with Log4net's ConverterRegistry via
log4net.Util.TypeConverters.ConverterRegistry.AddConverter(typeof(IPAddress), new IPAddressPatternConverter());
The xml configuration stays as-is:
(Notice to specify %property instead of %propery.)
<appender name="UdpAppender" type="log4net.Appender.UdpAppender">
<RemoteAddress value="%property{RemoteAddress}" />
<!-- Remaining settings. --->
</appender
You configure the actual IP address via the context property involved.
String ipAddress = "127.0.0.1";
log4net.GlobalContext.Properties["RemoteAddress"] = ipAddress;

Show filename and error in a list on a form

I'm creating a folder monitor that scans a folder for incomming xml files. When a xml file has been created in the folder, the file will be parsed, and after parsing it'll be moved to a "processed" folder. Now there are a lot of things that can go wrong obviously, and I'd like to be able to display a list of errors in this fasion:
Filename 1 >> error
Filename 2 >> error
But I realy dont know where to start. should I make a dictionary with string/string pair and display it in a tabel? Or what is the best option here. I tried googling, but it's hard to find examples of this.
thanks in advance.
You can use Enterprise Library for logging or create your own Logger class like this :
public static class Logger
{
public static List<Error> Logs = new List<Error>();
public static void Log(Exception ex,string fileName)
{
Logs.Add(new Error
{
Message = ex.Message,
FileName = fileName
});
//Here you can log errors to database,txt or xml too.
}
}
public class Error
{
public string Message { get; set; }
public string FileName { get; set; }
}
And use your logger in FileSystemWatcher class' Created event like this :
void watcher_Created(object sender, FileSystemEventArgs e)
{
try
{
//Your logic
}
catch (Exception ex)
{
Logger.Log(ex, e.Name);
//To show your logs in grid
dataGridView.DataSource = null;
dataGridView.DataSource = Logger.Logs;
}
}
Well as you said you could use a Dictionary then put in a data table and then probably bind it to a DataGrid like:
Dictionary<String, String> dict = new Dictionary<String,String>();
dict.Add("Filename1","Error1");
dict.Add("Filename2","Error2");
dict.Add("Filename3","Error3");
DataTable table = new DataTable();
table.Columns.Add("Filename", typeof(String));
table.Columns.Add("Error_Description", typeof(String));
foreach (KeyValuePair<String,String> dictval in dict)
{
table.Rows.Add(dictval.Key, dictval.Value);
}
dataGridView1.DataSource = table;
You really should consider using something like log4net to do the logging. It can be downloaded here. And the configuration is dead simple. In your app.config file you can add the section tag to the configSections tag and then add the log4net configuration tag.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="EventLogAppender" />
</root>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<threshold value="DEBUG" />
<applicationName value="Lantic YCS WebServer" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="(%-5level %date{hh}:%date{mm}:%date{ss} [%thread] %logger [%property{NDC}] ) %message %n" />
</layout>
</appender>
</log4net>
</configuration>
Then in your code you just need to initialize it, so at the top of every class file drop in this line:
private static readonly ILog log = LogManager.GetLogger(typeof(Global));
allowing you to say something like:
log.Error(...);
but also, when the application first starts, don't forget to run this line:
log4net.Config.XmlConfigurator.Configure();
Now, to address a more interesting concern that I personally have. You stated that you're building a folder monitor. You really don't want, or need, to build that. Please simply use the FileSystemWatcher that's already available. It's extremely efficient, and filterable as well, so you get the messages you want.

log4net database logging with custom parameters

I have database logging in place using the AdoNetAppender. What I'd like to do is log the user identity on each log statement. However, I don't want to use the standard log4net %identity parameter for two reasons:
log4net warn that its extremely slow as it has to look up the context identity.
In some service components the standard identity is a service account but we have already captured the user identity in a variable and I'd like to use that.
I have seen code where some people use the log4net.ThreadContext to add additional properties but I understand that this is 'unsafe' due to thread interleaving (and it is also a performance drain).
My approach has been to extend the AdoNetAppenderParameter class thus:
public class UserAdoNetAppenderParameter : AdoNetAppenderParameter
{
public UserAdoNetAppenderParameter()
{
DbType = DbType.String;
PatternLayout layout = new PatternLayout();
Layout2RawLayoutAdapter converter = new Layout2RawLayoutAdapter(layout);
Layout = converter;
ParameterName = "#username";
Size = 255;
}
public override void Prepare(IDbCommand command)
{
command.Parameters.Add(this);
}
public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
{
string[] data = loggingEvent.RenderedMessage.Split('~');
string username = data[0];
command.Parameters["#username"] = username;
}
}
and then programmatically add this to the current appender like so:
ILog myLog = LogManager.GetLogger("ConnectionService");
IAppender[] appenders = myLog.Logger.Repository.GetAppenders();
AdoNetAppender appender = (AdoNetAppender)appenders[0];
appender.AddParameter(new UserAdoNetAppenderParameter());
myLog.InfoFormat("{0}~{1}~{2}~{3}", userName, "ClassName", "Class Method", "Message");
The intention here is to use a standard format for messages and parse the first part of the string which should always be the username. The FormatValue() method of the custom appender parameter should then use only that part of the string so that it can be written to a separate field in the log database.
My problem is that no log statements are written to the database. Oddly, when debugging, a breakpoint in the FormatValue() method is only hit when I stop the service.
I've trawled through loads of stuff relating to this but haven't yet found any answers.
Has anyone managed to do this, or am I on the wrong trail.
P.S. I've also tried extending the AdoNetAppender but it doesnt give you access to set the parameter values.
I also needed to log structured data and liked to use logging interface like this:
log.Debug( new {
SomeProperty: "some value",
OtherProperty: 123
})
So I also wrote custom AdoNetAppenderParameter class to do the job:
public class CustomAdoNetAppenderParameter : AdoNetAppenderParameter
{
public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
{
// Try to get property value
object propertyValue = null;
var propertyName = ParameterName.Replace("#", "");
var messageObject = loggingEvent.MessageObject;
if (messageObject != null)
{
var property = messageObject.GetType().GetProperty(propertyName);
if (property != null)
{
propertyValue = property.GetValue(messageObject, null);
}
}
// Insert property value (or db null) into parameter
var dataParameter = (IDbDataParameter)command.Parameters[ParameterName];
dataParameter.Value = propertyValue ?? DBNull.Value;
}
}
Now log4net configuration can be used to log any property of given object:
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<appender name="MyAdoNetAppender" type="log4net.Appender.AdoNetAppender">
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="... your connection string ..." />
<commandText value="INSERT INTO mylog ([level],[someProperty]) VALUES (#log_level,#SomeProperty)" />
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter type="yourNamespace.CustomAdoNetAppenderParameter, yourAssemblyName">
<parameterName value="#SomeProperty" />
<dbType value="String" />
<size value="255" />
</parameter>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="MyAdoNetAppender" />
</root>
</log4net>
After some experimentation, I finally got this to work. Ensuring that log4net's internal logging helped identify the errors and downloading the log4net source code and reviewing the AdoNetAppenderParameter class showed how the FormatValue() method should be used. So, here's the amended custom appender parameter:
public class UserAdoNetAppenderParameter : AdoNetAppenderParameter
{
public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
{
string[] data = loggingEvent.RenderedMessage.Split('~');
string username = string.Empty;
if (data != null && data.Length >= 1)
username = data[0];
// Lookup the parameter
IDbDataParameter param = (IDbDataParameter)command.Parameters[ParameterName];
// Format the value
object formattedValue = username;
// If the value is null then convert to a DBNull
if (formattedValue == null)
{
formattedValue = DBNull.Value;
}
param.Value = formattedValue;
}
}
And to use this, I add it in the log4net config file like this:
<parameter type="MyAssembly.Logging.UserAdoNetAppenderParameter, MyAssembly">
<parameterName value="#username" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout" value="%message" />
</parameter>
And by convention, my log statements will be something like this:
if (log.IsDebugEnabled)
log.DebugFormat("{0}~{1}~{2}", username, someOtherParameter, message);
If you look at the class, it uses data[0] as the username, so it is dependant on following the convention. It does, however, get the username into its own parameter and into a separate field in the log database table, without resorting to stuffing it temporarily into the unsafe ThreadContext.
Yes, thread agility means that you might not get the correct data back. For log4net, you will want to stick it in your HttpContext's Items collection.
The trouble is you have to do some work to get it back out when it's time to write those values to the database because of this I have always used Marek's Adaptive Property Provider class to do the grunt work for me. It's super simple to use it as all you have to do is the following:
log4net.ThreadContext.Properties["UserName"] = AdaptivePropertyProvider.Create("UserName", Thread.CurrentPrincipal.Identity.Name);
The adaptive property will know the appropriate place to retrieve the value when log4net requests it.
Alternative Option
If you're not stuck with log4net, NLog makes logging for ASP.NET websites way more simple because they natively support ASP.NET applications. Usage and even configuration is almost identical to log4net!

NUnit & testing log4net dynamic log file location

I'm writing an application where the user can change (at runtime) the directory where the log4net log is stored. The directory string is stored in the app.config.
When I want to test if the log file is created in the right directory with NUnit, the logfile (and the corresponding directory) is not created.
When looking online for this problem I read that NUnit stops the logging from working because it uses log4net itself. The provided sample tells you to create a additional .config (Test.config) which also contains the log4net sections and to load the configuration inside the testing class, which I did.
There is still no log file created when using the unit test.
When starting the application, the log file is created as it should.
Method to set the log directory:
public void MyMethod()
{
string logDirectory = app.Settings["LogDirectory"].Value;
//assure that the file name can be appended to the path
if (!logDirectory.EndsWith(#"\"))
{
logDirectory += #"\";
}
//find the rolling file appender and set its file name
XmlConfigurator.Configure();
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
foreach (IAppender appender in hierarchy.Root.Appenders)
{
if (appender is RollingFileAppender)
{
RollingFileAppender fileAppender = (RollingFileAppender)appender;
string logFileLocation = string.Format("{0}Estimation_Protocol_{1}.txt",
logDirectory, EstimatorHelper.SoftwareVersionAndDateTime());
fileAppender.File = logFileLocation;
fileAppender.ActivateOptions();
break;
}
}
log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
log.Debug("Logging directory & file name set.");
}
The test class:
class EstimatorTests
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public EstimatorTests()
{
FileInfo fileInfo = new FileInfo(#"%property{LogName}");
log4net.Config.XmlConfigurator.Configure(fileInfo);
}
[Test]
public void TestLoadInputPaths()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
AppSettingsSection app = config.AppSettings;
string time = DateTime.Now.Ticks.ToString();
app.Settings["ProcessingProtocolDirectory"].Value = "C:\\thisFolderDoesntExist" + time;
Form mainForm = new Form();
Form.MyMethod();
Assert.IsTrue(Directory.Exists("C:\\thisFolderDoesntExist" + time));
//this assert fails!
}
}
The log4net config:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="%property{LogName}" />
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="5"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level %logger - %message%newline%exception%newline"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="RollingFileAppender"/>
</root>
</log4net>
I did not test it but I think you need to remove the call to XmlConfigurator.Configure(); in MyMethod because this will overwrite the configuration you do in the test class.

Using log4net to Log Powershell Output to Dynamic Database Variables

I'm building a web UI to help automate our deployment process and am going to write a powershell script to do the deployment and would like it's Write-Debug (or any statement to log, just let me know which to use :) ) statements to be logged to the deployed package's database variable Log. I haven't really used log4net before so please don't laugh if I'm doing this completely wrong.
I figure since the location is dynamic, I'd have to code the log4net appenders, but would it be easier/better to do all of the log4net stuff inside of the powershell script? I read this and found I should use ps.Streams.Debug.DataAdded += new EventHandler<DataAddedEventArgs>(delegate(object sender, DataAddedEventArgs e) to get the write-debug information.
Here is what I have so far:
public static void Test(Package pkg)
{
//Do roll_out
//Creates a cmd prompt
PowerShell ps = PowerShell.Create();
string myCommand = #"C:\Users\evan.layman\Desktop\test.ps1";
ps.AddCommand(myCommand);
ps.Streams.Debug.DataAdded += new EventHandler<DataAddedEventArgs>(delegate(object sender, DataAddedEventArgs e)
{
PSDataCollection<DebugRecord> debugStream = (PSDataCollection<DebugRecord>)sender;
DebugRecord record = debugStream[e.Index];
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Root.RemoveAllAppenders(); /*Remove any other appenders*/
AdoNetAppender appender = new AdoNetAppender();
appender.ConnectionString = ConfigurationManager.ConnectionStrings["DeploymentConnectionString"].ConnectionString;
appender.CommandText = "with cte as (SELECT * FROM Package PackageID =" + pkg.PackageID + ") UPDATE cte SET (Log) VALUES (?logText)";
AdoNetAppenderParameter param = new AdoNetAppenderParameter();
param.DbType = System.Data.DbType.String;
param.ParameterName = "logText";
param.Layout = new log4net.Layout.RawTimeStampLayout();
appender.AddParameter(param);
BasicConfigurator.Configure(appender);
ILog log = LogManager.GetLogger("PowerShell");
log.Debug(record.Message);
//log.DebugFormat("{0}:{1}", DateTime.UtcNow, record);
//log.Warn(record, new Exception("Log failed"));
});
Collection<PSObject> commandResults = ps.Invoke();
Hopefully I can get this working :)
I would keep as much log4net config out of your code as possible. In your code, the config is being recreated on each debug statement, which is inefficient.
It's possible to do what you want using event context properties in log4net. I've blogged about log4net event context a bit on my blog.
Here's a quick example that's close to your existing codebase....
This C# code shows how to use log4net global properties to store custom event context data; note the setting of the "PackageID" global property value before the pipeline is executed...
using System;
using System.Management.Automation;
using log4net;
// load log4net configuration from app.config
[assembly:log4net.Config.XmlConfigurator]
namespace ConsoleApplication1
{
class Program
{
private static PowerShell _ps;
private static ILog Log = log4net.LogManager.GetLogger(typeof (Program));
static void Main(string[] args)
{
string script = "write-debug 'this is a debug string' -debug";
for (int packageId = 1; packageId <= 5; ++packageId)
{
using (_ps = PowerShell.Create())
{
_ps.Commands.AddScript(script);
_ps.Streams.Debug.DataAdded += WriteDebugLog;
// set the PackageID global log4net property
log4net.GlobalContext.Properties["PackageID"] = packageId;
// sync invoke your pipeline
_ps.Invoke();
// clear the PackageID global log4net property
log4net.GlobalContext.Properties["PackageID"] = null;
}
}
}
private static void WriteDebugLog(object sender, DataAddedEventArgs e)
{
// get the debug record and log the message
var record = _ps.Streams.Debug[e.Index];
Log.Debug(record.Message);
}
}
}
And here is the app.config that drops the logs into the database; note the custom PackageID parameter in the SQL, and how the value is pulled from the log4net property stack:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="Ado" type="log4net.Appender.AdoNetAppender">
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="data source=vbox-xp-sql;initial catalog=test1;integrated security=false;persist security info=True;User ID=test1;Password=password" />
<commandText value="INSERT INTO Log ([Message],[PackageID]) VALUES (#message, #packageid)" />
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout" value="%message" />
</parameter>
<parameter>
<parameterName value="#packageid" />
<dbType value="Int32" />
<size value="4" />
<!-- use the current value of the PackageID property -->
<layout type="log4net.Layout.PatternLayout" value="%property{PackageID}" />
</parameter>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="Ado" />
</root>
</log4net>
</configuration>
Hope this helps.

Categories