簡體   English   中英

動態設置每個 Logger 實例的 Nlog 日志級別 ASP.Net Core 2.x

[英]Dynamically Set Nlog Log Level per Logger Instance ASP.Net Core 2.x

目標:動態選擇我想要詳細記錄的 HTTP 請求(不同的日志級別)。

概述:我有一個 ASP.Net core 2.1 Web 服務器正在運行,並且一旦投入生產,如果我需要調試問題,我希望能夠更改日志級別。 我找到了如何全局更改日志級別 但是,更改日志級別是持久的......也就是每次調用我的控制器后都不會重置。

    [HttpGet]
    public async Task<IEnumerable<string>> Get()
    {
        this.Logger.LogTrace("This should NOT get logged");
        SetMinLogLevel(LogLevel.Trace);
        this.Logger.LogTrace("This should be logged");

        return new string[] { "value1", "value2" };
    }

   public static void SetMinLogLevel(LogLevel NewLogLevel)
    {
        foreach (var rule in LogManager.Configuration.LoggingRules)
        {
            rule.EnableLoggingForLevel(NewLogLevel);
        }

        //Call to update existing Loggers created with GetLogger() or 
        //GetCurrentClassLogger()
        LogManager.ReconfigExistingLoggers();
    }

我希望請求者能夠在他們的 HTTP 請求(標頭或 cookie)中設置一個標志,以便為每個請求啟用更詳細的日志記錄級別。 這樣我就不會用來自請求者的詳細日志淹沒我的日志。

問題:如何動態設置每個記錄器實例的日志級別? (我相信這是正確的措辭)

我目前正在使用 NLog 包 4.5。

NLog 4.6.7 允許您在minLevel / maxLevel規則過濾器中使用布局

您可以使用默認日志級別的 NLog-Config-Variable,然后在您的 Web 應用程序上創建一個隱藏方法,該方法修改 NLog-Config-Variable 並調用ReconfigExistingLoggers()

然后設置一個計時器,在 30 秒后將該 NLog-Config-Variable 恢復為其原始值。 並且還調用ReconfigExistingLoggers()

另見: https : //github.com/NLog/NLog/wiki/Filtering-log-messages#semi-dynamic-routing-rules

也許您可以使用 session-cookie 來控制是否啟用調試模式:

<targets>
    <target type="file" name="logfile" filename="applog.txt" />
</targets>
<rules>
    <logger name="*" minlevel="Off" writeTo="logfile" ruleName="debugCookieRule">
      <filters defaultAction="Log">
         <when condition="'${aspnet-session:EnableDebugMode}' == ''" action="Ignore" />
      </filters>
    </logger>
</rules>

然后像這樣激活會話cookie:

public void SetMinLogLevel(LogLevel NewLogLevel)
{
    var cookieRule = LogManager.Configuration.FindRuleByName("debugCookieRule");
    if (cookieRule != null)
    {
        cookieRule.MinLevel = NewLogLevel;

        // Schedule disabling of logging-rule again in 60 secs.
        Task.Run(async () => { await Task.Delay(60000).ConfigureAwait(false); cookieRule.MinLevel = LogLevel.Off; LogManager.ReconfigExistingLoggers(); });

        // Activate EnableDebugMode for this session
        HttpContext.Session.SetString("EnableDebugMode", "Doctor has arrived");
    }

    LogManager.ReconfigExistingLoggers();  // Refresh loggers
}

如果不需要 session-cookies 和${aspnet-session} ,那么 NLog.Web.AspNetCore 有其他選項來提取 HttpContext-details。 另見: https : //nlog-project.org/config/?tab=layout-renderers&search=package : nlog.web.aspnetcore

與其嘗試自定義 NLog 日志記錄級別(這會影響整個過程),我認為您應該尋求一種解決方案來修改日志語句本身的日志級別。

要完成這項工作,您需要以下內容:

  1. 一種機制,用於識別您想要調試日志記錄的請求
  2. 記錄器的包裝器,以便您可以動態覆蓋日志級別

