127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
|
"""Provide info to system health."""
|
||
|
import logging
|
||
|
import re
|
||
|
import traceback
|
||
|
import uuid
|
||
|
from collections import deque
|
||
|
from datetime import datetime
|
||
|
from logging import Logger
|
||
|
|
||
|
from aiohttp import web
|
||
|
from homeassistant.components import system_health
|
||
|
from homeassistant.components.http import HomeAssistantView
|
||
|
from homeassistant.core import HomeAssistant, callback
|
||
|
|
||
|
from .core.const import DOMAIN, PRIVATE_KEYS, source_hash
|
||
|
|
||
|
|
||
|
@callback
|
||
|
def async_register(
|
||
|
hass: HomeAssistant, register: system_health.SystemHealthRegistration
|
||
|
) -> None:
|
||
|
register.async_register_info(system_health_info)
|
||
|
|
||
|
|
||
|
async def system_health_info(hass: HomeAssistant):
|
||
|
cloud_online = local_online = cloud_total = local_total = 0
|
||
|
|
||
|
for registry in hass.data[DOMAIN].values():
|
||
|
for device in registry.devices.values():
|
||
|
if "online" in device:
|
||
|
cloud_total += 1
|
||
|
if registry.cloud.online and device["online"]:
|
||
|
cloud_online += 1
|
||
|
# localtype - all discovered local devices
|
||
|
# host - all online local devices (maybe encrypted)
|
||
|
# params - all local unencrypted devices
|
||
|
if "localtype" in device:
|
||
|
local_total += 1
|
||
|
if "host" in device and "params" in device:
|
||
|
local_online += 1
|
||
|
|
||
|
integration = hass.data["integrations"][DOMAIN]
|
||
|
info = {
|
||
|
"version": f"{integration.version} ({source_hash()})",
|
||
|
"cloud_online": f"{cloud_online} / {cloud_total}",
|
||
|
"local_online": f"{local_online} / {local_total}",
|
||
|
}
|
||
|
|
||
|
if DebugView.url:
|
||
|
info["debug"] = {"type": "failed", "error": "", "more_info": DebugView.url}
|
||
|
|
||
|
return info
|
||
|
|
||
|
|
||
|
async def setup_debug(hass: HomeAssistant, logger: Logger):
|
||
|
view = DebugView(logger)
|
||
|
hass.http.register_view(view)
|
||
|
|
||
|
integration = hass.data["integrations"][DOMAIN]
|
||
|
info = await hass.helpers.system_info.async_get_system_info()
|
||
|
info[DOMAIN + "_version"] = f"{integration.version} ({source_hash()})"
|
||
|
logger.debug(f"SysInfo: {info}")
|
||
|
|
||
|
integration.manifest["issue_tracker"] = view.url
|
||
|
|
||
|
|
||
|
class DebugView(logging.Handler, HomeAssistantView):
|
||
|
"""Class generate web page with component debug logs."""
|
||
|
|
||
|
name = DOMAIN
|
||
|
requires_auth = False
|
||
|
|
||
|
def __init__(self, logger: Logger):
|
||
|
super().__init__()
|
||
|
|
||
|
# https://waymoot.org/home/python_string/
|
||
|
self.text = deque(maxlen=10000)
|
||
|
|
||
|
self.propagate_level = logger.getEffectiveLevel()
|
||
|
|
||
|
# random url because without authorization!!!
|
||
|
DebugView.url = f"/api/{DOMAIN}/{uuid.uuid4()}"
|
||
|
|
||
|
logger.addHandler(self)
|
||
|
logger.setLevel(logging.DEBUG)
|
||
|
|
||
|
def handle(self, rec: logging.LogRecord):
|
||
|
if isinstance(rec.args, dict):
|
||
|
rec.msg = rec.msg % {
|
||
|
k: v for k, v in rec.args.items() if k not in PRIVATE_KEYS
|
||
|
}
|
||
|
dt = datetime.fromtimestamp(rec.created).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
msg = f"{dt} [{rec.levelname[0]}] {rec.msg}"
|
||
|
if rec.exc_info:
|
||
|
exc = traceback.format_exception(*rec.exc_info, limit=1)
|
||
|
msg += "|" + "".join(exc[-2:]).replace("\n", "|")
|
||
|
self.text.append(msg)
|
||
|
|
||
|
# prevent debug to Hass log if user don't want it
|
||
|
if self.propagate_level > rec.levelno:
|
||
|
rec.levelno = -1
|
||
|
|
||
|
async def get(self, request: web.Request):
|
||
|
try:
|
||
|
lines = self.text
|
||
|
|
||
|
if "q" in request.query:
|
||
|
reg = re.compile(rf"({request.query['q']})", re.IGNORECASE)
|
||
|
lines = [p for p in lines if reg.search(p)]
|
||
|
|
||
|
if "t" in request.query:
|
||
|
tail = int(request.query["t"])
|
||
|
lines = lines[-tail:]
|
||
|
|
||
|
body = "\n".join(lines)
|
||
|
r = request.query.get("r", "")
|
||
|
|
||
|
return web.Response(
|
||
|
text="<!DOCTYPE html><html>"
|
||
|
f'<head><meta http-equiv="refresh" content="{r}"></head>'
|
||
|
f"<body><pre>{body}</pre></body>"
|
||
|
"</html>",
|
||
|
content_type="text/html",
|
||
|
)
|
||
|
except Exception:
|
||
|
return web.Response(status=500)
|