• 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.支持多个日志操作对象,比如想把用户操作日志和系统日志分开在不同表里记录,则可以再声明一个日志操作对象。

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

      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<object> LogQueue = new BlockingCollection<object>();
     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表示同步处理;大于0表示后台开asynThreadCount个线程异步处理日志任务队列。普通日志量推荐默认的1,这样系统可异步处理日志,如果日志出错也是会记录到本地tx;如果日志量较多,可设置大一些。</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                         try
    112                         {
    113                             if (item is string)
    114                             {
    115                                 DoInsertLogToTxt(item.ToString());
    116                             }
    117                             else
    118                             {
    119                                 DoInsertLogToDb(InsertLogSql, (TbLog)item);
    120                             }
    121                         }
    122                         catch (Exception e)
    123                         {//如果在处理任务过程失败,需要捕获以继续处理下一个任务
    124                         }
    125                     }
    126                 });
    127             }
    128         }
    129         #endregion
    130 
    131         #region Log、LogToDb、LogToTxt、LogToBoth
    132         /// <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                 try
    146                 {
    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))//如果写入数据库失败,则写入本地txt
    153                     {
    154                         LogToTxt(exMsg);
    155                         LogToTxt(msg, category, level, user, ip);
    156                     }
    157                     else
    158                     {
    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 TbLog
    174             {
    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 = ip
    183             };
    184             if (AsynThreadCount <= 0)
    185             {//同步处理
    186                 DoInsertLogToDb(InsertLogSql, sqlParams);
    187             }
    188             else
    189             {//异步处理
    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             else
    229             {//异步处理
    230                 LogQueue.Add(sb.ToString());
    231             }
    232         }
    233         private Object _lockWriteTxt = new object();
    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.BaseStream.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             try
    269             {
    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         #endregion
    281 
    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 DbHandleBase 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         #endregion
    19         /// <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             #endregion
    57         }
    58     }

    3.NetDh框架完整源码

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

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

    异步队列处理日志的线程数。0表示同步处理;大于0表示后台开asynThreadCount个线程异步处理日志任务队列.普通日志量推荐默认的1,这样系统可异步处理日志,如果日志出错也是会记录到本地tx;,如果日志量较多,可设置大一些。

  • 相关阅读:
    ffmpeg中的数据结构(AVFormatContext)
    string转wstring
    Bento4
    根据代码结构和注释生成接口文档
    IOS摄像头采和显示
    git主分支覆盖子分支
    Nginx 面试 40 连问,快顶不住了
    数据库日期类型字段设计,应该如何选择
    java各种输入输出流
    android 之 linkToDeath和unlinkToDeath。(死亡代理)
  • 原文地址:https://www.cnblogs.com/michaeldonghan/p/9321691.html
Copyright © 2020-2023  润新知