log4net and Web Configuration Transformations

Introduction

Microsoft puts it best in their documentation:

When you deploy a Web site, you often want some settings in the deployed application’s Web.config file to be different from the development Web.config file.
For example, you might want to disable debug options and change connection strings so that they point to different databases.^1

The project I currently work on has nine environment configurations with a tenth one potentially in the works.

Dev, Test, and Load have two environments each to support two releases.
Initially, we did only have a single instance for each environment, but development teams started halting when those environments were going through the deployment process.
While one is being finalized (bug-fixes found in a higher environment, stage-gate-reviews, security scanning, etc.) development on the next release can continue without impeding other teams.

User Acceptance Testing (UAT), Train, and Prod only have a single environment because they are focusing on accepting a single release at a time.

As far as I know Demo is no longer operational and should probably be removed.
Although, a new environment called Diag has servers set up but none of the configuration information has been communicated to the development team - yet.

log4net Configuration Transformations

log4net is a logging framework for .NET.
The log4net webpage describes it as:

The Apache log4net library is a tool to help the programmer output log statements to a variety of output targets.
log4net is a port of the excellent Apache log4j™ framework to the Microsoft® .NET runtime.
We have kept the framework similar in spirit to the original log4j while taking advantage of new features in the .NET runtime.
For more information on log4net see the features document.

Honestly, log4net is not my preferred logging library; (mostly) due to performance concerns.
Although, the article’s publish date is January 12, 2016 - which is a lifetime for a software application - meaning it is possible these concerns have been addressed and resolved.
However, checking log4net‘s NuGet Page reveals that only three versions have been released since the article was published.
This does not invoke confidence that the performance concerns have been addressed and resolved.

Unfortunately, that does not help, log4net v1.2.10 (release January 7th, 2011) is the version required by the dependency assemblies embedded as part of the applications.
There is no choice but to accept log4net as a dependency in this project - for the time being.

These dependency assemblies must have log4net.config files - for each environment configuration.

When we took over the project, all of the log4net.config files were bundled as part of the release and the environment’s web.config file had an appSetting that was used to select the proper log4net.config file for that environment.

The purpose in describing this is to illustrate why I set out to make the log4net.config files in my project behave like web.config files.

Setup

To start, I created a base file called log4net.Template.config (more on ‘why’ later).
This file will hold the initial configuration information that can be transformed for each environment.
It is set up something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0"?>
<configuration>
<log4net>
<!-- Appenders Go Here -->

<appender name="ApplicationLogAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="1"/>
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<connectionString value="server=(local)\MSSQLSERVER2012;uid=user;pwd=password;database=database"/>
<reconnectonerror value='true' />
<commandText value="INSERT INTO application_log (
server_name,
log_date,
thread_number,
log_message,
exception
)
VALUES
('${COMPUTERNAME}', @log_date, @thread_number, @log_message, @exception)"/>
<parameter>
<parameterName value="@log_date"/>
<dbType value="DateTime"/>
<layout type="log4net.Layout.RawTimeStampLayout">
</layout>
</parameter>
<parameter>
<parameterName value="@thread_number"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout" value="%thread"/>
</parameter>
<parameter>
<parameterName value="@log_message"/>
<dbType value="AnsiString"/>
<size value="8000"/>
<layout type="log4net.Layout.PatternLayout" value="%message"/>
</parameter>
<parameter>
<parameterName value="@exception"/>
<dbType value="AnsiString"/>
<size value="8000"/>
<layout type="log4net.Layout.ExceptionLayout">
</layout>
</parameter>
</appender>

<!-- Root Element Goes Here -->

<!-- Loggers Go Here -->
</log4net>
</configuration>

Then a separate log4net.config file is created for each environment (such as log4net.Debug.config) and nested under the base configuration.

These configurations can remove pieces of the base template when transformed like so:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<log4net>
<appender
name="ApplicationLogAppender"
type="log4net.Appender.AdoNetAppender"
xdt:Locator="Match(name)"
xdt:Transform="Remove" />
</log4net>
</configuration>

Or, the configuration can change specific settings - like the appender‘s type or connection string:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<log4net>
<appender
name="ApplicationLogAppender"
type="log4net.Appender.AdoNetAppender"
xdt:Locator="Match(name)">
<connectionString
value="server=(local);uid=user;pwd=password;database=database"
xdt:Transform="Replace" />
</appender>
</log4net>
</configuration>

Finally, an empty log4net.config file is created in the project.
The file is included as part of the project so that it can have a Build Action associated to it, be ‘built’, and be included as part of any artifacts generated from the process.
The contents of this file will be overwritten with the environment configuration selected, which is why a log4net.Template.config file is used as the base instead of log4net.config.
This will prevent the base from being lost after the first transform is applied.
It is best to leave the contents blank and exclude any additional changes from source control to preserve a clean source control history.

The end result looks something like this:

The next step is to get Visual Studio and/or the build server to apply the transforms that were set up.

Transforming

Visual Studio does not apply transformations for any configuration file in the project/solution - except the web.config file (and even then that is only when special circumstances have been met that will be mentioned in the next section).
To get Visual Studio to transform the log4net.config file, a build step must be configured for the project.

There are two ways this can be done.

  1. Project Build Events
  2. Project Build Targets

Project Build Events

Use build events to specify commands that run before the build starts or after the build finishes.
Build events are executed only if the build successfully reaches those points in the build process.^2

Project build events are configured by right clicking on the StartUp project in the Solution Explorer and selecting properties (Alt+Enter when project is highlighted).

In the window that appears, navigate to the Build Events tab.

This method has the advantage of only performing the Post-Build Event when a selected criteria is met.

Project Build Targets

The other method is to create MSBuild Targets.

MSBuild includes several .targets files that contain items, properties, targets, and tasks for common scenarios.
These files are automatically imported into most Visual Studio project files to simplify maintenance and readability.^3

Visual Studio

To do this, the StartUp project in the Solution Explorer must be Unloaded (some versions of Visual Studio may allow the project to be edited directly):

This allows the project to be Edited in Visual Studio (with the benefit of syntax highlighting):

When all changes have been made, the project can be Reloaded:

Text Editor

Alternatively, it can be opened in an external text editor (may have limited syntax highlighting):

The project will re-load every time Visual Studio becomes the active window and detects that changes were made to the project.

Transform Steps

Each method has pros and cons - my project opted for the Project Build Targets method which will be outlined here.
Regardless of the chosen method, the same tasks need to be run:

  1. TransformXml
  2. [Copy]
  3. [Delete]
1
2


The first one is obvious, and may seem like it should be the only one that needs to be done.

Regardless of the method chosen, the same steps need to be applied.
My project opted to use the Project Build Targets method.

Web Configuration Transformations

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×