From ecf825ac347879287da63952c9c8220b62b6d4f7 Mon Sep 17 00:00:00 2001
From: Niko Ehrenfeuchter <nikolaus.ehrenfeuchter@unibas.ch>
Date: Wed, 31 Jan 2018 00:10:03 +0100
Subject: [PATCH] Wrap mail targets for NLog by a rate-limiting wrapper.

To avoid flooding recipients and / or SMTP servers.

Refers to #3, #4
---
 ATXCommon/ATXCommon.csproj         |  1 +
 ATXCommon/NLog/RateLimitWrapper.cs | 34 ++++++++++++++++++++++++++++++
 AutoTx/AutoTx.cs                   | 20 ++++++++++++++----
 3 files changed, 51 insertions(+), 4 deletions(-)
 create mode 100644 ATXCommon/NLog/RateLimitWrapper.cs

diff --git a/ATXCommon/ATXCommon.csproj b/ATXCommon/ATXCommon.csproj
index dcc7ac5..a2297d0 100644
--- a/ATXCommon/ATXCommon.csproj
+++ b/ATXCommon/ATXCommon.csproj
@@ -49,6 +49,7 @@
     <Compile Include="ActiveDirectory.cs" />
     <Compile Include="Conv.cs" />
     <Compile Include="FsUtils.cs" />
+    <Compile Include="NLog\RateLimitWrapper.cs" />
     <Compile Include="Serializables\DriveToCheck.cs" />
     <Compile Include="Serializables\ServiceConfig.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/ATXCommon/NLog/RateLimitWrapper.cs b/ATXCommon/NLog/RateLimitWrapper.cs
new file mode 100644
index 0000000..b86dded
--- /dev/null
+++ b/ATXCommon/NLog/RateLimitWrapper.cs
@@ -0,0 +1,34 @@
+using System;
+using System.ComponentModel;
+using NLog.Common;
+using NLog.Targets;
+using NLog.Targets.Wrappers;
+
+namespace ATXCommon.NLog
+{
+    /// <summary>
+    /// A wrapper target for NLog, limiting the rate of messages being logged.
+    /// 
+    /// Meant to be used in conjunction with the MailTarget class, to avoid flooding the recipient
+    /// with too many emails and probably being banned by the SMTP server for spamming.
+    /// NOTE: should always be used in combination with another target (FileTarget) to ensure that
+    /// all messages are being logged, including those ones discarded by *this* target.
+    /// </summary>
+    [Target("FrequencyWrapper", IsWrapper = true)]
+    public class RateLimitWrapper : WrapperTargetBase
+    {
+        private DateTime _lastLogEvent = DateTime.MinValue;
+
+        protected override void Write(AsyncLogEventInfo logEvent) {
+            if ((DateTime.Now - _lastLogEvent).TotalMinutes >= MinLogInterval) {
+                _lastLogEvent = DateTime.Now;
+                WrappedTarget.WriteAsyncLogEvent(logEvent);
+            } else {
+                logEvent.Continuation(null);
+            }
+        }
+
+        [DefaultValue(30)]
+        public int MinLogInterval { get; set; }
+    }
+}
diff --git a/AutoTx/AutoTx.cs b/AutoTx/AutoTx.cs
index 70db56e..8afd10a 100644
--- a/AutoTx/AutoTx.cs
+++ b/AutoTx/AutoTx.cs
@@ -10,6 +10,7 @@ using NLog;
 using NLog.Config;
 using NLog.Targets;
 using ATXCommon;
+using ATXCommon.NLog;
 using ATXCommon.Serializables;
 using RoboSharp;
 
@@ -120,6 +121,8 @@ namespace AutoTx
                     _config.HostAlias, Environment.MachineName, LogFormatDefault);
 
                 var logConfig = LogManager.Configuration;
+
+                // "Fatal" target
                 var mailTargetFatal = new MailTarget {
                     Name = "mailfatal",
                     SmtpServer = _config.SmtpHost,
@@ -129,9 +132,14 @@ namespace AutoTx
                     Subject = subject,
                     Body = body,
                 };
-                logConfig.AddTarget(mailTargetFatal);
-                logConfig.AddRuleForOneLevel(LogLevel.Fatal, mailTargetFatal);
+                var mailTargetFatalLimited = new RateLimitWrapper {
+                    Name = "mailfatallimited",
+                    WrappedTarget = mailTargetFatal
+                };
+                logConfig.AddTarget(mailTargetFatalLimited);
+                logConfig.AddRuleForOneLevel(LogLevel.Fatal, mailTargetFatalLimited);
 
+                // "Error" target
                 if (!string.IsNullOrWhiteSpace(_config.AdminDebugEmailAdress)) {
                     var mailTargetError = new MailTarget {
                         Name = "mailerror",
@@ -142,8 +150,12 @@ namespace AutoTx
                         Subject = subject,
                         Body = body,
                     };
-                    logConfig.AddTarget(mailTargetError);
-                    logConfig.AddRuleForOneLevel(LogLevel.Error, mailTargetError);
+                    var mailTargetErrorLimited = new RateLimitWrapper {
+                        Name = "mailerrorlimited",
+                        WrappedTarget = mailTargetError
+                    };
+                    logConfig.AddTarget(mailTargetErrorLimited);
+                    logConfig.AddRuleForOneLevel(LogLevel.Error, mailTargetErrorLimited);
                     Log.Info("Configured mail notification for 'Error' messages to {0}", mailTargetError.To);
                 }
                 Log.Info("Configured mail notification for 'Fatal' messages to {0}", mailTargetFatal.To);
-- 
GitLab