磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess相关的知识,希望对你有一定的参考价值。


来源:坦荡

cnblogs.com/tdws/p/6298124.html


一.   写在前面


本文Log4Net介绍了基础的方式,大数据量生产环境不能使用,中等日志量请日志单库。 


希望爱技术的你不要错过exceptionless和ELK,第四节开始简单配置大牛们推荐的了ExceptionLess, 一款开源分布式日志系统。


日志系统对于任何项目都是必不可少的,无论对于测试阶段的debug,性能测试,执行时间,操作记录还是线上的问题排查,访问记录等,日志系统都扮演着重要的角色。


本篇分享的目的是能帮助需要的人快速搭建自己的LogSystem.,仅供参考。先上个图呗,自认为页面还算清爽吧。


我的LogSystem使用Log4net入库的方式,网上特别多的分享,但是能完整运行下来的真是很少,所以现在需要和以后用得上的小伙伴抓紧收藏咯。



 

磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess


二.  Log4Net自定义内容入库


Log4Net存日志的方式,给人的感觉实在是不实用,IT行业不都求一个自动化吗?废话不说了,先上Log4net入库系统的代码。


LogSystem数据库结构,我的建议是一个项目一个表。


磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess


在Log组件中,你需要这样几个类。下面分别给出代码:


磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess


LogContent.cs,这里定义了Log实体,在实体化实体的时候,通过给构造函数传参创建好这个对象。注释很详细了


using System;

namespace LogComponent

{

 public class LogContent

{

        public LogContent(string logLevel, string logMsg, string logModule, string description, string userName)

      {

            LogLevel = logLevel;

            UserName = userName;

            Description = description;

            LogMsg = logMsg;

            LogModule = logModule;

        }

        /// 日志级别

        public string LogLevel { get; set; }

        /// 日志消息

        public string LogMsg { get; set; }

        /// 系统登陆用户

        public string UserName { get; set; }

        /// 日志描述信息

        public string Description { get; set; }

        /// 记录时间

        public DateTime LogDate { get; set; }

        /// 模块名称

        public string LogModule { get; set; }

    }

}


LogHelper.cs,定义了日志级别,和写入方法


[assembly: log4net.Config.XmlConfigurator(Watch = true,ConfigFile = "log4net.config")]

namespace LogComponent

{

    public class LogHelper

    {

        static log4net.ILog log = log4net.LogManager.GetLogger("myLogger");

        /// 异常日志

        /// <param name="logMsg">日志信息</param>

        /// <param name="logModule">代码模块</param>

        /// <param name="description">其他描述</param>

        /// <param name="userName">用户名</param>

        public static void LogError(string logMsg, string logModule, string description = "", string userName = "")

        {

            log.Error(new LogContent("Error", SubLogString(logMsg), logModule, SubLogString(description), userName));

        }

        public static void LogInfo(string logMsg, string logModule, string description = "", string userName = "")

        {

            log.Info(new LogContent("Info", SubLogString(logMsg), logModule, SubLogString(description), userName));

        }

        public static void LogWarn(string logMsg, string logModule, string description = "", string userName = "")

        {

            log.Warn(new LogContent("Warn", SubLogString(logMsg), logModule, SubLogString(description), userName));

        }

        public static void LogDebug(string logMsg, string logModule, string description = "", string userName = "")

        {

            log.Debug(new LogContent("Debug", SubLogString(logMsg), logModule, SubLogString(description), userName));

        }

        private static string SubLogString(string str)

        {

            if (str.Length > 1500)

            {

                return str.Substring(0, 1500);

            }

            return str;

        }

    }

}


MessagePartternConverter.cs


using log4net.Core;

using log4net.Layout.Pattern;

using System.IO;

using System.Reflection;

namespace LogComponent

{

    class MessagePatternConverter : PatternLayoutConverter

    {

        protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)

        {

            if (Option != null)

            {

                // Write the value for the specified key

                WriteObject(writer, loggingEvent.Repository, LookupProperty(Option, loggingEvent));

            }

            else

            {

                // Write all the key value pairs

                WriteDictionary(writer, loggingEvent.Repository, loggingEvent.GetProperties());

            }

        }

        /// 通过反射获取传入的日志对象的某个属性的值

        private object LookupProperty(string property, log4net.Core.LoggingEvent loggingEvent)

        {

            object propertyValue = string.Empty;

            PropertyInfo propertyInfo = loggingEvent.MessageObject.GetType().GetProperty(property);

            if (propertyInfo != null)

                propertyValue = propertyInfo.GetValue(loggingEvent.MessageObject, null);

            return propertyValue;

        }

    }

}


