并行测试中NLog独立日志器配置及异步日志附件问题求助
我查过测试并行日志相关的讨论,但问题仍未解决。我的UI测试使用如下NLog XML配置:
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwExceptions="true"> <targets> <target type="File" name="FileA" fileName="${basedir}/Results/FileA-${event-properties:FileId}.txt" layout="${event-properties:item=CreatedOn} ${event-properties:item=Message}" keepFileOpen="true"/> <target type="File" name="FileB" fileName="${basedir}/Results/FileB-${event-properties:FileId}.txt" layout="${event-properties:item=CreatedOn} ${event-properties:item=Message}" keepFileOpen="true"/> </targets> <rules> <logger name="FileA" minlevel="Info" writeTo="FileA" /> <logger name="FileB" minlevel="Info" writeTo="FileB" /> </rules> </nlog>
其中${event-properties:FileId}对应TestContext.CurrentTest.Test.ID(每个测试的唯一标识)。
问题1
所有测试并行执行,我希望每个测试生成2个日志文件。当前100个测试是否仅共用2个日志器?能否实现每个测试拥有独立的FileA/FileB日志器?
问题2
当target设置async="true"时,其中一个日志文件未就绪,无法通过TestContext.AddTestAttachment(file)添加为测试附件;而使用LogManager.Flush()会获取其他测试的所有文件,该如何解决?
以下是Azure DevOps中添加测试附件的方法:
public static void CreateTestAttachments() { LogManager.Flush(); string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Results"); Directory .EnumerateFiles(filePath, $"*{FileId}*") .ToList() .ForEach(file => TestContext.AddTestAttachment(file)); }
解答
针对问题1
当前配置下,100个测试确实共用2个日志器实例(FileA和FileB),但NLog会通过FileId事件属性自动区分日志文件路径,最终每个测试还是会生成独立的FileA-{FileId}.txt和FileB-{FileId}.txt两个文件。
如果一定要让每个测试拥有独立的日志器实例,可以通过代码动态创建:
- 测试初始化时,基于
TestContext.CurrentTest.Test.ID创建命名日志器(比如FileA-{FileId}和FileB-{FileId}) - 动态注册对应File Target,指定专属文件名
- 测试结束后注销这些动态创建的日志器和Target,避免内存泄漏
不过这种方式会增加复杂度,默认配置已经能满足“每个测试生成2个独立日志文件”的需求,无需强行创建独立日志器。
针对问题2
使用异步Target时,全局LogManager.Flush()会等待所有日志队列完成,导致获取到其他测试的文件。可以通过以下方式解决:
- 局部刷新对应日志器
不要调用全局刷新,而是获取当前测试使用的日志器单独刷新:
var loggerA = LogManager.GetLogger("FileA"); var loggerB = LogManager.GetLogger("FileB"); // 仅刷新当前日志器的异步队列 loggerA.Flush(); loggerB.Flush();
这样只会等待当前测试发送的日志完成写入,不会影响其他测试。
- 验证文件就绪状态
刷新后添加文件就绪检查,确保文件可访问再添加附件:
public static void CreateTestAttachments(string currentFileId) { var loggerA = LogManager.GetLogger("FileA"); var loggerB = LogManager.GetLogger("FileB"); loggerA.Flush(); loggerB.Flush(); string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Results"); var testFiles = Directory.EnumerateFiles(filePath, $"*{currentFileId}*").ToList(); foreach (var file in testFiles) { // 等待文件可读写 while (!IsFileReady(file)) { Thread.Sleep(100); } TestContext.AddTestAttachment(file); } } private static bool IsFileReady(string filePath) { try { using (var stream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { return stream != null; } } catch (IOException) { return false; } }
- 使用MDC绑定测试标识
测试初始化时将FileId存入Mapped Diagnostics Context:
NLog.MappedDiagnosticsContext.Set("FileId", TestContext.CurrentTest.Test.ID);
配置中用${mdc:FileId}替代${event-properties:FileId},能更稳定地关联测试与日志文件。
内容的提问来源于stack exchange,提问作者Michał Cichoń

