• 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

    20191127212134.png
    微信扫一扫二维码关注订阅号杰哥技术分享
    出处:本文地址:https://www.cnblogs.com/Jack-Blog/p/11972400.html
    作者:杰哥很忙
    本文使用「CC BY 4.0」创作共享协议。欢迎转载,请在明显位置给出出处及链接。

  • 相关阅读:
    BZOJ 1040 (ZJOI 2008) 骑士
    BZOJ 1037 (ZJOI 2008) 生日聚会
    ZJOI 2006 物流运输 bzoj1003
    ZJOI 2006 物流运输 bzoj1003
    NOI2001 炮兵阵地 洛谷2704
    NOI2001 炮兵阵地 洛谷2704
    JLOI 2013 卡牌游戏 bzoj3191
    JLOI 2013 卡牌游戏 bzoj3191
    Noip 2012 day2t1 同余方程
    bzoj 1191 [HNOI2006]超级英雄Hero——二分图匹配
  • 原文地址:https://www.cnblogs.com/Jack-Blog/p/11972400.html
Copyright © 2020-2023  润新知