C#创建日志记录自定义插件
概述
程序开发与运行过程需要对各类信息进行记录,具体记录的内容应用实际需要进行设定,例如:程序运行Debug记录、程序运行错误、一般运行信息、运行警告、接口运行警告、接口运行错误、接口一般运行信息、接口运行警告、sql执行记录、LuckTrace等。根据日志记录发生频率需要可以预置天、时、分、不需要记录 等。
日志记录操作理解为“信息队列”,信息队列其实质为多线程的键值集合,记录日志就是从信息队列中获取信息,再写入文件。
创建项目
基于上述要求,创建一个基础类来实现日志记录功能,创建解决方案NetDBLogger,向解决方案添加用于日志记录的DBLoggerBase项目,向解决方案添加应用项目DBLoggerSite。
基础类定义
日志频率
定义枚举,在项目DBLoggerBase中创建LogRule.cs类文件,代码:
namespace DBLoggerBase
{
public enum LogRule
{
Day,
Hour,
Minute,
No
}
}
日志级别
定义日志级别枚举,在项目DBLoggerBase中创建LogLevel.cs类文件,代码:
namespace DBLoggerBase
{
public enum LogLevel
{
/// <summary>
/// 程序运行记录,Debug模式
/// </summary>
AppTrace,
/// <summary>
/// 程序运行错误
/// </summary>
AppException,
/// <summary>
/// 一般运行信息,主要日志方式
/// </summary>
AppInfo,
/// <summary>
/// 运行警告,由开发人员记录
/// </summary>
AppWarn,
/// <summary>
/// 接口运行记录,Debug模式
/// </summary>
ApiTrace,
/// <summary>
/// 接口运行错误
/// </summary>
ApiException,
/// <summary>
/// 接口一般运行信息,主要日志方式
/// </summary>
ApiInfo,
/// <summary>
/// 接口运行警告,由开发人员记录
/// </summary>
ApiWarn,
/// <summary>
/// sql执行记录
/// </summary>
SqlTrace,
/// <summary>
/// 抽奖统计记录
/// </summary>
LuckTrace
}
}
日志写入
程序调试或运行的写入的日志进入键值集合对象,再从集合中获取信息,写入设定目录的文件,文件路径由应用配置中设置的LogPath确定,创建类文件Logger.cs,代码:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Timers;
namespace DBLoggerBase
{
public class Logger
{
/// <summary>
/// 日志队列
/// </summary>
private static ConcurrentQueue<KeyValuePair<string, string>> _logQueue;
/// <summary>
/// 日志文件夹
/// </summary>
private string _logPath = System.Configuration.ConfigurationManager.AppSettings["LogPath"].ToString();
private int _size;
private Timer _watcher;
#region Instance
private static readonly object _lockObject = new object();
private static volatile Logger _instance = null;
/// <summary>
/// 写日志,通过队列容量或定时触发写入操作
/// </summary>
/// <param name="capacity">记录数,默认为100</param>
/// <param name="seconds">毫秒数,默认60秒</param>
public Logger(int size = 100, int milliseconds = 60000)
{
//如果目录不存在则创建
if (!Directory.Exists(this._logPath))
Directory.CreateDirectory(this._logPath);
_logQueue = new ConcurrentQueue<KeyValuePair<string, string>>();
_size = size;
_watcher = new Timer(milliseconds);
_watcher.Elapsed += (o, e) =>
{
Submit();
};
_watcher.Start();
}
public static Logger Instance()
{
if (_instance == null)
{
lock (_lockObject)
{
if (_instance == null)
{
#if DEBUG
_instance = new Logger(100, 3000);
#else
_instance = new Logger();
#endif
}
}
}
return _instance;
}
#endregion
#region 写日志
/// <summary>
/// 写日志 是一个入队操作
/// </summary>
/// <param name="str"></param>
public void Write(string s, string prefix, LogRule rule = LogRule.Day)
{
if (string.IsNullOrWhiteSpace(s))
return;
DateTime dt = DateTime.Now;
string val = string.Concat(dt, "\r\n", s);
string key = string.Concat(prefix, GetKey(dt, rule));
Write(key, val);
}
public void Write(string key, string val)
{
_logQueue.Enqueue(new KeyValuePair<string, string>(key, val));
if (_logQueue.Count() >= _size)
Submit();
}
/// <summary>
/// 写日志 是一个入队操作
/// </summary>
/// <param name="str"></param>
public void Write(string s, LogLevel level = LogLevel.AppInfo, LogRule rule = LogRule.Day, long ms = 0)
{
string prefix = level.ToString();
if (ms > 0)
ms = ms / 10 * 10;
if (ms > 0)
prefix = string.Concat("_", prefix, ms, "_");
Write(s, prefix, rule);
}
#endregion
#region 文本记录日志
/// <summary>
/// 写入文本记录
/// </summary>
public void Submit()
{
//独占方式,因为文件只能由一个进程写入.
StreamWriter writer = null;
var dict = GetLogText();
string filename;
FileInfo file;
try
{
lock (_lockObject)
{
foreach (var kv in dict)
{
filename = string.Concat(kv.Key, ".txt");
file = new FileInfo(this._logPath + "/" + filename);
//文件不存在就创建,true表示追加
writer = new StreamWriter(file.FullName, true, Encoding.UTF8);
writer.WriteLine(kv.Value);
writer.Close();
}
}
}
finally
{
if (writer != null)
writer.Close();
}
}
private string GetKey(DateTime dt, LogRule rule)
{
string key;
switch (rule)
{
case LogRule.Minute:
key = dt.ToString("yyyyMMddHHmm");
break;
case LogRule.No:
key = "";
break;
case LogRule.Day:
key = dt.ToString("yyyyMMdd");
break;
case LogRule.Hour:
default:
key = dt.ToString("yyyyMMddHH");
break;
}
return key;
}
/// <summary>
/// 得到日志文本 一个出队操作
/// 将主键相同的数据拼接到一起,减少写入的io操作
/// </summary>
/// <returns></returns>
private ConcurrentDictionary<string, string> GetLogText()
{
ConcurrentDictionary<string, string> dict = new ConcurrentDictionary<string, string>();
string key, val;
do
{
KeyValuePair<string, string> kv;
if (_logQueue.TryDequeue(out kv))
{
key = kv.Key;
val = string.Concat(kv.Value, "\r\n----------------------------------------\r\n");
dict.AddOrUpdate(key, val, (k, v) => string.Concat(v + "\r\n" + val));
}
} while (_logQueue.Count > 0);
return dict;
}
#endregion
}
}
日志方法
提供一些可供应用操作的方法,用以写入日志,创建类文件DbLogger.cs,代码:
using System;
using System.Data;
using System.Text;
namespace DBLoggerBase
{
#region Logger
public static class DbLogger
{
//初始化接口
public static void LogWrite(long userId,int teacherId,string techName, string createTime, string valiTime)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Concat("执行状态: 成功!"));
sb.AppendLine(string.Concat("模板标题:预售结束通知"));
sb.AppendLine(string.Concat("用户ID:" + userId));
sb.AppendLine(string.Concat("分析师ID:" + teacherId));
sb.AppendLine(string.Concat("拜讯时间:" + createTime));
sb.AppendLine(string.Concat("结束时间:" + valiTime));
Logger.Instance().Write(sb.ToString(), LogLevel.AppInfo);
}
public static void LogWrite(string message,string userId)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Concat("消息内容:" + message + " 用户ID:" + userId));
Logger.Instance().Write(sb.ToString(), LogLevel.AppInfo);
}
public static void LogWriteMessage(string message)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Concat("时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 消息内容:") + message);
Logger.Instance().Write(sb.ToString(), LogLevel.AppInfo);
}
public static void LogException(Exception ex, string lastCommand = null)
{
StringBuilder sb = new StringBuilder();
if (!string.IsNullOrWhiteSpace(lastCommand))
sb.AppendLine(string.Concat("LastCommand:", lastCommand));
if (ex.InnerException != null)
ex = ex.InnerException;
sb.AppendLine(string.Concat("异常信息: " + ex.Message));
sb.AppendLine(string.Concat("错误源:" + ex.Source));
sb.AppendLine(string.Concat("堆栈信息:\r\n" + ex.StackTrace));
Logger.Instance().Write(sb.ToString(), LogLevel.AppInfo);
}
public static void LogTrace(IDbCommand cmd, long elapsedMilliseconds = 0)
{
StringBuilder sb = new StringBuilder();
if (elapsedMilliseconds > 0)
sb.AppendLine(string.Format("执行时长: {0} ms", elapsedMilliseconds));
sb.AppendLine(string.Format("CommandText: {0}", cmd.CommandText));
sb.AppendLine(string.Format("CommandType: {0}", cmd.CommandType));
sb.AppendLine(string.Format("Parameters: {0}", cmd.Parameters.Count));
foreach (IDbDataParameter m in cmd.Parameters)
{
sb.Append(string.Format("\tDirection: {0}", m.Direction));
sb.Append(string.Format("\tParameterName: {0}", m.ParameterName));
sb.Append(string.Format("\tDbType: {0}", m.DbType));
sb.AppendLine(string.Format("\tDbValue: {0}", m.Value));
}
Logger.Instance().Write(sb.ToString(), LogLevel.SqlTrace, LogRule.Hour, elapsedMilliseconds);
}
public static void LogLuckTrace(string message,long elapsedMilliseconds = 0)
{
Logger.Instance().Write(message.ToString(), LogLevel.LuckTrace, LogRule.Day, elapsedMilliseconds);
}
}
#endregion
}
至此,日志记录功能所需要的类创建完成,下面在应用中调用。
应用访问
在DBLoggerSite项目中创建Web页Index.aspx,此处以记录一般信息、运行异常为例,代码:
using DBLoggerBase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DBLoggerSite
{
public partial class Index : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
try
{
var x = 10000;
var y = decimal.Zero;
DbLogger.LogWriteMessage("Index页面调用了日志记录功能!");
var i = x / y;
}
catch (Exception ex)
{
DbLogger.LogException(ex, "除数为零的异常");
}
}
}
}
打开D:\Logs\DBLogger目录下的日志文件,内容如下:
2019/3/16 20:56:45
时间:2019-03-16 20:56:45 消息内容:Index页面调用了日志记录功能!
----------------------------------------
2019/3/16 20:56:45
LastCommand:除数为零的异常
异常信息: 尝试除以零。
错误源:mscorlib
堆栈信息:
在 System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
在 System.Decimal.op_Division(Decimal d1, Decimal d2)
在 DBLoggerSite.Index.Page_Load(Object sender, EventArgs e) 位置 D:\NetDBLogger\DBLoggerSite\Index.aspx.cs:行号 20
----------------------------------------