背景
使用log4net的sqlite数据库记录程序日志,使用html页面查看日志内容,以达到不进服务器就能查看日志内容的目的。所有程序日志,log4net是写进一个sqlite数据库,随着日志的递增,在写日志的同时,查看日志就显得会有点慢了。
思路
1、设计一个具有3个日志级别【Off(不记录日志) > Debug(调试信息) > Trace(跟踪信息) > Error(异常信息)】信息的日志记录器,同样是使用sqlite数据库记录信息。
2、每一天每一级别日志独立sqlite数据库记录日志信息。这样,数据库就不太大,方便读取。
3、设计html页面,可单个或多个数据库读取日志
实现
1、设置一个日志缓存池,所有日志都写进缓存池,这样可避免程序要等待写完日志后,再进行其他操作。
2、设置一日志处理线程,专门负责处理日志缓存池里的日志,写进数据库。
/// <summary>
/// 是否初始化
/// </summary>
private static bool IsInit = false;
/// <summary>
/// 日志处理时间间隔。单位/毫秒,默认3000毫秒,最小值600毫秒。
/// </summary>
private static int ThreadTimeOut
{
get
{
try
{
string TimeOutString = System.Configuration.ConfigurationManager.AppSettings["LogHelper_LogThreadTimeOut"] ?? "3000";
if (string.IsNullOrEmpty(TimeOutString))
{
TimeOutString = "3000";
}
int TimeOut = Convert.ToInt32(TimeOutString);
if (TimeOut < 600)
{
TimeOut = 600;
}
return TimeOut;
}
catch
{
return 3000;
}
}
}
/// <summary>
/// 日志文件根目录
/// </summary>
private static string LogDbPathRoot
{
get
{
string root = System.Configuration.ConfigurationManager.AppSettings["LogHelper_LogDbPathRoot"] ?? "";
if (string.IsNullOrEmpty(root))
{
root = HttpRuntime.AppDomainAppPath + "App_Data\\logs\\";
}
return root;
}
}
/// <summary>
/// 日志缓存池
/// </summary>
private static Dictionary<string, Dictionary<string, LogContent>> _LogPool = new Dictionary<string, Dictionary<string, LogContent>>();
/// <summary>
/// 写日志到缓存池
/// </summary>
/// <param name="DbName">数据库名称,如:20200701_debug </param>
/// <param name="content">日志内容</param>
public static void Write(string DbName, LogContent content)
{
//线程初始化
if (!IsInit)
{
Thread thread = new Thread(WriterWork);
thread.Name = "LogHelper_LogDbHelper_Thread";
thread.IsBackground = true;
thread.Start();
IsInit = true;
}
if (!_LogPool.ContainsKey(DbName))
{
_LogPool[DbName] = new Dictionary<string, LogContent>();
}
_LogPool[DbName].Add(Guid.NewGuid().ToString("N"), content);
}
/// <summary>
/// 日志线程锁
/// </summary>
private static object locker = new object();
/// <summary>
/// 处理缓存池日志
/// </summary>
private static void WriterWork()
{
while (true)
{
lock (locker)
{
try
{
if (_LogPool.Count > 0)
{
KeyValuePair<string, Dictionary<string, LogContent>> dbDIC = _LogPool.FirstOrDefault();
if (dbDIC.Value.Count < 1)
{
_LogPool.Remove(dbDIC.Key);
Thread.Sleep(ThreadTimeOut);
}
else
{
List<SQLiteParams> parList = new List<SQLiteParams>();
List<string> keys = new List<string>();
foreach (KeyValuePair<string, LogContent> kvp in dbDIC.Value)
{
keys.Add(kvp.Key);
SQLiteParams par = new SQLiteParams();
#region sql
par.Sql = @"
INSERT INTO Logs (Time,Level,Type,Message,Data,UserID,UserName,UserIP)
VALUES(@Time,@Level,@Type,@Message,@Data,@UserID,@UserName,@UserIP)
";
#endregion
par.Parameters.Add(new SQLiteParameter("@Time", kvp.Value.Time));
par.Parameters.Add(new SQLiteParameter("@Type", string.IsNullOrEmpty(kvp.Value.Type) ? (object)DBNull.Value : kvp.Value.Type));
par.Parameters.Add(new SQLiteParameter("@Level", string.IsNullOrEmpty(kvp.Value.Level) ? (object)DBNull.Value : kvp.Value.Level));
par.Parameters.Add(new SQLiteParameter("@Message", string.IsNullOrEmpty(kvp.Value.Message) ? (object)DBNull.Value : kvp.Value.Message));
par.Parameters.Add(new SQLiteParameter("@Data", string.IsNullOrEmpty(kvp.Value.Data) ? (object)DBNull.Value : kvp.Value.Data));
par.Parameters.Add(new SQLiteParameter("@UserID", string.IsNullOrEmpty(kvp.Value.UserID) ? (object)DBNull.Value : kvp.Value.UserID));
par.Parameters.Add(new SQLiteParameter("@UserName", string.IsNullOrEmpty(kvp.Value.UserName) ? (object)DBNull.Value : kvp.Value.UserName));
par.Parameters.Add(new SQLiteParameter("@UserIP", string.IsNullOrEmpty(kvp.Value.UserIP) ? (object)DBNull.Value : kvp.Value.UserIP));
parList.Add(par);
}
if (parList.Count > 0)
{
WriteToDB(dbDIC.Key, parList);
foreach (string key in keys)
{
if (dbDIC.Value.ContainsKey(key))
{
dbDIC.Value.Remove(key);
}
}
}
}
}
}
catch { }
//每 ThreadTimeOut 毫秒处理一次
Thread.Sleep(ThreadTimeOut);
}
}
}
/// <summary>
/// 缓存池日志写入数据库
/// </summary>
/// <param name="DbName">20200110_debug</param>
/// <param name="pars"></param>
private static void WriteToDB(string DbName, List<SQLiteParams> pars)
{
string year = DbName.Substring(0, 4);
string month = DbName.Substring(4, 2);
string DbFolderPath = LogDbPathRoot + "\\" + year + "\\" + month;
if (!Directory.Exists(DbFolderPath))
{
Directory.CreateDirectory(DbFolderPath);
}
string DbPath = DbFolderPath + "\\" + DbName + ".db";
bool IsExistDb = File.Exists(DbPath);
SQLiteConnection conn = new SQLiteConnection("Data Source=" + DbPath + ";Pooling=true;Max Pool Size=100;");
conn.Open();
if (!IsExistDb)
{
string sqlDB = @"
CREATE TABLE IF NOT EXISTS Logs (
ID INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL,
Time DATETIME NOT NULL
DEFAULT (datetime('now', 'localtime') ),
Level VARCHAR (150) NOT NULL,
Type VARCHAR (150) NOT NULL,
UserID VARCHAR (50),
UserName VARCHAR (150),
UserIP VARCHAR (50),
Message TEXT,
Data TEXT
);
";
SQLiteCommand cmdDB = new SQLiteCommand(sqlDB, conn);
cmdDB.ExecuteNonQuery();
cmdDB.Dispose();
}
SQLiteTransaction transaction = conn.BeginTransaction();
try
{
foreach (SQLiteParams par in pars)
{
SQLiteCommand cmd = new SQLiteCommand(par.Sql, conn);
cmd.Transaction = transaction;
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 60;//单位为秒
if (par.Parameters != null && par.Parameters.Count > 0)
{
cmd.Parameters.AddRange(par.Parameters.ToArray());
}
cmd.ExecuteNonQuery();
}
transaction.Commit();
}
catch (Exception exp)
{
transaction.Rollback();
throw exp;
}
finally
{
transaction.Dispose();
}
}
3、日志读取
首先读取日志数据库文件
/// <summary>
/// 获取日志列表
/// </summary>
/// <param name="yearStr"></param>
/// <param name="monthStr"></param>
/// <param name="level"></param>
/// <returns></returns>
public static List<LogDbFile> GetLogDbFileList(string yearStr = "", string monthStr = "", string level = "")
{
List<LogDbFile> res = new List<LogDbFile>();
string logPath = LogDbPathRoot;
if (!Directory.Exists(logPath))
{
return res;
}
DirectoryInfo root = new DirectoryInfo(logPath);
foreach (DirectoryInfo year in root.GetDirectories())
{
if (!string.IsNullOrEmpty(yearStr) && yearStr != year.Name) { continue; }
foreach (DirectoryInfo month in year.GetDirectories())
{
if (!string.IsNullOrEmpty(monthStr) && monthStr != month.Name) { continue; }
foreach (FileInfo logFile in month.GetFiles())
{
if (!string.IsNullOrEmpty(level) && !logFile.Name.Contains(level)) { continue; }
try
{
LogDbFile logDb = new LogDbFile();
logDb.year = year.Name;
logDb.month = month.Name;
logDb.fileName = logFile.Name;
logDb.filePath = string.Format(@"{0}\{1}\{2}", year.Name, month.Name, logFile.Name);
logDb.fileUpdateTime = logFile.LastWriteTime;
logDb.fileSize = logFile.Length;
logDb.date = logFile.Name.Substring(0, 8);
logDb.level = Path.GetFileNameWithoutExtension(logFile.Name).Replace(logDb.date + "_", "");
logDb.logTime = DateTime.Parse(logDb.year + "-" + logDb.month + "-" + logFile.Name.Substring(6, 2) + " 00:00:00");
res.Add(logDb);
}
catch(Exception exp) {
LogHelper.Error("读取日志列表异常。文件“" + logFile.Name +"”不符合日志数据命名格式。"+ exp.Message, exp);
}
}
}
}
return res;
}
接下来就是正常的读取sqlite数据表数据了。
页面效果如下:
下面,附上GitHub源码地址
https://github.com/penn6699/LogHelper.git