mirror of
https://github.com/odoo/runbot.git
synced 2025-03-27 13:25:47 +07:00
[ADD] runbot_merge: sentry instrumentation
Currently sentry is only hooked from the outside, which doesn't necessarily provide sufficiently actionable information. Add some a few hooks to (try and) report odoo / mergebot metadata: - add the user to WSGI transactions - add a transaction (with users) around crons - add the webhook event info to webhook requests - add a few spans to the long-running crons, when they cover multiple units per iteration (e.g. a span per branch being staged) Closes #544
This commit is contained in:
parent
06a3a1bab5
commit
ed0fd88854
@ -1,6 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
import resource
|
import resource
|
||||||
import subprocess
|
import subprocess
|
||||||
import uuid
|
import uuid
|
||||||
@ -28,7 +31,8 @@ class Queue:
|
|||||||
def _process(self):
|
def _process(self):
|
||||||
for b in self.search(self._search_domain(), order='create_date, id', limit=self.limit):
|
for b in self.search(self._search_domain(), order='create_date, id', limit=self.limit):
|
||||||
try:
|
try:
|
||||||
b._process_item()
|
with sentry_sdk.start_span(description=self._name):
|
||||||
|
b._process_item()
|
||||||
b.unlink()
|
b.unlink()
|
||||||
self.env.cr.commit()
|
self.env.cr.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -68,6 +72,7 @@ class ForwardPortTasks(models.Model, Queue):
|
|||||||
|
|
||||||
def _process_item(self):
|
def _process_item(self):
|
||||||
batch = self.batch_id
|
batch = self.batch_id
|
||||||
|
sentry_sdk.set_tag('forward-porting', batch.prs.mapped('display_name'))
|
||||||
newbatch = batch.prs._port_forward()
|
newbatch = batch.prs._port_forward()
|
||||||
|
|
||||||
if newbatch:
|
if newbatch:
|
||||||
@ -109,6 +114,7 @@ class UpdateQueue(models.Model, Queue):
|
|||||||
|
|
||||||
def _process_item(self):
|
def _process_item(self):
|
||||||
previous = self.new_root
|
previous = self.new_root
|
||||||
|
sentry_sdk.set_tag("update-root", self.new_root.display_name)
|
||||||
with ExitStack() as s:
|
with ExitStack() as s:
|
||||||
for child in self.new_root._iter_descendants():
|
for child in self.new_root._iter_descendants():
|
||||||
self.env.cr.execute("""
|
self.env.cr.execute("""
|
||||||
|
@ -3,6 +3,7 @@ import hmac
|
|||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import sentry_sdk
|
||||||
import werkzeug.exceptions
|
import werkzeug.exceptions
|
||||||
|
|
||||||
from odoo.http import Controller, request, route
|
from odoo.http import Controller, request, route
|
||||||
@ -18,6 +19,13 @@ class MergebotController(Controller):
|
|||||||
def index(self):
|
def index(self):
|
||||||
req = request.httprequest
|
req = request.httprequest
|
||||||
event = req.headers['X-Github-Event']
|
event = req.headers['X-Github-Event']
|
||||||
|
with sentry_sdk.configure_scope() as scope:
|
||||||
|
if scope.transaction:
|
||||||
|
# only in 1.8.0 (or at least 1.7.2
|
||||||
|
if hasattr(scope, 'set_transaction_name'):
|
||||||
|
scope.set_transaction_name(f"webhook {event}")
|
||||||
|
else: # but our servers use 1.4.3
|
||||||
|
scope.transaction = f"webhook {event}"
|
||||||
|
|
||||||
github._gh.info(self._format(req))
|
github._gh.info(self._format(req))
|
||||||
|
|
||||||
@ -39,6 +47,7 @@ class MergebotController(Controller):
|
|||||||
req.headers.get('X-Hub-Signature'))
|
req.headers.get('X-Hub-Signature'))
|
||||||
return werkzeug.exceptions.Forbidden()
|
return werkzeug.exceptions.Forbidden()
|
||||||
|
|
||||||
|
sentry_sdk.set_context('webhook', request.jsonrequest)
|
||||||
return c(env, request.jsonrequest)
|
return c(env, request.jsonrequest)
|
||||||
|
|
||||||
def _format(self, request):
|
def _format(self, request):
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
from odoo import models, fields
|
from odoo import models, fields
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@ -69,7 +71,9 @@ class Project(models.Model):
|
|||||||
('staging_enabled', '=', True),
|
('staging_enabled', '=', True),
|
||||||
]):
|
]):
|
||||||
try:
|
try:
|
||||||
with self.env.cr.savepoint():
|
with self.env.cr.savepoint(), \
|
||||||
|
sentry_sdk.start_span(description=f'create staging {branch.name}') as span:
|
||||||
|
span.set_tag('branch', branch.name)
|
||||||
branch.try_staging()
|
branch.try_staging()
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.exception("Failed to create staging for branch %r", branch.name)
|
_logger.exception("Failed to create staging for branch %r", branch.name)
|
||||||
|
@ -19,6 +19,7 @@ from itertools import takewhile
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import sentry_sdk
|
||||||
import werkzeug
|
import werkzeug
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
|
|
||||||
@ -2031,7 +2032,10 @@ class Stagings(models.Model):
|
|||||||
FOR UPDATE
|
FOR UPDATE
|
||||||
''', [tuple(self.mapped('batch_ids.prs.id'))])
|
''', [tuple(self.mapped('batch_ids.prs.id'))])
|
||||||
try:
|
try:
|
||||||
self._safety_dance(gh, staging_heads)
|
with sentry_sdk.start_span(description="merge staging") as span:
|
||||||
|
span.set_tag("staging", self.id)
|
||||||
|
span.set_tag("branch", self.target.name)
|
||||||
|
self._safety_dance(gh, staging_heads)
|
||||||
except exceptions.FastForwardError as e:
|
except exceptions.FastForwardError as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Could not fast-forward successful staging on %s:%s",
|
"Could not fast-forward successful staging on %s:%s",
|
||||||
|
@ -6,7 +6,10 @@ from sentry_sdk.integrations.logging import LoggingIntegration
|
|||||||
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
||||||
|
|
||||||
from odoo import http
|
from odoo import http
|
||||||
from runbot_merge.exceptions import FastForwardError, Mismatch, MergeError, Unmergeable
|
from odoo.addons.base.models.ir_cron import ir_cron
|
||||||
|
from odoo.http import WebRequest
|
||||||
|
|
||||||
|
from .exceptions import FastForwardError, Mismatch, MergeError, Unmergeable
|
||||||
|
|
||||||
|
|
||||||
def delegate(self, attr):
|
def delegate(self, attr):
|
||||||
@ -41,8 +44,41 @@ def setup_sentry(dsn):
|
|||||||
LoggingIntegration(level=logging.INFO, event_level=logging.WARNING),
|
LoggingIntegration(level=logging.INFO, event_level=logging.WARNING),
|
||||||
],
|
],
|
||||||
before_send=event_filter,
|
before_send=event_filter,
|
||||||
|
# apparently not in my version of the sdk
|
||||||
|
# functions_to_trace = []
|
||||||
)
|
)
|
||||||
http.root = SentryWsgiMiddleware(http.root)
|
http.root = SentryWsgiMiddleware(http.root)
|
||||||
|
instrument_odoo()
|
||||||
|
|
||||||
|
def instrument_odoo():
|
||||||
|
"""Monkeypatches odoo core to copy odoo metadata into sentry for more
|
||||||
|
informative events
|
||||||
|
"""
|
||||||
|
# add user to wsgi request context
|
||||||
|
old_call_function = WebRequest._call_function
|
||||||
|
def _call_function(self, *args, **kwargs):
|
||||||
|
if self.uid:
|
||||||
|
sentry_sdk.set_user({
|
||||||
|
'id': self.uid,
|
||||||
|
'email': self.env.user.email,
|
||||||
|
'username': self.env.user.login,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
sentry_sdk.set_user({'username': '<public>'})
|
||||||
|
return old_call_function(self, *args, **kwargs)
|
||||||
|
WebRequest._call_function = _call_function
|
||||||
|
|
||||||
|
# create transaction for tracking crons, add user to that
|
||||||
|
old_callback = ir_cron._callback
|
||||||
|
def _callback(self, cron_name, server_action_id, job_id):
|
||||||
|
sentry_sdk.start_transaction(name=f"cron {cron_name}")
|
||||||
|
sentry_sdk.set_user({
|
||||||
|
'id': self.env.user.id,
|
||||||
|
'email': self.env.user.email,
|
||||||
|
'username': self.env.user.login,
|
||||||
|
})
|
||||||
|
return old_callback(self, cron_name, server_action_id, job_id)
|
||||||
|
ir_cron._callback = _callback
|
||||||
|
|
||||||
dummy_record = logging.LogRecord(name="", level=logging.NOTSET, pathname='', lineno=0, msg='', args=(), exc_info=None)
|
dummy_record = logging.LogRecord(name="", level=logging.NOTSET, pathname='', lineno=0, msg='', args=(), exc_info=None)
|
||||||
# mapping of exception types to predicates, if the predicate returns `True` the
|
# mapping of exception types to predicates, if the predicate returns `True` the
|
||||||
|
Loading…
Reference in New Issue
Block a user