C# NLog不会在进程退出时刷新所有日志条目
在此线程中,我看到“LogManager”在域卸载或进程退出时将配置设置为null“请参阅第一个答案中的编辑部分。据我所知,这将导致所有挂起的日志条目写入注册的目标。但是,在使用AsyncTargetTrapper包装的FileTarget进行测试后,这不成立。我在GitHub-上创建了一个最小的复制,其工作原理如下: LogLib是一个.netstandard2.0库,引用NLog 4.6.8 NuGet包并公开一个CompositeLogger类,该类通过编程方式配置NLog目标: LogConsoleRunner是一个.NET Framework 4.8控制台应用程序,它使用LogLib.CompositeLogger将指定数量的日志消息写入指定为命令行参数的文件,写入之间有很短的延迟:C# NLog不会在进程退出时刷新所有日志条目,c#,.net,logging,multiprocessing,nlog,C#,.net,Logging,Multiprocessing,Nlog,在此线程中,我看到“LogManager”在域卸载或进程退出时将配置设置为null“请参阅第一个答案中的编辑部分。据我所知,这将导致所有挂起的日志条目写入注册的目标。但是,在使用AsyncTargetTrapper包装的FileTarget进行测试后,这不成立。我在GitHub-上创建了一个最小的复制,其工作原理如下: LogLib是一个.netstandard2.0库,引用NLog 4.6.8 NuGet包并公开一个CompositeLogger类,该类通过编程方式配置NLog目标: LogC
public static class Program
{
public const int LogWritesCount = 10;
public static readonly TimeSpan DelayBetweenLogWrites = TimeSpan.FromMilliseconds(25);
static async Task Main(string[] args)
{
string logFilePath = args.FirstOrDefault();
if (string.IsNullOrWhiteSpace(logFilePath))
{
throw new InvalidOperationException("Must specify logging file path as an argument.");
}
logFilePath = Path.GetFullPath(logFilePath);
Process currentProcess = Process.GetCurrentProcess();
var logger = new CompositeLogger(logFilePath);
for(int i = 0; i < LogWritesCount; i++)
{
logger.Log($"Message from {currentProcess.ProcessName}#{currentProcess.Id} at {DateTimeOffset.Now:O}");
await Task.Delay(DelayBetweenLogWrites);
}
}
}
最后一行上的Assert.Equal总是失败,因为目标文件写入的行数总是少于预期的计数,即100。在我的计算机上,每次运行都会在96–99之间变化,但它从不包含所有100行
我的问题:如何配置NLog以确保在所有进程退出后,所有挂起的日志条目都写入目标日志文件?只需调用
它将刷新所有挂起的日志事件
旁注:如果您在刷新后需要NLog,则可以使用而不是关机。只需拨打电话即可
它将刷新所有挂起的日志事件
旁注:如果刷新后需要NLog,则可以使用而不是关机。查看了示例代码,您有多个进程写入同一文件名 认为您是性能和正确性之间妥协的受害者 当多个进程同时写入同一个文件时,需要进行一些锁定以进行协调。默认情况下,NLog使用最兼容的模式KeepFileOpen=false,这是来自操作系统的文件锁定适用于大多数平台 来自操作系统的文件锁定是不公平的,当有两个以上的进程写入同一个文件时,文件锁定无法扩展。当一个进程尝试打开另一个进程当前正在使用的文件时,将引发异常 NLog试图通过在错误ConcurrentWriteAttents=10时重试,并随机化重试前的等待时间来处理这些异常。这对两个进程都有效,但当您开始增加进程数时,则会增加一个进程连续10次倒霉的机会。在最后一次重试后,NLog丢弃日志事件(可能是您看到的) KeepFileOpen=false的写入速度为每秒300次,如果与重试逻辑结合使用,则速度会变得非常慢。但是通过在允许批处理时使用AsyncWrapper,它几乎可以消除性能影响。但是现在,当重试计数用完时,整个批处理可能会丢失 与依赖操作系统文件锁不同,您可以依赖NLog使用全局互斥进行进程间通信。此模式通过KeepFileOpen=True和ConcurrentWrites=True启用。与300次写入/秒相比,它变为100.000次写入/秒,锁定机制更公平,因此无需重试。并非所有平台支持这种模式,但在Windows上的.NET 4.8和Linux上的NetCore2上应该工作得很好
另请参见:已经查看了示例代码,您有多个进程正在写入同一文件名 认为您是性能和正确性之间妥协的受害者 当多个进程同时写入同一个文件时,需要进行一些锁定以进行协调。默认情况下,NLog使用最兼容的模式KeepFileOpen=false,这是来自操作系统的文件锁定适用于大多数平台 来自操作系统的文件锁定是不公平的,当有两个以上的进程写入同一个文件时,文件锁定无法扩展。当一个进程尝试打开另一个进程当前正在使用的文件时,将引发异常 NLog试图通过在错误ConcurrentWriteAttents=10时重试,并随机化重试前的等待时间来处理这些异常。这对两个进程都有效,但当您开始增加进程数时,则会增加一个进程连续10次倒霉的机会。在最后一次重试后,NLog丢弃日志事件(可能是您看到的) KeepFileOpen=false的写入速度为每秒300次,如果与重试逻辑结合使用,则速度会变得非常慢。但是通过在允许批处理时使用AsyncWrapper,它几乎可以消除性能影响。但是现在,当重试计数用完时,整个批处理可能会丢失 与依赖操作系统文件锁不同,您可以依赖NLog使用glob进行进程间通信 阿尔互斥体。此模式在KeepFileOpen=True和ConcurrentWrites=True时启用。它不再是300次写入/秒,而是100.000次写入/秒,并且锁定机制更加公平,因此无需重试。并非所有平台都支持这种模式,但在Windows上的.NET 4.8和Linux上的NetCore2上应该可以很好地工作
另请参阅:若要使其更好,请调用LogManager。Shutdown作为程序退出前的最后一条语句,它将刷新并关闭计时器和线程。这将避免在非Windows平台Linux、Android等上出现分段错误。。另见:谢谢!那确实更好。更新了应答器在此提交中添加了LogManager.Shutdown调用:-它仍然不工作,例如,在所有LogConsoleRunner.exe实例终止后,某些日志项仍然丢失。为了使其更好,请调用LogManager.Shutdown作为程序退出前的最后一条语句,它将刷新并关闭计时器和线程。这将避免在非Windows平台Linux、Android等上出现分段错误。。另见:谢谢!那确实更好。更新了应答器在此提交中添加了LogManager.Shutdown调用:-它仍然不起作用,例如,在所有LogConsoleRunner.exe实例终止后,一些日志条目仍然丢失。非常感谢您的解释!实际上,建议的FileTarget设置解决了并发写入问题:KeepFileOpen=true,ConcurrentWrites=true。我还更新了GitHub示例项目供其他人查看…非常感谢您的解释!实际上,建议的FileTarget设置解决了并发写入问题:KeepFileOpen=true,ConcurrentWrites=true。我还更新了GitHub示例项目以供其他人查看。。。
public static class Program
{
public const int LogWritesCount = 10;
public static readonly TimeSpan DelayBetweenLogWrites = TimeSpan.FromMilliseconds(25);
static async Task Main(string[] args)
{
string logFilePath = args.FirstOrDefault();
if (string.IsNullOrWhiteSpace(logFilePath))
{
throw new InvalidOperationException("Must specify logging file path as an argument.");
}
logFilePath = Path.GetFullPath(logFilePath);
Process currentProcess = Process.GetCurrentProcess();
var logger = new CompositeLogger(logFilePath);
for(int i = 0; i < LogWritesCount; i++)
{
logger.Log($"Message from {currentProcess.ProcessName}#{currentProcess.Id} at {DateTimeOffset.Now:O}");
await Task.Delay(DelayBetweenLogWrites);
}
}
}
[Fact]
public async Task LaunchMultipleRunners()
{
string logFilePath = Path.GetTempFileName();
using var ensureLogFileDisposed = new Nito.Disposables.AnonymousDisposable(() => File.Delete(logFilePath));
string logConsoleRunnerAppExePath = Path.GetFullPath(
Path.Combine(
Path.GetDirectoryName(this.GetType().Assembly.Location),
@"..\..\..\..\LogConsoleRunner\bin\Debug\LogConsoleRunner.exe"));
var startInfo = new ProcessStartInfo(logConsoleRunnerAppExePath)
{
Arguments = logFilePath,
UseShellExecute = false
};
const int LaunchProcessCount = 10;
Process[] processes = Enumerable
.Range(0, LaunchProcessCount)
.Select(i => Process.Start(startInfo))
.ToArray();
while (!processes.All(p => p.HasExited))
{
await Task.Delay(LogConsoleRunner.Program.DelayBetweenLogWrites);
}
string[] lines = File.ReadAllLines(logFilePath);
Assert.Equal(LaunchProcessCount * LogConsoleRunner.Program.LogWritesCount, lines.Length);
}