Configure layout in Common.Logging - c#

I'm currently working with Common.Logging and log4net. I have implemented a custom appender.
I'm trying to add the layout, specified in the code below, to my logs. But when I print the function RenderLoggingEvent(loggingEvent) in my custom appender, I only get the message (but no timestamps, ...).
// create properties
// EXTERNAL expects log4net being configured somewhere else in
// your code and does nothing.
NameValueCollection properties = new NameValueCollection();
properties["configType"] = "EXTERNAL";
// set Adapter
Common.Logging.LogManager.Adapter =
new Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter(properties);
// create an object of the custom appender
var appender = new SimpleAppender();
appender.Name = "SimpleAppender";
// add layout to the appender
var layout = new log4net.Layout.PatternLayout()
{
ConversionPattern =
"%date [%thread] %-5level %logger %ndc - %message%newline"
};
appender.Layout = layout;
//Let log4net configure itself based on the values provided
appender.ActivateOptions();
log4net.Config.BasicConfigurator.Configure(appender);
If I add the configuration in the App.config file, it works. But I need a code based configuration...

You need to call ActivateOptions on the layout as well as on the appender:
var layout = new log4net.Layout.PatternLayout()
{
ConversionPattern =
"%date [%thread] %-5level %logger %ndc - %message%newline"
};
layout.ActivateOptions();
appender.Layout = layout;
Sample output:
2014-03-26 20:29:49,816 [1] DEBUG test logger (null) - log test

Related

log4net managedcoloredconsole not working with dynamic creation

