5 Minute NLog Wrapper: Limiting The Number Of Emails Sent

If you’re using an Email target to send notifications when an error occurs, it may be useful to limit the frequency of those emails. One way of achieving this is to wrap your target that sends email in a Wrapper target. This custom target will intercept calls to the Email target and, based on the time since the last email was sent, either pass on the log event to the wrapped target or swallow it.

[Target("FrequencyWrapper", IsWrapper = true)]
public class NLogFrequencyWrapper : WrapperTargetBase
{
    private DateTime _lastLogEventOccurredAt = DateTime.MinValue;
 
    protected override void Write(AsyncLogEventInfo logEvent)
    {
        if ((DateTime.Now - _lastLogEventOccurredAt).TotalMinutes >= MinLogIntervalInMinutes)
        {
            _lastLogEventOccurredAt = DateTime.Now;
            this.WrappedTarget.WriteAsyncLogEvent(logEvent);
        }
        else
        {
            logEvent.Continuation(null);
        }
    }
 
    [DefaultValue(30)]
    public int MinLogIntervalInMinutes { get; set; }
}
<extensions>
  <add assembly="MyAssembly"/>
</extensions>
 
<target name="limitedEmail" type="FrequencyWrapper" minLogIntervalInMinutes="40">
  <target name="email" xsi:type="Mail"
      ...
  />
</target>
This entry was posted in Programming and tagged , . Bookmark the permalink.

3 Responses to 5 Minute NLog Wrapper: Limiting The Number Of Emails Sent

  1. Chris Hardie says:

    This was very helpful. I modified it a bit so that, instead of all emails stopping for a period of time, different log events were still able to be emailed if they hadn’t been raised before:

    [Target(“FrequencyWrapper”, IsWrapper = true)]
    class NLogMailWrapper : WrapperTargetBase
    {
    private DateTime _lastLogEventOccurredAt = DateTime.MinValue;

    private Dictionary logEvents = new Dictionary();

    protected override void Write(AsyncLogEventInfo logEvent)
    {
    //Ensure we don’t keep emailing the same messaages and exceptions
    KeyValuePair match = logEvents.FirstOrDefault(l => l.Key.LogEvent.Message == logEvent.LogEvent.Message);

    //KeyValuePair is a struct
    //Need to use default() for test if the statement above returned a match
    if (match.Equals(default(KeyValuePair)) ||
    (DateTime.Now – match.Value).TotalMinutes >= MinLogIntervalInMinutes)
    {
    lock (((IDictionary)logEvents).SyncRoot)
    {
    logEvents[logEvent] = DateTime.Now;
    }
    logEvent.LogEvent.Message = logEvent.LogEvent.Message;
    this.WrappedTarget.WriteAsyncLogEvent(logEvent);
    }
    else
    {
    logEvent.Continuation(null);
    }

    //Do a little clean up on the Dictionary
    //We can’t remove items from a collection as we enumerate over it, so this list will
    //hold all the items we want to mark for removal
    List keys = new List();
    foreach (var keypair in logEvents)
    {
    if ((DateTime.Now – keypair.Value).TotalMinutes > MinLogIntervalInMinutes)
    keys.Add(keypair.Key);
    }

    foreach (var key in keys)
    {
    lock (((IDictionary)logEvents).SyncRoot)
    {
    if (logEvents.ContainsKey(key))
    logEvents.Remove(key);
    }
    }

    }

  2. Chris Hardie says:

    Whoops, i fixed a couple typos:

    protected override void Write(AsyncLogEventInfo logEvent)
    {
    //Ensure we don’t keep emailing the same messaages and exceptions
    KeyValuePair match = logEvents.FirstOrDefault(l => l.Key.LogEvent.Message == logEvent.LogEvent.Message &&
    l.Key.LogEvent.Exception.GetType() == logEvent.LogEvent.Exception.GetType());

    //Apparently the default for KeyValuePair isn’t null, who knew?
    //Need to use default() for test if the statement above returned a match
    if (match.Equals(default(KeyValuePair)) ||
    (DateTime.Now – match.Value).TotalMinutes >= MinLogIntervalInMinutes)
    {
    lock (((IDictionary)logEvents).SyncRoot)
    {
    logEvents[logEvent] = DateTime.Now;
    }
    this.WrappedTarget.WriteAsyncLogEvent(logEvent);
    }
    else
    {
    logEvent.Continuation(null);
    }

    //Do a little clean up on the Dictionary
    //We can’t remove items from a collection as we enumerate over it, so this list will
    //hold all the items we want to mark for removal
    List keys = new List();
    foreach (var keypair in logEvents)
    {
    if ((DateTime.Now – keypair.Value).TotalMinutes > MinLogIntervalInMinutes)
    keys.Add(keypair.Key);
    }

    foreach (var key in keys)
    {
    lock (((IDictionary)logEvents).SyncRoot)
    {
    if (logEvents.ContainsKey(key))
    logEvents.Remove(key);
    }
    }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *