How do I log a custom field in NLog to database?

UPDATE 11 Feb 2022: newer versions of NLOG have other solutions- see Julian’s answer.

Rather than using GDC, which is for global static data and fails on concurrent logging, it is better to use EventProperties-Layout-Renderer that allows to pass custom  properties specific for the event

LogEventInfo theEvent = new LogEventInfo(logLevel, "", message);
theEvent.Properties["OrderId"] =orderId;`

log.Log(theEvent);

... and in your NLog.config file: 
${event-context:item=OrderId}  -- obsolete
${event-properties:item=OrderId} -- renders OrderId

Here is one approach using the GlobalContext.

Configuration:

<target type="Database" name="database" connectionstring="Server=localhost;Database=NLog;Trusted_Connection=True;">
  <commandText>
    INSERT INTO NLogEntries ([Origin], [Message], [LogLevel],[CreatedOn],[OrderId]) VALUES (@Origin,@Message,@LogLevel,@Date, @OrderId);
  </commandText>
  <parameter name="@Date" layout="${date}"/>
  <parameter name="@Origin" layout="${callsite}"/>
  <parameter name="@LogLevel" layout="${level}"/>
  <parameter name="@message" layout="${message}"/>
  <parameter name="@OrderId" layout="${gdc:OrderId}"/> <!-- custom field! -->
</target>

Call site:

var logger = LogManager.GetCurrentClassLogger();
GlobalDiagnosticsContext.Set("OrderId",123);
logger.Debug("What is going on here"); //If you use the logging configuration above, 123 will be logged to the OrderId column in your database

With a little more effort, you could wrap the NLog logger using one of the techniques illustrated here.

Or, you could create your own "context" object and write a custom LayoutRenderer to pull the values from it and write them to the log. Custom LayourRenderers are easy to write. You can see one example in my first answer to this question. There, I show how to write your own LayoutRenderer that appends the current value of System.Diagnostics.Trace.CorrelationManager.ActivityId to the log message.


NLog 4.5 introduces structured logging, so you can do this:

logger.Debug("What is going on here. OrderId={MyOrderId}", orderId);

With NLog 4.6.3 it possible to create temporary Logger, that is imbued with the desired property using WithProperty:

Call

int orderId = 123; 
logger.WithProperty("MyOrderId", orderId).Info("This is my message!"); 

Config:

<target type="Database" name="database" connectionstring="Server=localhost;Database=NLog;Trusted_Connection=True;">
  <commandText>
    INSERT INTO NLogEntries ([Origin], [Message], [LogLevel],[CreatedOn],[OrderId]) VALUES (@Origin,@Message,@LogLevel,@Date, @OrderId);
  </commandText>
  <parameter name="@Date" layout="${date}" dbType="DbType.Date"/>
  <parameter name="@Origin" layout="${callsite}"/>
  <parameter name="@LogLevel" layout="${level}"/>
  <parameter name="@message" layout="${message}"/>
  <parameter name="@OrderId" layout="${event-properties:MyOrderId}" dbType="DbType.Int32"/> <!-- custom field! Note also the DB Type -->
</target>

Note, NLog 4.6 has also support for DbType - See https://nlog-project.org/2019/03/20/nlog-4-6-is-live.html


If that's all one needs, as of NLog version 4.3.3 there's an easier way to declare and access custom variables. Beware: none of these solutions are thread-safe.

Add the following to the NLog.config

<nlog ...
    <!-- optional, add some variables -->  
    ...
    <variable name="myvarone" value="myvalue"/>
    <variable name="myvartwo" value=2/>
     ...
</nlog>

Variables can be changed/accessed in the code by:

LogManager.Configuration.Variables["myvarone"] = "New Value"
LogManager.Configuration.Variables["myvartwo"] = 2

The values can be referenced in NLog.config:

"${var:myvarone}"  -- renders "New Value"
"${var:myvartwo}"  -- renders 2

As I mentioned above var and LogEventInfo objects are global. So if multiple instances are defined, changing a value would change the value for all instances. I'm very interested if anyone knows a way to declare per instance custom variables for NLog.