磨刀不误砍柴工——统一日志系统 Log4Net/ExceptionLess
本文版权归博客园和作者吴双本人共同所有,转载和爬虫必须注明原文地址:www.cnblogs.com/tdws 。
一. 写在前面
本文Log4Net介绍了基础的方式,大数据量生产环境不能使用,中等日志量请日志单库。 希望爱技术的你不要错过exceptionless和ELK
第四节开始简单配置大牛们推荐的了ExceptionLess, 一款开源分布式日志系统。
日志系统对于任何项目都是必不可少的,无论对于测试阶段的debug,性能测试,执行时间,操作记录还是线上的问题排查,访问记录等,日志系统都扮演着重要的角色。本篇分享的目的是能帮助需要的人快速搭建自己的LogSystem.,仅供参考。 先上个图呗,自认为页面还算清爽吧:
我的LogSystem使用Log4net入库的方式,网上特别多的分享,但是能完整运行下来的真是很少,所以现在需要和以后用得上的小伙伴抓紧收藏咯。
二. Log4Net自定义内容入库
Log4Net存日志的方式,给人的感觉实在是不实用,IT行业不都求一个自动化吗?废话不说了,先上Log4net入库系统的代码。
LogSystem数据库结构,我的建议是一个项目一个表。
在Log组件中,你需要这样几个类。下面分别给出代码:
LogContent.cs,这里定义了Log实体,在实体化实体的时候,通过给构造函数传参创建好这个对象。注释很详细了
1 using System; 2 3 namespace LogComponent 4 { 5 public class LogContent 6 { 7 8 public LogContent(string logLevel, string logMsg, string logModule, string description, string userName) 9 { 10 LogLevel = logLevel; 11 UserName = userName; 12 Description = description; 13 LogMsg = logMsg; 14 LogModule = logModule; 15 } 16 17 /// <summary> 18 /// 日志级别 19 /// </summary> 20 public string LogLevel { get; set; } 21 22 /// <summary> 23 /// 日志消息 24 /// </summary> 25 public string LogMsg { get; set; } 26 27 /// <summary> 28 /// 系统登陆用户 29 /// </summary> 30 public string UserName { get; set; } 31 32 /// <summary> 33 /// 日志描述信息 34 /// </summary> 35 public string Description { get; set; } 36 37 /// <summary> 38 /// 记录时间 39 /// </summary> 40 public DateTime LogDate { get; set; } 41 42 /// <summary> 43 /// 模块名称 44 /// </summary> 45 public string LogModule { get; set; } 46 } 47 }
LogHelper.cs,定义了日志级别,和写入方法
1 [assembly: log4net.Config.XmlConfigurator(Watch = true,ConfigFile = "log4net.config")] 2 namespace LogComponent 3 { 4 public class LogHelper 5 { 6 static log4net.ILog log = log4net.LogManager.GetLogger("myLogger"); 7 8 /// <summary> 9 /// 异常日志 10 /// </summary> 11 /// <param name="logMsg">日志信息</param> 12 /// <param name="logModule">代码模块</param> 13 /// <param name="description">其他描述</param> 14 /// <param name="userName">用户名</param> 15 public static void LogError(string logMsg, string logModule, string description = "", string userName = "") 16 { 17 log.Error(new LogContent("Error", SubLogString(logMsg), logModule, SubLogString(description), userName)); 18 } 19 20 public static void LogInfo(string logMsg, string logModule, string description = "", string userName = "") 21 { 22 log.Info(new LogContent("Info", SubLogString(logMsg), logModule, SubLogString(description), userName)); 23 } 24 25 public static void LogWarn(string logMsg, string logModule, string description = "", string userName = "") 26 { 27 log.Warn(new LogContent("Warn", SubLogString(logMsg), logModule, SubLogString(description), userName)); 28 } 29 30 public static void LogDebug(string logMsg, string logModule, string description = "", string userName = "") 31 { 32 log.Debug(new LogContent("Debug", SubLogString(logMsg), logModule, SubLogString(description), userName)); 33 } 34 35 private static string SubLogString(string str) 36 { 37 if (str.Length > 1500) 38 { 39 return str.Substring(0, 1500); 40 } 41 return str; 42 } 43 } 44 }
MessagePartternConverter.cs
1 using log4net.Core; 2 using log4net.Layout.Pattern; 3 using System.IO; 4 using System.Reflection; 5 namespace LogComponent 6 { 7 class MessagePatternConverter : PatternLayoutConverter 8 { 9 protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) 10 { 11 if (Option != null) 12 { 13 // Write the value for the specified key 14 WriteObject(writer, loggingEvent.Repository, LookupProperty(Option, loggingEvent)); 15 } 16 else 17 { 18 // Write all the key value pairs 19 WriteDictionary(writer, loggingEvent.Repository, loggingEvent.GetProperties()); 20 } 21 } 22 /// <summary> 23 /// 通过反射获取传入的日志对象的某个属性的值 24 /// </summary> 25 /// <param name="property"></param> 26 /// <returns></returns> 27 private object LookupProperty(string property, log4net.Core.LoggingEvent loggingEvent) 28 { 29 object propertyValue = string.Empty; 30 PropertyInfo propertyInfo = loggingEvent.MessageObject.GetType().GetProperty(property); 31 if (propertyInfo != null) 32 propertyValue = propertyInfo.GetValue(loggingEvent.MessageObject, null); 33 return propertyValue; 34 } 35 } 36 }
MyLayout.cs
1 using log4net.Layout; 2 namespace LogComponent 3 { 4 class MyLayout : PatternLayout 5 { 6 public MyLayout() 7 { 8 this.AddConverter("property", typeof(MessagePatternConverter)); 9 } 10 } 11 }
其实看到这里,最重要的并不是代码了,核心部分Log4net都帮我们写好了,关键在于你的配置,下面是log4net.config的内容。拿到你的web项目里是一样用的。但是不要忘了在你的项目中引用nuget:log4net哟。
log4net.config如下:在其中主要配置了log入库的参数和sql语句,当然还有sql连接。注释已经很详细了
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <configSections> 4 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> 5 </configSections> 6 <log4net> 7 <root > 8 <level value="Debug"/> 9 <appender-ref ref="ADONetAppender"/> 10 </root> 11 <logger name="myLogger"> 12 <level value="Debug"/> 13 <appender-ref ref="ADONetAppender"/> 14 </logger> 15 <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender,log4net"> 16 <!--BufferSize为缓冲区大小,只有日志记录超value条才会一块写入到数据库--> 17 <bufferSize value="1"/> 18 <!--或写为<param name="BufferSize" value="1" />--> 19 <!--引用--> 20 <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> 21 <!--连接数据库字符串--> 22 <connectionString value="Data Source=115.29.54.31;Initial Catalog=LogSystem;uid=sa;pwd=sa.;MultipleActiveResultSets=True"/> 23 <!--插入到表Log--> 24 <commandText value="INSERT INTO HdPubLog ([LogDate],[LogMsg],[UserName],[Description],[LogLevel],[LogModule]) VALUES (@log_date,@LogMsg,@UserName,@Description,@LogLevel,@LogModule)"/> 25 <parameter> 26 <parameterName value="@log_date"/> 27 <dbType value="DateTime"/> 28 <layout type="log4net.Layout.RawTimeStampLayout"/> 29 <!--获取log4net中提供的日志时间RawTimeStampLayout为默认的时间输出格式--> 30 </parameter> 31 <parameter> 32 <parameterName value="@LogMsg"/> 33 <dbType value="String"/> 34 <size value="1510"/> 35 <layout type="LogComponent.MyLayout, LogComponent"> 36 <param name="ConversionPattern" value="%property{LogMsg}"/> 37 </layout> 38 </parameter> 39 <parameter> 40 <parameterName value="@UserName"/> 41 <dbType value="String"/> 42 <size value="50"/> 43 <layout type="LogComponent.MyLayout, LogComponent"> 44 <param name="ConversionPattern" value="%property{UserName}"/> 45 </layout> 46 </parameter> 47 <parameter> 48 <parameterName value="@Description"/> 49 <dbType value="String"/> 50 <size value="1510"/> 51 <layout type="LogComponent.MyLayout, LogComponent"> 52 <param name="ConversionPattern" value="%property{Description}"/> 53 </layout> 54 </parameter> 55 <parameter> 56 <parameterName value="@LogLevel"/> 57 <dbType value="String"/> 58 <size value="50"/> 59 <layout type="LogComponent.MyLayout, LogComponent"> 60 <param name="ConversionPattern" value="%property{LogLevel}"/> 61 </layout> 62 </parameter> 63 <parameter> 64 <parameterName value="@LogModule"/> 65 <dbType value="String"/> 66 <size value="50"/> 67 <layout type="LogComponent.MyLayout, LogComponent"> 68 <param name="ConversionPattern" value="%property{LogModule}"/> 69 </layout> 70 </parameter> 71 </appender> 72 </log4net> 73 </configuration>
这样一来,你的配置就完成了,你可以直接测试插入的情况:
三. 把Log信息可视化
我的UI使用的是Datatables.js,弹出框是layer,日期组件好像是layDate,下拉框是修改样式后的select2。UI代码是我自己的一个框架里的,内容太多就不贴出来了,你只需要和以前一样,把数据从库里查出来,绑定给任意你喜欢的数据表格上。由于单页面的日志系统没有什么复杂操作,就用个sqlHelper查一下就算了,代码和条件拼接如下
1 public class xxxDal 2 { 3 private SqlHelper _sqlHelper = new SqlHelper(); 4 5 /// <summary> 6 /// 获取xxx的日志 7 /// </summary> 8 /// <param name="model"></param> 9 /// <returns></returns> 10 public List<LogModel> GetxxxLog(SM_LogModel model) 11 { 12 StringBuilder sql = new StringBuilder(); 13 List<SqlParameter> sqlParameters = new List<SqlParameter>(); 14 StringBuilder sqlWhere = new StringBuilder(); 15 if (!string.IsNullOrWhiteSpace(model.LogStartTime)) 16 { 17 sqlParameters.Add(new SqlParameter("@LogStartTime", model.LogStartTime)); 18 sqlWhere.Append(@" AND h.LogDate > @LogStartTime"); 19 } 20 if (!string.IsNullOrWhiteSpace(model.LogEndTime)) 21 { 22 sqlParameters.Add(new SqlParameter("@LogEndTime", model.LogEndTime)); 23 sqlWhere.Append(@" AND h.LogDate < @LogEndTime"); 24 } 25 if (!string.IsNullOrWhiteSpace(model.LogLevel)) 26 { 27 sqlParameters.Add(new SqlParameter("@LogLevel", model.LogLevel)); 28 sqlWhere.Append(@" AND h.LogLevel = @LogLevel"); 29 } 30 if (!string.IsNullOrWhiteSpace(model.LogModule)) 31 { 32 sqlParameters.Add(new SqlParameter("@LogModule", model.LogModule)); 33 sqlWhere.Append(@" AND h.LogModule = @LogModule"); 34 } 35 sql.AppendFormat(@" 36 WITH t AS ( SELECT ROW_NUMBER() OVER ( ORDER BY id DESC ) AS IndexNum , 37 [Id] , 38 CONVERT(VARCHAR, [LogDate], 21) AS [LogDate] , 39 [UserName] , 40 SUBSTRING([Description], 0, 150) AS [Description] , 41 SUBSTRING([LogMsg], 0, 200) AS [LogMsg] , 42 [LogLevel] , 43 [LogModule] 44 FROM [LogSystem].[dbo].[xxxLog] h 45 WHERE 1 = 1 46 {0} 47 ) 48 SELECT * 49 FROM t 50 WHERE IndexNum > @startIndex 51 AND indexnum < @endIndex", sqlWhere); 52 sqlParameters.Add(new SqlParameter("@startIndex", model.Start)); 53 sqlParameters.Add(new SqlParameter("@endIndex", model.Start + model.Length)); 54 55 DataTable dt = _sqlHelper.ExecuteDataTable(sql.ToString(), sqlParameters.ToArray()); 56 return DataTableTools<LogModel>.DataTableToList(dt); 57 } 58 59 public int GetxxxLogTotalCount(SM_LogModel model) 60 { 61 StringBuilder sql = new StringBuilder(); List<SqlParameter> sqlParameters = new List<SqlParameter>(); 62 sql.Append(@" 63 SELECT COUNT(*) 64 FROM [HdPubLog] h where 1=1 "); 65 if (!string.IsNullOrWhiteSpace(model.LogStartTime)) 66 { 67 sqlParameters.Add(new SqlParameter("@LogStartTime", model.LogStartTime)); 68 sql.Append(@" AND h.LogDate > @LogStartTime"); 69 } 70 if (!string.IsNullOrWhiteSpace(model.LogEndTime)) 71 { 72 sqlParameters.Add(new SqlParameter("@LogEndTime", model.LogEndTime)); 73 sql.Append(@" AND h.LogDate < @LogEndTime"); 74 } 75 if (!string.IsNullOrWhiteSpace(model.LogLevel)) 76 { 77 sqlParameters.Add(new SqlParameter("@LogLevel", model.LogLevel)); 78 sql.Append(@" AND h.LogLevel = @LogLevel"); 79 } 80 if (!string.IsNullOrWhiteSpace(model.LogModule)) 81 { 82 sqlParameters.Add(new SqlParameter("@LogModule", model.LogModule)); 83 sql.Append(@" AND h.LogModule = @LogModule"); 84 } 85 return _sqlHelper.ExecuteScalar<int>(sql.ToString(), sqlParameters.ToArray()); 86 } 87 88 [HttpPost] 89 public LogModel GetxxxxSignelLog(int id) 90 { 91 string sql = @" 92 SELECT [Id] , 93 CONVERT(VARCHAR(30), [LogDate], 21) AS [LogDate] , 94 [UserName] , 95 [Description] , 96 [LogMsg] , 97 [LogLevel] , 98 [LogModule] , 99 [Id] IndexNum 100 FROM [LogSystem].[dbo].[xxxxLog] h 101 WHERE h.id = @Id"; 102 var row = _sqlHelper.ExecuteDataRow(sql, new SqlParameter("@Id", id)); 103 return DataTableTools<LogModel>.DataRowToModel(row); 104 } 105 }
话说到这,Log4Net数据库日志系统已经完成。
四. 更好的方式—— ExceptionLess本地部署
还是先上个本地部署图:
部署的过程中,参考了官方文档和一位园友的文章。
http://www.cnblogs.com/savorboard/p/exceptionless.html
http://www.cnblogs.com/uptothesky/p/5864863.html
https://github.com/exceptionless/Exceptionless/wiki/Self-Hosting
实际上参照着参考文档的Production配置文档,把Java环境配置好,然后装好ES服务并启动. 你的self hosting基本都不会有问题。
五. 写在最后
不准备给自己搭建一个LogSystem吗?如果用得上抓紧收藏吧。有疑问欢迎留言。
如果我的点滴分享对你有点滴帮助,欢迎点击下方红色按钮关注,我将持续输出干货分享。也欢迎为我也为你自己点赞支持。
2017.05.21补充 日志系统已升级为Mongo
--保持学习,谨记谦虚。不端不装,有趣有梦。