NLog:如何在运行时指定日志文件夹
NLog: how to specify log folder at runtime
例如,我有以下代码(ASP.NET 核心 6):
Program.cs:
//...
var config = new NLog.Config.LoggingConfiguration();
var logfile = new NLog.Targets.FileTarget("logfile")
{
FileName = Path.Combine(AppFolderPath, "log", "${var:myLogName}", "log_${date:format=dd-MM-yyyy}.txt")
};
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile);
LogManager.Configuration = config;
//...
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ISomeClass, SomeClass>(_ => new SomeClass(LogManager.GetCurrentClassLogger()));
}
SomeClass.cs:
public class SomeClass(ILogger logger)
{
_logger = logger;
}
private ILogger _logger;
... 然后在 class 的某处:
foreach(var a in b)
{
var thread = Task.Run(async () =>
{
var myLogName = a.Name;
_logger.Trace("Hello!");
}
}
我的目标是为每个启动的线程创建所有子文件夹,结果为 myLogName:
AppFolderPath\log\myLogName1\log_25-04-2022.txt
AppFolderPath\log\myLogName2\log_25-04-2022.txt
AppFolderPath\log\myLogName3\log_25-04-2022.txt
但是我只能看到一个文件
AppFolderPath\log\log_25-04-2022.txt
所以 myLogName 变量被跳过,这个文件包含一个日志字符串“Hello!”每个启动的线程。我如何配置 NLog 来制作我想要的文件夹?每次我想写日志时,只能使用 GDC 并制作扩展方法来更新全局资源吗?
UPD1:使用 GDC 的解决方案有效,但它不是那么线程安全,我可以看到一些日志文件包含许多字符串(预计只有 1 个)并且一些线程没有创建任何日志文件:
public static class LoggerExtensions
{
public static NLog.ILogger WithMyLog(this NLog.ILogger logger, string myLogName)
{
GlobalDiagnosticsContext.Set("myLogName", myLogName);
return logger;
}
}
...然后:
foreach(var a in b)
{
var thread = Task.Run(async () =>
{
var myLogName = a.Name;
_logger.WithMyLog(myLogName).Trace("Hello!");
}
}
非常期待,对此机制没有疑问。也许我应该添加更多扩展方法并用锁定语句填充它们以使其正常工作。
答案很简单:只需使用结构化日志记录!根据 this NLog article,NLog 4.5+ 可以直接从参数使用属性:
var logfile = new NLog.Targets.FileTarget("logfile")
{
FileName = Path.Combine(AppFolderPath, "log", "${event-properties:item=myLogName}", "log_${date:format=dd-MM-yyyy}.txt")
};
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile);
然后:
_logger.Trace("{myLogName}", a.Name);
也许可以考虑使用 ${logger} or ${threadName}
示例:
var logfile = new NLog.Targets.FileTarget("logfile")
{
FileName = Path.Combine(AppFolderPath, "log", "${logger}", "log_${date:format=dd-MM-yyyy}.txt")
};
通过在 Logger-Name-Filter 中指定自定义标记,您可以确保只有特殊的“thread-loggers”才能到达“日志文件”。例如:
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile, "*CustomToken*");
然后确保 Logger-Name 包含令牌:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ISomeClass, SomeClass>(_ => new SomeClass(LogManager.GetLogger("SomeClassCustomToken")));
}
如果您将 custom-token 作为前缀(例如 "CustomToken.SomeClass"
),那么您可以使用 FileName="..."
中的 ${logger:shortName=true}
将其删除
例如,我有以下代码(ASP.NET 核心 6):
Program.cs:
//...
var config = new NLog.Config.LoggingConfiguration();
var logfile = new NLog.Targets.FileTarget("logfile")
{
FileName = Path.Combine(AppFolderPath, "log", "${var:myLogName}", "log_${date:format=dd-MM-yyyy}.txt")
};
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile);
LogManager.Configuration = config;
//...
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ISomeClass, SomeClass>(_ => new SomeClass(LogManager.GetCurrentClassLogger()));
}
SomeClass.cs:
public class SomeClass(ILogger logger)
{
_logger = logger;
}
private ILogger _logger;
... 然后在 class 的某处:
foreach(var a in b)
{
var thread = Task.Run(async () =>
{
var myLogName = a.Name;
_logger.Trace("Hello!");
}
}
我的目标是为每个启动的线程创建所有子文件夹,结果为 myLogName:
AppFolderPath\log\myLogName1\log_25-04-2022.txt
AppFolderPath\log\myLogName2\log_25-04-2022.txt
AppFolderPath\log\myLogName3\log_25-04-2022.txt
但是我只能看到一个文件
AppFolderPath\log\log_25-04-2022.txt
所以 myLogName 变量被跳过,这个文件包含一个日志字符串“Hello!”每个启动的线程。我如何配置 NLog 来制作我想要的文件夹?每次我想写日志时,只能使用 GDC 并制作扩展方法来更新全局资源吗?
UPD1:使用 GDC 的解决方案有效,但它不是那么线程安全,我可以看到一些日志文件包含许多字符串(预计只有 1 个)并且一些线程没有创建任何日志文件:
public static class LoggerExtensions
{
public static NLog.ILogger WithMyLog(this NLog.ILogger logger, string myLogName)
{
GlobalDiagnosticsContext.Set("myLogName", myLogName);
return logger;
}
}
...然后:
foreach(var a in b)
{
var thread = Task.Run(async () =>
{
var myLogName = a.Name;
_logger.WithMyLog(myLogName).Trace("Hello!");
}
}
非常期待,对此机制没有疑问。也许我应该添加更多扩展方法并用锁定语句填充它们以使其正常工作。
答案很简单:只需使用结构化日志记录!根据 this NLog article,NLog 4.5+ 可以直接从参数使用属性:
var logfile = new NLog.Targets.FileTarget("logfile")
{
FileName = Path.Combine(AppFolderPath, "log", "${event-properties:item=myLogName}", "log_${date:format=dd-MM-yyyy}.txt")
};
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile);
然后:
_logger.Trace("{myLogName}", a.Name);
也许可以考虑使用 ${logger} or ${threadName}
示例:
var logfile = new NLog.Targets.FileTarget("logfile")
{
FileName = Path.Combine(AppFolderPath, "log", "${logger}", "log_${date:format=dd-MM-yyyy}.txt")
};
通过在 Logger-Name-Filter 中指定自定义标记,您可以确保只有特殊的“thread-loggers”才能到达“日志文件”。例如:
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile, "*CustomToken*");
然后确保 Logger-Name 包含令牌:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ISomeClass, SomeClass>(_ => new SomeClass(LogManager.GetLogger("SomeClassCustomToken")));
}
如果您将 custom-token 作为前缀(例如 "CustomToken.SomeClass"
),那么您可以使用 FileName="..."
${logger:shortName=true}
将其删除