A simple and complete logger for .net applications






1.71/5 (5 votes)
The very complete logger for .net
Introduction
Here you got a way to make a very good logger for your .NET applications.
Background
I wanted to make a logger with almost all functionality, I mean, getting line numbers, methods, thread ids, etc.Usage Example
First must call theInit
method
// Here is the method signature
public void Init(LogLevel minLevel, string dirPath, string appId, int closeFileMinutes,
long maxFileSize_KB, bool overwriteLogFile)
//Example:
CLog.Instance.Init(LogLevel.NOTICE, "." /*current dir*/,
"myAPpId", 1440 /*a day*/, 4096 /*4 MB */, false);
Now we can send anything to the logger
// Here are the methods signatures
public void Log(LogLevel logLevel, params string[] logData)
public void Log(int stackLevel, LogLevel logLevel, params string[] logData)
//Example:
catch (Exception ex){
CLog.Instance.Log(LogLevel.ERROR, "Error logging in", ex.ToString());
}
If you want to create another logger just add a delegate to the SendLog
event, and do wherever you want inside it (I am not using it, but it is there)
public delegate void DoLog(int stackLevel, LogLevel logLevel, params string[] logData);
public event DoLog SendLog{add;remove}
The Code
The definition of the log levels, very simple
public enum LogLevel
{
DEBUG = 0,
NOTICE,
WARNING,
ERROR,
FATAL,
}
Now a few vars that the logger needs to work
// CLog is a Singleton
public static readonly CLog Instance = new CLog();
private LogLevel m_minLevel; // The min level to be logged
private object m_lockLog; // Critical section
private StreamWriter m_logStream; // Log stream for writing
private long
m_maxFileSize, // Max size in bytes for file
m_currentFileSize; // Current file size
private System.Threading.Timer m_closeTimer; // Timer for reset on time out
private int
m_closeFileTime, // Time to reset (milliseconds)
m_delegateStackLevel, // Fucking delegates!
m_delegateCount; // Again fucking delegates
private bool m_overwriteLogFile; // If false, filename has the created date
// time on his name, if true, the name is
// always the app id
private string
m_logDirectory, // Log directory
m_appId, // Application Id
m_logFileName; // Full path of the log file
private DoLog m_sendLog; // Broadcast to all listening loggers
// (implementing DoLog delegate)
NOTE: Something curious is that if I add a listener for the delegate, CLR uses one stack to call the delegate. But if I add more delegates, it takes 2 stacks to calls that method, I didnt find why. That is why I used the variables m_delegateStackLevel
and m_delegateCount
Init
method:
public void Init(LogLevel minLevel, string dirPath, string appId,
int closeFileMinutes, long maxFileSize_KB, bool overwriteLogFile) {
lock (m_lockLog) {
if (m_logStream == null) {
m_overwriteLogFile = overwriteLogFile;
m_minLevel = minLevel;
m_appId = appId;
m_closeFileTime = closeFileMinutes * 60000;
m_maxFileSize = maxFileSize_KB * 1024;
try {
if (dirPath == String.Empty)
dirPath = ".";
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
DirectoryInfo dirInfo = new DirectoryInfo(dirPath);
m_logDirectory = dirInfo.FullName;
m_logFileName = Path.Combine(m_logDirectory,
String.Concat("Log_", m_appId,
m_overwriteLogFile ? String.Empty :
DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"),
".log"));
m_logStream = new StreamWriter(m_logFileName,
false, Encoding.ASCII);
m_logStream.AutoFlush = true;
m_closeTimer = new System.Threading.Timer(
new TimerCallback(newFile), null,
m_closeFileTime, m_closeFileTime);
Log(2, LogLevel.NOTICE, "Log started,
min log level is: " + minLevel);
}
catch { }
}
}
}
The Log main method
public void Log(int stackLevel, LogLevel logLevel, params string[] logData) {
if (logLevel >= m_minLevel) { // Maybe i had to lock the critical section here
try {
StackFrame sf = new StackFrame(stackLevel, true);
string fileName = sf.GetFileName();
StringBuilder logBuilder = new StringBuilder(
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + ";"
+ logLevel + ";"
+ "Thread:" + Thread.CurrentThread.ManagedThreadId + ";"
+ sf.GetMethod() + ";"
// Never fails, if it cannot get filename, will
// return -1
+ fileName.Substring(
fileName.LastIndexOf(Path.DirectorySeparatorChar) + 1) + ";"
+ "Line:" + sf.GetFileLineNumber()
,1024); // I think that will be enough
foreach (string data in logData) {
logBuilder.Append(";" + data);
}
// Locking as minimum as i can
lock (m_lockLog) {
#if(DEBUG)
Debug.WriteLine(logBuilder);
#endif
m_logStream.WriteLine(logBuilder);
m_currentFileSize += Encoding.ASCII.GetByteCount(
logBuilder.ToString()); // Best way to handle file size
if (m_currentFileSize > m_maxFileSize)
m_closeTimer.Change(0,
m_closeFileTime); // Max file size exceeded,
// reset file immediately
if (m_sendLog != null) {
m_sendLog(stackLevel + m_delegateStackLevel,
logLevel, logData);
}
}
}
catch { } // Only fails if it is called before Init(),
// or if someone misses up the things
}
}
And now the complete class code:
// Logger.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
/*************************************************************************
* Created by Juan Ramirez (ichr@mm)
* juanantonio.ram@gmail.com
*
* You can do wherever you want with that copy of the program, but just
* keep this lines on it, it is better than stealing all the credit for your self
*
* ***********************************************************************/
namespace Logger
{
public enum LogLevel
{
DEBUG = 0,
NOTICE,
WARNING,
ERROR,
FATAL,
}
public delegate void DoLog(int stackLevel, LogLevel logLevel,
params string[] logData);
public sealed class CLog // Singleton
{
public static readonly CLog Instance = new CLog();
private CLog() {
m_lockLog = new object();
m_currentFileSize = 0;
m_sendLog = null;
m_delegateStackLevel = 1;
}
#region member vars
private LogLevel m_minLevel;
private object m_lockLog; // Critical section
private StreamWriter m_logStream; // Log stream for writing
private long
m_maxFileSize, // Max size in bytes for file
m_currentFileSize; // Current file size
private System.Threading.Timer m_closeTimer; // Timer for reset on time out
private int
m_closeFileTime, // Time to reset (milliseconds)
m_delegateStackLevel, // Fucking delegates!
m_delegateCount; // Again fucking delegates
private bool m_overwriteLogFile; // If false, filename has the
// created date time on his name,
// if true, the name is always the app id
private string
m_logDirectory, // Log directory
m_appId, // Application Id
m_logFileName; // Full path of the log file
private DoLog m_sendLog; // Broadcast to all listening
// loggers (implementing DoLog delegate)
#endregion
#region Props
public event DoLog SendLog {
add {
lock (m_lockLog) {
m_sendLog += value;
m_delegateCount += 1;
if (m_delegateCount == 2) {
m_delegateStackLevel = 2;
}
}
}
remove {
lock (m_lockLog) {
m_sendLog -= value;
m_delegateCount -= 1;
if (m_delegateCount == 1) {
m_delegateStackLevel = 1;
}
}
}
}
public string LogFileName {
get {
lock (m_lockLog) {
return m_logFileName;
}
}
}
public LogLevel MinLevel {
get {
return m_minLevel;
}
set {
m_minLevel = value;
}
}
#endregion
public void Init(LogLevel minLevel, string dirPath, string appId,
int closeFileMinutes, long maxFileSize_KB, bool overwriteLogFile) {
lock (m_lockLog) {
if (m_logStream == null) {
m_overwriteLogFile = overwriteLogFile;
m_minLevel = minLevel;
m_appId = appId;
m_closeFileTime = closeFileMinutes * 60000;
m_maxFileSize = maxFileSize_KB * 1024;
try {
if (dirPath == String.Empty)
dirPath = ".";
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
DirectoryInfo dirInfo = new DirectoryInfo(dirPath);
m_logDirectory = dirInfo.FullName;
m_logFileName = Path.Combine(m_logDirectory,
String.Concat("Log_", m_appId,
m_overwriteLogFile ? String.Empty : DateTime.Now.ToString(
"yyyy-MM-dd_HH.mm.ss"), ".log"));
m_logStream = new StreamWriter(m_logFileName, false,
Encoding.ASCII);
m_logStream.AutoFlush = true;
m_closeTimer = new System.Threading.Timer(new TimerCallback(
newFile), null, m_closeFileTime, m_closeFileTime);
Log(2, LogLevel.NOTICE, "Log started, min log level is: " +
minLevel);
}
catch { }
}
}
}
public void Log(LogLevel logLevel, params string[] logData) {
Log(2, logLevel, logData);
}
public void Log(int stackLevel, LogLevel logLevel, params string[] logData) {
if (logLevel >= m_minLevel) { // Maybe i had to lock the critical section here
try {
StackFrame sf = new StackFrame(stackLevel, true);
string fileName = sf.GetFileName();
StringBuilder logBuilder = new StringBuilder(
DateTime.Now.ToString(
"yyyy-MM-dd HH:mm:ss.fff") + ";"
+ logLevel + ";"
+ "Thread:" +
Thread.CurrentThread.ManagedThreadId + ";"
+ sf.GetMethod() + ";"
// Never fails, if it cannot get filename,
// will return -1
+ fileName.Substring(
fileName.LastIndexOf(
Path.DirectorySeparatorChar) + 1) + ";"
+ "Line:" + sf.GetFileLineNumber()
,
1024); // I think that will be enough
foreach (string data in logData) {
logBuilder.Append(";" + data);
}
// Locking as minimum as i can
lock (m_lockLog) {
#if(DEBUG)
Debug.WriteLine(logBuilder);
#endif
m_logStream.WriteLine(logBuilder);
m_currentFileSize += Encoding.ASCII.GetByteCount(
logBuilder.ToString()); // Best way to handle file size
if (m_currentFileSize > m_maxFileSize)
m_closeTimer.Change(0, m_closeFileTime); // Max file size
// exceeded, reset file immediately
if (m_sendLog != null) {
m_sendLog(stackLevel + m_delegateStackLevel, logLevel, logData);
}
}
}
catch { } // Only fails if it is called before Init(), or if someone
// misses up the things
}
}
private void newFile(object o) {
lock (m_lockLog) {
try {
m_logStream.Close();
if (m_overwriteLogFile == false) {
m_logFileName = Path.Combine(m_logDirectory, string.Concat(
"Log_", m_appId, DateTime.Now.ToString(
"yyyy-MM-dd_HH.mm.ss"), ".log"));
}
m_currentFileSize = 0;
m_logStream = new StreamWriter(m_logFileName);
m_logStream.AutoFlush = true;
}
catch { }
}
}
}
}
Notes
This class has been developed taking care about performance, that is why tried to get a lot of info with less work, an example is the way I did to get the file size by the size of the string being writes.
Any suggestion, constructive critics, please, no doubt.
Regards