refactor(sentry): sync up with FC implementation
Co-authored-by: Aditya Hase <aditya@adityahase.com> Signed-off-by: Akhil Narang <me@akhilnarang.dev>
This commit is contained in:
parent
bedf270bc4
commit
067104ca9c
5 changed files with 167 additions and 41 deletions
|
|
@ -59,32 +59,6 @@ if _dev_server:
|
||||||
warnings.simplefilter("always", DeprecationWarning)
|
warnings.simplefilter("always", DeprecationWarning)
|
||||||
warnings.simplefilter("always", PendingDeprecationWarning)
|
warnings.simplefilter("always", PendingDeprecationWarning)
|
||||||
|
|
||||||
# Always initialize sentry SDK if the DSN is sent
|
|
||||||
if sentry_dsn := os.getenv("FRAPPE_SENTRY_DSN"):
|
|
||||||
import sentry_sdk
|
|
||||||
from sentry_sdk.integrations.argv import ArgvIntegration
|
|
||||||
from sentry_sdk.integrations.atexit import AtexitIntegration
|
|
||||||
from sentry_sdk.integrations.dedupe import DedupeIntegration
|
|
||||||
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
|
|
||||||
from sentry_sdk.integrations.modules import ModulesIntegration
|
|
||||||
|
|
||||||
from frappe.utils.sentry import before_send
|
|
||||||
|
|
||||||
sentry_sdk.init(
|
|
||||||
dsn=sentry_dsn,
|
|
||||||
before_send=before_send,
|
|
||||||
release=__version__,
|
|
||||||
auto_enabling_integrations=False,
|
|
||||||
default_integrations=False,
|
|
||||||
integrations=[
|
|
||||||
AtexitIntegration(),
|
|
||||||
ExcepthookIntegration(),
|
|
||||||
DedupeIntegration(),
|
|
||||||
ModulesIntegration(),
|
|
||||||
ArgvIntegration(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class _dict(dict):
|
class _dict(dict):
|
||||||
"""dict like object that exposes keys as attributes"""
|
"""dict like object that exposes keys as attributes"""
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ def application(request: Request):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run_after_request_hooks(request, response)
|
run_after_request_hooks(request, response)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# We can not handle exceptions safely here.
|
# We can not handle exceptions safely here.
|
||||||
frappe.logger().error("Failed to run after request hook", exc_info=True)
|
frappe.logger().error("Failed to run after request hook", exc_info=True)
|
||||||
|
|
||||||
|
|
@ -420,6 +420,50 @@ def sync_database(rollback: bool) -> bool:
|
||||||
return rollback
|
return rollback
|
||||||
|
|
||||||
|
|
||||||
|
# Always initialize sentry SDK if the DSN is sent
|
||||||
|
if sentry_dsn := os.getenv("FRAPPE_SENTRY_DSN"):
|
||||||
|
import sentry_sdk
|
||||||
|
from sentry_sdk.integrations.argv import ArgvIntegration
|
||||||
|
from sentry_sdk.integrations.atexit import AtexitIntegration
|
||||||
|
from sentry_sdk.integrations.dedupe import DedupeIntegration
|
||||||
|
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
|
||||||
|
from sentry_sdk.integrations.modules import ModulesIntegration
|
||||||
|
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
||||||
|
|
||||||
|
from frappe.utils.sentry import FrappeIntegration, before_send
|
||||||
|
|
||||||
|
integrations = [
|
||||||
|
AtexitIntegration(),
|
||||||
|
ExcepthookIntegration(),
|
||||||
|
DedupeIntegration(),
|
||||||
|
ModulesIntegration(),
|
||||||
|
ArgvIntegration(),
|
||||||
|
]
|
||||||
|
|
||||||
|
experiments = {}
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if os.getenv("ENABLE_SENTRY_DB_MONITORING"):
|
||||||
|
integrations.append(FrappeIntegration())
|
||||||
|
experiments["record_sql_params"] = True
|
||||||
|
|
||||||
|
if tracing_sample_rate := os.getenv("SENTRY_TRACING_SAMPLE_RATE"):
|
||||||
|
kwargs["traces_sample_rate"] = float(tracing_sample_rate)
|
||||||
|
application = SentryWsgiMiddleware(application)
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn=sentry_dsn,
|
||||||
|
before_send=before_send,
|
||||||
|
attach_stacktrace=True,
|
||||||
|
release=frappe.__version__,
|
||||||
|
auto_enabling_integrations=False,
|
||||||
|
default_integrations=False,
|
||||||
|
integrations=integrations,
|
||||||
|
_experiments=experiments,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def serve(
|
def serve(
|
||||||
port=8000,
|
port=8000,
|
||||||
profile=False,
|
profile=False,
|
||||||
|
|
|
||||||
|
|
@ -422,11 +422,13 @@ before_request = [
|
||||||
"frappe.recorder.record",
|
"frappe.recorder.record",
|
||||||
"frappe.monitor.start",
|
"frappe.monitor.start",
|
||||||
"frappe.rate_limiter.apply",
|
"frappe.rate_limiter.apply",
|
||||||
|
"frappe.utils.sentry.set_sentry_context",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Background Job Hooks
|
# Background Job Hooks
|
||||||
before_job = [
|
before_job = [
|
||||||
"frappe.monitor.start",
|
"frappe.monitor.start",
|
||||||
|
"frappe.utils.sentry.set_sentry_context",
|
||||||
]
|
]
|
||||||
after_job = [
|
after_job = [
|
||||||
"frappe.monitor.stop",
|
"frappe.monitor.stop",
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,49 @@ def start_worker(
|
||||||
if quiet:
|
if quiet:
|
||||||
logging_level = "WARNING"
|
logging_level = "WARNING"
|
||||||
|
|
||||||
|
# Always initialize sentry SDK if the DSN is sent
|
||||||
|
if sentry_dsn := os.getenv("FRAPPE_SENTRY_DSN"):
|
||||||
|
import sentry_sdk
|
||||||
|
from sentry_sdk.integrations.argv import ArgvIntegration
|
||||||
|
from sentry_sdk.integrations.atexit import AtexitIntegration
|
||||||
|
from sentry_sdk.integrations.dedupe import DedupeIntegration
|
||||||
|
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
|
||||||
|
from sentry_sdk.integrations.modules import ModulesIntegration
|
||||||
|
from sentry_sdk.integrations.rq import RqIntegration
|
||||||
|
|
||||||
|
from frappe.utils.sentry import FrappeIntegration, before_send
|
||||||
|
|
||||||
|
integrations = [
|
||||||
|
AtexitIntegration(),
|
||||||
|
ExcepthookIntegration(),
|
||||||
|
DedupeIntegration(),
|
||||||
|
ModulesIntegration(),
|
||||||
|
ArgvIntegration(),
|
||||||
|
RqIntegration(),
|
||||||
|
]
|
||||||
|
|
||||||
|
experiments = {}
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if os.getenv("ENABLE_SENTRY_DB_MONITORING"):
|
||||||
|
integrations.append(FrappeIntegration())
|
||||||
|
experiments["record_sql_params"] = True
|
||||||
|
|
||||||
|
if tracing_sample_rate := os.getenv("SENTRY_TRACING_SAMPLE_RATE"):
|
||||||
|
kwargs["traces_sample_rate"] = float(tracing_sample_rate)
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn=sentry_dsn,
|
||||||
|
before_send=before_send,
|
||||||
|
attach_stacktrace=True,
|
||||||
|
release=frappe.__version__,
|
||||||
|
auto_enabling_integrations=False,
|
||||||
|
default_integrations=False,
|
||||||
|
integrations=integrations,
|
||||||
|
_experiments=experiments,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
worker = Worker(queues, name=get_worker_name(queue_name), connection=redis_connection)
|
worker = Worker(queues, name=get_worker_name(queue_name), connection=redis_connection)
|
||||||
worker.work(
|
worker.work(
|
||||||
logging_level=logging_level,
|
logging_level=logging_level,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,93 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import rq
|
||||||
from sentry_sdk import capture_message as sentry_capture_message
|
from sentry_sdk import capture_message as sentry_capture_message
|
||||||
|
from sentry_sdk import configure_scope
|
||||||
from sentry_sdk.hub import Hub
|
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.integrations.wsgi import _make_wsgi_event_processor
|
||||||
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
||||||
from sentry_sdk.utils import event_from_exception
|
from sentry_sdk.tracing_utils import record_sql_queries
|
||||||
|
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.monitor
|
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_sentry_context():
|
||||||
|
with configure_scope() as 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()
|
||||||
|
|
||||||
|
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_tag("site", frappe.local.site)
|
||||||
|
user = getattr(frappe.session, "user", "Unidentified")
|
||||||
|
if "@" not in user:
|
||||||
|
user = f"{user}@{frappe.local.site}"
|
||||||
|
scope.set_user({"id": user, "email": 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 before_send(event, hint):
|
def before_send(event, hint):
|
||||||
# Not doing anything here for now - we can add some checks to clean up the data, strip PII, etc.
|
if event.get("logger", "") == "CSSUTILS":
|
||||||
|
return None
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,20 +105,8 @@ def capture_exception(message: str | None = None) -> None:
|
||||||
|
|
||||||
if frappe.request:
|
if frappe.request:
|
||||||
with hub.configure_scope() as scope:
|
with hub.configure_scope() as scope:
|
||||||
scope.set_transaction_name(
|
|
||||||
frappe.request.path,
|
|
||||||
source=SOURCE_FOR_STYLE["endpoint"],
|
|
||||||
)
|
|
||||||
|
|
||||||
evt_processor = _make_wsgi_event_processor(frappe.request.environ, False)
|
evt_processor = _make_wsgi_event_processor(frappe.request.environ, False)
|
||||||
scope.add_event_processor(evt_processor)
|
scope.add_event_processor(evt_processor)
|
||||||
scope.set_tag("site", frappe.local.site)
|
|
||||||
user = getattr(frappe.session, "user", "Unidentified")
|
|
||||||
scope.set_user({"id": user, "email": 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)
|
|
||||||
|
|
||||||
if client := hub.client:
|
if client := hub.client:
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue