Improve mqtt value template error logging (#110492)

* Refactor mqtt value template error logging

* Remove import
This commit is contained in:
Jan Bouwhuis 2024-03-04 08:49:12 +01:00 committed by GitHub
parent 5227976aa2
commit c13231fc00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 81 additions and 45 deletions

View file

@ -29,7 +29,6 @@ from .const import (
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
PAYLOAD_EMPTY_JSON, PAYLOAD_EMPTY_JSON,
PAYLOAD_NONE, PAYLOAD_NONE,
TEMPLATE_ERRORS,
) )
from .debug_info import log_messages from .debug_info import log_messages
from .mixins import ( from .mixins import (
@ -39,6 +38,7 @@ from .mixins import (
) )
from .models import ( from .models import (
MqttValueTemplate, MqttValueTemplate,
MqttValueTemplateException,
PayloadSentinel, PayloadSentinel,
ReceiveMessage, ReceiveMessage,
ReceivePayloadType, ReceivePayloadType,
@ -134,7 +134,8 @@ class MqttEvent(MqttEntity, EventEntity):
event_type: str event_type: str
try: try:
payload = self._template(msg.payload, PayloadSentinel.DEFAULT) payload = self._template(msg.payload, PayloadSentinel.DEFAULT)
except TEMPLATE_ERRORS: except MqttValueTemplateException as exc:
_LOGGER.warning(exc)
return return
if ( if (
not payload not payload

View file

@ -24,14 +24,19 @@ from homeassistant.util import dt as dt_util
from . import subscription from . import subscription
from .config import MQTT_BASE_SCHEMA from .config import MQTT_BASE_SCHEMA
from .const import CONF_ENCODING, CONF_QOS, TEMPLATE_ERRORS from .const import CONF_ENCODING, CONF_QOS
from .debug_info import log_messages from .debug_info import log_messages
from .mixins import ( from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA, MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity, MqttEntity,
async_setup_entity_entry_helper, async_setup_entity_entry_helper,
) )
from .models import MessageCallbackType, MqttValueTemplate, ReceiveMessage from .models import (
MessageCallbackType,
MqttValueTemplate,
MqttValueTemplateException,
ReceiveMessage,
)
from .util import get_mqtt_data, valid_subscribe_topic from .util import get_mqtt_data, valid_subscribe_topic
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -191,7 +196,8 @@ class MqttImage(MqttEntity, ImageEntity):
try: try:
url = cv.url(self._url_template(msg.payload)) url = cv.url(self._url_template(msg.payload))
self._attr_image_url = url self._attr_image_url = url
except TEMPLATE_ERRORS: except MqttValueTemplateException as exc:
_LOGGER.warning(exc)
return return
except vol.Invalid: except vol.Invalid:
_LOGGER.error( _LOGGER.error(

View file

@ -94,7 +94,6 @@ from .const import (
DOMAIN, DOMAIN,
MQTT_CONNECTED, MQTT_CONNECTED,
MQTT_DISCONNECTED, MQTT_DISCONNECTED,
TEMPLATE_ERRORS,
) )
from .debug_info import log_message, log_messages from .debug_info import log_message, log_messages
from .discovery import ( from .discovery import (
@ -109,6 +108,7 @@ from .discovery import (
from .models import ( from .models import (
MessageCallbackType, MessageCallbackType,
MqttValueTemplate, MqttValueTemplate,
MqttValueTemplateException,
PublishPayloadType, PublishPayloadType,
ReceiveMessage, ReceiveMessage,
) )
@ -482,7 +482,8 @@ def write_state_on_attr_change(
} }
try: try:
msg_callback(msg) msg_callback(msg)
except TEMPLATE_ERRORS: except MqttValueTemplateException as exc:
_LOGGER.warning(exc)
return return
if not _attrs_have_changed(tracked_attrs): if not _attrs_have_changed(tracked_attrs):
return return

View file

@ -223,6 +223,36 @@ class MqttCommandTemplate:
) from exc ) from exc
class MqttValueTemplateException(TemplateError):
"""Handle MqttValueTemplate exceptions."""
def __init__(
self,
*args: object,
base_exception: Exception,
value_template: str,
default: ReceivePayloadType | PayloadSentinel,
payload: ReceivePayloadType,
entity_id: str | None = None,
) -> None:
"""Initialize exception."""
super().__init__(base_exception, *args)
entity_id_log = "" if entity_id is None else f" for entity '{entity_id}'"
default_log = str(default)
default_payload_log = (
"" if default is PayloadSentinel.NONE else f", default value: {default_log}"
)
payload_log = str(payload)
self._message = (
f"{type(base_exception).__name__}: {base_exception} rendering template{entity_id_log}"
f", template: '{value_template}'{default_payload_log} and payload: {payload_log}"
)
def __str__(self) -> str:
"""Return exception message string."""
return self._message
class MqttValueTemplate: class MqttValueTemplate:
"""Class for rendering MQTT value template with possible json values.""" """Class for rendering MQTT value template with possible json values."""
@ -291,14 +321,13 @@ class MqttValueTemplate:
) )
) )
except TEMPLATE_ERRORS as exc: except TEMPLATE_ERRORS as exc:
_LOGGER.error( raise MqttValueTemplateException(
"%s: %s rendering template for entity '%s', template: '%s'", base_exception=exc,
type(exc).__name__, value_template=self._value_template.template,
exc, default=default,
self._entity.entity_id if self._entity else "n/a", payload=payload,
self._value_template.template, entity_id=self._entity.entity_id if self._entity else None,
) ) from exc
raise
return rendered_payload return rendered_payload
_LOGGER.debug( _LOGGER.debug(
@ -318,17 +347,13 @@ class MqttValueTemplate:
) )
) )
except TEMPLATE_ERRORS as exc: except TEMPLATE_ERRORS as exc:
_LOGGER.error( raise MqttValueTemplateException(
"%s: %s rendering template for entity '%s', template: " base_exception=exc,
"'%s', default value: %s and payload: %s", value_template=self._value_template.template,
type(exc).__name__, default=default,
exc, payload=payload,
self._entity.entity_id if self._entity else "n/a", entity_id=self._entity.entity_id if self._entity else None,
self._value_template.template, ) from exc
default,
payload,
)
raise
return rendered_payload return rendered_payload

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
import functools import functools
import logging
import voluptuous as vol import voluptuous as vol
@ -15,7 +16,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import subscription from . import subscription
from .config import MQTT_BASE_SCHEMA from .config import MQTT_BASE_SCHEMA
from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC, TEMPLATE_ERRORS from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC
from .discovery import MQTTDiscoveryPayload from .discovery import MQTTDiscoveryPayload
from .mixins import ( from .mixins import (
MQTT_ENTITY_DEVICE_INFO_SCHEMA, MQTT_ENTITY_DEVICE_INFO_SCHEMA,
@ -25,10 +26,17 @@ from .mixins import (
send_discovery_done, send_discovery_done,
update_device, update_device,
) )
from .models import MqttValueTemplate, ReceiveMessage, ReceivePayloadType from .models import (
MqttValueTemplate,
MqttValueTemplateException,
ReceiveMessage,
ReceivePayloadType,
)
from .subscription import EntitySubscription from .subscription import EntitySubscription
from .util import get_mqtt_data, valid_subscribe_topic from .util import get_mqtt_data, valid_subscribe_topic
_LOGGER = logging.getLogger(__name__)
LOG_NAME = "Tag" LOG_NAME = "Tag"
TAG = "tag" TAG = "tag"
@ -138,7 +146,8 @@ class MQTTTagScanner(MqttDiscoveryDeviceUpdate):
async def tag_scanned(msg: ReceiveMessage) -> None: async def tag_scanned(msg: ReceiveMessage) -> None:
try: try:
tag_id = str(self._value_template(msg.payload, "")).strip() tag_id = str(self._value_template(msg.payload, "")).strip()
except TEMPLATE_ERRORS: except MqttValueTemplateException as exc:
_LOGGER.warning(exc)
return return
if not tag_id: # No output from template, ignore if not tag_id: # No output from template, ignore
return return

View file

@ -19,6 +19,7 @@ from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
from homeassistant.components.mqtt.models import ( from homeassistant.components.mqtt.models import (
MessageCallbackType, MessageCallbackType,
MqttCommandTemplateException, MqttCommandTemplateException,
MqttValueTemplateException,
ReceiveMessage, ReceiveMessage,
) )
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
@ -433,37 +434,30 @@ async def test_value_template_value(hass: HomeAssistant) -> None:
assert template_state_calls.call_count == 1 assert template_state_calls.call_count == 1
async def test_value_template_fails( async def test_value_template_fails(hass: HomeAssistant) -> None:
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test the rendering of MQTT value template fails.""" """Test the rendering of MQTT value template fails."""
# test rendering a value fails
entity = MockEntity(entity_id="sensor.test") entity = MockEntity(entity_id="sensor.test")
entity.hass = hass entity.hass = hass
tpl = template.Template("{{ value_json.some_var * 2 }}") tpl = template.Template("{{ value_json.some_var * 2 }}")
val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass, entity=entity) val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass, entity=entity)
with pytest.raises(TypeError) as exc: with pytest.raises(MqttValueTemplateException) as exc:
val_tpl.async_render_with_possible_json_value('{"some_var": null }') val_tpl.async_render_with_possible_json_value('{"some_var": null }')
assert str(exc.value) == "unsupported operand type(s) for *: 'NoneType' and 'int'" assert str(exc.value) == (
assert (
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' " "TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' "
"rendering template for entity 'sensor.test', " "rendering template for entity 'sensor.test', "
"template: '{{ value_json.some_var * 2 }}' " "template: '{{ value_json.some_var * 2 }}' "
) in caplog.text 'and payload: {"some_var": null }'
caplog.clear() )
with pytest.raises(TypeError) as exc: with pytest.raises(MqttValueTemplateException) as exc:
val_tpl.async_render_with_possible_json_value( val_tpl.async_render_with_possible_json_value(
'{"some_var": null }', default=100 '{"some_var": null }', default=100
) )
assert str(exc.value) == "unsupported operand type(s) for *: 'NoneType' and 'int'" assert str(exc.value) == (
assert (
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' " "TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' "
"rendering template for entity 'sensor.test', " "rendering template for entity 'sensor.test', "
"template: '{{ value_json.some_var * 2 }}', default value: 100 and payload: " "template: '{{ value_json.some_var * 2 }}', default value: 100 and payload: "
'{"some_var": null }' '{"some_var": null }'
) in caplog.text )
await hass.async_block_till_done()
async def test_service_call_without_topic_does_not_publish( async def test_service_call_without_topic_does_not_publish(