2017-03-05 5 views
4

NLog ermöglicht mir, eine custom target schreiben. Ich möchte mich mit Entity Framework Core bei meiner Datenbank anmelden.Benutzerdefinierte NLog Ziel mit asynchronen Schreiben

In NLog.Targets.Target gibt es diese:

protected virtual void Write(LogEventInfo logEvent); 

aber mein Code async ist, so muss ich tun:

protected override async Task WriteAsync(LogEventInfo logEvent) 
{ 
    await WriteToEfContext(logEvent); 
    // and so on... 
} 

Aber es gibt keine Task WriteAsync Version des Schreibmethode.

Wie schreibe ich ein benutzerdefiniertes Ziel mit Async-Unterstützung?

+3

auch als Frage hier gepostet: https://github.com/NLog/NLog/issues/2004 –

Antwort

5

Wenn Auftrag von LogEvents nicht wichtig sind und richtig bündig ist nicht wichtig, dann können Sie wie folgt vorgehen:

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); 
     } 
    } 
} 

eine abstrakte Klasse implementiert, die richtige Reihenfolge gewährleistet (und doesn‘ erschöpfen t thread~~POS=TRUNC):

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(); 
     } 
    } 
} 
+0

Danke, das ist großartig! – grokky

+0

Update PR hier https://github.com/NLog/NLog/pull/2006 –

4

Es gibt WriteAsyncLogEvent

public void WriteAsyncLogEvent(
    AsyncLogEventInfo logEvent 
) 

Blick auf die Verwendung im Bereich Tests, zum Beispiel 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); 
} 

auch einen Blick auf die Wiki AbschnittAsynchronous processing and wrapper targets für mehr Details

Weil asynch roonous processing ist ein übliches Szenario. NLog unterstützt eine Kurzschreibweise , um es für alle Ziele zu aktivieren, ohne dass explizite Wrapper angegeben werden müssen. Sie können einfachasync="true" auf Ziele Element setzen und alle Ihre Ziele in diesem Element werden mit der AsyncWrapper-Ziel verpackt.

<nlog> 
    <targets async="true"> 
    <!-- all targets in this section will automatically be asynchronous --> 
    </targets> 
</nlog> 
+0

die Der zweite Teil Ihrer Idee ist ein guter, bedeutet aber, dass wir das Ding in xml registrieren müssen, und ein benutzerdefiniertes Ziel wird bereits programmgesteuert erstellt. Daher ist es sinnvoll, es asynchron zu machen, ohne an der XML-Datei herumzuhantieren. Nicht sicher wie. – grokky