Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NLog - Parameter for File Target FileName

Tags:

c#

nlog

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?

like image 286
Some User Avatar asked Sep 17 '25 19:09

Some User


1 Answers

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>
like image 189
gnud Avatar answered Sep 20 '25 07:09

gnud