适用于 .net 应用程序的简单且完整的记录器






1.71/5 (5投票s)
适用于 .net 的非常完整的记录器
引言
这里提供了一种为您的 .NET 应用程序创建非常好的记录器的方法。
背景
我希望创建一个具有几乎所有功能的记录器,也就是说,获取行号、方法、线程 ID 等。使用示例
首先必须调用Init
方法// 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);
现在我们可以将任何内容发送到记录器// 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());
}
如果您想创建另一个记录器,只需将委托添加到 SendLog
事件,并在其中执行您想要的操作(我没有使用它,但它在那里)public delegate void DoLog(int stackLevel, LogLevel logLevel, params string[] logData);
public event DoLog SendLog{add;remove}
代码
日志级别的定义,非常简单
public enum LogLevel
{
DEBUG = 0,
NOTICE,
WARNING,
ERROR,
FATAL,
}
现在记录器需要工作的几个变量
// 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)
注意:一个有趣的事情是,如果我为委托添加一个监听器,CLR 使用一个堆栈来调用委托。但是如果我添加更多的委托,它会使用 2 个堆栈来调用该方法,我没有找到原因。这就是为什么我使用了变量 m_delegateStackLevel
和 m_delegateCount
Init
方法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 { }
}
}
}
Log 主要方法
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
}
}
现在是完整的类代码
// 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 { }
}
}
}
}
注释
该类在开发时注重性能,因此尝试用更少的工作获取大量信息,例如,我获取文件大小的方式是通过正在写入的字符串的大小。
任何建议、建设性的批评,请随时提出。
此致