在运行时更新NLog目标文件名

27

在我的应用程序中,我每天处理数千个文档。在某些情况下,我需要一些日志,每个文档一个日志。然后我想在运行时针对特定目标更改输出文件名(仅更改文件名)。

在网上,我找到了如何通过编程创建目标的方法,但我只想通过编程更新文件名。我尝试了下面的代码。我收到的错误是“LayoutRender无法找到'logDirectory'。

有什么想法吗?

谢谢,

var target = (FileTarget)LogManager.Configuration.FindTargetByName("logfile");
target.FileName = "${logDirectory}/file2.txt";

LoggingConfiguration config = new LoggingConfiguration();
var asyncFileTarget = new AsyncTargetWrapper(target);
config.AddTarget("logfile", asyncFileTarget);

LogManager.Configuration = config;

配置文件是:

  <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <variable name="logDirectory" value="C:/MyLogs"/>
    <targets>
      <target name="logfile" xsi:type="File" layout="${date:format=dd/MM/yyyy HH\:mm\:ss.fff}|${level}|${stacktrace}|${message}" fileName="${logDirectory}/file.txt" />
    </targets>

    <rules>
      <logger name="*" minlevel="Info" writeTo="logfile" />
    </rules>    
  </nlog>

你有解决这个问题的方案吗?如果没有,我可以提供一些答案。 - Joel Bourbonnais
这里有一个非常清晰的答案:手动设置日志文件 - aozan88
这里有一个非常清晰的答案:手动设置日志目标 - aozan88
7个回答

30

尝试使用ReconfigExistingLoggers方法:

var target = (FileTarget)LogManager.Configuration.FindTargetByName("logfile");
target.FileName = "${logDirectory}/file2.txt";
LogManager.ReconfigExistingLoggers();

根据文档所述:

循环遍历之前由 GetLogger 返回的所有记录器,并重新计算它们的目标和筛选器列表。在以编程方式修改配置后,执行此操作非常有用,以确保所有记录器都已正确配置。

编辑:

尝试使用自定义布局渲染器:NLog 配置文件从 Web.config 获取配置设置值


尝试添加自己的LayoutRenderer作为可能的解决方法:https://dev59.com/9mw05IYBdhLWcg3wqzes - Tony
1
需要注意的是,如果你想替换文件名的一部分,target.FileName.ToString() 可能会被单引号包围,因此你不能直接重新应用它,例如 Path.GetDirectoryName(target.FileName.ToString().Trim('\'')) - drzaus

6

Tony's的解决方案似乎无法在使用NLog Async(<targets async="true">)时起作用。我不得不使用包装目标来获取我的FileTarget,否则会出现很多错误。我正在使用NLog 2.1。

if (LogManager.Configuration != null && LogManager.Configuration.ConfiguredNamedTargets.Count != 0)
{
    Target target = LogManager.Configuration.FindTargetByName("yourFileName");
    if (target == null)
    {
        throw new Exception("Could not find target named: " + "file");
    }

    FileTarget fileTarget = null;
    WrapperTargetBase wrapperTarget = target as WrapperTargetBase;

    // Unwrap the target if necessary.
    if (wrapperTarget == null)
    {
        fileTarget = target as FileTarget;
    }
    else
    {
        fileTarget = wrapperTarget.WrappedTarget as FileTarget;
    }

    if (fileTarget == null)
    {
        throw new Exception("Could not get a FileTarget from " + target.GetType());
    }

    fileTarget.FileName = "SetFileNameHere";
    LogManager.ReconfigExistingLoggers();
}

这也不会改变配置文件,只是改变运行时的值。因此,我还需要使用下面的代码手动编辑配置文件到新的值:
var nlogConfigFile = "NLog.config";
var xdoc = XDocument.Load(nlogConfigFile);
var ns = xdoc.Root.GetDefaultNamespace();
var fTarget = xdoc.Descendants(ns + "target")
         .FirstOrDefault(t => (string)t.Attribute("name") == "yourFileName");
fTarget.SetAttributeValue("fileName", "SetFileNameHere");
xdoc.Save(nlogConfigFile);

我正在使用Visual Studio 2017和.NET Core 2.2。然而,根据Tony的解决方案,由于目标对象没有公开的FileName属性,所以该解决方案无法工作。非常感谢您提供的解决方案。 - JustLooking