I have a program that has a different set of modules called based an input parameter. Modules like Orders, Shipments, Pricing etc. I wrote a logging class with log4net being the foundation though there is a need for some custom logging as well. What I want is to have each module have its own logging file and to that point, I was able to get log4net to dynamically create the appenders for each file.
I was also able to get a console display for the times when it may be run manually, but what I lost (and cannot figure how to get it to work is the Colored Console appender. I found the basic solution here for creating appenders and I then used this link to figure out how to create console and ManagedColoredConsole appenders, but while it still writes to the console, I do not get color.
There is something missing, but I I don't know what. I wrote a small testing program to try and figure this out and this is the logging class:
using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using System;
using System.Linq;
namespace TestLogging
{
public class Logging
{
// Since the current version of logging will require more custom fields passed into the logging table
// I'm going to set up a wrapper around the log for net processing. This should simplify the way we call it in
// the main program sections so we don't have to keep adding constants like pid and we can deal with variables
// like item, order number, shipping numbers
public static ILog log = null;
public string transType = "";
public string pid = "0";
private string logModule = "main";
private string path = "";
public Logging(string LogModule)
{
logModule = LogModule; // set up to default to main then pass in the specific log file name for log4net
SetLevel("Log4net.MainForm", "ALL");
path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
string execPath = AppDomain.CurrentDomain.BaseDirectory;
if (log.Logger.Repository.GetAppenders().Count() == 0)
{
//CreateConsoleAppender();
CreateManagedColorConsoleAppender();
}
AddAppender(LogModule, CreateFileAppender(logModule, execPath + "\\logs\\" + logModule + ".log"));
}
public void Info(string message, string sohnum = null, string itmref = null, string sdhnum = null, double processtime = 0.0)
{
setCustom(sohnum, itmref, sdhnum, processtime);
log.Info(message);
}
private void setCustom(string sohnum = null, string itmref = null, string sdhnum = null, double processtime = 0.0)
{
log4net.ThreadContext.Properties["TransType"] = transType;
log4net.ThreadContext.Properties["sohnum_0"] = sohnum;
log4net.ThreadContext.Properties["itmref_0"] = itmref;
log4net.ThreadContext.Properties["sdhnum_0"] = sdhnum;
log4net.ThreadContext.Properties["processtime"] = processtime.ToString();
log4net.ThreadContext.Properties["pid"] = pid;
}
// Set the level for a named logger
public static void SetLevel(string loggerName, string levelName)
{
log = LogManager.GetLogger(loggerName);
Logger l = (Logger)log.Logger;
l.Level = l.Hierarchy.LevelMap[levelName];
}
// Add an appender to a logger
public static void AddAppender(string loggerName, IAppender appender)
{
log = LogManager.GetLogger(loggerName);
Logger l = (Logger)log.Logger;
l.Repository.Configured = true;
l.AddAppender(appender);
}
// Create a new file appender
public static IAppender CreateFileAppender(string name, string fileName)
{
FileAppender appender = new
FileAppender();
appender.Name = name;
appender.File = fileName;
appender.AppendToFile = true;
PatternLayout layout = new PatternLayout();
layout.ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n";
layout.ActivateOptions();
appender.Layout = layout;
appender.ActivateOptions();
return appender;
}
public static IAppender CreateConsoleAppender()
{
ConsoleAppender appender = new ConsoleAppender();
appender.Name = "console";
PatternLayout layout = new PatternLayout();
layout.ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n";
layout.ActivateOptions();
appender.Layout = layout;
appender.ActivateOptions();
var hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Configured = true;
hierarchy.Root.AddAppender(appender);
return appender;
}
public static IAppender CreateManagedColorConsoleAppender()
{
ManagedColoredConsoleAppender appender = new ManagedColoredConsoleAppender();
ManagedColoredConsoleAppender.LevelColors mapping = new ManagedColoredConsoleAppender.LevelColors();
appender.Name = "ManagedColoredConsoleAppender";
mapping.Level = log4net.Core.Level.Debug;
mapping.ForeColor = ConsoleColor.Blue;
appender.AddMapping(mapping);
mapping.Level = log4net.Core.Level.Info;
mapping.ForeColor = ConsoleColor.Green;
appender.AddMapping(mapping);
mapping.Level = log4net.Core.Level.Error;
mapping.ForeColor = ConsoleColor.Yellow;
appender.AddMapping(mapping);
mapping.Level = log4net.Core.Level.Fatal;
mapping.ForeColor = ConsoleColor.Red;
appender.AddMapping(mapping);
PatternLayout layout = new PatternLayout();
layout.ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n";
layout.ActivateOptions();
appender.Layout = layout;
appender.ActivateOptions();
var hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Root.AddAppender(appender);
hierarchy.Configured = true;
hierarchy.Root.Level = log4net.Core.Level.Info;
return appender;
}
}
}
It is rough, but this is just for testing and learning.
This is the main program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestLogging
{
public class Program
{
//private Logging logging = new Logging("file");
private static Logging logit = new Logging("main");
static void Main(string[] args)
{
logit.Info("This is the main program");
ordersClass orders = new ordersClass();
orders.callMe();
shipments shipit = new shipments();
shipit.shipMe();
}
}
}
and one of the classes that writes to a different log file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestLogging
{
public class ordersClass
{
private Logging logit = new Logging("orders");
public void callMe()
{
logit.Info("Just placed an order");
}
}
}
When I set a break point to look at the log object I can see managed color is there as a root appender and the others added when first created. there is not a lot of info on using log4net programmatically, but I am hoping someone got this to work.
As I was reading more I discovered how to turn on internal logging for log4net. Put that in the app.config file and yes, it helps for it showed me how to fix my issue though why it does not work dynamically still alludes me.
<appSettings>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
What I found was a few things:
Log4net does not need a config file to work if you are setting things up programmatically. Found that out because I had not set the 'copy to output directory' to other then 'do not copy' so no config file was being put in the the exe folder. This showed me you don't need a config file to do logging, but it still did not answer why no color.
If you decide to use a config file, but don't put in a appender that is referenced in the root, log4net logs the error, but keeps working. I had this
<appender-ref ref="ManagedColoredConsoleAppender" />
but no appender in the file. I added the ManagedColorConsole Appender and now I am getting both colored console messages AND logging into multiple files. This is a solution, but does not explain why I could add the color appender dynamically, but not have it work. If there is an answer please post. In the mean time this is a solved question.
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="ManagedColoredConsoleAppender" />
</root>
<appender name="ManagedColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="INFO" />
<foreColor value="Green, HighIntensity" />
</mapping>
<mapping>
<level value="DEBUG" />
<foreColor value="Green" />
</mapping>
<mapping>
<level value="ERROR" />
<foreColor value="Yellow, HighIntensity" />
</mapping>
<mapping>
<level value="FATAL" />
<foreColor value="Red, HighIntensity" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
</log4net>

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

I have a log4net.config file in which I want to be able to inject the RemotePort variable dynamically via Program.cs file. But I am getting the following error :
System.FormatException: Input string was not in a correct format.
I was able to pass RemoteAddress dynamically with this strategy :
log4net config file for net.core with UDP Append remote address dynamically
The code in the log4net file
<appender name="UdpAppender" type="log4net.Appender.UdpAppender">
<RemoteAddress value="%property{RemoteAddress}" />
<RemotePort value="%property{RemotePort}" />
<encoding value="utf-8"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level - %property{log4net:HostName} - %message%newline" />
</layout>
</appender>
The code in Program.cs
log4net.GlobalContext.Properties["RemotePort"] = 514;
The same concept as shown in the referenced article is also applicable here.
You need a translation from a PatternString to an Int32 which requires a custom type converter implementing IConvertFrom.
Reusing the implementation of a PatternString the %property{RemotePort} will be replaced by the value passed in via log4net.GlobalContext.Properties["RemotePort"] = 514, which will result into 514.
public class NumericConverter : IConvertFrom
{
public NumericConverter()
{}
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 Int32.Parse(value);
}
}
Register this type converter at startup as shown below
log4net.Util.TypeConverters.ConverterRegistry.AddConverter(typeof(int), new NumericConverter());

