Files
StarPilot/openpilot/system/sentry.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

160 lines
5.8 KiB
Python

"""Exception reporting through the packaged Sentry SDK."""
import datetime
import glob
import importlib
import os
import sys
import traceback
from enum import Enum
from pathlib import Path
from types import ModuleType
from openpilot.common.basedir import BASEDIR
from openpilot.common.hardware import HARDWARE, PC
from openpilot.common.hardware.hw import Paths
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.common.version import get_build_metadata
from openpilot.system.crash_storage import writer, write_text
class SentryProject(Enum):
SELFDRIVE = 'https://7305139359a548fcb348ec09497dc389@bugsink.firestar.link/1'
SELFDRIVE_NATIVE = SELFDRIVE
EXIT_FLUSH_TIMEOUT = 0.5
HTTP_TIMEOUT = 2
TRANSPORT_QUEUE_SIZE = 32
sdk: ModuleType | None = None
_initialized_pid: int | None = None
def _load_sdk() -> ModuleType:
# Device prebuilt launches do not install pyproject dependencies. This complete
# MIT package is tracked and included by the ordinary Git release packager.
vendor = str(Path(BASEDIR) / "sentry_sdk_repo")
if vendor not in sys.path:
sys.path.insert(0, vendor)
return importlib.import_module("sentry_sdk")
def init(project: SentryProject = SentryProject.SELFDRIVE) -> bool:
global sdk, _initialized_pid
if PC:
return False
fresh = _initialized_pid != os.getpid()
try:
if fresh:
sdk = _load_sdk()
transport_module = importlib.import_module("sentry_sdk.transport")
threading_module = importlib.import_module("sentry_sdk.integrations.threading")
class BoundedHttpTransport(transport_module.HttpTransport):
TIMEOUT = HTTP_TIMEOUT
# Explicit capture and the threading integration own errors. Enabling the
# logging integration would report cloudlog.exception a second time.
sdk.init(dsn=project.value, default_integrations=False, auto_enabling_integrations=False,
integrations=[threading_module.ThreadingIntegration(propagate_scope=True)],
transport=BoundedHttpTransport, transport_queue_size=TRANSPORT_QUEUE_SIZE,
shutdown_timeout=EXIT_FLUSH_TIMEOUT, enable_backpressure_handling=False,
max_value_length=8192, traces_sample_rate=0.0,
release=os.getenv("GIT_COMMIT", "unknown"), environment="Release")
_initialized_pid = os.getpid()
if sdk is None:
return False
except Exception:
cloudlog.warning("Crash reporting unavailable; continuing without remote reporting")
sdk = None
_initialized_pid = None
return False
# Configure transport before reading metadata: a failed manager_init must be
# reportable even before registration/logmessaged. Refresh after registration
# and after fork, using the existing typed Params and current build metadata.
try:
build = get_build_metadata()
params = Params()
sdk.get_client().options.update(release=build.canonical,
environment="Testing" if "test" in build.channel.lower() else "Release")
sdk.set_user({"id": params.get("DongleId") or os.getenv("DONGLE_ID", "unregistered")})
for key, value in {"origin": build.openpilot.git_origin, "branch": build.channel,
"commit": build.openpilot.git_commit, "dirty": build.openpilot.is_dirty,
"installed": params.get("InstallDate"), "updated": params.get("LastUpdateTime"),
"device": HARDWARE.get_device_type()}.items():
sdk.set_tag(key, str(value) if value is not None else "unknown")
except Exception:
cloudlog.warning("Crash reporting metadata unavailable")
if fresh:
try:
sdk.start_session()
except Exception:
cloudlog.warning("Crash reporting session unavailable")
return True
def set_tag(key: str, value: str) -> None:
if sdk is not None:
try:
sdk.set_tag(key, value)
except Exception:
cloudlog.warning("Crash reporting tag unavailable")
def _attach_qlog(scope) -> None:
try:
qlogs = [path for name in ("qlog.zst", "qlog.bz2", "qlog")
for path in glob.glob(f"{Paths.log_root()}/*/{name}")]
if qlogs:
scope.add_attachment(path=max(qlogs, key=os.path.getmtime))
except Exception:
cloudlog.warning("Crash log attachment unavailable")
def save_exception(text: str) -> None:
path = Path(Paths.log_root()) / "crash"
stamp = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S-%f")
with writer(path) as directory:
write_text(directory, f"{stamp}.log", text)
write_text(directory, "error.txt", "\n".join(text.splitlines()[-10:]))
def capture_exception(exc: BaseException | None = None, *, crash_log: bool = True) -> None:
text = "".join(traceback.format_exception(exc)) if exc is not None else traceback.format_exc()
if any(message in text for message in ("already exists. To overwrite it, set 'overwrite' to True", "failed after retry")):
return
if crash_log:
try:
save_exception(text)
except Exception:
cloudlog.warning("Local crash report could not be saved")
if sdk is not None:
try:
with sdk.new_scope() as scope:
_attach_qlog(scope)
sdk.capture_exception(exc)
except Exception:
cloudlog.warning("Crash report could not be queued")
def report_tombstone(fn: str, message: str, contents: str) -> None:
if sdk is not None:
try:
with sdk.new_scope() as scope:
scope.set_extra("tombstone_fn", fn)
scope.set_extra("tombstone", contents)
_attach_qlog(scope)
sdk.capture_message(message, level="error")
except Exception:
cloudlog.warning("Native crash report could not be queued")
def flush() -> None:
"""Only call on fatal process exit; ordinary captures never wait for HTTP."""
if sdk is not None:
try:
sdk.flush(timeout=EXIT_FLUSH_TIMEOUT)
except Exception:
cloudlog.warning("Crash report flush interrupted")