From 0ad559adcd27dff78efedafd7303052e78d0bd2d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 27 Feb 2025 15:45:11 +0100 Subject: [PATCH] Add more context to Sentry reports early during startup (#5682) * Initialize machine information before Sentry * Set user and machine for all reports Now that we initialize machine earlier we can report user and machine for all events, even before Supervisor is completely initialized. Also use the new tag format which is a dictionary. Note that it seems that with the current Sentry SDK version the AioHttpIntegration no longer sets the URL as a tag. So sanitation is no longer reuqired. * Update pytests --- supervisor/bootstrap.py | 18 +++--------------- supervisor/coresys.py | 28 +++++++++++++++++++++++++++- supervisor/misc/filter.py | 22 ++++++++++------------ tests/misc/test_filter_data.py | 5 +---- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index fb419dc02..766f3d53e 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -18,7 +18,6 @@ from .const import ( ENV_SUPERVISOR_MACHINE, ENV_SUPERVISOR_NAME, ENV_SUPERVISOR_SHARE, - MACHINE_ID, SOCKET_DOCKER, LogLevel, UpdateChannel, @@ -81,6 +80,9 @@ async def initialize_coresys() -> CoreSys: coresys.bus = Bus(coresys) coresys.mounts = await MountManager(coresys).load_config() + # Set Machine/Host ID + await coresys.init_machine() + # diagnostics if coresys.config.diagnostics: init_sentry(coresys) @@ -88,10 +90,6 @@ async def initialize_coresys() -> CoreSys: # bootstrap config initialize_system(coresys) - # Set Machine/Host ID - if MACHINE_ID.exists(): - coresys.machine_id = MACHINE_ID.read_text(encoding="utf-8").strip() - # Check if ENV is in development mode if coresys.dev: _LOGGER.warning("Environment variable 'SUPERVISOR_DEV' is set") @@ -105,16 +103,6 @@ async def initialize_coresys() -> CoreSys: # Convert datetime logging.Formatter.converter = lambda *args: coresys.now().timetuple() - # Set machine type - if os.environ.get(ENV_SUPERVISOR_MACHINE): - coresys.machine = os.environ[ENV_SUPERVISOR_MACHINE] - elif os.environ.get(ENV_HOMEASSISTANT_REPOSITORY): - coresys.machine = os.environ[ENV_HOMEASSISTANT_REPOSITORY][14:-14] - _LOGGER.warning( - "Missing SUPERVISOR_MACHINE environment variable. Fallback to deprecated extraction!" - ) - _LOGGER.info("Setting up coresys for machine: %s", coresys.machine) - return coresys diff --git a/supervisor/coresys.py b/supervisor/coresys.py index 90a76adba..e525fc4b1 100644 --- a/supervisor/coresys.py +++ b/supervisor/coresys.py @@ -15,7 +15,13 @@ from typing import TYPE_CHECKING, Any, Self, TypeVar import aiohttp from .config import CoreConfig -from .const import ENV_SUPERVISOR_DEV, SERVER_SOFTWARE +from .const import ( + ENV_HOMEASSISTANT_REPOSITORY, + ENV_SUPERVISOR_DEV, + ENV_SUPERVISOR_MACHINE, + MACHINE_ID, + SERVER_SOFTWARE, +) from .utils.dt import UTC, get_time_zone if TYPE_CHECKING: @@ -107,6 +113,26 @@ class CoreSys: await self.config.read_data() return self + async def init_machine(self): + """Initialize machine information.""" + + def _load_machine_id() -> str | None: + if MACHINE_ID.exists(): + return MACHINE_ID.read_text(encoding="utf-8").strip() + return None + + self.machine_id = await self.run_in_executor(_load_machine_id) + + # Set machine type + if os.environ.get(ENV_SUPERVISOR_MACHINE): + self.machine = os.environ[ENV_SUPERVISOR_MACHINE] + elif os.environ.get(ENV_HOMEASSISTANT_REPOSITORY): + self.machine = os.environ[ENV_HOMEASSISTANT_REPOSITORY][14:-14] + _LOGGER.warning( + "Missing SUPERVISOR_MACHINE environment variable. Fallback to deprecated extraction!" + ) + _LOGGER.info("Setting up coresys for machine: %s", self.machine) + @property def dev(self) -> bool: """Return True if we run dev mode.""" diff --git a/supervisor/misc/filter.py b/supervisor/misc/filter.py index cf04e06d1..3e5e94421 100644 --- a/supervisor/misc/filter.py +++ b/supervisor/misc/filter.py @@ -35,6 +35,12 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict: return None event.setdefault("extra", {}).update({"os.environ": dict(os.environ)}) + event.setdefault("user", {}).update({"id": coresys.machine_id}) + event.setdefault("tags", {}).update( + { + "machine": coresys.machine, + } + ) # Not full startup - missing information if coresys.core.state in (CoreState.INITIALIZE, CoreState.SETUP): @@ -47,7 +53,6 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict: ] # Update information - event.setdefault("user", {}).update({"id": coresys.machine_id}) event.setdefault("contexts", {}).update( { "supervisor": { @@ -92,19 +97,12 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict: {plugin.slug: plugin.version for plugin in coresys.plugins.all_plugins} ) - event.setdefault("tags", []).extend( - [ - ["installation_type", "os" if coresys.os.available else "supervised"], - ["machine", coresys.machine], - ], + event["tags"].update( + { + "installation_type": "os" if coresys.os.available else "supervised", + } ) - # Sanitize event - for i, tag in enumerate(event.get("tags", [])): - key, value = tag - if key == "url": - event["tags"][i] = [key, sanitize_url(value)] - if event.get("request"): if event["request"].get("url"): event["request"]["url"] = sanitize_url(event["request"]["url"]) diff --git a/tests/misc/test_filter_data.py b/tests/misc/test_filter_data.py index 5cb8e7695..299565abb 100644 --- a/tests/misc/test_filter_data.py +++ b/tests/misc/test_filter_data.py @@ -72,7 +72,7 @@ def test_defaults(coresys): with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))): filtered = filter_data(coresys, SAMPLE_EVENT, {}) - assert ["installation_type", "supervised"] in filtered["tags"] + assert filtered["tags"]["installation_type"] == "supervised" assert filtered["contexts"]["host"]["arch"] == "amd64" assert filtered["contexts"]["host"]["machine"] == "qemux86-64" assert filtered["contexts"]["versions"]["supervisor"] == AwesomeVersion( @@ -84,7 +84,6 @@ def test_defaults(coresys): def test_sanitize(coresys): """Test event sanitation.""" event = { - "tags": [["url", "https://mydomain.com"]], "request": { "url": "https://mydomain.com", "headers": [ @@ -101,8 +100,6 @@ def test_sanitize(coresys): with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))): filtered = filter_data(coresys, event, {}) - assert ["url", "https://example.com"] in filtered["tags"] - assert filtered["request"]["url"] == "https://example.com" assert ["Host", "example.com"] in filtered["request"]["headers"]