第一個要求很簡單 - 設置 cookie 或自定義 HTTP 標頭,並檢查兩者是否存在。 您需要將此檢查的結果提供給您的 LogWrapper 實例,以便它知道什么時候應該做一些特殊的事情。

LogWrapper 必須針對每個請求進行實例化,以便實例不會跨請求共享。 最簡單的方法是在控制器的構造函數中按需創建它(但你也可以將它連接到 DI 容器以進行自動注入)。

這看起來像這樣:

public class HomeController : Controller
{
    private readonly LogWrapper _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        var isDebugRequest = ...;
        _logger = new LogWrapper<HomeController>(logger, isDebugRequest);
    }    

    ...
}

此處解釋為 NLog 創建日志包裝器的基礎知識,但您似乎已經在使用為 Microsoft.Extensions.Logging 創建的包裝器,因此您需要改為包裝該接口:

public class LogWrapper<T> : Microsoft.Extensions.Logging.ILogger
{
    private readonly ILogger<T> _logger;
    private readonly bool _debug;

    public LogWrapper(ILogger<T> logger, bool isDebug)
    {
        _logger = logger;
        _debug = isDebug;
    }    

    public void Log<TState>(LogLevel logLevel,
                            EventId eventId,
                            TState state,
                            Exception exception,
                            Func<TState, Exception, string> formatter)
    {
        if (_debug) 
        {
            // override log level here
            _logger.Log(LogLevel.Warning, eventId, state, exception, formatter); 
        }
        else 
        {
            _logger.Log(logLevel, eventId, state, exception, formatter);
        }
    }

    // ILogger has two other methods you'll need to implement
}    

這種方法的缺點是日志語句沒有其原始日志級別,這對您的用例可能重要也可能不重要。

我們知道 NLog 4.6.7 添加了對使用 NLog 布局(如${gdc:globalLevel}在運行時動態更改級別屬性的支持 如果可能,更好的解決方案是升級您的 NLog。

更新:新的解決方案我在 4.5 版上嘗試了這段代碼,它工作正常。 看來您不需要升級您的 NLog 版本。 在這種情況下,所有配置都以編程方式設置。 您可以在標題中將所需級別作為loglevel 如果您在標頭中發送loglevel ,它將被使用。 否則, logLevel 將為Error 請看這里

注意:只使用using NLog; . 您不需要using Microsoft.Extensions.Logging;

[Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        private readonly Logger _log = LogManager.GetCurrentClassLogger();

        [HttpGet]
        public async Task<IEnumerable<string>> Get()
        {
            var requestLogLevel = Request.Headers.SingleOrDefault(x => x.Key == "loglevel");
            LogLevel logLevel = LogLevel.Error;
            switch (requestLogLevel.Value.ToString().ToLower())
            {
                case "trace":
                    logLevel = LogLevel.Trace;
                    break;
                case "debug":
                    logLevel = LogLevel.Debug;
                    break;
                case "info":
                    logLevel = LogLevel.Info;
                    break;
                case "warn":
                case "warning":
                    logLevel = LogLevel.Warn;
                    break;
                case "error":
                    logLevel = LogLevel.Error;
                    break;
                case "fatal":
                    logLevel = LogLevel.Fatal;
                    break;
            }

            var config = new NLog.Config.LoggingConfiguration();
            var defaultMode = new NLog.Targets.FileTarget("defaultlog") { FileName = "log.txt" };
            config.AddRule(logLevel, LogLevel.Fatal, defaultMode);
            NLog.LogManager.Configuration = config;

            _log.Trace("Some logs");

            return new string[] { "value1", "value2" };
        }
    }

解決方案1)將NLog升級到4.6.7或更高版本:

var config = new NLog.Config.LoggingConfiguration();

// Targets where to log to: File and Console
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "file.txt" };
var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
            
// Rules for mapping loggers to targets            
config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile);
            
// Apply config           
NLog.LogManager.Configuration = config;

解決方案2)以編程方式更改配置文件:由於您的NLog版本不支持自動更改配置,我們將通過編程方式更改它:

[Route("api/[controller]/[action]")]
[ApiController]
public class HomeController : ControllerBase
{
    private readonly Logger _log = LogManager.GetCurrentClassLogger();