How to add log4net appender in runtime?

How do I add an extra log appender in runtime? (all pre-existing appenders must keep on working)
I'm trying it this way:
var layout = new PatternLayout("%utcdate %-5level - %message%newline");
layout.ActivateOptions();
_log4netAppender = new FileAppender
{
Layout = layout,
File = logFilePath,
};
_log4netAppender.ActivateOptions();
BasicConfigurator.Configure(_log4netAppender);
but it doesn't write anything to the file.
You should also add the appender to a logger.
Take a look here Adding Appenders programmatically
If the logger you are using is, for example ILog logger do:
((log4net.Repository.Hierarchy.Logger)logger.Logger).AddAppender(appender)
var patternLayout = new log4net.Layout.PatternLayout
{
ConversionPattern = "%date %level %message%newline"
};
patternLayout.ActivateOptions();
var rollingFileAppender = new log4net.Appender.RollingFileAppender
{
File = "MyApp.log",
Layout = patternLayout
};
rollingFileAppender.ActivateOptions();
var hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
hierarchy.Root.AddAppender(rollingFileAppender);
hierarchy.Root.Level = log4net.Core.Level.All; // Default is Debug
log4net.Config.BasicConfigurator.Configure(hierarchy);

Can you configure log4net in code instead of using a config file?

