Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The sentry integration."""
2 
3 from __future__ import annotations
4 
5 import re
6 from types import MappingProxyType
7 from typing import Any
8 
9 import sentry_sdk
10 from sentry_sdk.integrations.aiohttp import AioHttpIntegration
11 from sentry_sdk.integrations.logging import LoggingIntegration
12 from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
13 
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.const import (
16  EVENT_HOMEASSISTANT_STARTED,
17  __version__ as current_version,
18 )
19 from homeassistant.core import HomeAssistant, get_release_channel
20 from homeassistant.helpers import entity_platform, instance_id
21 from homeassistant.helpers.event import async_call_later
22 from homeassistant.helpers.system_info import async_get_system_info
23 from homeassistant.loader import Integration, async_get_custom_components
24 from homeassistant.setup import SetupPhases, async_pause_setup
25 
26 from .const import (
27  CONF_DSN,
28  CONF_ENVIRONMENT,
29  CONF_EVENT_CUSTOM_COMPONENTS,
30  CONF_EVENT_HANDLED,
31  CONF_EVENT_THIRD_PARTY_PACKAGES,
32  CONF_LOGGING_EVENT_LEVEL,
33  CONF_LOGGING_LEVEL,
34  CONF_TRACING,
35  CONF_TRACING_SAMPLE_RATE,
36  DEFAULT_LOGGING_EVENT_LEVEL,
37  DEFAULT_LOGGING_LEVEL,
38  DEFAULT_TRACING_SAMPLE_RATE,
39  ENTITY_COMPONENTS,
40 )
41 
42 LOGGER_INFO_REGEX = re.compile(r"^(\w+)\.?(\w+)?\.?(\w+)?\.?(\w+)?(?:\..*)?$")
43 
44 
45 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
46  """Set up Sentry from a config entry."""
47 
48  # Migrate environment from config entry data to config entry options
49  if (
50  CONF_ENVIRONMENT not in entry.options
51  and CONF_ENVIRONMENT in entry.data
52  and entry.data[CONF_ENVIRONMENT]
53  ):
54  options = {**entry.options, CONF_ENVIRONMENT: entry.data[CONF_ENVIRONMENT]}
55  data = entry.data.copy()
56  data.pop(CONF_ENVIRONMENT)
57  hass.config_entries.async_update_entry(entry, data=data, options=options)
58 
59  # https://docs.sentry.io/platforms/python/logging/
60  sentry_logging = LoggingIntegration(
61  level=entry.options.get(CONF_LOGGING_LEVEL, DEFAULT_LOGGING_LEVEL),
62  event_level=entry.options.get(
63  CONF_LOGGING_EVENT_LEVEL, DEFAULT_LOGGING_EVENT_LEVEL
64  ),
65  )
66 
67  # Additional/extra data collection
68  channel = get_release_channel()
69  huuid = await instance_id.async_get(hass)
70  system_info = await async_get_system_info(hass)
71  custom_components = await async_get_custom_components(hass)
72 
73  tracing = {}
74  if entry.options.get(CONF_TRACING):
75  tracing = {
76  "traces_sample_rate": entry.options.get(
77  CONF_TRACING_SAMPLE_RATE, DEFAULT_TRACING_SAMPLE_RATE
78  ),
79  }
80 
81  with async_pause_setup(hass, SetupPhases.WAIT_IMPORT_PACKAGES):
82  # sentry_sdk.init imports modules based on the selected integrations
83  def _init_sdk():
84  """Initialize the Sentry SDK."""
85  sentry_sdk.init(
86  dsn=entry.data[CONF_DSN],
87  environment=entry.options.get(CONF_ENVIRONMENT),
88  integrations=[
89  sentry_logging,
90  AioHttpIntegration(),
91  SqlalchemyIntegration(),
92  ],
93  release=current_version,
94  before_send=lambda event, hint: process_before_send(
95  hass,
96  entry.options,
97  channel,
98  huuid,
99  system_info,
100  custom_components,
101  event,
102  hint,
103  ),
104  **tracing,
105  )
106 
107  await hass.async_add_import_executor_job(_init_sdk)
108 
109  async def update_system_info(now):
110  nonlocal system_info
111  system_info = await async_get_system_info(hass)
112 
113  # Update system info every hour
114  async_call_later(hass, 3600, update_system_info)
115 
116  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, update_system_info)
117 
118  return True
119 
120 
122  hass: HomeAssistant,
123  options: MappingProxyType[str, Any],
124  channel: str,
125  huuid: str,
126  system_info: dict[str, bool | str],
127  custom_components: dict[str, Integration],
128  event: dict[str, Any],
129  hint: dict[str, Any],
130 ):
131  """Process a Sentry event before sending it to Sentry."""
132  # Filter out handled events by default
133  if (
134  "tags" in event
135  and event["tags"].get("handled", "no") == "yes"
136  and not options.get(CONF_EVENT_HANDLED)
137  ):
138  return None
139 
140  # Additional tags to add to the event
141  additional_tags = {
142  "channel": channel,
143  "installation_type": system_info["installation_type"],
144  "uuid": huuid,
145  }
146 
147  # Find out all integrations in use, filter "auth", because it
148  # triggers security rules, hiding all data.
149  integrations = [
150  integration
151  for integration in hass.config.components
152  if integration != "auth" and "." not in integration
153  ]
154 
155  # Add additional tags based on what caused the event.
156  if (platform := entity_platform.current_platform.get()) is not None:
157  # This event happened in a platform
158  additional_tags["custom_component"] = "no"
159  additional_tags["integration"] = platform.platform_name
160  additional_tags["platform"] = platform.domain
161  elif "logger" in event:
162  # Logger event, try to get integration information from the logger name.
163  matches = LOGGER_INFO_REGEX.findall(event["logger"])
164  if matches:
165  group1, group2, group3, group4 = matches[0]
166  # Handle the "homeassistant." package differently
167  if group1 == "homeassistant" and group2 and group3:
168  if group2 == "components":
169  # This logger is from a component
170  additional_tags["custom_component"] = "no"
171  additional_tags["integration"] = group3
172  if group4 and group4 in ENTITY_COMPONENTS:
173  additional_tags["platform"] = group4
174  else:
175  # Not a component, could be helper, or something else.
176  additional_tags[group2] = group3
177  else:
178  # Not the "homeassistant" package, this third-party
179  if not options.get(CONF_EVENT_THIRD_PARTY_PACKAGES):
180  return None
181  additional_tags["package"] = group1
182 
183  # If this event is caused by an integration, add a tag if this
184  # integration is custom or not.
185  if (
186  "integration" in additional_tags
187  and additional_tags["integration"] in custom_components
188  ):
189  if not options.get(CONF_EVENT_CUSTOM_COMPONENTS):
190  return None
191  additional_tags["custom_component"] = "yes"
192 
193  # Update event with the additional tags
194  event.setdefault("tags", {}).update(additional_tags)
195 
196  # Set user context to the installation UUID
197  event.setdefault("user", {}).update({"id": huuid})
198 
199  # Update event data with Home Assistant Context
200  event.setdefault("contexts", {}).update(
201  {
202  "Home Assistant": {
203  "channel": channel,
204  "custom_components": "\n".join(sorted(custom_components)),
205  "integrations": "\n".join(sorted(integrations)),
206  **system_info,
207  },
208  }
209  )
210  return event
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
def process_before_send(HomeAssistant hass, MappingProxyType[str, Any] options, str channel, str huuid, dict[str, bool|str] system_info, dict[str, Integration] custom_components, dict[str, Any] event, dict[str, Any] hint)
Definition: __init__.py:130
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:45
ReleaseChannel get_release_channel()
Definition: core.py:315
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597
dict[str, Any] async_get_system_info(HomeAssistant hass)
Definition: system_info.py:44
dict[str, Integration] async_get_custom_components(HomeAssistant hass)
Definition: loader.py:317
Generator[None] async_pause_setup(core.HomeAssistant hass, SetupPhases phase)
Definition: setup.py:691