How to redirect the standard console output, to assert logmessages written by log4net.

By using a log4net ConsoleAppender, you can write all log messages in your application to the console. These message will show up in the Microsoft Visual Studio output window. I needed a way to redirect the messages written to the console, so I could verify if the correct messages were send to the console. For this task I redirected the standard console output in my unit test:

 

Code

using System;
using System.IO;
using System.Text;
using log4net;
using log4net.Config;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
// Save original console output writer.
TextWriter originalConsole = Console.Out;
// Configure log4net based on the App.config
XmlConfigurator.Configure();
var builder = new StringBuilder();
using (var writer = new StringWriter(builder))
{
// Redirect all Console messages to the StringWriter.
Console.SetOut(writer);
// Log a debug message.
ILog logger = LogManager.GetLogger("Unittest logger");
logger.Debug("This is a debug message");
}
// Get all messages written to the console.
string consoleOutput = string.Empty;
using (var reader = new StringReader(builder.ToString()))
{
consoleOutput = reader.ReadToEnd();
}
// Assert.
string expected = "This is a debug message" + Environment.NewLine;
Assert.AreEqual(expected, consoleOutput);
// Redirect back to original console output.
Console.SetOut(originalConsole);
}
}
}

App.config

 

<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

Microsoft Visual Studio

 

image

 

I used NuGet to add a reference to the latest log4net build.

How to solve: The type or namespace name ‘log4net’ could not be found in .NET 4.0?

 

If you create a new .NET 4.0 console application in Microsoft Visual Studio .NET 2010 and add a reference to the Log4Net v1.2.10 dll, you can add the using statement [using log4net;], but after a rebuild, Microsoft Visual Studio still reports the error:

Error    29    The type or namespace name ‘log4net’ could not be found (are you missing a using directive or an assembly reference?)

This is caused by the [Target framework] is set to the [.NET Framework 4 Client Profile]. This profile does not include some .NET assemblies found in the Full .NET Framework, that are used by log4net.

image

 

As can be seen with RegGate Reflactor, log4net uses Full .NET Framework assemblies:

image

 

Solution

Set the [Target framework] of your application to [.NET Framework 4].

image

Log4net performance test on [Root > Level], [Threshold], [LevelRangeFilter] and [LevelMatchFilter]

In many cases you want to allow only ERROR logging in production for performance reasons.

I did a little experiment on log4net performance, based on different configurations in the App.config, to try and find out what the best settings are for only allowing ERROR logging in production.

 

First some explanation on the log4net settings used

1. The [Root > Level] is used to only allow logging based on the given value and above for all appenders.

2. The threshold setting is used to allow logging for the given value and above for a specific appender.

3. LevelMatchFiler is used to allow only logging for a specified level for a specific appender.

4. LevelRangeFilter is used to allow only logging between the given values for a specific appender.

 

The App.config with all settings

This App.config shows all settings used, it is not a working App.config.

<?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">
<!-- Allow only ERROR logging for this adapter -->
<threshold value="ERROR"/>
<!-- Allow only ERROR logging for this adapter -->
<filter type="log4net.Filter.LevelMatchFilter">
<acceptOnMatch value="true" />
<levelToMatch  value="ERROR" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<!-- Allow only DEBUG, INFO, WARN and ERROR logging for this adapter -->
<filter type="log4net.Filter.LevelRangeFilter">
<acceptOnMatch value="true"/>
<levelMin value="DEBUG"/>
<levelMax value="ERROR"/>
</filter>
<file value="Log.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="1"/>
<maximumFileSize value="100MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %type.%method - %message%newline"/>
</layout>
</appender>
<appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
<!-- Allow only ERROR logging for this adapter -->
<threshold value="ERROR"/>
<!-- Allow only ERROR logging for this adapter -->
<filter type="log4net.Filter.LevelMatchFilter">
<acceptOnMatch value="true" />
<levelToMatch  value="ERROR" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<!-- Allow only DEBUG, INFO, WARN and ERROR logging for this adapter -->
<filter type="log4net.Filter.LevelRangeFilter">
<acceptOnMatch value="true"/>
<levelMin value="DEBUG"/>
<levelMax value="ERROR"/>
</filter>
<bufferSize value="1"/>
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message]) VALUES (@log_date, @thread, @log_level, @logger, @message)"/>
<connectionString value="Data Source=myserver.dev.nl\dev2008;Initial Catalog=MyDatabase;Integrated Security=SSPI"/>
<parameter>
<parameterName value="@log_date"/>
<dbType value="DateTime"/>
<layout type="log4net.Layout.PatternLayout" value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}"/>
</parameter>
<parameter>
<parameterName value="@thread"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout" value="%thread"/>
</parameter>
<parameter>
<parameterName value="@log_level"/>
<dbType value="String"/>
<size value="50"/>
<layout type="log4net.Layout.PatternLayout" value="%level"/>
</parameter>
<parameter>
<parameterName value="@logger"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout" value="%type.%method"/>
</parameter>
<parameter>
<parameterName value="@message"/>
<dbType value="String"/>
<size value="4000"/>
<layout type="log4net.Layout.PatternLayout" value="%message"/>
</parameter>
</appender>
<root>
<level value="ERROR" />
<appender-ref ref="RollingLogFileAppender" />
<appender-ref ref="ADONetAppender" />
</root>
</log4net>
</configuration>

 

 

