For an example, i have following code (ASP.NET core 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;
… then somewhere in class:
foreach(var a in b)
{
var thread = Task.Run(async () =>
{
var myLogName = a.Name;
_logger.Trace("Hello!");
}
}
My goal is to create all subfolders for each started thread with myLogName as result:
AppFolderPathlogmyLogName1log_25-04-2022.txt
AppFolderPathlogmyLogName2log_25-04-2022.txt
AppFolderPathlogmyLogName3log_25-04-2022.txt
But i can see only one file
AppFolderPathloglog_25-04-2022.txt
So myLogName variable is just skipped and this file contains one log string "Hello!" per started thread. How can i configure NLog to make folders as i want? Only by using GDC and making extension-methods with updating global resource each time i want write a log?
UPD1: solution with GDC works, but its not so thread-safe, i can see that some log files contains many strings (expected only 1) and some threads were’nt created any log files:
public static class LoggerExtensions
{
public static NLog.ILogger WithMyLog(this NLog.ILogger logger, string myLogName)
{
GlobalDiagnosticsContext.Set("myLogName", myLogName);
return logger;
}
}
… and then:
foreach(var a in b)
{
var thread = Task.Run(async () =>
{
var myLogName = a.Name;
_logger.WithMyLog(myLogName).Trace("Hello!");
}
}
Very expected tho, no questions to this mechanism. Maybe i should add some more extension methods and fill them with lock statements to make this work properly.
2
Answers
The answer is very simple: just use structured logging! According to this NLog article, NLog 4.5+ can use properties directly from arguments:
and then:
Maybe consider using ${logger} or ${threadName}
Example:
By specifying a custom token in the Logger-Name-Filter, then you can ensure only special "thread-loggers" will reach the "logfile". Ex:
Then just ensure the Logger-Name contains the token:
If you place the custom-token as prefix (Ex.
"CustomToken.SomeClass"
), then you can strip it away using${logger:shortName=true}
inFileName="..."