[英]Custom NLog target with async writing
NLog 允許我編寫自定義目標。 我想使用 Entity Framework Core 登錄到我的數據庫。
在NLog.Targets.Target
有這個:
protected virtual void Write(LogEventInfo logEvent);
但是我的代碼是異步的,所以我必須這樣做:
protected override async Task WriteAsync(LogEventInfo logEvent)
{
await WriteToEfContext(logEvent);
// and so on...
}
但是沒有寫方法的Task WriteAsync
版本。
如何編寫具有異步支持的自定義目標?
使用 NLog 4.6 更新 AsyncTaskTarget:
public class MyCustomTarget : AsyncTaskTarget
{
protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token)
{
return await MyLogMethodAsync(logEvent.LogEvent).ConfigureAwait(false);
}
}
另見: https : //github.com/NLog/NLog/wiki/How-to-write-a-custom-async-target
上一個答案:
如果 LogEvents 的順序不重要並且正確刷新不重要,那么您可以執行以下操作:
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);
}
}
}
實現了一個抽象類,以確保正確的排序(並且不會耗盡線程池):
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();
}
}
}
public void WriteAsyncLogEvent(
AsyncLogEventInfo logEvent
)
查看測試部分的用法,例如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);
}
另請查看wiki 部分異步處理和包裝器目標以獲取更多詳細信息
因為異步處理是一個常見的場景,NLog 支持一種速記符號來為所有目標啟用它,而無需指定顯式包裝器。 您可以簡單地在目標元素上設置
async="true"
,並且該元素中的所有目標都將使用 AsyncWrapper 目標進行包裝。
<nlog>
<targets async="true">
<!-- all targets in this section will automatically be asynchronous -->
</targets>
</nlog>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.