144

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.

Shog9
  • 156,901
  • 35
  • 231
  • 235
Michael Mankus
  • 4,628
  • 9
  • 37
  • 63
  • Related post - [Configuring log4net with xml file](https://stackoverflow.com/q/1321261/465053) – RBT Sep 15 '21 at 05:37

7 Answers7

239

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)

Community
  • 1
  • 1
Philipp M
  • 1,877
  • 7
  • 27
  • 38
  • 2
    Just a note if you use variables in your roller.File string you can use the log4net.Util.PatternString class to format it before assigning the result to foller.File. – Eric Scherrer Sep 29 '15 at 18:29
  • 2
    I ended up getting duplicate log entries using this method. I solved it by adding "hierarchy.Root.RemoveAllAppenders();" to the beginning of the Setup(). – Philip Jun 13 '16 at 10:15
  • 6
    From all this, how do i get the ILog ? – Mickey Perlstein Oct 06 '16 at 11:58
  • @MickeyPerlstein private static readonly ILog Log = LogManager.GetLogger(typeof(YourType)); – Chris Berry Nov 25 '16 at 23:36
  • How to read the same log file? When i tried to read, it gave the error of `The process cannot access the file because it is being used by another process`. – Pritam Feb 21 '17 at 10:39
  • 6
    Doesn't work for me unless I call `BasicConfigurator.Configure(hierarchy);` instead of just setting `hierarchy.Configured = true;`. – Eivind Gussiås Løkseth Apr 05 '17 at 14:49
  • @PhilipBergström you're my hero! Finally no more duplicated log output, phew! – Søren Boisen Mar 14 '18 at 17:09
  • 1
    Trying to use this for Godot engine logging but I'm getting nothing in the logfile. The logfile does get created though. I'm using @EivindGussiåsLøkseth idea but still didn't work.. – codah Jun 16 '18 at 02:13
  • 3
    Make sure the process that you expect to write to the log file has write permissions in the folder to which you want the log file to be written. To diagnose log4net problems add `log4net.Util.LogLog.InternalDebugging = true;` before any other log4net call, then run under debugger and inspect the output. log4net will tell you where things go wrong. – Manfred Jul 05 '18 at 23:20
  • @codah were you able to solve your problem? I am facing the exact same issue – Raj Kamal Jun 28 '19 at 04:46
  • @RajKamal sorry can't remember, haven't used Godot for a long time – codah Jun 29 '19 at 07:26
  • @codah no need. In my case, I needed to create and add [assembly: Repository()] to ensure that log4net uses my configuration. instead of default config – Raj Kamal Jul 02 '19 at 16:56
  • Rough F# translation: https://gist.github.com/akeeton/e97e0eac3a6723e1ff67ca7f3fd47c0c – Andrew Keeton Mar 20 '20 at 15:04
12

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;
inwenis
  • 368
  • 7
  • 24
oleksii
  • 35,458
  • 16
  • 93
  • 163
11

Yes, you can configure log4net by calling:

log4net.Config.XmlConfigurator.Configure(XmlElement element)

See the log4net documentation.

Joe
  • 122,218
  • 32
  • 205
  • 338
5

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]
Jas
  • 51
  • 2
  • 2
2

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);
Vladislav Kostenko
  • 1,155
  • 11
  • 18
1

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.
AaA
  • 3,600
  • 8
  • 61
  • 86
0

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"))
ympostor
  • 909
  • 7
  • 16