2.NetDh框架之简单高效的日志操作类(附源码和示例代码)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 前言NetDh框架适用于C/S、B/S的服务端框架,可用于项目开发和学习。目前包含以下四个模块1.数据库操作层封装Dapper,支持多种数据库类型、多库实例,简单强大;此部分具体说明可参考博客: https://www.cnblogs.com/michaeldonghan/p/9317078.html2.提供简单高效的日志操作类使用,支持日志写入Db和txt、支持任何数据库类型写入(包括传统sql数据库和nosql数据库等)、支持同步写入日志和后台独立线程异步处理日志队列;此部分具体说明可参考博客: 本文以下章节内容。

前言

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<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表示同步处理;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                         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

 

分享、互相交流学习
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
12天前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
87 3
|
17天前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
|
1月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
35 2
|
12天前
|
SQL XML 监控
SpringBoot框架日志详解
本文详细介绍了日志系统的重要性及其在不同环境下的配置方法。日志用于记录系统运行时的问题,确保服务的可靠性。文章解释了各种日志级别(如 info、warn、error 等)的作用,并介绍了常用的日志框架如 SLF4J 和 Logback。此外,还说明了如何在 SpringBoot 中配置日志输出路径及日志级别,包括控制台输出与文件输出的具体设置方法。通过这些配置,开发者能够更好地管理和调试应用程序。
|
1月前
|
Java
日志框架log4j打印异常堆栈信息携带traceId,方便接口异常排查
日常项目运行日志,异常栈打印是不带traceId,导致排查问题查找异常栈很麻烦。
|
18天前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
15 0
|
1月前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
160 1
|
28天前
|
存储 运维 监控
超级好用的C++实用库之日志类
超级好用的C++实用库之日志类
28 0
|
2月前
|
Kubernetes Ubuntu Windows
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
110 3
|
13天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1576 12