![](/img/trans.png)
[英]ASP.NET Core 2.x - HttpClient api returns error 'Object reference not set to an instance of an object'
[英]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 日志記錄級別(這會影響整個過程),我認為您應該尋求一種解決方案來修改日志語句本身的日志級別。
要完成這項工作,您需要以下內容:
第一個要求很簡單 - 設置 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.config
和defaultmode.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.