Friday, October 3, 2014

Logging to SQL Server with Log4Net

How do you know what’s happening on your production servers? Logging off course (if you wonder; no, ‘debug & breakpoints’ is never the correct answer. Never ever. Ever.).

We have been using Log4Net as our logging tool for 3-4 years by now and I just wanted to share how we are using it and how incredibly powerful good logging can be.

First of all, if you are not familiar with Log4Net it is an open source, free-for-use logging framework under the Apache Foundation umbrella. Among its strengths is that it is fairly easy to get started with, it has a low impact on the application performance and it has a lot of adapters that lets you log to a lot of different destinations (console, file, database, event log, etc).

At the beginning we set up logging to console (for those systems that had console output) and file, but after a while we added logging to SQL Server. It is the combination of logs stored in a SQL database and full-text indexing of these logs that really gives us eyes in to what happens on our production servers.

Log to console

Logging to console is definitely the easiest way to get started with Log4Net. But writing to the console output is also the one that gives you least payback in form of long-term insight into your production systems. Log4Net can be configured either using xml or code, but xml is by far the most used. Typically you do the xml configuration in your app/web.config, but you can also keep the Log4Net configuration in separate xml files if you prefer. We chose the app/web.config approach and so the xml for console logging looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section
name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net" />
</configSections>
<log4net>
<root>
<level
value="DEBUG" />
<appender-ref
ref="ConsoleAppender" />
</root>
<appender
name="ConsoleAppender"
type="log4net.Appender.ConsoleAppender">
<layout
type="log4net.Layout.PatternLayout">
<param
name="ConversionPattern"
value="%d [%t] %-5p [%x] - %m%n" />
</layout>
<filter
type="log4net.Filter.LevelRangeFilter">
<param
name="LevelMin"
value="DEBUG" />
<param
name="LevelMax"
value="FATAL" />
</filter>
</appender>
</log4net>
</configuration>


You can do this configuration in code as well, but the great benefit of using xml for the configuration is that you can change the settings (for instance the log level threshold) without re-deploying your application. In the case of web hosts you can even change it without restarting the application. If you’ve been a good boy/girl and set up debug-level logging in your code, you can just flip an xml-switch and additional log entries will start flowing in.


Log to file


If you want your logs to survive application restarts (and the console window buffer) and/or have an application that doesn’t have console output, logging to file would be the next step on logging ladder.

The main thing to keep in mind when logging to file is to set limits on how large each log file can get. Log4Net has some defaults that might not suit your situation so be sure to check out the documentation on how you can configure logging to file. For one of our systems we chose to have a 10 mb limit on each file which you can see in this xml config:

<log4net>
<root>
<level value="DEBUG" />
<appender-ref ref="LogFileAppender" />
</root>
<appender
name="LogFileAppender"
type="log4net.Appender.RollingFileAppender">
<param
name="File"
value="logs.txt" />
<param
name="AppendToFile"
value="true" />
<!-- Logfiles are rolled over to backup files when size limit is reached -->
<rollingStyle
value="Size" />
<!-- Maximum number of backup files that are kept before the oldest is erased -->
<maxSizeRollBackups
value="10" />
<!-- Maximum size that the output file is allowed to reach before being rolled over to backup files -->
<maximumFileSize
value="10MB" />
<!-- Indicating whether to always log to the same file -->
<staticLogFileName
value="true" />
<layout type="log4net.Layout.PatternLayout">
<param
name="ConversionPattern"
value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n" />
</layout>
</appender>
</log4net>



The above config specifies that maximum 100 mb of logs will be kept on file (10 mb pr file and max 10 files).


Log to console and file


There is no problem logging to both console and file simultaneously and you can even set different log levels on each appender. If you want to have different files for different log levels (e.g. ‘debug.log’, ‘info.log’, etc), you can just configure as many file appenders as you need. Here is an example of logging to both console and file at the same time:

<log4net>
<root>
<level value="INFO" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="WARN" />
<param name="LevelMax" value="FATAL" />
</filter>
...
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
...
</appender>
</log4net>


The default log level is set to INFO, which means that unless otherwise specified in the appenders, messages with level INFO, WARN, ERROR and FATAL will be logged. The file appender is set to only log WARN, ERROR and FATAL though.


Log to SQL Server


As already mentioned the logging to file and console is easy to get started with and does not take much effort to set up. Setting up logging to a database takes a bit more work, but it is far from difficult. Here is how we configured logging to a SQL database from one of our web hosts:

<root>
<level value="DEBUG" />
<appender-ref ref="AdoNetAppender" />
</root>
<appender
name="AdoNetAppender"
type="log4net.Appender.AdoNetAppender">
<threshold>INFO</threshold>
<bufferSize
value="50" />
<connectionType
value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString
value="data source=SERVERNAME;initial catalog=DATABASE;integrated security=false;persist security info=True;User ID=USERNAMEN;Password=PASSWORD" />
<commandText
value="INSERT INTO Log ([Date],[Thread],[Source],[Level],[Logger],[Message],[Exception],[HostName]) VALUES (@log_date, @thread, 'LOG SOURCE',@log_level, @logger, @message, @exception, @hostname)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@hostname" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%property{log4net:HostName}" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="-1" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="-1" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
</log4net>


The xml config is the same whether you are configuring logging in web- or app.config (you need to insert your own values for servername, database and login).