I understand why log4net uses app.config files for setting up logging - so you can easily change how information is logged without needing to recompile your code. But in my case I do not want to pack a app.config file with my executable. And I have no desire to modify my logging setup.
Is there a way for me to set up logging in code rather than using the app.config?
Here is my simple config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs\EventLog.txt" />
<appendToFile value="false" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="1GB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="MemoryAppender" type="log4net.Appender.MemoryAppender">
</appender>
<root>
<level value="Info" />
<appender-ref ref="RollingLogFileAppender" />
<appender-ref ref="MemoryAppender" />
</root>
</log4net>
</configuration>
EDIT:
To be completely clear: It is my goal to have no XML file. Not even as an embedded resource that I turn into a stream. My goal was to define the logger completely programmatically. Just curious if it's possible and if so where I might find an example of the syntax.
FINAL SOLUTION:1
For anyone who may stumble upon this in the future, here is what I did. I made the static class below:
using log4net;
using log4net.Repository.Hierarchy;
using log4net.Core;
using log4net.Appender;
using log4net.Layout;
namespace Spectrum.Logging
{
public class Logger
{
public static void Setup()
{
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
PatternLayout patternLayout = new PatternLayout();
patternLayout.ConversionPattern = "%date [%thread] %-5level %logger - %message%newline";
patternLayout.ActivateOptions();
RollingFileAppender roller = new RollingFileAppender();
roller.AppendToFile = false;
roller.File = #"Logs\EventLog.txt";
roller.Layout = patternLayout;
roller.MaxSizeRollBackups = 5;
roller.MaximumFileSize = "1GB";
roller.RollingStyle = RollingFileAppender.RollingMode.Size;
roller.StaticLogFileName = true;
roller.ActivateOptions();
hierarchy.Root.AddAppender(roller);
MemoryAppender memory = new MemoryAppender();
memory.ActivateOptions();
hierarchy.Root.AddAppender(memory);
hierarchy.Root.Level = Level.Info;
hierarchy.Configured = true;
}
}
}
And then all I had to do was replace the code where I called the XML file with the following call:
//XmlConfigurator.Configure(new FileInfo("app.config")); // Not needed anymore
Logger.Setup();
1(this answer was edited into the question by the OP, I took the liberty to make it a community answer, see here why)
You can also escape XML completely, I wrote a sample with minimal programmatic configuration here.
In a nutshell, here is what you need
var tracer = new TraceAppender();
var hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Root.AddAppender(tracer);
var patternLayout = new PatternLayout {ConversionPattern = "%m%n"};
patternLayout.ActivateOptions();
tracer.Layout = patternLayout;
hierarchy.Configured = true;
Yes, you can configure log4net by calling:
log4net.Config.XmlConfigurator.Configure(XmlElement element)
See the log4net documentation.
Alternatively you could create a custom attribute that inherits from log4net.Config.ConfiguratorAttribute and hard-code you configuration there:
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using log4net.Repository;
using log4net.Repository.Hierarchy;
using System;
using System.Reflection;
namespace ConsoleApplication1
{
[AttributeUsage(AttributeTargets.Assembly)]
public class MyConfiguratorAttribute : ConfiguratorAttribute
{
public MyConfiguratorAttribute()
: base(0)
{
}
public override void Configure(Assembly sourceAssembly, ILoggerRepository targetRepository)
{
var hierarchy = (Hierarchy)targetRepository;
var patternLayout = new PatternLayout();
patternLayout.ConversionPattern = "%date [%thread] %-5level %logger - %message%newline";
patternLayout.ActivateOptions();
var roller = new RollingFileAppender();
roller.AppendToFile = false;
roller.File = #"Logs\EventLog.txt";
roller.Layout = patternLayout;
roller.MaxSizeRollBackups = 5;
roller.MaximumFileSize = "1GB";
roller.RollingStyle = RollingFileAppender.RollingMode.Size;
roller.StaticLogFileName = true;
roller.ActivateOptions();
hierarchy.Root.AddAppender(roller);
hierarchy.Root.Level = Level.Info;
hierarchy.Configured = true;
}
}
}
Then add the following to a .cs file:
[assembly: ConsoleApplication1.MyConfigurator]
For those who don't want to add appender to Root logger, but to current/other logger:
//somewhere you've made a logger
var logger = LogManager.GetLogger("MyLogger");
// now add appender to it
var appender = BuildMyAppender();
((log4net.Repository.Hierarchy.Logger)logger).AddAppender(appender);
logger.Debug("MyLogger with MyAppender must work now");
// and remove it later if this code executed multiple times (loggers are cached, so you'll get logger with your appender attached next time "MyLogger")
((log4net.Repository.Hierarchy.Logger)logger).RemoveAppender(sbAppender);
Although the accepted answer works in most cases, It has a few drawbacks.
It only keeps 5 last logs.
Log size is set to 1GB which is too large for most notepad applications to open.
Since it locks the log file, it is not suitable for multi-threaded apps such as web applications.
Since date is prefixed to the file name it is not convenient to use in windows
It overwrites the log each time application re-starts which is again not suitable if you are planning to keep the logs.
As mentioned in comments, it needs some modifications to make it work correctly in some cases.
Thus the little more extensive configuration. I created a class which makes logging a little easier. Obviously you can just pick the configuration part if you wish.
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
using System.Web;
public enum LogType { Info, Debug, Warn, Error, Fatal };
public class Logger {
private const string LOG_NAMESPACE = "APP_NAME";
private const string LOG_FILENAME_PREFIX = #"D:\Logs\";
private static readonly Level LOG_LEVEL = Level.Debug;
private static readonly ILog log = LogManager.GetLogger(LOG_NAMESPACE);
#region static Logger()
// Static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced
static Logger() {
Hierarchy hierarchy = (Hierarchy) LogManager.GetRepository();
hierarchy.Root.RemoveAllAppenders(); // Clear all previously added repositories.
hierarchy.Root.Level = LOG_LEVEL; // Set Log level
PatternLayout layout = new PatternLayout() { ConversionPattern = "%d{yyyy-MM-dd HH:mm:ss.fff} %4t %-5p %m%n" }; // Log line format: Include millisecond precision, thread ID, Log type,
layout.ActivateOptions(); // Apply Configuration
RollingFileAppender RFA = new RollingFileAppender();
RFA.Name = LOG_NAMESPACE; // Set name of appender
RFA.File = LOG_FILENAME_PREFIX + LOG_NAMESPACE; // Set file name prefix
RFA.LockingModel = new FileAppender.MinimalLock(); // Minimum lock time required, makes file available for reading
RFA.AppendToFile = true; // Do not overwrite existing logs, append to them.
RFA.DatePattern = ".yyyy.MM.dd'.log'"; // Add file extension here, to preserve the file extension
RFA.Encoding = Encoding.UTF8; // Set format of file to UTF8 for international characters.
RFA.CountDirection = 1; // Increment file name in bigger number is newest, instead of default backward.
RFA.MaximumFileSize = "100MB"; // Maximum size of file that I could open with common notepad applications
RFA.RollingStyle = RollingFileAppender.RollingMode.Composite; // Increment file names by both size and date.
RFA.StaticLogFileName = false;
RFA.MaxSizeRollBackups = -1; // Keep all log files, do not automatically delete any
RFA.PreserveLogFileNameExtension = true; // This plus extension added to DatePattern, causes to rolling size also work correctly
RFA.Layout = layout;
RFA.ActivateOptions(); // Apply Configuration
hierarchy.Root.AddAppender(RFA);
BasicConfigurator.Configure(hierarchy); // Apply Configuration
}
#endregion
#region public static int Log(...)
public static void Log(string Description, LogType logtype = LogType.Info) {
switch (logtype) {
case LogType.Debug:
log.Debug(Description);
break;
case LogType.Info:
log.Info(Description);
break;
case LogType.Warn:
log.Warn(Description);
break;
case LogType.Error:
log.Error(Description);
break;
case LogType.Fatal:
log.Fatal(Description);
break;
}
}
#endregion
#region public static int Log(...)
public static void Log(string Message, Exception ex) {
log.Fatal(Message, ex);
}
#endregion
}
And then to log messages and exceptions call it like following
Logger.Log("I was here", LogType.Debug);
Logger.Log("I am info message");
Logger.Log("An error", LogType.Error);
Logger.Log("An Exception", ex); // ex is of type Exception
Due to the weird way log4net adds rolling data to file name (added after file extension) files loose windows explorer association. To fix that .log was added to DatePattern instead. It will also correctly adds file increment before extension (maybe because of a bug) Tested on version 1.2.11.0
Notes:
No external call for initialization is required, this will initialize on application start (or when you call Logger.Log for the first time
You can move all class constants out to your own config file and make this class more generic and reusable.
Note that no namespace is specified, this will make Logger class available in all namespaces.
Log4net provides 5 methods to log messages which is a little inconvenient to remember. Thus Logger.Log method by default uses info.
An Unrelated note: If your application is running on a server or web, keep your log files off the OS drive and application folder.
The accepted answer works after I found two caveats:
It was not working for me at first, but after using a full absolue path for the roller.File property, it started work.
I had to use this in F# (in a fsx script), so had some issues when converting it from C#. If you're interested in the end result (including a way to download log4net nuget package), see below:
nuget_log4net.fsx:
#!/usr/bin/env fsharpi
open System
open System.IO
open System.Net
#r "System.IO.Compression.FileSystem"
open System.IO.Compression
type DummyTypeForLog4Net () =
do ()
module NetTools =
let DownloadNuget (packageId: string, packageVersion: string) =
use webClient = new WebClient()
let fileName = sprintf "%s.%s.nupkg" packageId packageVersion
let pathToUncompressTo = Path.Combine("packages", packageId)
if (Directory.Exists(pathToUncompressTo)) then
Directory.Delete(pathToUncompressTo, true)
Directory.CreateDirectory(pathToUncompressTo) |> ignore
let fileToDownload = Path.Combine(pathToUncompressTo, fileName)
let nugetDownloadUri = Uri (sprintf "https://www.nuget.org/api/v2/package/%s/%s" packageId packageVersion)
webClient.DownloadFile (nugetDownloadUri, fileToDownload)
ZipFile.ExtractToDirectory(fileToDownload, pathToUncompressTo)
let packageId = "log4net"
let packageVersion = "2.0.5"
NetTools.DownloadNuget(packageId, packageVersion)
let currentDirectory = Directory.GetCurrentDirectory()
// https://stackoverflow.com/a/19538654/6503091
#r "packages/log4net/lib/net45-full/log4net"
open log4net
open log4net.Repository.Hierarchy
open log4net.Core
open log4net.Appender
open log4net.Layout
open log4net.Config
let patternLayout = PatternLayout()
patternLayout.ConversionPattern <- "%date [%thread] %-5level %logger - %message%newline";
patternLayout.ActivateOptions()
let roller = RollingFileAppender()
roller.AppendToFile <- true
roller.File <- Path.Combine(currentDirectory, "someLog.txt")
roller.Layout <- patternLayout
roller.MaxSizeRollBackups <- 5
roller.MaximumFileSize <- "1GB"
roller.RollingStyle <- RollingFileAppender.RollingMode.Size
roller.StaticLogFileName <- true
roller.ActivateOptions ()
let hierarchy = box (LogManager.GetRepository()) :?> Hierarchy
hierarchy.Root.AddAppender (roller)
hierarchy.Root.Level <- Level.Info
hierarchy.Configured <- true
BasicConfigurator.Configure(hierarchy)
let aType = typedefof<DummyTypeForLog4Net>
let logger = LogManager.GetLogger(aType)
logger.Error(new Exception("exception test"))

How can I programmatically update log4net configuration file?

I currently have the log4net config in the applications' app.config file, as such:
...
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="logs\Service.log"/>
<appendToFile value="true"/>
<rollingStyle value="Date"/>
<datePattern value="yyyyMMdd"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
</appender>
<logger name="Data.WebService">
<level value="ALL"/>
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="RollingLogFileAppender"/>
</logger>
<logger name="Data.Host.HostService">
<level value="ALL"/>
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="RollingLogFileAppender"/>
</logger>
</log4net>
I know I can read this in via log4net.Config.XmlConfigurator.Configure();, however, I would like to be able to update it via some sort of call as well. I'll be accepting configuration from a web service and, once I've set the new config (currently only log level, but I'm not precluding other things being configurable down the road), I need to update what is in the config file.
Having all of the configs in one file is very convenient, however, I'm open to locating the config in another file if that makes it simpler.
Since there is no official method to do so, I wound up writing a method that uses xpath to locate the element(s) to change and then update accordingly. Works well enough for what I need to do and is more elegant than a brute-force "readinthefiletoastringthenreplacethetextthensavethestringtothefile" approach.
public enum Log4NetConfigItem
{
LOGLEVEL
}
public const string LOG4NET_CONFIGFILE = "log4net.config";
public void UpdateConfiguration(Log4NetConfigItem which, object value)
{
// Load the config file.
XmlDocument doc = new XmlDocument();
doc.Load(LOG4NET_CONFIGFILE);
// Create an XPath navigator for the document.
XPathNavigator nav = doc.CreateNavigator();
try
{
XPathExpression expr;
// Compile the correct XPath expression for the element we want to configure.
switch (which)
{
default:
case Log4NetConfigItem.LOGLEVEL:
// Compile a standard XPath expression
expr = nav.Compile("/configuration/log4net/logger/level");
break;
}
// Locate the node(s) defined by the XPath expression.
XPathNodeIterator iterator = nav.Select(expr);
// Iterate on the node set
while (iterator.MoveNext())
{
XPathNavigator nav2 = iterator.Current.Clone();
// Update the element as required.
switch (which)
{
default:
case Log4NetConfigItem.LOGLEVEL:
// Update the 'value' attribute for the log level.
SetAttribute(nav2, String.Empty, "value", nav.NamespaceURI, value.ToString());
break;
}
}
// Save the modified config file.
doc.Save(LOG4NET_CONFIGFILE);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void SetAttribute(System.Xml.XPath.XPathNavigator navigator, String prefix, String localName, String namespaceURI, String value)
{
if (navigator.CanEdit == true)
{
// Check if given localName exist
if (navigator.MoveToAttribute(localName, namespaceURI))
{
// Exist, so set current attribute with new value.
navigator.SetValue(value);
// Move navigator back to beginning of node
navigator.MoveToParent();
}
else
{
// Does not exist, create the new attribute
navigator.CreateAttribute(prefix, localName, namespaceURI, value);
}
}
}
Note: The SetAttribute code I got from here.
This is basically this open issue for log4net - unfortunately it is not resolved yet.

Categories

Resources