6

如果不需要在运行时更改LogDirectory,则可以这样做:

target.FileName = "${var:logDirectory}\\file2.txt");

若需要在运行时修改日志目录,请使用GDC: https://github.com/NLog/NLog/wiki/Gdc-layout-renderer
NLog.GlobalDiagnosticsContext.Set("logDirectory","C:\Temp\");

接下来您可以使用以下布局:

target.FileName = "${gdc:item=logDirectory}\\file2.txt";

在NLog 4.1之后,使用${var:...}在布局中看起来是一个适当的方法,详情请见https://nlog-project.org/2015/08/31/nlog-4-1-0-is-now-available.html。但请注意,它仅适用于布局,而不适用于常规字符串。 - Mikhail
@Mikhail 在这个问题得到解决之前,我更喜欢使用GDC:https://github.com/NLog/NLog/issues/2960 - Rolf Kristensen
感谢您指出线程安全问题。虽然对于我的情况来说这不是问题。现在更让我担心的是在该问题中提到的性能损失。您看过GDC和Vars的性能比较吗? - Mikhail
1
我所说的性能损失是比较旧式变量${myvar}和新式${var:myvar}之间的差异。${var:myvar}${gdc:myvar}具有相同的开销,但当与AsyncWrapper一起使用时,${var:myvar}会受到额外的性能惩罚,因为NLog引擎无法推迟渲染(因为${var:myvar}具有使用需要线程上下文预先捕获的动态布局的能力)。 - Rolf Kristensen

1
这对我有用。
using NLog;
using NLog.Targets;
using NLog.Targets.Wrappers;

var target = LogManager.Configuration.FindTargetByName("logfile");
var asyncTarget = target as AsyncTargetWrapper;
var fileTarget = asyncTarget.WrappedTarget as FileTarget;
var file = fileTarget.FileName;
var archiveFile = fileTarget.ArchiveFileName;

//FileName和ArchiveFileName都是Layout类型。ToString方法可以得到字符串表示形式。我们可以更改它并重新分配。记得加上.Trim('\'')以删除在转换为字符串时添加的多余单引号。


1
你遇到错误的原因是NLog不知道“logDirectory”这个键名。你可以自己实现它(阅读此处的说明),或使用此处中预定义的键名。

然后,你可以使用此处的指令在运行时更改NLog目标。


或者在NLog.config中读取(如果可能)logDirectory值。找到这个:https://dev59.com/GGHVa4cB1Zd3GeqPl1qc - TheBoubou

1
对于任何卡在这里的人,我终于找到了解决方案。我试图在运行时更新一些系统日志目标设置,但是没有任何作用。简单地更新配置是不起作用的,你需要重置配置对象,只需执行以下操作即可:
LogManager.Configuration = LogManager.Configuration;

这将导致内部事件触发并实际使用更新的配置。

0

我恰好写了一个适合你问题的答案

本质上,与其试图重写配置,你最好创建一个允许你动态选择所需文件名的配置。


针对您的用例进行了适应的示例:

Logger myLog = LogManager.GetLogger(name);
LogLevel level = LogLevel.Error;
string message = "This is an error message!";

你可以将这些信息转换为一个LogEventInfo对象:

LogEventInfo logEvent = new LogEventInfo(level , myLog.Name, message);

你可以为这个事件添加属性(字符串索引可自由选择):

logEvent.Properties["CustomFileName"] = "mycustomfilename";

然后你写入日志:

myLog.Log(logEvent);

有趣的是,在您的NLog配置中,您可以在任何被Nlog文档称为“Layout”值的字段中使用此自定义属性

您可以在布局中使用${event-properties:item=CustomFileName}来访问该属性。例如:

<target xsi:type="File" 
        name="file" 
        fileName="${basedir}/logs/${event-properties:item=CustomFileName}.log"
        layout="${message}" />

根据发布的示例,此消息将记录在mycustomfilename.log文件中。这使您能够通过将logEvent.Properties["CustomFileName"]值设置为您想要使用的任何文件名来动态切换目标文件。

请注意,您可以进一步优化此过程,例如仅能选择文件名的一部分。

另一个好处是,您只需要在配置文件中拥有一个目标和一个规则即可使其正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接