The main thing to point out here is the ‘buffer’ element, which tells Log4Net how many log entries to buffer up before writing them the database. There isn’t any correct number here and you need to figure out what suits your environment the best. The trade-offs are performance versus reliability, since a low buffer will take more resources because of the many writes to the database table (and yes, we learned that the hard way off course). A high buffer limit will be less reliable because if your application crashes, the logs not yet written will never be written.

Also; it might make sense to have different buffer limits for different environments. In the development and test/QA environments, a low limit might be preferable since the logs will be written faster to the database. And since the number of log entries will be far less than in the production system, it might be long time to wait for the logs to be available if you run with the same limits as in production. In a production environment, instant logs are in most cases not relevant and performance is more critical. Then again, reliability is also a good thing so you need to find a good trade off.

Another thing to notice is that we have a lot of subsystems (web hosts, windows services, message bus, cron jobs, etc) that logs to the database. To know where the logs come from we add the ‘LOG SOURCE’ as the name of the subsystem where the config is defined in (e.g ‘CommandsHost’ as the web host that receives commands from our application).

To get the logs into a database, you will need to create a table that matches the log entry that you have defined in the appender config. Here is the t-sql to create a table that matches the above config:

CREATE TABLE [dbo].[Logs](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Date] [datetime] NOT NULL,
[Thread] [varchar](255) NOT NULL,
[Level] [varchar](50) NOT NULL,
[Logger] [varchar](255) NOT NULL,
[Message] [nvarchar](max) NOT NULL,
[Exception] [nvarchar](max) NULL,
[Source] [varchar](100) NULL,
[HostName] [nvarchar](255) NULL
CONSTRAINT [PK_Log] PRIMARY KEY CLUSTERED
(
[Id] ASC
)


Xml transforms


Using xml transforms is an easy way to set up different settings for different environments. For web projects this is built into Visual Studio and MSBuild/MSDeploy, so the tooling support for this is pretty good. The only caveat is that the transformation is only run during deployment – not during the build. So if your switching between different build configs in Visual Studio, the web host on your dev machine will only use the web.config – not any of the web.debug.config, web.release.config, etc (unless you are actually deploying to your local IIS).

If you are developing a console/WPF/WebForms application you still can take advantage of the same xml transform as web projects, but the tooling is not built into Visual Studio or MSBuild/MSDeploy. There is however an excellent free tool (VS extension) called SlowCheetah developed by Sayed Ibrahim Hashimi that will do this for you. You can download it as a Visual Studio extension, and it has an extra gem that Visual Studio doesn’t have; transformation preview.

SQL Server Full-Text search


The real power when it comes to database log entries is when you pair it with full-text searching. Full-text search will require quite a bit of resources in the form of hardware (disk, memory, cpu), but you don’t have to (and shouldn’t) set up the full-text indexing on your production database server. Instead you should set up log shipping in SQL Server (or some other form of pulling the logs off your production servers) and then do your full-text indexing and searching on a separate database server.


Pair full-text search of logs with a message based (event driven) system, and you have an incredible insight to your production system and an invaluable, searchable history.



Resources

Log4Net: http://logging.apache.org/log4net/


SlowCheetah: https://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5

Monday, August 11, 2014

Getting started with Powershell Desired State Configuration (DSC)

I wanted to try out the DSC in Powershell 4.0 on my Windows 8.1 Pro machine, but I got stuck on the ‘getting started’ part. I just couldn’t figure out how to generate the actual configuration files (.mof-files).

I google around quite a bit before I finally got it; the configuration file that you create is off course just like a normal script file in the sense that it doesn’t actually do anything. All it does is to define a function that you need to call!

So in order to actually generate the .mof-files, I ‘dot-sourced’ the script into the current session and called the function from the ps1-file.

Here’s an example configuration file called ‘demoConfig.ps1’:

configuration Demo
{
Node localhost
{
File TestFiles
{
SourcePath = "c:\temp\test.txt"
DestinationPath = "c:\temp\testdir"
Ensure = "Present"
Type = "File"
}
}
}

And to generate the .mof files:

PS C:\temp> . .\demoConfig.ps1
PS C:\temp> Demo

Tuesday, June 25, 2013

T-SQL joins

What's the difference between an inner and full join in T-SQL? Or a right versus left join? I never have this at the top of my head when I need it, so for future references I've assembled a little example that shows the resulting difference between them.

Given the following t-sql:

declare @t1 table (id int)
declare @t2 table (id int)
 
insert into @t1 values(1),(2),(3)
insert into @t2 values(3),(4)
 
select 't1' as 'Table name', * from @t1
select 't2' as 'Table name', * from @t2
 
select 'inner join' as 'Join', t1.id as 'Left', t2.id as 'Right'
from @t1 as t1 inner join @t2 as t2 on t1.id = t2.id
 
select 'left join' as 'Join', t1.id as 'Left', t2.id as 'Right'
from @t1 as t1 left join @t2 as t2 on t1.id = t2.id
 
select 'right join' as 'Join', t1.id as 'Left', t2.id as 'Right'
from @t1 as t1 right join @t2 as t2 on t1.id = t2.id
 
select 'full join' as 'Join', t1.id as 'Left', t2.id as 'Right'
from @t1 as t1 full join @t2 as t2 on t1.id = t2.id

 

This is the result from the joins:

image