Skip to content

How to write a custom layout renderer

Rolf Kristensen edited this page Nov 11, 2024 · 42 revisions

Layout renderers can capture, format and output additional context details that can be used by NLog Layouts. There are 2 ways to create a custom layout renderer.

  1. Register custom lambda-delegate-function with a symbol-name (Introduced with NLog v4.4)
  2. Register custom class that inherits from LayoutRenderer that also supports additional configuration options.

⚠️ Don't forget to register your custom component when loading NLog config!

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.

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