Skip to content

Commit fe83030

Browse files
authored
feat: Make error throttling smarter by factoring in error message (#287)
1 parent 779bf36 commit fe83030

File tree

3 files changed

+60
-20
lines changed

3 files changed

+60
-20
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- Intoduce `SentryTimestamp` class to improve timestamp handling ([#286](https://github.com/getsentry/sentry-godot/pull/286))
88
- Support for iOS & macOS using Sentry Cocoa SDK integration ([#266](https://github.com/getsentry/sentry-godot/pull/266))
99

10+
### Improvements
11+
12+
- Make error throttling smarter by factoring in error message ([#287](https://github.com/getsentry/sentry-godot/pull/287))
13+
1014
## 1.0.0-alpha.3
1115

1216
### Breaking changes

src/sentry/sentry_logger.cpp

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,36 @@ Vector<SentryEvent::StackFrame> _extract_error_stack_frames_from_backtraces(
160160
return frames;
161161
}
162162

163+
template <class T>
164+
inline size_t _hash(const T &p_value) {
165+
std::hash<T> hasher;
166+
return hasher(p_value);
167+
}
168+
169+
template <class T>
170+
inline void _hash_combine(std::size_t &p_hash, const T &p_value) {
171+
// NOTE: Hash combining technique, originally from boost.
172+
std::hash<T> hasher;
173+
p_hash ^= hasher(p_value) + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2);
174+
}
175+
163176
} // unnamed namespace
164177

165178
namespace sentry {
166179

180+
std::size_t SentryLogger::ErrorKeyHash::operator()(const ErrorKey &p_key) const {
181+
CharString message_cstr = p_key.message.utf8();
182+
CharString filename_cstr = p_key.file.utf8();
183+
184+
std::string_view message_sv{ message_cstr.get_data() };
185+
std::string_view filename_sv{ filename_cstr.get_data() };
186+
187+
size_t hash_value = _hash(message_sv);
188+
_hash_combine(hash_value, filename_sv);
189+
_hash_combine(hash_value, p_key.line);
190+
return hash_value;
191+
}
192+
167193
void SentryLogger::_connect_process_frame() {
168194
SceneTree *scene_tree = Object::cast_to<SceneTree>(Engine::get_singleton()->get_main_loop());
169195
if (scene_tree) {
@@ -198,8 +224,8 @@ void SentryLogger::_process_frame() {
198224
}
199225

200226
// Clear source_line_times if it's too big. Cheap and efficient.
201-
if (unlikely(source_line_times.size() > 100)) {
202-
source_line_times.clear();
227+
if (unlikely(error_timepoints.size() > 100)) {
228+
error_timepoints.clear();
203229
}
204230
}
205231

@@ -214,7 +240,12 @@ void SentryLogger::_log_error(const String &p_function, const String &p_file, in
214240
return;
215241
}
216242

217-
SourceLine source_line{ p_file.utf8(), p_line };
243+
String error_message = p_rationale.is_empty() ? p_code : p_rationale;
244+
245+
ErrorKey error_key;
246+
error_key.message = error_message;
247+
error_key.file = p_file;
248+
error_key.line = p_line;
218249

219250
TimePoint now = std::chrono::high_resolution_clock::now();
220251

@@ -227,8 +258,8 @@ void SentryLogger::_log_error(const String &p_function, const String &p_file, in
227258
// Reject errors based on per-source-line throttling window to prevent
228259
// repetitive logging caused by loops or errors recurring in each frame.
229260
// The timestamps are tracked for each source line that produced an error.
230-
auto it = source_line_times.find(source_line);
231-
bool is_spammy_error = it != source_line_times.end() && now - it->second < limits.repeated_error_window;
261+
auto it = error_timepoints.find(error_key);
262+
bool is_spammy_error = it != error_timepoints.end() && now - it->second < limits.repeated_error_window;
232263

233264
bool within_frame_limit = frame_events < limits.events_per_frame;
234265
bool within_throttling_limit = event_times.size() < limits.throttle_events;
@@ -248,7 +279,7 @@ void SentryLogger::_log_error(const String &p_function, const String &p_file, in
248279

249280
if (as_event || as_breadcrumb) {
250281
// Store timestamp to prevent repetitive logging from the same line of code.
251-
source_line_times[source_line] = now;
282+
error_timepoints[error_key] = now;
252283
}
253284
}
254285

@@ -257,10 +288,8 @@ void SentryLogger::_log_error(const String &p_function, const String &p_file, in
257288
return;
258289
}
259290

260-
String error_value = p_rationale.is_empty() ? p_code : p_rationale;
261-
262291
sentry::util::print_debug(
263-
"Capturing error: ", error_value,
292+
"Capturing error: ", error_message,
264293
"\n at: ", p_function, " (", p_file, ":", p_line, ")",
265294
"\n event: ", as_event, " breadcrumb: ", as_breadcrumb);
266295

@@ -282,7 +311,7 @@ void SentryLogger::_log_error(const String &p_function, const String &p_file, in
282311
ev->set_level(sentry::get_sentry_level_for_godot_error_type((GodotErrorType)p_error_type));
283312
SentryEvent::Exception exception = {
284313
error_type_as_string[int(p_error_type)],
285-
error_value,
314+
error_message,
286315
frames
287316
};
288317
ev->add_exception(exception);
@@ -300,7 +329,7 @@ void SentryLogger::_log_error(const String &p_function, const String &p_file, in
300329
data["error_type"] = String(error_type_as_string[int(p_error_type)]);
301330

302331
SentrySDK::get_singleton()->add_breadcrumb(
303-
error_value,
332+
error_message,
304333
"error",
305334
sentry::get_sentry_level_for_godot_error_type((GodotErrorType)p_error_type),
306335
"error",

src/sentry/sentry_logger.h

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,34 @@ class SentryLogger : public Logger {
1818
GDCLASS(SentryLogger, Logger);
1919

2020
private:
21+
using GodotErrorType = sentry::GodotErrorType;
22+
using TimePoint = std::chrono::high_resolution_clock::time_point;
23+
2124
struct Limits {
2225
int events_per_frame;
2326
std::chrono::milliseconds repeated_error_window;
2427
std::chrono::milliseconds throttle_window;
2528
int throttle_events;
2629
} limits;
2730

28-
std::mutex error_mutex;
31+
struct ErrorKey {
32+
String message;
33+
String file;
34+
int line;
2935

30-
using GodotErrorType = sentry::GodotErrorType;
31-
using SourceLine = std::pair<std::string, int>;
32-
using TimePoint = std::chrono::high_resolution_clock::time_point;
33-
34-
struct SourceLineHash {
35-
std::size_t operator()(const SourceLine &p_source_line) const {
36-
return std::hash<std::string>()(p_source_line.first) ^ std::hash<int>()(p_source_line.second);
36+
bool operator==(const ErrorKey &p_other) const {
37+
return message == p_other.message && file == p_other.file && line == p_other.line;
3738
}
3839
};
3940

41+
struct ErrorKeyHash {
42+
std::size_t operator()(const ErrorKey &p_key) const;
43+
};
44+
45+
std::mutex error_mutex;
46+
4047
// Stores the last time an error was logged for each source line that generated an error.
41-
std::unordered_map<SourceLine, TimePoint, SourceLineHash> source_line_times;
48+
std::unordered_map<ErrorKey, TimePoint, ErrorKeyHash> error_timepoints;
4249

4350
// Time points for events captured within throttling window.
4451
std::deque<TimePoint> event_times;

0 commit comments

Comments
 (0)