-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathErrorGovernor.cs
More file actions
159 lines (133 loc) · 5.61 KB
/
Copy pathErrorGovernor.cs
File metadata and controls
159 lines (133 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using StackifyLib.Models;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StackifyLib.Utils
{
/// <summary>
/// Handles error throttling from the client side appender
/// </summary>
public class ErrorGovernor
{
/// <summary>
/// Number of instances of a unique error that are allowed to be sent in one minute
/// </summary>
public static int MaxDupErrorPerMinute = 100;
/// <summary>
/// Elapsed time before the errorToCounter dictionary is purged of expired entries
/// </summary>
private static readonly int cleanUpMinutes = 5;
/// <summary>
/// Dictionary from
/// MD5(<type>-<typeCode>-<method>)
/// to
/// Unix epoch minute, error count for that minute
/// </summary>
private readonly Dictionary<string, Tuple<long, int>> errorToCounter = new Dictionary<string, Tuple<long, int>>();
/// <summary>
/// The next time the errorToCounter dictionary needs to be purged of expired entries
/// </summary>
private long nextErrorToCounterCleanUp = DateTime.UtcNow.ToUnixEpochMinutes() + cleanUpMinutes;
/// <summary>
/// Determines if the error should be sent based on our throttling criteria
/// </summary>
/// <param name="error">The error</param>
/// <returns>True if this error should be sent to Stackify, false otherwise</returns>
public bool ErrorShouldBeSent(StackifyError error)
{
if (error == null)
return false;
bool shouldBeProcessed = false;
ErrorItem baseError = GetBaseError(error);
if (baseError != null)
{
string uniqueKey = GetUniqueKey(baseError);
long epochMinute = DateTime.UtcNow.ToUnixEpochMinutes();
lock (errorToCounter)
{
// get the counter for this error
if (errorToCounter.ContainsKey(uniqueKey))
{
// counter exists
Tuple<long, int> counter = errorToCounter[uniqueKey];
if (counter.Item1 == epochMinute)
{
// counter exists for this current minute
// increment the counter
Tuple<long, int> incCounter = new Tuple<long, int>(counter.Item1, counter.Item2 + 1);
errorToCounter[uniqueKey] = incCounter;
// check our throttling criteria
if (incCounter.Item2 <= MaxDupErrorPerMinute)
{
shouldBeProcessed = true;
}
}
else
{
// counter did not exist for this minute so overwrite the entry with a new one
errorToCounter[uniqueKey] = new Tuple<long, int>(epochMinute, 1);
shouldBeProcessed = true;
}
}
else
{
// counter did not exist so create a new one
errorToCounter[uniqueKey] = new Tuple<long, int>(epochMinute, 1);
shouldBeProcessed = true;
}
// see if we need to purge our counters of expired entries
if (nextErrorToCounterCleanUp < epochMinute)
{
nextErrorToCounterCleanUp = PurgeErrorToCounter(epochMinute);
}
}
}
return shouldBeProcessed;
}
/// <summary>
/// Gets the base error (the last error in the causal chain)
/// </summary>
/// <param name="error">The error</param>
/// <returns>The inner most error</returns>
private ErrorItem GetBaseError(StackifyError error)
{
ErrorItem errorItem = error.Error;
if (errorItem != null)
{
while (errorItem.InnerError != null)
{
errorItem = errorItem.InnerError;
}
}
return errorItem;
}
/// <summary>
/// Generates a unique key based on the error. The key will be an MD5 hask of the type, type code, and method.
/// </summary>
/// <param name="errorItem">The error item</param>
/// <returns>The unique key for the error</returns>
private string GetUniqueKey(ErrorItem errorItem)
{
string type = errorItem.ErrorType;
string typeCode = errorItem.ErrorTypeCode;
string method = errorItem.SourceMethod;
string uniqueKey = string.Format("{0}-{1}-{2}", type ?? "", typeCode ?? "", method ?? "");
return uniqueKey.ToMD5Hash();
}
/// <summary>
/// Purges the errorToCounter dictionary of expired entries
/// </summary>
/// <param name="epochMinute">The current time</param>
/// <returns>The next time the purge needs to run</returns>
private long PurgeErrorToCounter(long epochMinute)
{
foreach (var entry in errorToCounter.Where(kvp => kvp.Value.Item1 < epochMinute).ToList())
{
errorToCounter.Remove(entry.Key);
}
return epochMinute + cleanUpMinutes;
}
}
}