Skip to content

How to write a custom layout renderer

Rolf Kristensen edited this page Sep 4, 2023 · 34 revisions

Since NLog 4.4 there are two ways to create a custom layout renderer.

You could write a custom layout with one lambda function - it will be registered intermediately, or you could write a class which can be reused across projects along with configuration options.

Lambda Function

NLog 4.7 introduces a fluent registration API using LogManager.Setup(), where you create a layout renderer with a lambda:

NLog.LogManager.Setup().SetupExtensions(s =>
   s.RegisterLayoutRenderer("trace_id", (logevent) => CorrelationIdentifier.TraceId.ToString())
);

NLog 4.4 was the first edition to support lambda function will accept 1 or 2 parameters and should return a string.

  • 1 parameter: the logEventInfo.
  • 2 parameters: logEventInfo and the current NLog config.

Examples

//register ${text-fixed}
LayoutRenderer.Register("text-fixed", (logEvent) => "2");

//register ${trace-identifier}
LayoutRenderer.Register("trace-identifier", (logEvent) => HttpContext.Current.TraceIdentifier);

//Using logEventInfo, ${message-length}
LayoutRenderer.Register("message-length", (logEvent) => logEvent.FormattedMessage.Length);

//Using config, ${targetCount}
LayoutRenderer.Register("targetCount",(logEvent, config) => config.AllTargets.Count);

ASP.NET HttpContext

Need the HTTP-context (e.g. Request, Session etc) for ASP.NET or ASP.NET Core?

Include the NLog.Web (ASP.NET Classic) or NLog.Web.AspNetCore nuget-package.

And usage:

using NLog.Web.LayoutRenderers; 

AspNetLayoutRendererBase.Register("SessionItem1", 
      (logEventInfo, httpContext, loggingConfiguration)
           => httpContext.Session["SessionItem"]); // usage ${SessionItem1}

Class

Create a class that inherits from NLog.LayoutRenderers.LayoutRenderer, set the [LayoutRenderer("your-name")] on the class and override the Append(StringBuilder builder, LogEventInfo logEvent) method. Invoke in this method builder.Append(..) to render your custom layout renderer.

Don't forget to register your custom component!

Example

We create a ${hello-world} layout renderer, which renders..."hello world!".

[LayoutRenderer("hello-world")]
public class HelloWorldLayoutRenderer : LayoutRenderer
{
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        builder.Append("hello world!");
    }
}

How to pass configuration options to the layout render?

Just create public properties on the Layout Renderer. The properties could be decorated with the [RequiredParameter] and [DefaultParameter] attributes. The [DefaultParameter] can be passed to the layout renderer without using the name.

for example:

[LayoutRenderer("hello-world")]
public class HelloWorldLayoutRenderer : LayoutRenderer
{
        /// <summary>
        /// I'm not required or default
        /// </summary>
        public bool OtherOption { get; set; }

        /// <summary>
        /// I'm required, and will fail to initialize when not specified
        /// </summary>
        [RequiredParameter]
        public string RequiredOption { get; set; }

        /// <summary>
        /// I'm the default parameter, and can be assigned without specifying option-name
        /// </summary>
        [DefaultParameter]
        public string DefaultPlanet { get; set; }

Example usages

  • ${hello-world} - raises exception: required parameter RequiredOption isn't set
  • ${hello-world:RequiredOption=abc} - OK, RequiredOption property set
  • ${hello-world:Earth:RequiredOption=abc} - Default parameter DefaultPlanet set to Earth
  • ${hello-world:Earth:RequiredOption=abc:OtherOption=true} - All 3 properties set

How to optimize performance for the layout renderer

NLog will automatically capture relevant context state, when using AsyncWrapper-target to perform actual writing on background-thread (to avoid logging objects after they have been disposed). The following class-attributes can used for the LayoutRenderer that enables additional performance:

  • [ThreadAgnostic]

    LayoutRenderer does not capture state from the application-thread logging. Ex. ${threadid} cannot be [ThreadAgnostic].

    For LayoutRenderer marked as [ThreadAgnostic] then NLog can skip the overhead of capturing state.

    If just a single LayoutRenderer in a Layout is not marked as [ThreadAgnostic], then NLog introduces the overhead of state capture.

  • [ThreadSafe]

    Introduced with NLog 4.5.3, and made obsolete with NLog 5.0 that expects all to be threadsafe.

    LayoutRenderer will render correct output regardless of the number of application-threads running inside.

    For LayoutRenderer marked as [ThreadSafe] then NLog will skip using "global" locks when capturing state, thus application-threads will not experience lock-congestion inside NLog.

    If just a single LayoutRenderer in a Layout is not marked as [ThreadSafe], then NLog introduces the overhead of "global" lock when doing state capture.

Example of class-attributes for LayoutRenderer:

using NLog.Config;

[LayoutRenderer("hello-world")]
[ThreadAgnostic]
public class HelloWorldLayoutRenderer : LayoutRenderer
{
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        builder.Append("hello world!");
    }
}

Multiple type-alias attributes

NLog 5.0 enables you to have multiple type-aliases for a single class. Before one had to inherit from the same class to provide additional type-names.

[LayoutRenderer("hello-world")]   // ${hello-world}
[LayoutRenderer("hello-earth")]   // ${hello-earth}
public class HelloWorldLayoutRenderer : LayoutRenderer
{

The type-alias can then be used when wanting to use the LayoutRenderer in NLog SimpleLayout.

Notice NLog 5.0 automatically ignores dashes - in type-alias, so no extra alias is needed for this: Ex. ${helloworld}

Clone this wiki locally