It overrides what we set with information that isn't really useful for us. Set a more readable method name, and add in some additional job metadata. Signed-off-by: Akhil Narang <me@akhilnarang.dev>
152 lines
4.2 KiB
Python
152 lines
4.2 KiB
Python
import os
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
import rq
|
|
from sentry_sdk import capture_message as sentry_capture_message
|
|
from sentry_sdk.hub import Hub
|
|
from sentry_sdk.integrations import Integration
|
|
from sentry_sdk.integrations.wsgi import _make_wsgi_event_processor
|
|
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
|
from sentry_sdk.tracing_utils import record_sql_queries
|
|
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
|
|
|
|
import frappe
|
|
import frappe.monitor
|
|
from frappe.database.database import Database, EmptyQueryValues
|
|
|
|
|
|
class FrappeIntegration(Integration):
|
|
identifier = "frappe"
|
|
|
|
@staticmethod
|
|
def setup_once():
|
|
real_connect = Database.connect
|
|
real_sql = Database.sql
|
|
|
|
def sql(self, query, values=None, *args, **kwargs):
|
|
hub = Hub.current
|
|
|
|
if not self._conn:
|
|
self.connect()
|
|
|
|
with record_sql_queries(
|
|
hub, self._cursor, query, values, paramstyle="pyformat", executemany=False
|
|
):
|
|
return real_sql(self, query, values or EmptyQueryValues, *args, **kwargs)
|
|
|
|
def connect(self):
|
|
hub = Hub.current
|
|
with capture_internal_exceptions():
|
|
hub.add_breadcrumb(message="connect", category="query")
|
|
|
|
with hub.start_span(op="db", description="connect"):
|
|
return real_connect(self)
|
|
|
|
Database.connect = connect
|
|
Database.sql = sql
|
|
|
|
|
|
def set_scope(scope):
|
|
if job := rq.get_current_job():
|
|
kwargs = job._kwargs
|
|
transaction_name = str(kwargs["method"])
|
|
context = frappe._dict({"scheduled": False, "wait": 0})
|
|
if "run_scheduled_job" in transaction_name:
|
|
transaction_name = kwargs.get("kwargs", {}).get("job_type", "")
|
|
context.scheduled = True
|
|
|
|
waitdiff = datetime.utcnow() - job.enqueued_at
|
|
context.uuid = job.id
|
|
context.wait = waitdiff.total_seconds()
|
|
context.kwargs = kwargs
|
|
|
|
scope.set_extra("job", context)
|
|
scope.set_transaction_name(transaction_name)
|
|
else:
|
|
if frappe.form_dict.cmd:
|
|
path = f"/api/method/{frappe.form_dict.cmd}"
|
|
else:
|
|
path = frappe.request.path
|
|
|
|
scope.set_transaction_name(
|
|
path,
|
|
source=SOURCE_FOR_STYLE["endpoint"],
|
|
)
|
|
|
|
scope.set_user({"id": frappe.local.site})
|
|
user = getattr(frappe.session, "user", "Unidentified")
|
|
scope.set_tag("frappe_user", user)
|
|
# Extract `X-Frappe-Request-ID` to store as a separate field if its present
|
|
if trace_id := frappe.monitor.get_trace_id():
|
|
scope.set_tag("frappe_trace_id", trace_id)
|
|
|
|
|
|
def set_sentry_context():
|
|
if not frappe.get_system_settings("enable_telemetry"):
|
|
return
|
|
|
|
hub = Hub.current
|
|
with hub.configure_scope() as scope:
|
|
set_scope(scope)
|
|
|
|
|
|
def before_send(event, hint):
|
|
if event.get("logger", "") == "CSSUTILS":
|
|
return None
|
|
return event
|
|
|
|
|
|
def capture_exception(message: str | None = None) -> None:
|
|
"""
|
|
Function to upload exception data to entry
|
|
|
|
:param message: A message to be sent if we can't find an exception
|
|
"""
|
|
# Don't report anything if the user hasn't opted-in to telemetry
|
|
if not frappe.get_system_settings("enable_telemetry"):
|
|
return
|
|
try:
|
|
hub = Hub.current
|
|
with hub.configure_scope() as scope:
|
|
if (
|
|
os.getenv("ENABLE_SENTRY_DB_MONITORING") is None
|
|
or os.getenv("SENTRY_TRACING_SAMPLE_RATE") is None
|
|
):
|
|
set_scope(scope)
|
|
if frappe.request:
|
|
evt_processor = _make_wsgi_event_processor(frappe.request.environ, False)
|
|
scope.add_event_processor(evt_processor)
|
|
if frappe.request.is_json:
|
|
scope.set_context("JSON Body", frappe.request.json)
|
|
elif frappe.request.form:
|
|
scope.set_context("Form Data", frappe.request.form)
|
|
|
|
if client := hub.client:
|
|
exc_info = sys.exc_info()
|
|
if any(exc_info):
|
|
# Don't report validation errors
|
|
if isinstance(exc_info[0], frappe.ValidationError):
|
|
return
|
|
|
|
event, hint = event_from_exception(
|
|
exc_info,
|
|
client_options=client.options,
|
|
mechanism={"type": "wsgi", "handled": False},
|
|
)
|
|
hub.capture_event(event, hint=hint)
|
|
elif message:
|
|
sentry_capture_message(message, level="error")
|
|
|
|
except Exception:
|
|
frappe.logger().error("Failed to capture exception", exc_info=True)
|
|
pass
|
|
|
|
|
|
def add_bootinfo(bootinfo):
|
|
"""Called from hook, sends DSN so client side can setup error monitoring."""
|
|
if not frappe.get_system_settings("enable_telemetry"):
|
|
return
|
|
|
|
if sentry_dsn := os.getenv("FRAPPE_SENTRY_DSN"):
|
|
bootinfo.sentry_dsn = sentry_dsn
|