1. 程式人生 > >利用log4net建立日誌檔案時過濾日誌,這是坑還是?

利用log4net建立日誌檔案時過濾日誌,這是坑還是?

前言

網上貌似沒有太多關於log4net過濾日誌的資料,在研究過程中發現一點小問題,這裡做下記錄,希望對後續有用到的童鞋起到一丟丟幫助作用。

log4net日誌過濾

由於是在.NET Core中使用,所以這裡為了演示,我們建立一個.NET Core控制檯程式,同時呢通過安裝log4net最新穩定版本(2.0.8),好了,對於.NET Core而言,在開發時可直接配置web.config啟用日誌功能,此時會將不同級別日誌直接放在同一檔案中,在實際開發中無論我們使用log4net還是serilog或者自己寫一個也好,大部分都會根據不同級別建立不同目錄,這樣更加易於後續跟蹤和排查問題。但是也會存在特殊的需求,比如本文中,我們只需要建立兩個日誌檔案,一個用於正常的資訊檔案記錄,一個是審計資訊記錄,而且日誌檔名可能不是日期格式,而是由我們自己根據配置給定,所以基於以上需求,我們來完成此項任務,首先,我們在控制檯根目錄下建立如下單獨的log4net .config配置檔案:

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

  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="log.txt" />
    <appendToFile value="true" />
    <countDirection value="1"/>
    <maximumFileSize value="10MB" />
    <maxSizeRollBackups value="-1" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"  />
    </layout>
  </appender>
  <root>
    <level value="DEBUG"/>
    <appender-ref ref="RollingFileAppender" />
  </root>
</log4net>

接下來則是讀取上述配置檔案,在.NET Framework中我們需要在Properties資料夾下的AssemblyInfo類中新增讀取上述日誌配置檔案類,如下:

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

但是到了.NET Core中壓根就沒有了上述Properties資料夾,此時我能想到的辦法只能根據日誌配置檔案所在的目錄去讀取(不知是否還有其他更好的辦法),並按照所提供的api,建立控制檯程式集倉儲和讀取配置檔案中XML的根節點並最終寫到log4net配置中,如下:

            var log4netFullPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\"));

            var log4netConfig = new XmlDocument();
            log4netConfig.Load(File.OpenRead(Path.Combine(log4netFullPath, "log4net.config")));

            var repo = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(Repository.Hierarchy.Hierarchy));

            Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);

