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

C#.NetLoggingLog4net

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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)

Solution 2 - C#

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;

Solution 3 - C#

Solution 4 - C#

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]

Solution 5 - C#

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);

Solution 6 - C#

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.

Solution 7 - C#

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"))

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichael MankusView Question on Stackoverflow
Solution 1 - C#Philipp MView Answer on Stackoverflow
Solution 2 - C#oleksiiView Answer on Stackoverflow
Solution 3 - C#JoeView Answer on Stackoverflow
Solution 4 - C#JasView Answer on Stackoverflow
Solution 5 - C#Vladislav KostenkoView Answer on Stackoverflow
Solution 6 - C#AaAView Answer on Stackoverflow
Solution 7 - C#ympostorView Answer on Stackoverflow