MyLayout.cs


using log4net.Layout;

namespace LogComponent

{

    class MyLayout : PatternLayout

    {

        public MyLayout()

        {

            this.AddConverter("property", typeof(MessagePatternConverter));

        }

    }

}


其实看到这里,最重要的并不是代码了,核心部分Log4net都帮我们写好了,关键在于你的配置,下面是log4net.config的内容。拿到你的web项目里是一样用的。但是不要忘了在你的项目中引用nuget:log4net哟。


磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess


log4net.config如下:在其中主要配置了log入库的参数和sql语句,当然还有sql连接。注释已经很详细了


<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>

  </configSections>

  <log4net>

    <root >

      <level value="Debug"/>

      <appender-ref ref="ADONetAppender"/>

    </root>

    <logger name="myLogger">

      <level value="Debug"/>

      <appender-ref ref="ADONetAppender"/>

    </logger>

    <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender,log4net">

      <!--BufferSize为缓冲区大小,只有日志记录超value条才会一块写入到数据库-->

      <bufferSize value="1"/>

      <!--或写为<param name="BufferSize" value="1" />-->

      <!--引用-->

      <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

      <!--连接数据库字符串-->

      <connectionString value="Data Source=115.29.54.31;Initial Catalog=LogSystem;uid=sa;pwd=sa.;MultipleActiveResultSets=True"/>

      <!--插入到表Log-->

      <commandText value="INSERT INTO HdPubLog ([LogDate],[LogMsg],[UserName],[Description],[LogLevel],[LogModule]) VALUES (@log_date,@LogMsg,@UserName,@Description,@LogLevel,@LogModule)"/>

      <parameter>

        <parameterName value="@log_date"/>

        <dbType value="DateTime"/>

        <layout type="log4net.Layout.RawTimeStampLayout"/>

        <!--获取log4net中提供的日志时间RawTimeStampLayout为默认的时间输出格式-->

      </parameter>

      <parameter>

        <parameterName value="@LogMsg"/>

        <dbType value="String"/>

        <size value="1510"/>

        <layout type="LogComponent.MyLayout, LogComponent">

          <param name="ConversionPattern" value="%property{LogMsg}"/>

        </layout>

      </parameter>

      <parameter>

        <parameterName value="@UserName"/>

        <dbType value="String"/>

        <size value="50"/>

        <layout type="LogComponent.MyLayout, LogComponent">

          <param name="ConversionPattern" value="%property{UserName}"/>

        </layout>

      </parameter>

      <parameter>

        <parameterName value="@Description"/>

        <dbType value="String"/>

        <size value="1510"/>

        <layout type="LogComponent.MyLayout, LogComponent">

          <param name="ConversionPattern" value="%property{Description}"/>

        </layout>

      </parameter>

      <parameter>

        <parameterName value="@LogLevel"/>

        <dbType value="String"/>

        <size value="50"/>

        <layout type="LogComponent.MyLayout, LogComponent">

          <param name="ConversionPattern" value="%property{LogLevel}"/>

        </layout>

      </parameter>

      <parameter>

        <parameterName value="@LogModule"/>

        <dbType value="String"/>

        <size value="50"/>

        <layout type="LogComponent.MyLayout, LogComponent">

          <param name="ConversionPattern" value="%property{LogModule}"/>

        </layout>

      </parameter>

    </appender>

  </log4net>

</configuration>


这样一来,你的配置就完成了,你可以直接测试插入的情况:


磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess


三.   把Log信息可视化


我的UI使用的是Datatables.js,弹出框是layer,日期组件好像是layDate,下拉框是修改样式后的select2。UI代码是我自己的一个框架里的,内容太多就不贴出来了,你只需要和以前一样,把数据从库里查出来,绑定给任意你喜欢的数据表格上。由于单页面的日志系统没有什么复杂操作,就用个sqlHelper查一下就算了,代码和条件拼接如下


public class xxxDal

{

        private SqlHelper _sqlHelper = new SqlHelper();

        /// 获取xxx的日志

        public List<LogModel> GetxxxLog(SM_LogModel model)

