2.NetDh框架之简单高效的日志操作类(附源码和示例代码)
前言
NetDh框架适用于C/S、B/S的服务端框架,可用于项目开发和学习。目前包含以下四个模块
1.数据库操作层封装Dapper,支持多种数据库类型、多库实例,简单强大;
此部分具体说明可参考博客: https://www.cnblogs.com/michaeldonghan/p/9317078.html
2.提供简单高效的日志操作类使用,支持日志写入Db和txt、支持任何数据库类型写入(包括传统sql数据库和nosql数据库等)、支持同步写入日志和后台独立线程异步处理日志队列;
此部分具体说明可参考博客: 本文以下章节内容。
3.提供简单缓存设计和使用;
此部分具体说明可参考博客: https://www.cnblogs.com/michaeldonghan/p/9321745.html
4.业务逻辑层服务简单设计,可方便支持二次开发模式。
此部分具体说明可参考博客: https://www.cnblogs.com/michaeldonghan/p/9321745.html
1.日志操作类LogHandle
NetDh.EasyLogger.LogHandle是一个轻便快捷的日志操作类。
1.支持日志写入数据库和txt文件;
2.支持所有数据库类型的写入日志,包括传统sql数据库和nosql数据库等(开放委托给调用方) ;
3.支持同步写入日志,也支持后台独立线程异步处理日志任务,后台线程数可通过构造函数配置。在构造函数中的asynThreadCount参数指定,asynThreadCount是异步队列处理日志的线程数,0表示同步处理;大于0表示后台开asynThreadCount个线程异步处理日志任务队列。普通日志量推荐默认的1,这样系统可异步处理日志,如果日志出错也是会记录到本地txt;如果日志量较多,再酌情设置大一些。
4.支持多个日志操作对象,比如想把用户操作日志和系统日志分开在不同表里记录,则可以再声明一个日志操作对象。
直接上源码(以源码中的注释作为说明):
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace NetDh.EasyLogger
{
/*
* 此LogHandle是一个轻便快捷的日志操作类。
* 1.支持日志写入数据库和txt文件;
* 2.支持所有数据库类型的写入日志,包括传统sql数据库和nosql数据库等,因为是开放"Db写入的委托"给调用方:) ;
* 3.支持同步写入日志,也支持后台独立线程异步处理日志任务。
* 说明:
* 此日志操作类可支持95%以上的场景。但不适用的场景是大并发超大量日志写入,这种情况需要考虑缓存队列、批次写入、故障处理等。
* 一般的,超大量的日志,有点失去了“日志”的意义,因为很难分析。
* 总之,不要用此类来做大并发超大量数据写入。
*/ /// <summary>
/// 轻便快捷的日志操作类
/// </summary>
public class LogHandle
{
#region 属性
/// <summary>
/// 日志记录者
/// </summary>
public string Recorder { get; set; }
/// <summary>
/// txt日志的目录;如果不需要记录到txt则为null
/// </summary>
public string DirectoryForTxt { get; set; }
/// <summary>
/// 定义写入日志到数据库的委托;如果不需要记录到数据库则为null
/// </summary>
public Action<string, TbLog> DoInsertLogToDb { get; set; }
/// <summary>
/// 异步队列处理日志的线程数。0表示同步处理;1表示后台开一个线程异步处理日志任务队列..
/// (建议异步处理的线程不需要太多,按日志量:1到2个线程就好。)
/// </summary>
protected int AsynThreadCount { get; set; }
/// <summary>
/// 需要写入日志的队列。
/// (BlockingCollection多线程安全队列,可自动阻塞线程,默认是Queue结构)
/// </summary>
protected BlockingCollection<object> LogQueue = new BlockingCollection<object>();
/// <summary>
/// 默认insert Sql语句。调用方可修改InsertLogSql,比如如果是oracle数据库,则要把InsertLogSql语句中的@改为:
/// (表名称可自定义。1 支持不同的表命名规则;2 支持实例化不同的表名称对象用于多表日志记录(比如分操作日志和系统后台日志等))
/// </summary>
public string InsertLogSql = @" insert into {0}(Message,Recorder,LogLevel,LogCategory,CreateTime,Thread,LogUser,Ip) values (@Message,@Recorder,@LogLevel,@LogCategory,@CreateTime,@Thread,@LogUser,@Ip) ";
#endregion #region 构造函数,配置日志
/// <summary>
/// 日志操作类,支持保存在数据库和本地txt
/// </summary>
/// <param name="recorder">日志记录者</param>
/// <param name="directoryForTxt">winform程式参考:Path.Combine(Environment.CurrentDirectory, "Logs");
/// web程式参考:System.Web.Hosting.HostingEnvironment.MapPath("~/Logs")</param>
/// <param name="logToDbAction">日志写入数据库的委托。由调用方自动选择db日志写入方式,这样就可支持任何数据库类型写入日志</param>
/// <param name="asynThreadCount">异步队列处理日志的线程数。0表示同步处理;大于0表示后台开asynThreadCount个线程异步处理日志任务队列。普通日志量推荐默认的1,这样系统可异步处理日志,如果日志出错也是会记录到本地tx;如果日志量较多,可设置大一些。</param>
/// <param name="logTableName">日志表名,表名称默认是TbLog,可以自定义,比如TbLog等。1. 为了不同的表命名规则;2. 为了支持多表日志记录(比如分操作日志和系统后台日志等)。</param>
/// <param name="needStartLog">实例化日志对象时,是否记录一条start日志</param>
public LogHandle(string recorder, string directoryForTxt = "", Action<string, TbLog> logToDbAction = null,
int asynThreadCount = , string logTableName = "TbLog", bool needStartLog = true)
{
if (string.IsNullOrWhiteSpace(directoryForTxt) && logToDbAction == null)
{
throw new Exception("没有指定任何日志记录方式");
}
Recorder = recorder;
DirectoryForTxt = directoryForTxt;
//初始化时确保日志文件夹存在,之后写入txt不用一直判断
if (!string.IsNullOrWhiteSpace(DirectoryForTxt) && !Directory.Exists(DirectoryForTxt))
{
Directory.CreateDirectory(DirectoryForTxt);
}
DoInsertLogToDb = logToDbAction;
//指定日志表名
InsertLogSql = string.Format(InsertLogSql, logTableName);
AsynThreadCount = asynThreadCount;
//如果AsynThreadCount>=0,则异步处理日志写入;如果如果AsynThreadCount<=0,则是同步写入日志。
InitQueueConsume();
if (needStartLog)
{
if (!string.IsNullOrWhiteSpace(DirectoryForTxt))
{
LogToTxt(string.Format("init loghandle:{0}", Recorder), "start");
}
if (DoInsertLogToDb != null)
{
LogToDb(string.Format("init loghandle:{0}", Recorder), "start");
}
}
}
/// <summary>
/// 初始化异步处理队列
/// </summary>
protected virtual void InitQueueConsume()
{
for (int i = ; i < AsynThreadCount; i++)//AsynThreadCount<=0的话,不会进入循环
{
Task.Factory.StartNew(() =>
{
//GetConsumingEnumerable 如果队列中没有项,会自动阻塞等待Add。这个线程会一直在后台占用。
foreach (var item in LogQueue.GetConsumingEnumerable())
{
try
{
if (item is string)
{
DoInsertLogToTxt(item.ToString());
}
else
{
DoInsertLogToDb(InsertLogSql, (TbLog)item);
}
}
catch (Exception e)
{//如果在处理任务过程失败,需要捕获以继续处理下一个任务
}
}
});
}
}
#endregion #region Log、LogToDb、LogToTxt、LogToBoth
/// <summary>
/// 日志优先写入Db,当写入Db失败,才会写入txt。如果DoInsertLogToDb为null,则会自动选择写入txt。
/// (这也是最常用的模式,太多日志是不建议写入txt)
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="category">自定义类别</param>
/// <param name="level">日志等级:Info,Warn,Error,Fatal,Debug</param>
/// <param name="user"></param>
/// <param name="ip"></param>
public virtual void Log(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")
{
if (DoInsertLogToDb != null)
{
try
{
LogToDb(msg, category, level, user, ip);
}
catch (Exception e)
{
var exMsg = "-------------执行Log中的LogToDb时异常:" + LogHandle.GetExceptionDetailMsg(e);
if (!string.IsNullOrWhiteSpace(DirectoryForTxt))//如果写入数据库失败,则写入本地txt
{
LogToTxt(exMsg);
LogToTxt(msg, category, level, user, ip);
}
else
{
throw new Exception(exMsg);
}
}
}
else if (!string.IsNullOrWhiteSpace(DirectoryForTxt))
{
LogToTxt(msg, category, level, user, ip);
}
}
/// <summary>
/// 日志记录到Db中。
/// </summary>
public virtual void LogToDb(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")
{
var sqlParams = new TbLog
{
Message = msg,
Recorder = Recorder,
LogLevel = level.ToString(),
LogCategory = category,
CreateTime = DateTime.Now,
Thread = Thread.CurrentThread.ManagedThreadId,
LogUser = user,
Ip = ip
};
if (AsynThreadCount <= )
{//同步处理
DoInsertLogToDb(InsertLogSql, sqlParams);
}
else
{//异步处理
LogQueue.Add(sqlParams);
}
} /// <summary>
/// 日志记录到txt中。
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="category">自定义类别</param>
/// <param name="level">日志等级:Info,Warn,Error,Fatal,Debug</param>
/// <param name="user"></param>
/// <param name="ip"></param>
public virtual void LogToTxt(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")
{
var threadId = Thread.CurrentThread.ManagedThreadId;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("[Thread]:{0} [Recorder]:{1} [Msg]:{2} ", threadId, Recorder, msg);
if (!string.IsNullOrWhiteSpace(category))
{
sb.AppendFormat("[Category]:{0}", category);
}
if (level != EnLogLevel.Info)
{
sb.AppendFormat("[Level]:{0}", level.ToString());
}
if (!string.IsNullOrWhiteSpace(user))
{
sb.AppendFormat("[User]:{0}", user);
}
if (!string.IsNullOrWhiteSpace(ip))
{
sb.AppendFormat("[Ip]:{0}", ip);
} if (AsynThreadCount <= )
{//同步处理
DoInsertLogToTxt(sb.ToString());
}
else
{//异步处理
LogQueue.Add(sb.ToString());
}
}
private Object _lockWriteTxt = new object();
/// <summary>
/// 日志记录到txt中。
/// (注意,此日志处理类,是为了支持普通量txt日志写入。如果是大并发写入txt,则要另外设计此场景的txt写入方式)
/// </summary>
/// <param name="strLog">需要记录的信息</param>
public virtual void DoInsertLogToTxt(string strLog)
{
strLog = string.Format("{0} {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), strLog);
//每天一个txt文件,如果需要可以改成每小时一个文件
string logPath = Path.Combine(DirectoryForTxt, string.Format(@"Log{0}.txt", DateTime.Now.ToString("yyyyMMdd")));
lock (_lockWriteTxt)
{
//这边实现场景是一条一条日志记录。不适用大并发超大量txt写入,这种情况要另外设计此场景的txt写入方式,比如要考虑缓存队列、批次写入、故障处理等。
using (FileStream fs = new FileStream(logPath, FileMode.OpenOrCreate, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.BaseStream.Seek(, SeekOrigin.End);
sw.WriteLine(strLog);
sw.Flush();
}
}
}
}
/// <summary>
/// 日志写入Db和txt。
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="category">自定义类别</param>
/// <param name="level">日志等级:Info,Warn,Error,Fatal,Debug</param>
/// <param name="user"></param>
/// <param name="ip"></param>
public virtual void LogToBoth(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")
{
try
{
LogToDb(msg, category, level, user, ip);
}
catch (Exception e)
{
LogToTxt("-------------执行LogToBoth中的LogToDb时异常:" + e.Message);
LogToTxt(msg, category, level, user, ip);
return;
}
LogToTxt(msg, category, level, user, ip);
}
#endregion /// <summary>
/// 生成自定义异常消息,包含异常的堆栈
/// </summary>
/// <param name="ex">异常对象</param>
/// <returns>异常字符串文本</returns>
public static string GetExceptionDetailMsg(Exception ex)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("异常时间:{0}", DateTime.Now);
sb.AppendFormat("异常信息:{0}", ex.Message);
sb.AppendLine(string.Empty);
sb.AppendFormat("异常堆栈:{0}", ex.StackTrace);
sb.AppendLine(string.Empty);
return sb.ToString();
}
}
}
2.使用的示例代码
直接看代码和注释:
/// <summary>
/// NetDh模块使用示例代码
/// </summary>
public class NetDhExample
{
#region 用全局静态变量实现单例。
/// <summary>
/// 服务端使用数据库操作对象
/// </summary>
public static DbHandleBase DbHandle { get; set; }
/// <summary>
/// 日志操作对象
/// </summary>
public static LogHandle LogHandle { get; set; } //说明:比如如果你想把用户操作日志和系统日志分开在不同表里记录,则可以再声明一个日志操作对象
public static LogHandle SysLogHandle { get; set; }
#endregion
/// <summary>
/// 静态构造函数,只会初始化一次
/// </summary>
static NetDhExample()
{ //初始化数据库操作对象
var connStr = "Data Source=.;Initial Catalog=Test;User Id=sa;Password=***;";
DbHandle = new SqlServerHandle(connStr);
//如果有多库,可再new个对象
//ReadDbHandle = new SqlServerHandle(connStrForRead); //初始化日志操作对象
//先定义日志写入数据库的委托
Action<string, TbLog> doInsert = (sql, model) =>
{
DbHandle.ExecuteNonQuery(sql, model);//你想要用什么方式把日志写入Db,是可以自己指定。
//DbHandle.Insert(model);
//如果你的表结构和TbLog类一样,则可直接用:DbHandle.Insert(model);这样就不会用到InsertLogSql,也就不用管InsertLogSql的语法是否支持所有数据库.
};
//其中的asynThreadCount参数默认是1,代表后台独立线程独立处理日志;我这边设置为0,代表同步处理日志。
LogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "Logs"), doInsert, , "TbLog");
//如果你想要有多个日志操作对象,则再new一个,把日志放不同目录不同数据表中
SysLogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "SysLogs"), doInsert, , "TbSysLog");
}
/// <summary>
/// 各模块使用的示例代码
/// </summary>
public static void TestMain()
{
#region 日志处理类
LogHandle.LogToTxt("日志写入txt");
LogHandle.LogToTxt("日志写入txt", "logcategory1");//可用第二个参数来自定义分类日志
LogHandle.LogToDb("日志写入db", "logcategory2");//可用第二个参数来自定义分类日志
LogHandle.LogToBoth("日志同时写入txt和Db");
//LogHandle.Log是最常用的函数,太多日志是不建议写入txt。
LogHandle.Log("日志优先写入Db,当写入Db失败,才会写入txt。如果LogHandle对象DoInsertLogToDb属性为null,则会自动选择写入txt。");
#endregion
}
}
3.NetDh框架完整源码
国外有github,国内有码云,在国内使用码云速度非常快。NetDh框架源码放在码云上:
https://gitee.com/donghan/NetDh-Framework
异步队列处理日志的线程数。0表示同步处理;大于0表示后台开asynThreadCount个线程异步处理日志任务队列.普通日志量推荐默认的1,这样系统可异步处理日志,如果日志出错也是会记录到本地tx;,如果日志量较多,可设置大一些。
2.NetDh框架之简单高效的日志操作类(附源码和示例代码)的更多相关文章
- 3.NetDh框架之缓存操作类和二次开发模式简单设计(附源码和示例代码)
前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...
- 1.NetDh框架之数据库操作层--Dapper简单封装,可支持多库实例、多种数据库类型等(附源码和示例代码)
1.NetDh框架开始的需求场景 需求场景: 1.之前公司有不同.net项目组,有的项目是用SqlServer做数据库,有的项目是用Oracle,后面也有可能会用到Mysql等,而且要考虑后续扩展成主 ...
- PHP简单的长文章分页教程 附源码
PHP简单的长文章分页教程 附源码.本文将content.txt里的内容分割成3页,这样浏览起来用户体验很好. 根据分页参数ipage,获取对应文章内容 include('page.class.php ...
- Util应用程序框架公共操作类(二):数据类型转换公共操作类(源码篇)
上一篇介绍了数据类型转换的一些情况,可以看出,如果不进行封装,有可能导致比较混乱的代码.本文通过TDD方式把数据类型转换公共操作类开发出来,并提供源码下载. 我们在 应用程序框架实战十一:创建VS解决 ...
- 为何要打印日志?C++在高并发下如何写日志文件(附源码)?
为何要打印日志?让程序裸奔不是一件很快乐的事么? 有些BUG就像薛定谔的猫,具有波粒二象性,当你试图去观察它时它就消失了,当你不去观察它时,它又会出现.当你在测试人员面前赌咒发誓,亲自路演把程序跑一遍 ...
- 一个简单的IM系统(Demo附源码)-- ESFramework 4.0 快速上手(08)
前面的文章已经介绍完了基于ESFramework/ESPlus进行二次开发的所有要点,现在,我们可以开始小试牛刀了. 本文将介绍使用ESFramework的Rapid引擎开发的两个最简单的Demo,E ...
- 原创SQlServer数据库生成简单的说明文档小工具(附源码)
这是一款简单的数据库文档生成工具,主要实现了SQlServer生成说明文档的小工具,目前不够完善,主要可以把数据库的表以及表的详细字段信息,导出到 Word中,可以方便开发人员了解数据库的信息或写技术 ...
- 日志组件Log2Net的介绍和使用(附源码开源地址)
Log2Net是一个用于收集日志到数据库或文件的组件,支持.NET和.NetCore平台. 此组件自动收集系统的运行日志(服务器运行情况.在线人数等).异常日志.程序员还可以添加自定义日志. 该组件支 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
随机推荐
- 【模板】插头dp
题目描述 题解: 插头$dp$中经典的回路问题. 首先了解一下插头. 一个格子,上下左右四条边对应四个插头.就像这样: 四个插头. 一个完整的哈密顿回路,经过的格子一定用且仅用了两个插头. 所以所有被 ...
- maven构建springmvc项目
1.Eclipse中 NEW ->OTHER->Maven->maven project 2.选择项目路径 3.选择项目类型->next->输入groupid和artif ...
- 第十章:C++标准模板库
主要内容: 1.泛型程序设计 2.与STL有关的概念和术语 3.STL的容器 4.迭代器 5.STL的算法 6.函数对象 暂时略,内容有点多,而且也很重要!但我看完了,日后补上.
- 03 数据解析-Xpath
Xpath解析 XPath在Python的爬虫学习中,起着举足轻重的地位,对比正则表达式 re两者可以完成同样的工作,实现的功能也差不多,但XPath明显比re具有优势,在网页分析上使re退居二线. ...
- Python2和Python3共存安装robotframework
1.下载Python2.Python3安装包 https://www.python.org/ 2.下载pip.tar.gz https://pypi.python.org/pypi/pip#downl ...
- NYOJ90-整数划分,经典递归思想~~
整数划分 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 将正整数n表示成一系列正整数之和:n=n1+n2+-+nk, 其中n1≥n2≥-≥nk≥1,k≥1. 正整数 ...
- [luoguP1772] [ZJOI2006]物流运输(DP + spfa)
传送门 预处理cost[i][j]表示从第i天到第j天起点到终点的最短距离 f[i]表示前i天到从起点到终点的最短距离 f[0] = -K f[i] = min(f[i], f[j - 1] + co ...
- 多边形之战(bzoj 2927)
Description 多边形之战是一个双人游戏.游戏在一个有n个顶点的凸多边形上进行,这个凸多边形的n-3条对角线将多边形分成n-2个三角形,这n-3条对角线在多边形的顶点相交.三角形中的一个被染成 ...
- Linux中命令选项及参数简介
登录Linux后,我们就可以在#或$符后面去输入命令,有的时候命令后面还会跟着“选项”(英文options)或“参数”(英文arguments).即Linux中命令格式为: command [opti ...
- eclispe使用
eclipse 快捷键 ctrl+shif+o :去除多余引用 ctrl+shift+x :转大写 ctrl+shift+y :转小写 ctrl+o :查找方法 Alt+ ← :回 ...