Tutorial :How to configure log4net programmatically from scratch (no config)


This is a Bad Idea, I know, but... I want to configure log4net programmatically from scratch with no config file. I'm working on a simple logging application for me and my team to use for a bunch of relatively small departmental applications we're responsible for. I want them to all log to the same database. The logging application is just a wrapper around log4net with the AdoNetAppender preconfigured.

All of the applications are ClickOnce deployed, which presents a small problem with deploying the config file. If the config file were part of the core project, I could set its properties to deploy with the assembly. But it's part of a linked application, so I don't have the option of deploying it with the main application. (If that's not true, somebody please let me know).

Probably because it's a Bad Idea, there doesn't seem to be much sample code available for programmatically configruating log4net from scratch. Here's what I have so far.

Dim apndr As New AdoNetAppender()  apndr.CommandText = "INSERT INTO LOG_ENTRY (LOG_DTM, LOG_LEVEL, LOGGER, MESSAGE, PROGRAM, USER_ID, MACHINE, EXCEPTION) VALUES (@log_date, @log_level, @logger, @message, @program, @user, @machine, @exception)"  apndr.ConnectionString = connectionString  apndr.ConnectionType = "System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  apndr.CommandType = CommandType.Text  Dim logDate As New AdoNetAppenderParameter()  logDate.ParameterName = "@log_date"  logDate.DbType = DbType.DateTime  logDate.Layout = New RawTimeStampLayout()  apndr.AddParameter(logDate)  Dim logLevel As New AdoNetAppenderParameter()  logLevel.ParameterName = "@log_level"  'And so forth...  

After configuring all the parameters for apndr, I at first tried this...

Dim hier As Hierarchy = DirectCast(LogManager.GetRepository(), Hierarchy)  hier.Root.AddAppender(apndr)  

It didn't work. Then, as a shot in the dark, I tried this instead.


That didn't work either. Does anybody have any good references on how to configure log4net programmatically from scratch with no config file?


One way I've done this in the past is to include the configuration file as an embedded resource, and just used log4net.Config.Configure(Stream).

That way, I could use the configuration syntax I was familiar with, and didn't have to worry about getting a file deployed.


Here's an example class that creates log4net config completely in code. I should mention that creating a logger via a static method is generally viewed as bad, but in my context, this is what I wanted. Regardless, you can carve up the code to meet your needs.

using log4net;  using log4net.Repository.Hierarchy;  using log4net.Core;  using log4net.Appender;  using log4net.Layout;    namespace dnservices.logging  {  public class Logger  {      private PatternLayout _layout = new PatternLayout();      private const string LOG_PATTERN = "%d [%t] %-5p %m%n";        public string DefaultPattern      {          get { return LOG_PATTERN; }      }        public Logger()      {          _layout.ConversionPattern = DefaultPattern;          _layout.ActivateOptions();      }        public PatternLayout DefaultLayout      {          get { return _layout; }      }        public void AddAppender(IAppender appender)      {          Hierarchy hierarchy =               (Hierarchy)LogManager.GetRepository();            hierarchy.Root.AddAppender(appender);      }        static Logger()      {          Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();          TraceAppender tracer = new TraceAppender();          PatternLayout patternLayout = new PatternLayout();            patternLayout.ConversionPattern = LOG_PATTERN;          patternLayout.ActivateOptions();            tracer.Layout = patternLayout;          tracer.ActivateOptions();          hierarchy.Root.AddAppender(tracer);            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 = "dnservices.txt";          roller.ActivateOptions();          hierarchy.Root.AddAppender(roller);            hierarchy.Root.Level = Level.All;          hierarchy.Configured = true;      }        public static ILog Create()      {          return LogManager.GetLogger("dnservices");      }  }  



More concise solution:

var layout = new PatternLayout("%-4timestamp [%thread] %-5level %logger %ndc - %message%newline");  var appender = new RollingFileAppender {      File = "my.log",      Layout = layout  };  layout.ActivateOptions();  appender.ActivateOptions();  BasicConfigurator.Configure(appender);  

Don't forget to call ActivateOptions method:

The ActivateOptions method must be called on this object after the configuration properties have been set. Until ActivateOptions is called this object is in an undefined state and must not be used.


As Jonathan says, using a resource is a good solution.

It's a bit restrictive in that the embedded resource contents will be fixed at compile time. I have a logging component that generates an XmlDocument with a basic Log4Net configuration, using variables defined as appSettings (e.g. filename for a RollingFileAppender, default logging level, maybe connection string name if you want to use an AdoNetAppender). And then I call log4net.Config.XmlConfigurator.Configure to configure Log4Net using the root element of the generated XmlDocument.

Then administrators can customise the "standard" configuration by modifying a few appSettings (typically level, filename, ...) or can specify an external configuration file to get more control.


I can't tell in the question's code snippet if the "'And so forth..." includes the very important apndr.ActivateOptions() which is indicated in Todd Stout's answer. Without ActivateOptions() the Appender is inactive and will not do anything which could explain why it is failing.


Dr. Netjes has this for setting the connectionstring programmatically:

// Get the Hierarchy object that organizes the loggers  log4net.Repository.Hierarchy.Hierarchy hier =     log4net.LogManager.GetLoggerRepository() as log4net.Repository.Hierarchy.Hierarchy;    if (hier != null)  {    //get ADONetAppender    log4net.Appender.ADONetAppender adoAppender =       (log4net.Appender.ADONetAppender)hier.GetLogger("MyProject",        hier.LoggerFactory).GetAppender("ADONetAppender");    if (adoAppender != null)    {      adoAppender.ConnectionString =        System.Configuration.ConfigurationSettings.AppSettings["MyConnectionString"];      adoAppender.ActivateOptions(); //refresh settings of appender    }  }  


A bit late for the party. But here is a minimal config that worked for me.

Sample class

public class Bar  {      private readonly ILog log = LogManager.GetLogger(typeof(Bar));      public void DoBar() { log.Info("Logged"); }  }  

Minimal log4net trace config (inside NUnit test)

[Test]  public void Foo()  {      var tracer = new TraceAppender();      var hierarchy = (Hierarchy)LogManager.GetRepository();      hierarchy.Root.AddAppender(tracer);      var patternLayout = new PatternLayout {ConversionPattern = "%m%n"};      tracer.Layout = patternLayout;      hierarchy.Configured = true;        var bar = new Bar();      bar.DoBar();  }  

Prints to the trace listener

Namespace+Bar: Logged  


// I've embedded three config files as an Embedded Resource and access them like this:

using System;  using System.Collections.Generic;  using System.Linq;  using System.Text;  using System.Resources;  using System.IO;    namespace Loader  {    class Program    {      private static log4net.ILog CustomerLog = log4net.LogManager.GetLogger("CustomerLogging");      private static log4net.ILog OrderLog = log4net.LogManager.GetLogger("OrderLogging");      private static log4net.ILog DetailsLog = log4net.LogManager.GetLogger("OrderDetailLogging");          static void Main(string[] args)      {        // array of embedded log4net config files        string[] configs = { "Customer.config", "Order.config", "Detail.config"};          foreach (var config in configs)        {          // build path to assembly config          StringBuilder sb = new StringBuilder();          sb.Append(System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);          sb.Append(".");          sb.Append(config);            // convert to a stream          Stream configStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(sb.ToString());            // configure logger with ocnfig stream          log4net.Config.XmlConfigurator.Configure(configStream);            // test logging          CustomerLog.Info("Begin logging with: " + config);          OrderLog.Info("Begin logging with: " + config);          DetailsLog.Info("Begin logging with: " + config);          for (int iX = 0; iX < 10; iX++)          {            CustomerLog.Info("iX=" + iX);            OrderLog.Info("iX=" + iX);            DetailsLog.Info("iX=" + iX);          }          CustomerLog.Info("Ending logging with: " + config);          OrderLog.Info("Ending logging with: " + config);          DetailsLog.Info("Ending logging with: " + config);        }        }    }  }  


It is strange that BasicConfigurator.Configure(apndr) did not work. In my case it did its job... But, anyway, here goes the answer - you should've wrote hier.Configured = true; (c# code) after you've finished all setup.


I ended up using this:


After 4 hours fiddling with the config and getting progressively more frustrated.

Hope it helps someone.


Here's a soup-to-nuts example of how you can create and use an AdoNetAdapter entirely in code, completely in the absence of any App.config file (not even for Common.Logging). Go ahead, delete it!

This has the added benefit of being resilient against updates under the new naming conventions, where the assembly name now reflects the version. (Common.Logging.Log4Net1213, etc.)


CREATE TABLE [Log](    [Id] [int] IDENTITY(1,1) NOT NULL,    [Date] [datetime] NOT NULL,    [Thread] [varchar](255) NOT NULL,    [Level] [varchar](20) NOT NULL,    [Source] [varchar](255) NOT NULL,    [Message] [varchar](max) NOT NULL,    [Exception] [varchar](max) NOT NULL  )  


Imports log4net  Imports log4net.Core  Imports log4net.Layout  Imports log4net.Config  Imports log4net.Appender    Module Main    Sub Main()      Dim oLogger As ILog      Dim sInput As String      Dim iOops As Integer        BasicConfigurator.Configure(New DbAppender)      oLogger = LogManager.GetLogger(GetType(Main))        Console.Write("Command: ")        Do        Try          sInput = Console.ReadLine.Trim            Select Case sInput.ToUpper            Case "QUIT" : Exit Do            Case "OOPS" : iOops = String.Empty            Case Else : oLogger.Info(sInput)          End Select          Catch ex As Exception          oLogger.Error(ex.Message, ex)          End Try          Console.Clear()        Console.Write("Command: ")      Loop    End Sub  End Module  


Imports log4net  Imports log4net.Core  Imports log4net.Layout  Imports log4net.Appender  Imports log4net.Repository.Hierarchy    Public Class DbAppender    Inherits AdoNetAppender      Public Sub New()      MyBase.BufferSize = 1      MyBase.CommandText = Me.CommandText        Me.Parameters.ForEach(Sub(Parameter As DbParameter)                              MyBase.AddParameter(Parameter)                            End Sub)        Me.ActivateOptions()    End Sub          Protected Overrides Function CreateConnection(ConnectionType As Type, ConnectionString As String) As IDbConnection      Return MyBase.CreateConnection(GetType(System.Data.SqlClient.SqlConnection), "Data Source=(local);Initial Catalog=Logger;Persist Security Info=True;User ID=username;Password=password")    End Function          Private Overloads ReadOnly Property CommandText As String      Get        Dim _          sColumns,          sValues As String          sColumns = Join(Me.Parameters.Select(Function(P As DbParameter) P.DbColumn).ToArray, ",")        sValues = Join(Me.Parameters.Select(Function(P As DbParameter) P.ParameterName).ToArray, ",")          Return String.Format(COMMAND_TEXT, sColumns, sValues)      End Get    End Property          Private ReadOnly Property Parameters As List(Of DbParameter)      Get        Parameters = New List(Of DbParameter)        Parameters.Add(Me.LogDate)        Parameters.Add(Me.Thread)        Parameters.Add(Me.Level)        Parameters.Add(Me.Source)        Parameters.Add(Me.Message)        Parameters.Add(Me.Exception)      End Get    End Property          Private ReadOnly Property LogDate As DbParameter      Get        Return New DbParameter("Date", DbType.Date, 0, New DbPatternLayout("%date{yyyy-MM-dd HH:mm:ss.fff}"))      End Get    End Property          Private ReadOnly Property Thread As DbParameter      Get        Return New DbParameter("Thread", DbType.String, 255, New DbPatternLayout("%thread"))      End Get    End Property          Private ReadOnly Property Level As DbParameter      Get        Return New DbParameter("Level", DbType.String, 50, New DbPatternLayout("%level"))      End Get    End Property          Private ReadOnly Property Source As DbParameter      Get        Return New DbParameter("Source", DbType.String, 255, New DbPatternLayout("%logger.%M()"))      End Get    End Property          Private ReadOnly Property Message As DbParameter      Get        Return New DbParameter("Message", DbType.String, 4000, New DbPatternLayout("%message"))      End Get    End Property          Private ReadOnly Property Exception As DbParameter      Get        Return New DbParameter("Exception", DbType.String, 2000, New DbExceptionLayout)      End Get    End Property          Private Const COMMAND_TEXT As String = "INSERT INTO Log ({0}) VALUES ({1})"  End Class  


Imports log4net  Imports log4net.Core  Imports log4net.Layout  Imports log4net.Appender  Imports log4net.Repository.Hierarchy    Public Class DbParameter    Inherits AdoNetAppenderParameter      Private ReadOnly Name As String      Public Sub New(Name As String, Type As DbType, Size As Integer, Layout As ILayout)      With New RawLayoutConverter        Me.Layout = .ConvertFrom(Layout)      End With        Me.Name = Name.Replace("@", String.Empty)      Me.ParameterName = String.Format("@{0}", Me.Name)      Me.DbType = Type      Me.Size = Size    End Sub          Public ReadOnly Property DbColumn As String      Get        Return String.Format("[{0}]", Me.Name)      End Get    End Property  End Class  


Imports log4net  Imports log4net.Core  Imports log4net.Layout  Imports log4net.Appender  Imports log4net.Repository.Hierarchy    Public Class DbPatternLayout    Inherits PatternLayout      Public Sub New(Pattern As String)      Me.ConversionPattern = Pattern      Me.ActivateOptions()    End Sub  End Class  


Imports log4net  Imports log4net.Core  Imports log4net.Layout  Imports log4net.Appender  Imports log4net.Repository.Hierarchy    Public Class DbExceptionLayout    Inherits ExceptionLayout      Public Sub New()      Me.ActivateOptions()    End Sub  End Class  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Next Post »