Skip to content

Commit 6813bab

Browse files
committed
feat(monitoring): integrate Sentry for frontend and backend error tracking (#879)
1 parent 33b9e3e commit 6813bab

3 files changed

Lines changed: 80 additions & 0 deletions

File tree

‎backend/app.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@
124124
# This will raise ValueError if any required variables are missing
125125
validate_required_env_vars()
126126

127+
# =====================================================================
128+
# SECURITY COMPLIANCE UPDATE: SENTRY ERROR MONITORING
129+
# =====================================================================
130+
import sentry_sdk
131+
from sentry_sdk.integrations.flask import FlaskIntegration
132+
133+
sentry_dsn = os.getenv('SENTRY_DSN')
134+
if sentry_dsn:
135+
def strip_sensitive_data(event, hint):
136+
# Scrub sensitive data from request data before sending to Sentry
137+
if 'request' in event:
138+
request_data = event['request']
139+
if 'headers' in request_data:
140+
for key in ['Authorization', 'Cookie', 'X-CSRF-Token']:
141+
if key in request_data['headers'] or key.lower() in request_data['headers']:
142+
request_data['headers'][key] = '[Filtered]'
143+
request_data['headers'][key.lower()] = '[Filtered]'
144+
if 'data' in request_data and isinstance(request_data['data'], dict):
145+
for key in ['password', 'token', 'secret']:
146+
if key in request_data['data']:
147+
request_data['data'][key] = '[Filtered]'
148+
return event
149+
150+
sentry_sdk.init(
151+
dsn=sentry_dsn,
152+
integrations=[FlaskIntegration()],
153+
traces_sample_rate=1.0,
154+
before_send=strip_sensitive_data,
155+
environment=env
156+
)
157+
127158
# Apply configuration to Flask app
128159
app.config.update(app_config.flask_config)
129160

@@ -358,6 +389,15 @@ def handle_unhandled_exception(e):
358389
tuple: A Flask JSON response object and an HTTP status code.
359390
"""
360391

392+
# Capture in Sentry if it's not a standard HTTP exception
393+
if not isinstance(e, HTTPException):
394+
try:
395+
import sentry_sdk
396+
if os.getenv('SENTRY_DSN'):
397+
sentry_sdk.capture_exception(e)
398+
except ImportError:
399+
pass
400+
361401
# 1. Handle HTTP Exceptions Normally
362402
# If the exception is an intentional HTTP error (e.g., abort(404)),
363403
# we should return its intended status code and message, assuming it's
@@ -517,6 +557,13 @@ def handle_sqlalchemy_exception(e):
517557
Dedicated handler for database-related exceptions to prevent SQL injection
518558
reconnaissance and schema leakage.
519559
"""
560+
try:
561+
import sentry_sdk
562+
if os.getenv('SENTRY_DSN'):
563+
sentry_sdk.capture_exception(e)
564+
except ImportError:
565+
pass
566+
520567
# 1. Ensure the session is rolled back safely
521568
try:
522569
db.session.rollback()

‎backend/requirements.txt‎

38 Bytes
Binary file not shown.

‎frontend/js/config.js‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,36 @@ if (typeof module !== 'undefined' && module.exports) {
100100
module.exports = CONFIG;
101101
}
102102

103+
// =====================================================================
104+
// SECURITY COMPLIANCE UPDATE: SENTRY ERROR MONITORING
105+
// =====================================================================
106+
(function() {
107+
const dsn = window.SENTRY_FRONTEND_DSN || (typeof localStorage !== 'undefined' ? localStorage.getItem('SENTRY_FRONTEND_DSN') : '');
108+
if (dsn) {
109+
const script = document.createElement('script');
110+
script.src = 'https://browser.sentry-cdn.com/7.60.0/bundle.min.js';
111+
script.crossOrigin = 'anonymous';
112+
script.onload = () => {
113+
if (typeof Sentry !== 'undefined') {
114+
Sentry.init({
115+
dsn: dsn,
116+
tracesSampleRate: 1.0,
117+
beforeSend(event) {
118+
// Scrub sensitive data from request data
119+
if (event.request && event.request.data) {
120+
try {
121+
let data = typeof event.request.data === 'string' ? JSON.parse(event.request.data) : event.request.data;
122+
['password', 'token', 'secret'].forEach(key => {
123+
if (data[key]) data[key] = '[Filtered]';
124+
});
125+
event.request.data = typeof event.request.data === 'string' ? JSON.stringify(data) : data;
126+
} catch (e) {}
127+
}
128+
return event;
129+
}
130+
});
131+
}
132+
};
133+
document.head.appendChild(script);
134+
}
135+
})();

0 commit comments

Comments
 (0)