前言

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.支持同步写入日志,也支持后台独立线程异步处理日志任务,后台线程数可通过构造函数配置;

4.支持多个日志操作对象,比如想把用户操作日志和系统日志分开在不同表里记录,则可以再声明一个日志操作对象。

直接上源码(以源码中的注释作为说明):

  1 using System;  2 using System.Collections.Concurrent;  3 using System.IO;  4 using System.Text;  5 using System.Threading;  6 using System.Threading.Tasks;  7   8 namespace NetDh.EasyLogger  9 { 10     /* 11      * 此LogHandle是一个轻便快捷的日志操作类。 12      * 1.支持日志写入数据库和txt文件; 13      * 2.支持所有数据库类型的写入日志,包括传统sql数据库和nosql数据库等,因为是开放"Db写入的委托"给调用方:) ; 14      * 3.支持同步写入日志,也支持后台独立线程异步处理日志任务。 15      * 说明: 16      * 此日志操作类可支持95%以上的场景。但不适用的场景是大并发超大量日志写入,这种情况需要考虑缓存队列、批次写入、故障处理等。 17      * 一般的,超大量的日志,有点失去了“日志”的意义,因为很难分析。 18      * 总之,不要用此类来做大并发超大量数据写入。 19      */ 20  21     /// <summary> 22     /// 轻便快捷的日志操作类 23     /// </summary> 24     public class LogHandle 25     { 26         #region 属性 27         /// <summary> 28         /// 日志记录者 29         /// </summary> 30         public string Recorder { get; set; } 31         /// <summary> 32         /// txt日志的目录;如果不需要记录到txt则为null 33         /// </summary> 34         public string DirectoryForTxt { get; set; } 35         /// <summary> 36         /// 定义写入日志到数据库的委托;如果不需要记录到数据库则为null 37         /// </summary> 38         public Action<string, TbLog> DoInsertLogToDb { get; set; } 39         /// <summary> 40         /// 异步队列处理日志的线程数。0表示同步处理;1表示后台开一个线程异步处理日志任务队列.. 41         /// (建议异步处理的线程不需要太多,按日志量:1到2个线程就好。) 42         /// </summary> 43         protected int AsynThreadCount { get; set; } 44         /// <summary> 45         /// 需要写入日志的队列。 46         /// (BlockingCollection多线程安全队列,可自动阻塞线程,默认是Queue结构) 47         /// </summary> 48         protected BlockingCollection< > LogQueue = new BlockingCollection< >(); 49         /// <summary> 50         /// 默认insert Sql语句。调用方可修改InsertLogSql,比如如果是oracle数据库,则要把InsertLogSql语句中的@改为: 51         /// (表名称可自定义。1 支持不同的表命名规则;2 支持实例化不同的表名称对象用于多表日志记录(比如分操作日志和系统后台日志等)) 52         /// </summary> 53         public string InsertLogSql = @" insert into {0}(Message,Recorder,LogLevel,LogCategory,CreateTime,Thread,LogUser,Ip) values (@Message,@Recorder,@LogLevel,@LogCategory,@CreateTime,@Thread,@LogUser,@Ip) "; 54         #endregion 55  56         #region 构造函数,配置日志 57         /// <summary> 58         /// 日志操作类,支持保存在数据库和本地txt 59         /// </summary> 60         /// <param name="recorder">日志记录者</param> 61         /// <param name="directoryForTxt">winform程式参考:Path.Combine(Environment.CurrentDirectory, "Logs"); 62         /// web程式参考:System.Web.Hosting.HostingEnvironment.MapPath("~/Logs")</param> 63         /// <param name="logToDbAction">日志写入数据库的委托。由调用方自动选择db日志写入方式,这样就可支持任何数据库类型写入日志</param> 64         /// <param name="asynThreadCount">异步队列处理日志的线程数。0表示同步处理;1表示后台开一个线程异步处理日志任务队列..强烈建议为1就好,如果日志量较多一点,为2就好。</param> 65         /// <param name="logTableName">日志表名,表名称默认是TbLog,可以自定义,比如TbLog等。1. 为了不同的表命名规则;2. 为了支持多表日志记录(比如分操作日志和系统后台日志等)。</param> 66         /// <param name="needStartLog">实例化日志对象时,是否记录一条start日志</param> 67         public LogHandle(string recorder, string directoryForTxt = "", Action<string, TbLog> logToDbAction = null, 68             int asynThreadCount = 1, string logTableName = "TbLog", bool needStartLog = true) 69         { 70             if (string.IsNullOrWhiteSpace(directoryForTxt) && logToDbAction == null) 71             { 72                 throw new Exception("没有指定任何日志记录方式"); 73             } 74             Recorder = recorder; 75             DirectoryForTxt = directoryForTxt; 76             //初始化时确保日志文件夹存在,之后写入txt不用一直判断 77             if (!string.IsNullOrWhiteSpace(DirectoryForTxt) && !Directory.Exists(DirectoryForTxt)) 78             { 79                 Directory.CreateDirectory(DirectoryForTxt); 80             } 81             DoInsertLogToDb = logToDbAction; 82             //指定日志表名 83             InsertLogSql = string.Format(InsertLogSql, logTableName); 84             AsynThreadCount = asynThreadCount; 85             //如果AsynThreadCount>=0,则异步处理日志写入;如果如果AsynThreadCount<=0,则是同步写入日志。 86             InitQueueConsume(); 87             if (needStartLog) 88             { 89                 if (!string.IsNullOrWhiteSpace(DirectoryForTxt)) 90                 { 91                     LogToTxt(string.Format("init loghandle:{0}", Recorder), "start"); 92                 } 93                 if (DoInsertLogToDb != null) 94                 { 95                     LogToDb(string.Format("init loghandle:{0}", Recorder), "start"); 96                 } 97             } 98         } 99         /// <summary>100         /// 初始化异步处理队列101         /// </summary>102         protected virtual void InitQueueConsume()103         {104             for (int i = 0; i < AsynThreadCount; i++)//AsynThreadCount<=0的话,不会进入循环105             {106                 Task.Factory.StartNew(() =>107                 {108                     //GetConsumingEnumerable 如果队列中没有项,会自动阻塞等待Add。这个线程会一直在后台占用。109                     foreach (var item in LogQueue.GetConsumingEnumerable())110                     {111                         try112                         {113                             if (item is string)114                             {115                                 DoInsertLogToTxt(item.ToString());116                             }117                             else118                             {119                                 DoInsertLogToDb(InsertLogSql, (TbLog)item);120                             }121                         }122                         catch (Exception e)123                         {//如果在处理任务过程失败,需要捕获以继续处理下一个任务124                         }125                     }126                 });127             }128         }129         #endregion130 131         #region Log、LogToDb、LogToTxt、LogToBoth132         /// <summary>133         /// 日志优先写入Db,当写入Db失败,才会写入txt。如果DoInsertLogToDb为null,则会自动选择写入txt。134         /// (这也是最常用的模式,太多日志是不建议写入txt)135         /// </summary>136         /// <param name="msg">日志信息</param>137         /// <param name="category">自定义类别</param>138         /// <param name="level">日志等级:Info,Warn,Error,Fatal,Debug</param>139         /// <param name="user"></param>140         /// <param name="ip"></param>141         public virtual void Log(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")142         {143             if (DoInsertLogToDb != null)144             {145                 try146                 {147                     LogToDb(msg, category, level, user, ip);148                 }149                 catch (Exception e)150                 {151                     var exMsg = "-------------执行Log中的LogToDb时异常:" + LogHandle.GetExceptionDetailMsg(e);152                     if (!string.IsNullOrWhiteSpace(DirectoryForTxt))//如果写入数据库失败,则写入本地txt153                     {154                         LogToTxt(exMsg);155                         LogToTxt(msg, category, level, user, ip);156                     }157                     else158                     {159                         throw new Exception(exMsg);160                     }161                 }162             }163             else if (!string.IsNullOrWhiteSpace(DirectoryForTxt))164             {165                 LogToTxt(msg, category, level, user, ip);166             }167         }168         /// <summary>169         /// 日志记录到Db中。170         /// </summary>171         public virtual void LogToDb(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")172         {173             var sqlParams = new TbLog174             {175                 Message = msg,176                 Recorder = Recorder,177                 LogLevel = level.ToString(),178                 LogCategory = category,179                 CreateTime = DateTime.Now,180                 Thread = Thread.CurrentThread.ManagedThreadId,181                 LogUser = user,182                 Ip = ip183             };184             if (AsynThreadCount <= 0)185             {//同步处理186                 DoInsertLogToDb(InsertLogSql, sqlParams);187             }188             else189             {//异步处理190                 LogQueue.Add(sqlParams);191             }192         }193 194         /// <summary>195         /// 日志记录到txt中。196         /// </summary>197         /// <param name="msg">日志信息</param>198         /// <param name="category">自定义类别</param>199         /// <param name="level">日志等级:Info,Warn,Error,Fatal,Debug</param>200         /// <param name="user"></param>201         /// <param name="ip"></param>202         public virtual void LogToTxt(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")203         {204             var threadId = Thread.CurrentThread.ManagedThreadId;205             StringBuilder sb = new StringBuilder();206             sb.AppendFormat("[Thread]:{0} [Recorder]:{1} [Msg]:{2} ", threadId, Recorder, msg);207             if (!string.IsNullOrWhiteSpace(category))208             {209                 sb.AppendFormat("[Category]:{0}", category);210             }211             if (level != EnLogLevel.Info)212             {213                 sb.AppendFormat("[Level]:{0}", level.ToString());214             }215             if (!string.IsNullOrWhiteSpace(user))216             {217                 sb.AppendFormat("[User]:{0}", user);218             }219             if (!string.IsNullOrWhiteSpace(ip))220             {221                 sb.AppendFormat("[Ip]:{0}", ip);222             }223 224             if (AsynThreadCount <= 0)225             {//同步处理226                 DoInsertLogToTxt(sb.ToString());227             }228             else229             {//异步处理230                 LogQueue.Add(sb.ToString());231             }232         }233         private   _lockWriteTxt = new  ();234         /// <summary>235         /// 日志记录到txt中。236         /// (注意,此日志处理类,是为了支持普通量txt日志写入。如果是大并发写入txt,则要另外设计此场景的txt写入方式)237         /// </summary>238         /// <param name="strLog">需要记录的信息</param>239         public virtual void DoInsertLogToTxt(string strLog)240         {241             strLog = string.Format("{0} {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), strLog);242             //每天一个txt文件,如果需要可以改成每小时一个文件243             string logPath = Path.Combine(DirectoryForTxt, string.Format(@"Log{0}.txt", DateTime.Now.ToString("yyyyMMdd")));            244             lock (_lockWriteTxt)245             {246                 //这边实现场景是一条一条日志记录。不适用大并发超大量txt写入,这种情况要另外设计此场景的txt写入方式,比如要考虑缓存队列、批次写入、故障处理等。247                 using (FileStream fs = new FileStream(logPath, FileMode.OpenOrCreate, FileAccess.Write))248                 {249                     using (StreamWriter sw = new StreamWriter(fs))250                     {251                         sw. Stream.Seek(0, SeekOrigin.End);252                         sw.WriteLine(strLog);253                         sw.Flush();254                     }255                 }256             }257         }258         /// <summary>259         /// 日志写入Db和txt。260         /// </summary>261         /// <param name="msg">日志信息</param>262         /// <param name="category">自定义类别</param>263         /// <param name="level">日志等级:Info,Warn,Error,Fatal,Debug</param>264         /// <param name="user"></param>265         /// <param name="ip"></param>266         public virtual void LogToBoth(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "")267         {268             try269             {270                 LogToDb(msg, category, level, user, ip);271             }272             catch (Exception e)273             {274                 LogToTxt("-------------执行LogToBoth中的LogToDb时异常:" + e.Message);275                 LogToTxt(msg, category, level, user, ip);276                 return;277             }278             LogToTxt(msg, category, level, user, ip);279         }280         #endregion281 282         /// <summary>283         /// 生成自定义异常消息,包含异常的堆栈284         /// </summary>285         /// <param name="ex">异常对象</param>286         /// <returns>异常字符串文本</returns>287         public static string GetExceptionDetailMsg(Exception ex)288         {289             StringBuilder sb = new StringBuilder();290             sb.AppendFormat("异常时间:{0}", DateTime.Now);291             sb.AppendFormat("异常信息:{0}", ex.Message);292             sb.AppendLine(string.Empty);293             sb.AppendFormat("异常堆栈:{0}", ex.StackTrace);294             sb.AppendLine(string.Empty);295             return sb.ToString();296         }297     }298 }

2.使用的示例代码

直接看代码和注释:

 1 /// <summary> 2     /// NetDh模块使用示例代码 3     /// </summary> 4     public class NetDhExample 5     { 6         #region 用全局静态变量实现单例。 7         /// <summary> 8         /// 服务端使用数据库操作对象 9         /// </summary>10         public static DbHandle  DbHandle { get; set; }11         /// <summary>12         /// 日志操作对象13         /// </summary>14         public static LogHandle LogHandle { get; set; }15 16         //说明:比如如果你想把用户操作日志和系统日志分开在不同表里记录,则可以再声明一个日志操作对象17         public static LogHandle SysLogHandle { get; set; }18         #endregion19         /// <summary>20         /// 静态构造函数,只会初始化一次21         /// </summary>22         static NetDhExample()23         {24            25             //初始化数据库操作对象26             var connStr = "Data Source=.;Initial Catalog=Test;User Id=sa;Password=***;";27             DbHandle = new SqlServerHandle(connStr);28             //如果有多库,可再new个对象29             //ReadDbHandle = new SqlServerHandle(connStrForRead);30 31             //初始化日志操作对象32             //先定义日志写入数据库的委托33             Action<string, TbLog> doInsert = (sql, model) =>34             {35                 DbHandle.ExecuteNonQuery(sql, model);//你想要用什么方式把日志写入Db,是可以自己指定。36                 //DbHandle.Insert(model);37                 //如果你的表结构和TbLog类一样,则可直接用:DbHandle.Insert(model);这样就不会用到InsertLogSql,也就不用管InsertLogSql的语法是否支持所有数据库.38             };39             //其中的asynThreadCount参数默认是1,代表后台独立线程独立处理日志;我这边设置为0,代表同步处理日志。40             LogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "Logs"), doInsert, 0, "TbLog");41             //如果你想要有多个日志操作对象,则再new一个,把日志放不同目录不同数据表中42             SysLogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "SysLogs"), doInsert, 0, "TbSysLog");43         }44         /// <summary>45         /// 各模块使用的示例代码46         /// </summary>47         public static void TestMain()48         {49             #region 日志处理类50             LogHandle.LogToTxt("日志写入txt");51             LogHandle.LogToTxt("日志写入txt", "logcategory1");//可用第二个参数来自定义分类日志52             LogHandle.LogToDb("日志写入db", "logcategory2");//可用第二个参数来自定义分类日志53             LogHandle.LogToBoth("日志同时写入txt和Db");54             //LogHandle.Log是最常用的函数,太多日志是不建议写入txt。55             LogHandle.Log("日志优先写入Db,当写入Db失败,才会写入txt。如果LogHandle对象DoInsertLogToDb属性为null,则会自动选择写入txt。");56             #endregion57         }58     }

 

3.NetDh框架完整源码

国外有github,国内有码云,在国内使用码云速度非常快。NetDh框架源码放在码云上:

https://gitee.com/donghan/NetDh- work

 

分享、互相交流学习
收藏 打印