为每个 Logger 实例动态设置 Nlog 日志级别 ASP.Net Core 2.x

Posted

技术标签:

【中文标题】为每个 Logger 实例动态设置 Nlog 日志级别 ASP.Net Core 2.x【英文标题】:Dynamically Set Nlog Log Level per Logger Instance ASP.Net Core 2.x 【发布时间】:2019-01-16 04:12:55 【问题描述】:

目标:动态选择我想要详细记录的 HTTP 请求(不同的日志级别)。

概述:我有一个 ASP.Net core 2.1 Web 服务器正在运行,并且一旦投入生产,如果我需要调试问题,我希望能够更改日志级别。我找到了如何globally change the log level;但是,更改日志级别是持久的……也就是说,每次调用我的控制器后都不会重置。

    [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。

【问题讨论】:

没有什么东西可以开箱即用,而且日志系统的构建方式也不能让您轻松完成这项工作。您也许可以推出自己的记录器实现,但这将是相当多的工作。 好的,谢谢!我在网上搜索,很难找到任何东西 这并不容易。您必须使用知道如何确定请求的日志记录级别的包装器来包装所有记录器实例,因为在您的请求范围内,可以在应用程序的不同部分使用多个记录器。其中,您通常有单例部分,因此您将考虑从静态上下文访问范围数据,这通常涉及操作异步上下文/HTTP 上下文等。您尝试解决的问题是什么?感觉日志级别不是这里真正的问题。 【参考方案1】:

也许您可以使用 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

【讨论】:

【参考方案2】:

与其尝试自定义 NLog 日志级别(这会影响整个过程),我认为您应该寻求一种解决方案,即修改日志语句本身的日志级别。

要完成这项工作,您需要以下内容:

    一种用于识别您想要调试日志记录的请求的机制 记录器的包装器,以便您可以动态覆盖日志级别

第一个要求很简单 - 设置 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);
        

    ...

here 解释了为 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
    

这种方法的缺点是日志语句没有其原始日志级别,这对您的用例可能重要也可能不重要。

【讨论】:

【参考方案3】:

我们知道 NLog 4.6.7 添加了对使用 NLog 布局的支持,例如 $gdc:globalLevel 到 dynamically change level attributes at runtime。如果可能的话,更好的解决方案是升级您的 NLog。

更新:新解决方案 我在 4.5 版上试过这段代码,它工作正常。看来您不需要升级您的 NLog 版本。 在这种情况下,所有配置都以编程方式设置。您可以在标题中将所需级别发送为loglevel。如果您在标头中发送loglevel,它将被使用。否则,logLevel 将为Error。请参阅here。

注意:请使用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 会自动重新加载配置,您无需重新启动应用程序。你需要看看autoReloadincludehere

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" autoReload="true">
  <include file="defaultmode.config" />
</nlog>

defaultmode.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>

debugmode.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();
    

问题是,这种情况每次都需要发送日志级别。 在这些屏幕截图中,您会看到如何在调试模式下发送日志级别。

【讨论】:

【参考方案4】:

NLog 4.6.7 允许您在 minLevel / maxLevel 的日志规则过滤器中使用布局

您可以拥有一个具有默认日志级别的 NLog-Config-Variable,然后在您的网络应用程序上创建一个隐藏方法来修改 NLog-Config-Variable 并调用 ReconfigExistingLoggers()

然后设置一个计时器,在 30 秒后将该 NLog-Config-Variable 恢复为其原始值。并且还打电话给ReconfigExistingLoggers()

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

【讨论】:

这似乎不是每个记录器实例,但我不想只为我的 API 的所有用户设置一个日志。

以上是关于为每个 Logger 实例动态设置 Nlog 日志级别 ASP.Net Core 2.x的主要内容,如果未能解决你的问题,请参考以下文章

nlog 通过记录器名称设置全局属性

NLog 基于静态变量创建日志文件

升级你的Logger(注解+动态代理)

NLog 不创建日志文件

封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil

Nlog不会写入Windows服务中的日志文件