The code

public void TestLog4NetPerformance()
{
// Configure Log4Net based on de App.config settings.
XmlConfigurator.Configure();
// Create a Log4Net logger object.
ILog _logger = LogManager.GetLogger("Testing Log4Net on Performance");
// Capture the start date and time
DateTime startDateTime = DateTime.Now;
// Set max for logging callss.
int max = 100000000;
for (int i = 0; i < max; i++)
{
_logger.Debug("This is a log4net debug log entry.");
}
// Capture the end date and time
DateTime endDateTime = DateTime.Now;
// Calculate the time spent on logging.
TimeSpan duration = endDateTime - startDateTime;
// Output to user
Console.WriteLine(string.Format("Total duration in seconds: [{0}s]", (int)duration.TotalSeconds));
}

The Results

1. [Root > Level value=”ERROR”] – 100.000.000 – Total duration in seconds: [17s]

2. [threshold value=”ERROR”] – 100.000.000 – Total duration in seconds: [656s]

3. [LevelMatchFilter levelToMatch=”ERROR”] – 100.000.000 – Total duration in seconds: [716s]

4. [LevelRangeFilter levelMin=”ERROR” levelMax = ERROR] – 100.000.000 – Total duration in seconds: [715s]

 

When a setting was used, all other settings where removed from the app.config.

 

So the winner is: Root > Level!!!!!!

Showing your log4net logging in your C# application

If you want to show the log4net logging direct in your application by using a textbox, you can use the following link:

http://weblogs.asp.net/psteele/archive/2010/01/25/live-capture-of-log4net-logging.aspx

I tried it and it works great:

 

using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using log4net.Appender; using System.Threading; namespace Rvl.Demo.WindowsFormsApplication.UserControls { public partial class SettingsUserControl : UserControl, IAppender {

        private static ILog _logger = LogManager.GetLogger(typeof(SettingsUserControl));
/// <summary>
/// This logger property is used to log events
/// </summary>
public static ILog Logger
{
get { return _logger; }
}

/// <summary>
///
Constructor
/// </summary>
public SettingsUserControl()
{
InitializeComponent();
}
/// <summary>
///
Show progress in outputTextBox
/// </summary>
/// <param name="loggingEvent"></param>
public void DoAppend(log4net.Core.LoggingEvent loggingEvent)
{
outPutTextBox.AppendText(loggingEvent.MessageObject.ToString() + Environment.NewLine);
}
/// <summary>
///
Close the log4net appender
/// </summary>
public void Close()
{
//throw new NotImplementedException();
}
/// <summary>
///
Configure the databases
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ConfigureDatabasesButton_Click(object sender, EventArgs e)
{
try
{
Cursor = Cursors.WaitCursor;

outPutTextBox.Clear();
outPutTextBox.AppendText("Start configuration of the databases" + Environment.NewLine);
Logger.Debug("Sp 1 execute...");
Thread.Sleep(2000);
Logger.Debug("Sp 2 execute...");
Thread.Sleep(2000);
Logger.Debug("Sp 3 execute...");
Thread.Sleep(2000);
Logger.Debug("Sp 4 execute...");
Thread.Sleep(2000);
Logger.Debug("Finished");
}
finally
{
Cursor = Cursors.Default;
}
}
}
}

 

Result

clip_image002

Log4Net error when using a renderer in you’re App.config or Web.config

If you use a Log4Net renderer in you’re App.config, you might get the error:

Error: log4net:ERROR XmlHierarchyConfigurator: Cannot find Property [renderer] to set object on [log4net.Repository.Hierarchy.RootLogger]
log4net:ERROR XmlHierarchyConfigurator: Cannot find Property [renderer] to set object on [log4net.Repository.Hierarchy.RootLogger]

 

Solution

Place the <renderer> tag not in the <root> tag, but in the <log4net> tag.

 

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821" />
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%type.%method() %message%newline" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<param name="LogName" value="Application" />
<applicationName value="Ada.Cdf.Test" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%type.%method()%newline%newline%message%newline" />
</layout>
</appender>
<renderer renderingClass="Ada.Cdf.Logging.ExceptionRenderer, Ada.Cdf" renderedClass="System.Exception" />
<root>
<level value="ALL" />
<appender-ref ref="EventLogAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
</configuration>

Reading and changing Log4Net configuration at runtime

You can read and change the Log4Net configuration at runtime, the following code, shows the eventlogname en eventlogsource for each eventlogappender defined, but if you change the eventLogAppender object properties the configuration will be changed.

            XmlConfigurator.Configure(new FileInfo(string.Format("{0}.config", @"C:\Temp\TestApplication.exe.config")));
var repository = LogManager.GetRepository() as Hierarchy;
if (repository != null)
{
var appenders = repository.GetAppenders();
if (appenders != null)
{
foreach (var appender in appenders)
{
if (appender is EventLogAppender)
{
var eventLogAppender = appender as EventLogAppender;
Console.WriteLine(string.Format("Eventlogname [{0}] and Eventlogsource [{1}] ", eventLogAppender.LogName, eventLogAppender.ApplicationName));
}
}
}
}