    // Special Authorization needed
    public bool ChangeToDebugMode()
    {
        try
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(AppDomain.CurrentDomain.BaseDirectory +  "nlog.config");
            XmlNode root = doc.DocumentElement;
            XmlNode myNode = root["include"].Attributes["file"];
            myNode.Value = "debugmode.config";
            doc.Save(AppDomain.CurrentDomain.BaseDirectory + "nlog.config");
        }
        catch (Exception)
        {
            return false;
        }

        return true;
    }

    // Special Authorization needed
    public bool RestToDefault()
    {
        try
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(AppDomain.CurrentDomain.BaseDirectory + "nlog.config");
            XmlNode root = doc.DocumentElement;
            XmlNode myNode = root["include"].Attributes["file"];
            myNode.Value = "defaultmode.config";
            doc.Save(AppDomain.CurrentDomain.BaseDirectory + "nlog.config");
        }
        catch (Exception)
        {
            return false;
        }

        return true;
    }

    [HttpGet]
    public async Task<IEnumerable<string>> Get()
    {
        _log.Trace("Some logs");

        return new string[] { "value1", "value2" };
    }
}

在這種情況下,您需要對配置文件進行一些更改。 您需要將autoReload=true添加到配置中。 現在,當任何配置更改時,NLog 會自動重新加載配置,您無需重新啟動應用程序。 您需要查看autoReload在此處include

配置文件

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true">
  <include file="defaultmode.config" />
</nlog>

默認模式.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
  </targets>

  <rules>
    <logger name="*" minlevel="Debug" writeTo="logfile" />
  </rules>
  <!-- ... -->
</nlog>

調試模式.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
  </targets>

  <rules>
    <logger name="*" minlevel="Trace" writeTo="logfile" />
  </rules>
  <!-- ... -->
</nlog>

我制作了另外兩個配置文件。 debugmode.configdefaultmode.config 默認情況下,在nlog.config文件中,包含deafultmode.config ChangeToDebugMode被調用時,它更改為debugmode.config ,當RestToDefault被調用時,它更改為defaultmode.config 我使用include和單獨的配置到兩個文件中只是為了簡單起見。

解決方案 3)根據您的問題:在這種情況下,我使用了您在問題中提供的代碼。 如果您在請求標頭中發送日志級別,則會被考慮。 如果您不發送,它將使用您在配置中設置的默認值。 因此,您無需在客戶端更改應用程序。 它工作正常。 只需在調試時發送所需的日志級別。

[Route("api/[controller]/[action]")]
[ApiController]
public class HomeController : ControllerBase
{
    private readonly Logger _log = LogManager.GetCurrentClassLogger();

    [HttpGet]
    public async Task<IEnumerable<string>> Get()
    {
        var requestLogLevel = Request.Headers.SingleOrDefault(x => x.Key == "loglevel");
        LogLevel logLevel = LogLevel.Error;
        switch (requestLogLevel.Value.ToString().ToLower())
        {
            case "trace":
                logLevel = LogLevel.Trace;
                break;
            case "debug":
                logLevel = LogLevel.Debug;
                break;
            case "info":
                logLevel = LogLevel.Info;
                break;
            case "warn":
            case "warning":
                logLevel = LogLevel.Warn;
                break;
            case "error":
                logLevel = LogLevel.Error;
                break;
            case "fatal":
                logLevel = LogLevel.Fatal;
                break;
        }
        SetMinLogLevel(logLevel);               

        _log.Trace("Some logs.");

        return new string[] { "value1", "value2" };
    }

    public static void SetMinLogLevel(LogLevel NewLogLevel)
    {
        foreach (var rule in LogManager.Configuration.LoggingRules)
        {
            rule.EnableLoggingForLevel(NewLogLevel);
        }

        //Call to update existing Loggers created with GetLogger() or 
        //GetCurrentClassLogger()
        LogManager.ReconfigExistingLoggers();
    }
}

問題是,這種情況每次都需要發送日志級別。 在這些屏幕截圖中,您可以看到如何在調試模式下發送日志級別。 郵遞員結果

輸出文本文件

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM