Last active
          August 29, 2015 14:26 
        
      - 
      
- 
        Save cocowalla/a7e86ece4d9488689eef to your computer and use it in GitHub Desktop. 
    Elmah email rate limiter
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | 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