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; }
} |
[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> |
<extensions>
<add assembly="MyAssembly"/>
</extensions>
<target name="limitedEmail" type="FrequencyWrapper" minLogIntervalInMinutes="40">
<target name="email" xsi:type="Mail"
...
/>
</target>
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);
}
}
}
Great idea!
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);
}
}
}