© 2011 By: Dov Trietsch. All rights reserved
Logging – A log blog
In a another blog (Missing Fields and Defaults) I spoke about not doing a blog about log files, but then I looked at it again and realized that this is a nice opportunity to show a simple yet powerful tool and also deal with static variables and functions in C#.
My log had to be able to answer a few simple logging rules:
To log or not to log? That is the question – Always log! That is the answer
Do we share a log? Even when a file is opened with a minimal lock, it does not share well and performance greatly suffers. So sharing a log is not a good idea. Also, when sharing, it is harder to find your particular entries and you have to establish rules about retention. My recommendation – Do Not Share!
How verbose? Your log can be very verbose – a good thing when testing, very terse – a good thing in day-to-day runs, or somewhere in between. You must be the judge. In my Blog, I elect to always report a run with start and end times, and always report errors. I normally use 5 levels of logging: 4 – write all, 3 – write more, 2 – write some, 1 – write errors and timing, 0 – write none. The code sample below is more general than that. It uses the config file to set the max log level and each call to the log assigns a level to the call itself. If the level is above the .config highest level, the line will not be written. Programmers decide which log belongs to which level and thus we can set the .config differently for production and testing.
Where do I keep the log? If your career is important to you, discuss this with the boss and with the system admin. We keep logs in the L: drive of our server and make sure that we have a directory for each app that needs a log. When adding a new app, add a new directory. The default location for the log is also found in the .config file
Print One or Many? There are two options here:
1. Print many, Open but once once – you start the stream and close it only when the program ends. This is what you can do when you perform in “batch” mode like in a console app or a stsadm extension.The advantage to this is that starting a closing a stream is expensive and time consuming and because we use a unique file, keeping it open for a long time does not cause contention problems.
2. Print one entry at a time or Open many – every time you write a line, you start the stream, write to it and close it. This work for event receivers, feature receivers, and web parts. Here scalability requires us to create objects on the fly and get rid of them as soon as possible.
A default value of the onceOrMany resides in the .config.
All of the above applies to any windows or web application, not just SharePoint.
So as usual, here is a routine that does it all, and a few simple functions that call it for a variety of purposes.
So without further ado, here is app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral,
ublicKeyToken=b77a5c561934e089" >
<section name="statics.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<statics.Properties.Settings>
<setting name="oneOrMany" serializeAs="String">
<value>False</value>
</setting>
<setting name="logURI" serializeAs="String">
<value>C:\staticLog.txt</value>
</setting>
<setting name="highestLevel" serializeAs="String">
<value>2</value>
</setting>
</statics.Properties.Settings>
</applicationSettings>
</configuration>
And now the code:
In order to persist the variables between calls and also to be able to persist (or not to persist) the log file itself, I created an EventLog class with static variables and functions. Static functions do not need an instance of the class in order to work. If you ever wondered why our Main function is static, the answer is that something needs to run before instantiation so that other objects may be instantiated, and this is what the “static” Main does. The various logging functions and variables are created as static because they do not need instantiation and as a fringe benefit they remain un-destroyed between calls.
The Main function here is just used for testing. Note that it does not instantiate anything, just uses the log functions. This is possible because the functions are static. Also note that the function calls are of the form: Class.Function.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace statics
{
class Program
{
static void Main(string[] args)
{
//write a single line
EventLog.LogEvents("ha ha", 3, "C:\\hahafile.txt", 4, true, false);
//this single line will not be written because the msgLevel is too high
EventLog.LogEvents("baba", 3, "C:\\babafile.txt", 2, true, false);
//The next 4 lines will be written in succession - no closing
EventLog.LogLine("blah blah", 1);
EventLog.LogLine("da da", 1);
EventLog.LogLine("ma ma", 1);
EventLog.LogLine("lah lah", 1);
EventLog.CloseLog(); // log will close
//now with specific functions
EventLog.LogSingleLine("one line", 1);
//this is just a test, the log is already closed
EventLog.CloseLog();
}
}
public class EventLog
{
public static string logURI = Properties.Settings.Default.logURI;
public static bool isOneLine = Properties.Settings.Default.oneOrMany;
public static bool isOpen = false;
public static int highestLevel = Properties.Settings.Default.highestLevel;
public static StreamWriter sw;
/// <summary>
/// the program will "print" the msg into the log
/// unless msgLevel is > msgLimit
/// onceOrMany is true when once - the program will open the log
/// print the msg and close the log. False when many the program will
/// keep the log open until close = true
/// normally all the arguments will come from the app.config
/// called by many overloads of logLine
/// </summary>
/// <param name="msg"></param>
/// <param name="msgLevel"></param>
/// <param name="logFileName"></param>
/// <param name="msgLimit"></param>
/// <param name="onceOrMany"></param>
/// <param name="close"></param>
public static void LogEvents(string msg, int msgLevel, string logFileName, int msgLimit, bool oneOrMany, bool close)
{
//to print or not to print
if (msgLevel <= msgLimit)
{
//open the file. from the argument (logFileName) or from the config (logURI)
if (!isOpen)
{
string logFile = logFileName;
if (logFileName == "")
{
logFile = logURI;
}
sw = new StreamWriter(logFile, true);
sw.WriteLine("Started At: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
isOpen = true;
}
//print
sw.WriteLine(msg);
}
//close when instructed
if (close || oneOrMany)
{
if (isOpen)
{
sw.WriteLine("Ended At: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
sw.Close();
isOpen = false;
}
}
}
/// <summary>
/// The simplest, just msg and level
/// </summary>
/// <param name="msg"></param>
/// <param name="msgLevel"></param>
public static void LogLine(string msg, int msgLevel)
{
//use the given msg and msgLevel and all others are defaults
LogEvents(msg, msgLevel, "", highestLevel, isOneLine, false);
}
/// <summary>
/// one line at a time - open print close
/// </summary>
/// <param name="msg"></param>
/// <param name="msgLevel"></param>
public static void LogSingleLine(string msg, int msgLevel)
{
LogEvents(msg, msgLevel, "", highestLevel, true, true);
}
/// <summary>
/// used to close. high level, low limit, once and close are set
/// </summary>
/// <param name="close"></param>
public static void CloseLog()
{
LogEvents("", 15, "", 1, true, true);
}
}
}
}
That’s all folks!