NLog {asp-server-variables} renderer for ASP.NET

The NLog {asp-request} layout renderer requires specifying the keys in the nlog.config file. This works well if there are only few keys to log or we know exactly what we are looking for. If we need to log many keys this quickly becomes verbose as seen in the markup below.

<target
  xsi:type="File"
  layout="${asp-request:form=full_name}
          ${asp-request:form=ship_address}
          ${asp-request:form=home_address}
          ${asp-request:form=email_address}
          ${asp-request:form=city}
          ${asp-request:form=state}
          ${asp-request:form=zip_code}>
</target>

I wanted to log all the key/value pairs for a particular page to troubleshoot an intermittent bug. I was not able to find a layout renderer that did what I wanted so I decided to write one.

First copy the AspServerVariableLayoutRenderer class to your project. I format the log as html because I wanted to get a nicely formatted email.

using NLog;
using NLog.LayoutRenderers;

namespace App.Web.Helpers
{
    [LayoutRenderer("asp-server-variables")]
    public class AspServerVariableLayoutRenderer : LayoutRenderer
    {
        protected override void Append(StringBuilder builder, LogEventInfo logEvent)
        {
            var html = builder;

            html.AppendFormat("<h1 style='font-size: small;'>Request & Server Variables</h1>");

            html.Append("<table cellpadding='5' cellspacing='0' border='1'>");

            // form variables
            foreach (string key in HttpContext.Current.Request.Form.AllKeys)
            {
                // skip logging certain known name/value pairs
                if (key.IndexOf("__VIEWSTATE", StringComparison.OrdinalIgnoreCase) != -1
                    || key.Equals("__SCROLLPOSITIONX", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("__SCROLLPOSITIONY", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("__EVENTVALIDATION", StringComparison.OrdinalIgnoreCase)
                    || key.IndexOf("password", StringComparison.OrdinalIgnoreCase) != -1
                    )
                {
                    continue;
                }

                html.AppendFormat("<tr><td>{0}</td><td>{1}&nbsp;</td></tr>",
                    HttpContext.Current.Server.HtmlEncode(key),
                    HttpContext.Current.Server.HtmlEncode(HttpContext.Current.Request.Form[key]));
            }

            // server variables
            foreach (string key in HttpContext.Current.Request.ServerVariables.AllKeys)
            {
                // skip logging certain known name/value pairs
                if (key.Equals("ALL_HTTP", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("ALL_RAW", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("HTTP_ACCEPT", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("HTTP_ACCEPT_ENCODING", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("HTTP_ACCEPT_LANGUAGE", StringComparison.OrdinalIgnoreCase)
                    || key.Equals("HTTP_COOKIE", StringComparison.OrdinalIgnoreCase)
                    || key.StartsWith("CERT_", StringComparison.OrdinalIgnoreCase)
                    || key.IndexOf("password", StringComparison.OrdinalIgnoreCase) != -1
                    )
                {
                    continue;
                }

                html.AppendFormat("<tr><td>{0}</td><td>{1}&nbsp;</td></tr>",
                    HttpContext.Current.Server.HtmlEncode(key),
                    HttpContext.Current.Server.HtmlEncode(HttpContext.Current.Request.ServerVariables[key]));
            }

            html.Append("</table>");
        }
    }
}

Next register the custom layout renderer in the application start event in the Global.asax.cs file.

using App.Web.Helpers;

public class Global : HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        // register custom nlog renderer as the first thing when the app starts
        ConfigurationItemFactory
                  .Default
                  .LayoutRenderers
                  .RegisterDefinition("asp-server-variables", typeof(AspServerVariableLayoutRenderer));
    }
}

Finally use the {asp-server-variables} layout renderer in the nlog.config file.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  
    <targets>
        <target xsi:type="Mail"
                name="MailTarget">
            <body xsi:type="SimpleLayout">
                <text>
                    <![CDATA[
                        <html>
                            <body>
                                ${asp-server-variables}
                            </body>
                        </html>
                    ]]>
                </text>
            </body>
        </target>
    </targets>

    <rules>
        <logger name="PageLogger" minlevel="Debug" writeTo="MailTarget"/>
    </rules>
</nlog>

It is important to turn off the logger after its purpose is over. Definitely not recommended for use in a production environment.

Related:

https://github.com/NLog/NLog/blob/master/src/NLog/LayoutRenderers/AspRequestValueLayoutRenderer.cs