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」創作共享協議。歡迎轉載,請在明顯位置給出出處及鏈接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM