• NLog日志框架使用探究-2


     目录


    前言

    在一年前,我写过一篇关于NLog入门文章《NLog日志框架使用探究-1》,文章简单的介绍了Nlog的基本使用以及如何使用Log4View2工具配合统一收集日志查看。本篇文章会记录一些NLog常用的用法。

    自定义参数

    有时候我们需要根据我们的业务特征自定义一些参数。比如有个唯一的Id。这时候我们可以自定义参数,将Id提取出来,而不是放到日志内容中,这样可以方便检索。
    EventProperties Layout Renderer文档中,支持自定义EventPropertie动态的渲染到Layout中。
    Nlog已经有一些自定义的参数了,如${counter}${longdate}${message:format=message}等。
    自定义的参数使用格式为${event-properties:item=String:culture=String:format=String}

    官方示例如下,代码中定义了四种Property

    ...
    Logger logger = LogManager.GetCurrentClassLogger();
    LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, "", "Pass my custom value");
    theEvent.Properties["MyValue"] = "My custom string";
    theEvent.Properties["MyDateTimeValue"] = new DateTime(2015, 08, 30, 11, 26, 50);
    theEvent.Properties["MyDateTimeValueWithCulture"] = new DateTime(2015, 08, 30, 11, 26, 50);
    theEvent.Properties["MyDateTimeValueWithCultureAndFormat"] = new DateTime(2015, 08, 30, 11, 26, 50);
    logger.Log(theEvent);
    ...
    

    在配置文件中可以通过${event-properties:item=String:culture=String:format=String}获取到

    ${event-properties:item=MyValue} -- renders "My custom string"
    ${event-properties:MyDateTimeValue:format=yyyy-M-dd}"; -- renders "2015-8-30"
    ${event-properties:MyDateTimeValueWithCulture:culture=en-US} -- renders "8/30/2015 11:26:50 AM"
    ${event-properties:MyDateTimeValueWithCultureAndFormat:format=yyyy-M-dd HH:mm:ss:culture=en-US} -- renders "2015-8-30 11:26:50"
    

    从上可以看出,若我们需要自定义参数,我们需要创建LogEventInfo对象,并通过Log()方法记录LogEventInfo对象

    调用 Info()等方法内部实际也是创建了LogEventInfo对象,最终还是调用Log()方法。

    下面我们可以自己的代码测试一下。

    修改上面的Json输出的配置,由于Memo参含有中文,因此需要将encode设置为false,防止被编码为Unicode。以下为Nlog相关源码

    protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength)
    {
        Inner.RenderAppendBuilder(logEvent, builder);
        if (JsonEncode && builder.Length > orgLength)
        {
            if (RequiresJsonEncode(builder, orgLength))
            {
                var str = builder.ToString(orgLength, builder.Length - orgLength);
                builder.Length = orgLength;
                Targets.DefaultJsonSerializer.AppendStringEscape(builder, str, EscapeUnicode);
            }
        }
    }
    private bool RequiresJsonEncode(StringBuilder target, int startPos = 0)
    {
        for (int i = startPos; i < target.Length; ++i)
        {
            if (Targets.DefaultJsonSerializer.RequiresJsonEscape(target[i], EscapeUnicode))
            {
                return true;
            }
        }
        return false;
    }
    

    在上一篇文章中有同学提问,当encode设置为false时,输出的内容不会有双引号。查阅了下一源码,确实如此。具体为什么这样设计不是很理解,有知道的同学可以说明一下。

    public class JsonAttribute
    {
        ...
        public bool Encode
        {
            get => LayoutWrapper.JsonEncode;
            set => LayoutWrapper.JsonEncode = value;
        }
        ...
    }
    private bool RenderAppendJsonPropertyValue(JsonAttribute attrib, LogEventInfo logEvent, StringBuilder sb, bool beginJsonMessage)
    {
        BeginJsonProperty(sb, attrib.Name, beginJsonMessage);
        if (attrib.Encode)
        {
            // ""{0}":{1}"{2}""
            sb.Append('"');
        }
        ...
        if (attrib.Encode)
        {
            sb.Append('"');
        }
        return true;
    }
    

    接下来在nlog.Config的target配置中添加以下配置

    <layout xsi:type="JsonLayout" >
        <attribute name="counter" layout="${counter}" />
        <attribute name="time" layout="${longdate}" />
        <attribute name="level" layout="${level:upperCase=true}"/>
        <attribute name="message" layout="${message:format=message}"  encode="false" />
        <attribute name="Id" layout="${event-properties:item=Id}" />
        <attribute name="No" layout="${event-properties:item=No}" />
        <attribute name="Memo" layout="${event-properties:item=Memo}"  encode="false" />
    </layout>
    

    代码对应的Properites

    LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, "", "自定义动态参数");
    theEvent.Properties["Id"] = Guid.NewGuid();
    theEvent.Properties["No"] = "1";
    theEvent.Properties["Memo"] = "备注";
    logger.Log(theEvent);
    

    输出如下图
    17.png

    需要注意若我们自定义参数,使用json格式写入到文件,则代码中的Properties的Key必须和配置文件的${event-properties:item=key}中的key大小写一致。

    日志输出方式

    文件

    当我们日志需要以文件存放时,通常情况需要根据服务名、模块名等区分日志目录,同样可以通过自定义参数输出。

    假设我们需要将日志按服务分目录,同时日志文件名含有我们指定的内容前缀,在每一条日志中需要记录一个唯一的编号。日志配置如下

    ...
    <targets>
    <target xsi:type="File"
            name="InfoFile"
            fileName="${basedir}/logs/${event-properties:item=ServiceName}/${logger:shortName=true}/${shortdate}/${event-properties:item=LogPrefix}_log.txt"
            encoding="utf-8">
        <layout xsi:type="JsonLayout" >
            <attribute name="counter" layout="${counter}" />
            <attribute name="time" layout="${longdate}" />
            <attribute name="level" layout="${level:upperCase=true}"/>
            <attribute name="UniqueNo" layout="${event-properties:item=UniqueNo}" />
            <attribute name="Message" layout="${message:format=message}"  encode="false" />
        </layout>
    </target>
    </targets>
    <rules>
        <logger name="*" maxlevel="Info" writeTo="InfoFile" />
    </rules>
    

    先看下记录下来的日志,圈出来的都是我们自定义生成的值。
    20191202143515.png

    1. ${basedir}是程序的运行目录
    2. ${logger:shortName=true}是代码中指定的程序名名称,在代码中可以通过NLog.LogManager.GetLogger("test")指定日志名或NLog.LogManager.GetCurrentClassLogger()指向当前类的全名(包括命名空间)。
    3. ${shortdate}是短日期格式,Nlog也内置了${date}获取完整的时间,前面我们说过了可以通过${event-properties:item=String:culture=String:format=String}自定义格式,这里也可以通过${date:format=String}自定义日期格式,比如${date:format=yyyyMMdd}输出的就是如20191201的日期格式。

    网络传输

    上一章我们提到,日志通过网络发送到Log4View2等工具统一汇总。

    <targets async="true">
        <target xsi:type="Network" address="udp://127.0.0.1:878" name="network" newLine="false" maxMessageSize="65000" encoding="gbk" layout="${log4jxmlevent:includeCallSite=true:includeNLogData=true}"/>
    </targets>
    
    <rules>
        <logger name="*" minlevel="Info" writeTo="network" />
        ...
    </rules>
    

    Nlog支持tcp或udp协议进行网络传输。

    • 通过xsi:type="Network"指定网络传输
    • 通过address="协议://ip:端口"指定协议和地址。
    • 通过layout="${log4jxmlevent:includeCallSite=true:includeNLogData=true}序列化为XML传输,当然我们也可以传输自定义的格式或Json格式。只要在目标端使用对应的格式解析即可。

    下面还是通过Xml序列化传输到Log4View2为例。当我们代码中自定义的字段序列化成Xml发送到对端。我们可以在Log4View2界面上的列右键选择Show Column Chooser项选择哪些字段显示,非常方便。

    18.png

    还支持筛选列,方便我们查找。

    19.png

    数据库

    一般情况下日志也不需要实时查看,通常都是排查问题的时候需要看,因此有时候我们可以希望先将日志统一汇总后在做日志分析等工作。Nlog支持将数据插入数据库,比如我将日志入库到Oracle数据库中,使用Oracle.ManagedDataAccess。我们可以通过nuget安装库包Install-Package Oracle.ManagedDataAccess

    <target xsi:type="Database"
                  name="DB45"
                  dbProvider="Oracle.ManagedDataAccess.Client.OracleConnection, Oracle.ManagedDataAccess"
                  keepConnection="true" optimizeBufferReuse="true"
                  connectionString="Data Source=10.60.45.239/devdb;user id=fgmain10001;password=test1"
                  commandText="insert into NETWORKLAYERLOG (TIME, LOGLEVEL, APPID, LOGGER,IDENTITY, REQUESTID, MESSAGE,EXCEPTION)
                  values (to_timestamp(:TIME,'YYYY-MM-DD HH24:MI:SS.FF'), :LOGLEVEL, :APPID, :LOGGER,:IDENTITY, :REQUESTID, :MESSAGE,:EXCEPTION)">
            <parameter name=":TIME" layout="${longdate}" />
            <parameter name=":LOGLEVEL" layout="${level}" />
            <parameter name=":APPID" layout="${event-properties:item=AppId}" />
            <parameter name=":LOGGER" layout="${logger}" />
            <parameter name=":IDENTITYID" layout="${event-properties:item=IdentityId}" />
            <parameter name=":REQUESTID" layout="${event-properties:item=RequestId}" />
            <parameter name=":MESSAGE" layout="${message}" />
            <parameter name=":EXCEPTION" layout="${exception:format=toString,Data}" />
          </target>
    
    • dbProvider:首先配置dbProvider。
    • keepConnection:表示是否需要保持连接。
    • optimizeBufferReuse:表示是否是否使用连接池。
    • connectionString:入库的语句。
    • connectionString:连接字符串。
    • commandText:sql语句,sql语句支持参数化。
    • <parameter name="ColumnName" layout="Value" />:参数,name为参数名,layout为参数值。

    在Log4View2可以将数据源指向日志所在的表。选择数据库接收器。
    20191202195835.png
    填写相关配置后,选择表。
    20191202163947.png

    选择表的时候需要选择一个Key,但并不是所有Key都是可选的。

    从Log4View2源码看,Key需要满足是id结尾时类型是数值或时间类型或者列设置了自增。

    this.IsKey = ((this.Name.ToLowerInvariant().EndsWith("id") && DbMessageKey.IsValidKeyType(this.Type)) || column.AutoIncrement);
    public static bool IsValidKeyType(Type type)
    {
        return type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) || type == typeof(decimal) || type == typeof(DateTime);
    }
    

    1.gif

    这个key是用于时间筛选的。在日志配置中也可以设置获取指定的日志等级,或者,我们可以指定一个列作为时间筛选条件。

    看下Log4View2的源码,在DBReceiver初始化的时候会把我们选择的Key传入赋值给_dbMessageKey

    
    public DbReceiver(IReceiverFactory factory, ReceiverConfig recRow) : base(factory, recRow)
    {
        ...
        this._columns = new DbColumns(dbReceiverConfig.DbColumns);
        this._dbMessageKey = this._columns.Key;
        ...
    }
    
    

    在初始化查询sql的时候就会用到该值

    private DbCommand CreateReadQuery()
    {
        ...
        DbCommand dbCommand = this._database.CreateCommand();
        dbCommand.CommandTimeout = this._commandTimeout;
        string parameterName = this._database.GetParameterName(0);
        string arg = this._dbMessageKey.IsUnique ? ">" : ">=";
        string text = this._database.QuoteName(this._dbMessageKey.Name);
        string tableName = this._database.QuoteName(this._tableName);
        DbParameter dbParameter = dbCommand.CreateParameter();
        dbParameter.ParameterName = parameterName;
        dbParameter.DbType = this._dbMessageKey.DbType;
        dbParameter.Value = this._dbMessageKey.ParameterValue;
        dbCommand.Parameters.Add(dbParameter);
        string text2 = string.Format("WHERE ({0} {2} {1})", text, parameterName, arg);
        ...
        return dbCommand;
    }
    

    20191202180402.png

    20191202180450.png

    科学使用

    Log4View2工具首次安装使用有30天的试用期,试用期过了一些功能就会被限制。下一章我会讲解如何是使用反编译工具科(po)学(jie)使用Log4View2。


    参考文档

    1. Event Context Layout Renderer
    2. EventProperties Layout Renderer
    3. Log4ViewHelp
    4. DatabaseTarget.DBProvider Property
    5. Database target

    出处:https://www.cnblogs.com/Jack-Blog/p/11972400.html

  • 相关阅读:
    c# TCP高性能通信
    c#实现的HTTP服务端
    c#的二进制序列化组件MessagePack介绍
    c# 任务超时执行
    c#项目总结
    etcd客户端c#
    开发的服务集群部署方案,以etcd为基础(java)
    udt的java版本judt项目持续升级1.2版本
    udt通信java再次升级1.1版
    (转)Spring Boot(二) & lombok
  • 原文地址:https://www.cnblogs.com/mq0036/p/12747801.html
Copyright © 2020-2023  润新知