        {

            StringBuilder sql = new StringBuilder();

            List<SqlParameter> sqlParameters = new List<SqlParameter>();

            StringBuilder sqlWhere = new StringBuilder();

            if (!string.IsNullOrWhiteSpace(model.LogStartTime))

            {

                sqlParameters.Add(new SqlParameter("@LogStartTime", model.LogStartTime));

                sqlWhere.Append(@" AND h.LogDate > @LogStartTime");

            }

            if (!string.IsNullOrWhiteSpace(model.LogEndTime))

            {

                sqlParameters.Add(new SqlParameter("@LogEndTime", model.LogEndTime));

                sqlWhere.Append(@"  AND h.LogDate < @LogEndTime");

            }

            if (!string.IsNullOrWhiteSpace(model.LogLevel))

            {

                sqlParameters.Add(new SqlParameter("@LogLevel", model.LogLevel));

                sqlWhere.Append(@" AND h.LogLevel = @LogLevel");

            }

            if (!string.IsNullOrWhiteSpace(model.LogModule))

            {

                sqlParameters.Add(new SqlParameter("@LogModule", model.LogModule));

                sqlWhere.Append(@" AND h.LogModule = @LogModule");

            }

            sql.AppendFormat(@"

                    WITH    t AS ( SELECT   ROW_NUMBER() OVER ( ORDER BY id DESC ) AS IndexNum ,

                        [Id] ,

                        CONVERT(VARCHAR, [LogDate], 21) AS [LogDate] ,

                        [UserName] ,

                        SUBSTRING([Description], 0, 150) AS [Description] ,

                        SUBSTRING([LogMsg], 0, 200) AS [LogMsg] ,

                        [LogLevel] ,

                        [LogModule]

               FROM     [LogSystem].[dbo].[xxxLog] h

               WHERE    1 = 1

                       {0}

             )

    SELECT  *

    FROM    t

    WHERE   IndexNum > @startIndex

            AND indexnum < @endIndex", sqlWhere);

            sqlParameters.Add(new SqlParameter("@startIndex", model.Start));

            sqlParameters.Add(new SqlParameter("@endIndex", model.Start + model.Length));

            DataTable dt = _sqlHelper.ExecuteDataTable(sql.ToString(), sqlParameters.ToArray());

            return DataTableTools<LogModel>.DataTableToList(dt);

        }

        public int GetxxxLogTotalCount(SM_LogModel model)

        {

            StringBuilder sql = new StringBuilder(); List<SqlParameter> sqlParameters = new List<SqlParameter>();

            sql.Append(@"

                    SELECT  COUNT(*)

                    FROM    [HdPubLog] h where 1=1 ");

            if (!string.IsNullOrWhiteSpace(model.LogStartTime))

            {

                sqlParameters.Add(new SqlParameter("@LogStartTime", model.LogStartTime));

                sql.Append(@" AND h.LogDate > @LogStartTime");

            }

            if (!string.IsNullOrWhiteSpace(model.LogEndTime))

            {

                sqlParameters.Add(new SqlParameter("@LogEndTime", model.LogEndTime));

                sql.Append(@" AND h.LogDate < @LogEndTime");

            }

            if (!string.IsNullOrWhiteSpace(model.LogLevel))

            {

                sqlParameters.Add(new SqlParameter("@LogLevel", model.LogLevel));

                sql.Append(@" AND h.LogLevel = @LogLevel");

            }

            if (!string.IsNullOrWhiteSpace(model.LogModule))

            {

                sqlParameters.Add(new SqlParameter("@LogModule", model.LogModule));

                sql.Append(@" AND h.LogModule = @LogModule");

            }

            return _sqlHelper.ExecuteScalar<int>(sql.ToString(), sqlParameters.ToArray());

        }

        [HttpPost]

        public LogModel GetxxxxSignelLog(int id)

        {

            string sql = @"

                    SELECT  [Id] ,

                            CONVERT(VARCHAR(30), [LogDate], 21) AS [LogDate] ,

                            [UserName] ,

                            [Description] ,

                            [LogMsg] ,

                            [LogLevel] ,

                            [LogModule] ,

                            [Id] IndexNum 

                    FROM    [LogSystem].[dbo].[xxxxLog] h

                    WHERE   h.id = @Id";

            var row = _sqlHelper.ExecuteDataRow(sql, new SqlParameter("@Id", id));

            return DataTableTools<LogModel>.DataRowToModel(row);

        }

    }


话说到这,Log4Net数据库日志系统已经完成。


四.  更好的方式—— ExceptionLess本地部署


 还是先上个本地部署图:


磨刀不误砍柴工:统一日志系统 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吗?如果用得上抓紧收藏吧。有疑问欢迎留言。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

以上是关于磨刀不误砍柴工:统一日志系统 Log4Net/ExceptionLess的主要内容,如果未能解决你的问题,请参考以下文章

磨刀不误砍柴工—Exceptionless搭配log4net记录日志

windows一键部署java项目

系统梳理业务日志记录

SpringBoot统一日志管理

从 iOS 设备获取统一系统日志

systemd的统一日志记录系统journalctl,助力问题定位