Custom NLog target with async writing
Update AsyncTaskTarget with NLog 4.6:
public class MyCustomTarget : AsyncTaskTarget
{
protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token)
{
return await MyLogMethodAsync(logEvent.LogEvent).ConfigureAwait(false);
}
}
See also: https://github.com/NLog/NLog/wiki/How-to-write-a-custom-async-target
Previous Answer:
If order of LogEvents are not important and correct flush is not important, then you can do the following:
public class MyCustomTarget : TargetWithLayout
{
protected override async void Write(AsyncLogEventInfo logEvent)
{
try
{
await MyLogMethodAsync(logEvent.LogEvent).ConfigureAwait(false);
logEvent.Continuation(null);
}
catch (Exception ex)
{
logEvent.Continuation(ex);
}
}
}
Implemented an abstract class, that ensures correct ordering (and doesn't exhaust the threadpool):
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NLog.Common;
/// <summary>
/// Abstract Target with async Task support
/// </summary>
public abstract class AsyncTaskTarget : Target
{
private readonly CancellationTokenSource _cancelTokenSource;
private readonly Queue<AsyncLogEventInfo> _requestQueue;
private readonly Action _taskStartNext;
private readonly Action<Task, object> _taskCompletion;
private Task _previousTask;
/// <summary>
/// Constructor
/// </summary>
protected AsyncTaskTarget()
{
_taskStartNext = TaskStartNext;
_taskCompletion = TaskCompletion;
_cancelTokenSource = new CancellationTokenSource();
_requestQueue = new Queue<AsyncLogEventInfo>(10000);
}
/// <summary>
/// Override this to create the actual logging task
/// <example>
/// Example of how to override this method, and call custom async method
/// <code>
/// protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token)
/// {
/// return CustomWriteAsync(logEvent, token);
/// }
///
/// private async Task CustomWriteAsync(LogEventInfo logEvent, CancellationToken token)
/// {
/// await MyLogMethodAsync(logEvent, token).ConfigureAwait(false);
/// }
/// </code></example>
/// </summary>
/// <param name="logEvent">The log event.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns></returns>
protected abstract Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken);
/// <summary>
/// Schedules the LogEventInfo for async writing
/// </summary>
/// <param name="logEvent">The log event.</param>
protected override void Write(AsyncLogEventInfo logEvent)
{
if (_cancelTokenSource.IsCancellationRequested)
{
logEvent.Continuation(null);
return;
}
this.MergeEventProperties(logEvent.LogEvent);
this.PrecalculateVolatileLayouts(logEvent.LogEvent);
_requestQueue.Enqueue(logEvent);
if (_previousTask == null)
{
_previousTask = Task.Factory.StartNew(_taskStartNext, _cancelTokenSource.Token, TaskCreationOptions.None, TaskScheduler.Default);
}
}
/// <summary>
/// Schedules notification of when all messages has been written
/// </summary>
/// <param name="asyncContinuation"></param>
protected override void FlushAsync(AsyncContinuation asyncContinuation)
{
if (_previousTask == null)
{
InternalLogger.Debug("{0} Flushing Nothing", this.Name);
asyncContinuation(null);
}
else
{
InternalLogger.Debug("{0} Flushing {1} items", this.Name, _requestQueue.Count + 1);
_requestQueue.Enqueue(new AsyncLogEventInfo(null, asyncContinuation));
}
}
/// <summary>
/// Closes Target by updating CancellationToken
/// </summary>
protected override void CloseTarget()
{
_cancelTokenSource.Cancel();
_requestQueue.Clear();
_previousTask = null;
base.CloseTarget();
}
/// <summary>
/// Releases any managed resources
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
_cancelTokenSource.Dispose();
}
private void TaskStartNext()
{
AsyncLogEventInfo logEvent;
do
{
lock (this.SyncRoot)
{
if (_requestQueue.Count == 0)
{
_previousTask = null;
break;
}
logEvent = _requestQueue.Dequeue();
}
} while (!TaskCreation(logEvent));
}
private bool TaskCreation(AsyncLogEventInfo logEvent)
{
try
{
if (_cancelTokenSource.IsCancellationRequested)
{
logEvent.Continuation(null);
return false;
}
if (logEvent.LogEvent == null)
{
InternalLogger.Debug("{0} Flush Completed", this.Name);
logEvent.Continuation(null);
return false;
}
var newTask = WriteAsyncTask(logEvent.LogEvent, _cancelTokenSource.Token);
if (newTask == null)
{
InternalLogger.Debug("{0} WriteAsync returned null", this.Name);
}
else
{
lock (this.SyncRoot)
{
_previousTask = newTask;
_previousTask.ContinueWith(_taskCompletion, logEvent.Continuation, _cancelTokenSource.Token);
if (_previousTask.Status == TaskStatus.Created)
_previousTask.Start(TaskScheduler.Default);
}
return true;
}
}
catch (Exception ex)
{
try
{
InternalLogger.Error(ex, "{0} WriteAsync failed on creation", this.Name);
logEvent.Continuation(ex);
}
catch
{
// Don't wanna die
}
}
return false;
}
private void TaskCompletion(Task completedTask, object continuation)
{
try
{
if (completedTask.IsCanceled)
{
if (completedTask.Exception != null)
InternalLogger.Warn(completedTask.Exception, "{0} WriteAsync was cancelled", this.Name);
else
InternalLogger.Info("{0} WriteAsync was cancelled", this.Name);
}
else if (completedTask.Exception != null)
{
InternalLogger.Warn(completedTask.Exception, "{0} WriteAsync failed on completion", this.Name);
}
((AsyncContinuation)continuation)(completedTask.Exception);
}
finally
{
TaskStartNext();
}
}
}
There is WriteAsyncLogEvent
public void WriteAsyncLogEvent(
AsyncLogEventInfo logEvent
)
Look at the usage in the tests section, for example ConsoleTargetTests
try
{
var exceptions = new List<Exception>();
target.Initialize(null);
target.WriteAsyncLogEvent(new LogEventInfo(LogLevel.Info, "Logger1", "message1").WithContinuation(exceptions.Add));
target.WriteAsyncLogEvent(new LogEventInfo(LogLevel.Info, "Logger1", "message2").WithContinuation(exceptions.Add));
target.WriteAsyncLogEvents(
new LogEventInfo(LogLevel.Info, "Logger1", "message3").WithContinuation(exceptions.Add),
new LogEventInfo(LogLevel.Info, "Logger2", "message4").WithContinuation(exceptions.Add),
new LogEventInfo(LogLevel.Info, "Logger2", "message5").WithContinuation(exceptions.Add),
new LogEventInfo(LogLevel.Info, "Logger1", "message6").WithContinuation(exceptions.Add));
Assert.Equal(6, exceptions.Count);
target.Close();
}
finally
{
Console.SetOut(oldConsoleOutWriter);
}
Look also at the wiki section Asynchronous processing and wrapper targets for more details
Because asynchronous processing is a common scenario, NLog supports a shorthand notation to enable it for all targets without the need to specify explicit wrappers. You can simply set
async="true"
on targets element and all your targets within that element will be wrapped with the AsyncWrapper target.
<nlog>
<targets async="true">
<!-- all targets in this section will automatically be asynchronous -->
</targets>
</nlog>