Some User
Some User

Reputation: 5827

NLog - Parameter for File Target FileName

I am using NLog in a .NET application. Currently, my config section in my App.config looks like this:

<nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <targets>
    <target name="assemblyLogFile" xsi:type="File" fileName="${basedir}/logs/${shortdate}/assembly.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
    <target name="appLogFile" xsi:type="File" fileName="${basedir}/logs/app.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
  </targets>

  <rules>
    <logger name="Assembly" minlevel="Trace" writeTo="assemblyLogFile" />
    <logger name="*" minlevel="Trace" writeTo="appLogFile" />
  </rules>
</nlog>

My app is dynamically loading the assemblies. I would like to log each assemblies logs to their own file. In essence, I would like to update the filename attribute on the target element to something like this:

fileName="${basedir}/logs/${shortdate}/assembly.[Name].log"

In this pattern, "[Name]" is defined in the code. My question is, is there a way to programmatically pass a variable to a target via NLog? If so, how?

Upvotes: 4

Views: 3719

Answers (2)

gnud
gnud

Reputation: 78518

If you want this to be completely dynamic, I think you have two approaches.

1) Create a custom log factory and wrapper for NLog.ILogger that keeps track of and injects the assembly name into the NLog logEvent. This means all assemblies must use your logger factory, and not NLog directly.

2) Create a NLog extension that lets you access the assembly name as a layout variable, derived from the logger name. This works if you use the default LogManager.GetCurrentClassLogger() in all the assemblies.

Here is a naive caching renderer that uses reflection to find the correct assembly. You could probably make this more efficient by scanning assemblies when you load them - or maybe just do string wrangling on the logger name.

[NLog.LayoutRenderers.LayoutRenderer("assemblyName")]
public class AssemblyNameLayoutRenderer : NLog.LayoutRenderers.LayoutRenderer
{
    static ConcurrentDictionary<string, string> loggerNameToAssemblyNameCache = new ConcurrentDictionary<string, string>();

    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        var fullClassName = logEvent.LoggerName;
        var assemblyName = FindAssemblyNameFromClassName(fullClassName);

        if (!string.IsNullOrEmpty(assemblyName))
            builder.Append(assemblyName);
    }

    private string FindAssemblyNameFromClassName(string fullClassName)
    {
        return loggerNameToAssemblyNameCache.GetOrAdd(fullClassName, cl =>
        {
            var klass = (
                from a in AppDomain.CurrentDomain.GetAssemblies()
                from t in a.GetTypes()
                where t.FullName == fullClassName
                select t).FirstOrDefault();

            return klass?.Assembly.GetName().Name;
        });
    }
}

And then use this in the config file like this:

<extensions>
  <add assembly="AssemblyContainingCustomRenderer" />
</extensions>

<targets>
  <target xsi:type="FilteringWrapper" name="assemblyLogFile" condition="'${assemblyName}' != ''">
    <target name="realAssemblyLogFile" xsi:type="File" fileName="logs/${shortdate}/assembly.${assemblyName}.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}" /
  </target>  
</targets>

Upvotes: 2

Rolf Kristensen
Rolf Kristensen

Reputation: 19847

If the assembly being loaded creates the logger using NLog.LogManager.GetLogger("assemblyName"), then you can do this (Works best with NLog ver. 4.5 or higher):

<nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <targets>
    <target name="assemblyLogFile" xsi:type="File" fileName="${basedir}/logs/${shortdate}/${logger}.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
    <target name="appLogFile" xsi:type="File" fileName="${basedir}/logs/app.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
  </targets>

  <rules>
    <logger name="AssemblyName" minlevel="Trace" writeTo="assemblyLogFile" />
    <logger name="*" minlevel="Trace" writeTo="appLogFile" />
  </rules>
</nlog>

Upvotes: 0

Related Questions