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 the
Init
method
public void Init(LogLevel minLevel, string dirPath, string appId, int closeFileMinutes,
long maxFileSize_KB, bool overwriteLogFile)
CLog.Instance.Init(LogLevel.NOTICE, "." ,
"myAPpId", 1440 , 4096 , false);
Now we can send anything to the logger
public void Log(LogLevel logLevel, params string[] logData)
public void Log(int stackLevel, LogLevel logLevel, params string[] logData)
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
public static readonly CLog Instance = new CLog();
private LogLevel m_minLevel;
private object m_lockLog;
private StreamWriter m_logStream;
private long
m_maxFileSize, m_currentFileSize;
private System.Threading.Timer m_closeTimer;
private int
m_closeFileTime, m_delegateStackLevel, m_delegateCount;
private bool m_overwriteLogFile;
private string
m_logDirectory, m_appId, m_logFileName;
private DoLog m_sendLog;
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
The
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) { 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() + ";"
+ fileName.Substring(
fileName.LastIndexOf(Path.DirectorySeparatorChar) + 1) + ";"
+ "Line:" + sf.GetFileLineNumber()
,1024);
foreach (string data in logData) {
logBuilder.Append(";" + data);
}
lock (m_lockLog) {
#if(DEBUG)
Debug.WriteLine(logBuilder);
#endif
m_logStream.WriteLine(logBuilder);
m_currentFileSize += Encoding.ASCII.GetByteCount(
logBuilder.ToString());
if (m_currentFileSize > m_maxFileSize)
m_closeTimer.Change(0,
m_closeFileTime); if (m_sendLog != null) {
m_sendLog(stackLevel + m_delegateStackLevel,
logLevel, logData);
}
}
}
catch { } }
}
And now the complete class code:
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
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 {
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;
private StreamWriter m_logStream;
private long
m_maxFileSize, m_currentFileSize;
private System.Threading.Timer m_closeTimer;
private int
m_closeFileTime, m_delegateStackLevel, m_delegateCount;
private bool m_overwriteLogFile;
private string
m_logDirectory, m_appId, m_logFileName;
private DoLog m_sendLog;
#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) { 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() + ";"
+ fileName.Substring(
fileName.LastIndexOf(
Path.DirectorySeparatorChar) + 1) + ";"
+ "Line:" + sf.GetFileLineNumber()
,
1024);
foreach (string data in logData) {
logBuilder.Append(";" + data);
}
lock (m_lockLog) {
#if(DEBUG)
Debug.WriteLine(logBuilder);
#endif
m_logStream.WriteLine(logBuilder);
m_currentFileSize += Encoding.ASCII.GetByteCount(
logBuilder.ToString());
if (m_currentFileSize > m_maxFileSize)
m_closeTimer.Change(0, m_closeFileTime);
if (m_sendLog != null) {
m_sendLog(stackLevel + m_delegateStackLevel, logLevel, logData);
}
}
}
catch { } }
}
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