接下來我們則是建立類的日誌介面,測試並列印日誌,如下:

    class Program
    {
        static readonly ILog log = LogManager.GetLogger(typeof(Program));
static void Main(string[] args) { var log4netFullPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\")); var log4netConfig = new XmlDocument(); log4netConfig.Load(File.OpenRead(Path.Combine(log4netFullPath, "log4net.config"))); var repo = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(Repository.Hierarchy.Hierarchy)); Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]); log.Info("Program start success......"); } }

在log4net中,通過新增節點PropertyFilter來實現屬性過濾,如下:

 <filter type="log4net.Filter.PropertyFilter">
      <key value="" />
      <stringToMatch value="" />
      <acceptOnMatch value="" />
 </filter>

如上key代表需要我們定義的鍵,而stringToMatch代表我們通過對應鍵所新增的值,最後acceptOnMatch代表是否接受匹配,為布林值,預設為true(接收匹配就記錄)。若我們在配置檔案appender節點小新增如下節點:

<filter type="log4net.Filter.PropertyFilter">
      <key value="filter" />
      <stringToMatch value="1" />
      <acceptOnMatch value="false" />
 </filter>

如上設定代表,當我們對應設定屬性的鍵為filter,而值為1時則記錄日誌,接下來我們在上述控制檯基礎通過log4net提供的api去設定執行緒級別的屬性,新增如下:

ThreadContext.Properties["filter"] = "1";
log.Info("1......");

ThreadContext.Properties["filter"] = "0";
log.Info("0......");

我們通過設定鍵filter的值為1和0時都記錄下來了,很顯然沒達到要求,我們只想記錄對應值等於1的日誌,此時需要在上述節點繼續新增如下一行來只記錄此屬性鍵對應值的日誌:

好了,到此有了對log4net日誌過濾的基礎鋪墊,接下來實現我們的需求:建立兩個日誌檔案,一個記錄正常資訊(排除審計),一個只記錄審計資訊 。接下來我們對配置檔案修改如下:

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

  <!--只接收auditing日誌-->
  <appender name="AuditingRollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="auditing.txt" />
    <appendToFile value="true" />
    <countDirection value="1"/>
    <maximumFileSize value="10MB" />
    <maxSizeRollBackups value="-1" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"  />
    </layout>
    
    <filter type="log4net.Filter.PropertyFilter">
      <key value="filter" />
      <stringToMatch value="auditing" />
    </filter>
    <filter type="log4net.Filter.DenyAllFilter" />
  
  </appender>

  <!--只接收除auditing以外的日誌-->
  <appender name="ExceptAuditingRollingFileAppender" type="log4net.Appender.RollingFileAppender">
  <file value="non-auditing.txt" />
  <appendToFile value="true" />
  <countDirection value="1"/>
  <maximumFileSize value="10MB" />
  <maxSizeRollBackups value="-1" />
  <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"  />
  </layout>
    
  <filter type="log4net.Filter.PropertyFilter">
    <key value="filter" />
    <stringToMatch value="auditing" />
    <acceptOnMatch value="false" />
  </filter>
    
  </appender>
  <root>
    <level value="DEBUG"/>
    <appender-ref ref="AuditingRollingFileAppender" />
    <appender-ref ref="ExceptAuditingRollingFileAppender" />
  </root>
</log4net>

上述配置檔案就不用我多講,接下來我們將控制檯中上述測試列印的日誌給移除,我們新增如下程式碼進行測試:

 //只接收auditing日誌
ThreadContext.Properties["filter"] = "auditing";
log.Info("auditing.......");

//只接收除了auditing以外日誌
ThreadContext.Properties["filter"] = "non-auditing";
log.Info("non-auditing.......");
log.Warn("測試非審計......");

//只接收auditing日誌
ThreadContext.Properties["filter"] = "auditing";
log.Info("測試審計.......");

雖然建立一個審計日誌檔案和一個非審計日誌檔案,我們的配置也沒任何毛病,但是通過上述日誌輸出發現,非審計檔案壓根沒有日誌卻全部到了審計檔案裡,這是為何呢? 經過排查,我開始猜測log4net難道是對值進行模糊匹配嗎?為了驗證猜想,我將上述對非審計的值(多加一個字母Z)修改成如下:

 

只要我們將上述非審計日誌的值設定時並不包含審計日誌裡的值(auditing)就沒問題,真香,哈哈。上述排除auditing節點以外日誌的節點是從反向考慮,當然我們也可以從正向考慮,設定為其他值,還是注意不要包含auditing,並且只記錄該值的記錄,如下

  <!--只接收除auditing以外的日誌-->
  <appender name="ExceptAuditingRollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="non-auditing.txt" />
    <appendToFile value="true" />
    <countDirection value="1"/>
    <maximumFileSize value="10MB" />
    <maxSizeRollBackups value="-1" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"  />
    </layout>

    <filter type="log4net.Filter.PropertyFilter">
      <key value="filter" />
      <stringToMatch value="other" />
      <acceptOnMatch value="true" />
    </filter>
    <filter type="log4net.Filter.DenyAllFilter" />
  </appender>

 

在log4net中除了設定屬性過濾外,還可以比如通過日誌中的字串進行匹配,比如我們新增如下字串匹配節點,說明包含cache_log的值將不會被記錄:

<filter type="log4net.Filter.StringMatchFilter">
      <stringToMatch value="cache_log" />
      <acceptOnMatch value="false" />
 </filter>

log4net執行時建立日誌

我們可能會遇到根據什麼規則或者需要在執行時建立日誌檔案,這個時候就不能如上寫死日誌檔名了,我們通過log4net提供給我們的api【%property】來實現,我們將上述節點file進行如下修改:

<file type="log4net.Util.PatternString" value="D:\logs\%property{LogName}" />

接著新增建立日誌檔名程式碼,注意要將如下第一行放在第二行前面,否則建立的檔名將為null,如下:

 ThreadContext.Properties["LogName"] = "runtime.log";

 Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);

總結 

log4net在以前.NET Framework中用到的比較多,但是並未使用過過濾日誌這一特性,最近要使用時研究發現的問題,看到網上對此資料甚少,所以在此作為備忘錄,能夠幫到有需要的童鞋當然再好不過