27

In my application, I work on several thousand of document a day. I'd like, in some cases some logs, one log by document. Then I'd like for a specific target change the output filename (and only the filename) at runtime.

Around the web I found how to create a target by programming me I'd like just update a the filename by programming. I tried the code below. The error I receive is "LayoutRender cannot be found 'logDirectory'.

Any idea ?

Thanks,

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;

The config file is :

  <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>
TheBoubou
  • 19,487
  • 54
  • 148
  • 236
  • Have you ever found a solution to this ? If not, I could propose some answers. – Joel Bourbonnais Sep 17 '15 at 20:06
  • Here is a very clear answer : [Setting log file manually](http://stackoverflow.com/questions/3516242/add-remove-logfiles-during-runtime-in-nlog) – aozan88 Oct 02 '16 at 20:59
  • Here is a very clear answer : [Setting log targets manually](http://stackoverflow.com/questions/3516242/add-remove-logfiles-during-runtime-in-nlog) – aozan88 Oct 02 '16 at 21:01

7 Answers7

30

Try ReconfigExistingLoggers method:

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

As written in docs:

Loops through all loggers previously returned by GetLogger. and recalculates their target and filter list. Useful after modifying the configuration programmatically to ensure that all loggers have been properly configured.

EDIT:

Try use custom layout renderer: NLog config file to get configuration setting values from a web.config

Community
  • 1
  • 1
Tony
  • 7,345
  • 3
  • 26
  • 34
  • As possible workaround try add your own LayoutRenderer: http://stackoverflow.com/questions/7107499/nlog-config-file-to-get-configuration-setting-values-from-a-web-config – Tony Dec 24 '13 at 10:14
  • 1
    Important to mention that if you're trying to replace part of the filename, `target.FileName.ToString()` may be surrounded by single-quotes, so you can't reapply it directly e.g. `Path.GetDirectoryName(target.FileName.ToString().Trim('\''))` – drzaus Feb 01 '17 at 16:41
6

Tony's solution doesn't seem to work if you use NLog Async (<targets async="true">). I had to use the wrapper target to get my FileTarget, otherwise I get a lot of errors. I'm using 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();
}

This also doesn't change the config file, it just changes the runtime value. So I also manually edit the config file to my new value using the code below:

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);
Community
  • 1
  • 1
Baddack
  • 1,947
  • 1
  • 24
  • 33
  • I'm using Visual Studio 2017 and .net core 2.2. And with Tony's solution, it won't work because I don't have a FileName property exposed off of the target object. Thanks so much for your solution. – JustLooking Apr 04 '19 at 21:30
6

If not needing to change the LogDirectory at runtime, then you can do this:

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

If needing to modify the logDirectory at runtime, then use the GDC:

https://github.com/NLog/NLog/wiki/Gdc-layout-renderer

NLog.GlobalDiagnosticsContext.Set("logDirectory","C:\Temp\");

Then you can use the following layout:

target.FileName = "${gdc:item=logDirectory}\\file2.txt";
Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70
  • using `${var:...}` in layout looks like a proper way to do this after NLog 4.1, see https://nlog-project.org/2015/08/31/nlog-4-1-0-is-now-available.html. But note that it is said to work only in layouts, not in regular strings. – Mikhail Apr 21 '19 at 11:43
  • @Mikhail I prefer GDC until this is resolved: https://github.com/NLog/NLog/issues/2960 – Rolf Kristensen Apr 21 '19 at 15:28
  • thanks for pointing to thread safety issue. Though for my case it's not a problem. What's worrying me more now is the performance hit which is mentioned in the issue. Have you seen any comparisons of the performance for GDC and Vars? – Mikhail Apr 21 '19 at 20:45
  • 1
    The performance hit that I talk about is comparison of old style variables like `${myvar}` with new style `${var:myvar}`. `${var:myvar}` has the same overhead as `${gdc:myvar}` but `${var:myvar}` gets an extra performance penalty when used with AsyncWrapper because the NLog engine cannot defer rendering (Because `${var:myvar}` has the ability to use dynamic layout that requires upfront capture of thread context). – Rolf Kristensen Apr 21 '19 at 21:19
1

The reason why you get the error is that NLog knows nothing about "logDirectory" keyname. You may implement it by yourself (read instructions here) or use predefined ones from here.

Then you can use instructuions from here to change NLog targets during runtime.

Community
  • 1
  • 1
kravasb
  • 696
  • 5
  • 16
  • Or read (if possible) the logDirectory value in NLog.config. Found this : http://stackoverflow.com/questions/9635616/nlog-configuration-api-using-layouts-stored-in-variables – TheBoubou Dec 24 '13 at 09:54
1

For anyone stuck on this, I finally found a solution. I was trying to update some syslog target settings at runtime and nothing was working. Simply updating the configuration does not work, you need to reset the Configuration object, which is as simple as doing this:

LogManager.Configuration = LogManager.Configuration;

This causes an internal event to fire and actually use the updated configuration.

Mark
  • 149
  • 11
1

This worked for me.

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 and ArchiveFileName are of type Layout. ToString gives the string representaion. We can change it and assign it back. Remember to put .Trim('\'') for removing the extra single quotes that gets added while converting to string.

spiairsh
  • 21
  • 1
  • 2
0

I happen to have written an answer that fits your question as well.

In essence, instead of trying to rewrite the configuration, you'd be better off simply creating a configuration that allows you to dynamically choose the filename you want.


An adapted example for your use case:

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

You turn this information into a LogEventInfo object:

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

You can then add properties to this event (the string indexes are free to choose):

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

And then you write to the log:

myLog.Log(logEvent);

The interesting thing here is that in your NLog configuration, you can use this custom property in any field that the Nlog documentation refers to as a "Layout" value.

You use ${event-properties:item=CustomFileName} in the layout to access the property. For example:

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

Following the posted example, this message will be logged in the mycustomfilename.log file. This enables you to dynamically switch your targeted file as you please by setting the logEvent.Properties["CustomFileName"] value to whatever filename you want to use.

Note that you can further optimize this, and e.g. only be able to choose part of the file name.

The additional benefit is that you only need one target and one rule in your config file to make this work.

Flater
  • 12,908
  • 4
  • 39
  • 62