Skip to content

Instantly share code, notes, and snippets.

@cocowalla
Last active August 29, 2015 14:26
Show Gist options
  • Save cocowalla/a7e86ece4d9488689eef to your computer and use it in GitHub Desktop.
Save cocowalla/a7e86ece4d9488689eef to your computer and use it in GitHub Desktop.
Elmah email rate limiter
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Elmah;
public class ElmahRateLimiter
{
private readonly static ConcurrentDictionary<string, DateTime> Exceptions = new ConcurrentDictionary<string, DateTime>();
private readonly static ThreadLocal<HashAlgorithm> Hash = new ThreadLocal<HashAlgorithm>(() => new MD5CryptoServiceProvider());
private readonly TimeSpan rateLimitTime = TimeSpan.FromMinutes(5);
public ElmahRateLimiter(TimeSpan rateLimitTime)
{
this.rateLimitTime = rateLimitTime;
}
/// <summary>
/// Throttle errors to avoid flooding log sinks with the same error
/// </summary>
public void RateLimit(ExceptionFilterEventArgs e)
{
var key = Fingerprint(e.Exception);
// If an email for this event has already been sent within the rate limiting period, don't send another
DateTime expiry;
if (Exceptions.TryGetValue(key, out expiry))
{
if (expiry > DateTime.UtcNow)
{
e.Dismiss();
return;
}
}
expiry = DateTime.UtcNow.Add(this.rateLimitTime);
Exceptions.AddOrUpdate(key, expiry, (k, v) => expiry);
// Remove expired exceptions
var keysToRemove = Exceptions
.Where(x => x.Value < DateTime.UtcNow)
.Select(x => x.Key)
.ToList();
foreach (var keyToRemove in keysToRemove)
{
DateTime tmp;
Exceptions.TryRemove(keyToRemove, out tmp);
}
}
/// <summary>
/// Create a unique fingerprint from an exception
/// </summary>
/// <remarks>
/// Note that we only consider the exception message, as the full stacktrace is likely to contain
/// text that we expect to change in every exception instance, such as timestamps
/// </remarks>
public string Fingerprint(Exception e)
{
var bytes = Encoding.Unicode.GetBytes(e.Message.ToCharArray());
var hash = Hash.Value.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment