Replaced disable_logging with a more generic logging_level.

The `disable_logging` attribute was only available on events and
responses, and it could only either entirely disable or enable logging
for all the events of a certain type.

The new flag allows more customization by setting the default logging
level used for any message of a certain type (or `None` to disable
logging). This makes it possible to e.g. set some verbose events to
debug level, and the user can see them if they configure the application
in debug mode.

It also delegates the logging logic to the message itself, instead of
having different parts of the application handling their own logic.
This commit is contained in:
Fabio Manganiello 2022-12-11 11:39:38 +01:00
parent aa3479abeb
commit 2ee2a1d7b5
Signed by: blacklight
GPG key ID: D90FBA7F76362774
8 changed files with 180 additions and 77 deletions

View file

@ -213,10 +213,9 @@ class Daemon:
): ):
self.stop_app() self.stop_app()
elif isinstance(msg, Response): elif isinstance(msg, Response):
logger.info('Received response: {}'.format(msg)) msg.log()
elif isinstance(msg, Event): elif isinstance(msg, Event):
if not msg.disable_logging: msg.log()
logger.info('Received event: {}'.format(msg))
self.event_processor.process_event(msg) self.event_processor.process_event(msg)
return _f return _f

View file

@ -19,7 +19,7 @@ class JSONAble(ABC):
raise NotImplementedError() raise NotImplementedError()
class Message(object): class Message:
""" """
Message generic class Message generic class
""" """
@ -45,9 +45,7 @@ class Message(object):
@staticmethod @staticmethod
def parse_datetime(obj): def parse_datetime(obj):
if isinstance(obj, datetime.datetime) or \ if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
isinstance(obj, datetime.date) or \
isinstance(obj, datetime.time):
return obj.isoformat() return obj.isoformat()
def default(self, obj): def default(self, obj):
@ -68,11 +66,33 @@ class Message(object):
try: try:
return super().default(obj) return super().default(obj)
except Exception as e: except Exception as e:
logger.warning('Could not serialize object type {}: {}: {}'.format( logger.warning(
type(obj), str(e), obj)) 'Could not serialize object type {}: {}: {}'.format(
type(obj), str(e), obj
)
)
def __init__(self, timestamp=None, *_, **__): def __init__(self, *_, timestamp=None, logging_level=logging.INFO, **__):
self.timestamp = timestamp or time.time() self.timestamp = timestamp or time.time()
self.logging_level = logging_level
def log(self, prefix=''):
if self.logging_level is None:
return # Skip logging
log_func = logger.info
if self.logging_level == logging.DEBUG:
log_func = logger.debug
elif self.logging_level == logging.WARNING:
log_func = logger.warning
elif self.logging_level == logging.ERROR:
log_func = logger.error
elif self.logging_level == logging.FATAL:
log_func = logger.fatal
if not prefix:
prefix = f'Received {self.__class__.__name__}: '
log_func(f'{prefix}{self}')
def __str__(self): def __str__(self):
""" """
@ -80,12 +100,15 @@ class Message(object):
the message into a UTF-8 JSON string the message into a UTF-8 JSON string
""" """
return json.dumps({ return json.dumps(
{
attr: getattr(self, attr) attr: getattr(self, attr)
for attr in self.__dir__() for attr in self.__dir__()
if (attr != '_timestamp' or not attr.startswith('_')) if (attr != '_timestamp' or not attr.startswith('_'))
and not inspect.ismethod(getattr(self, attr)) and not inspect.ismethod(getattr(self, attr))
}, cls=self.Encoder).replace('\n', ' ') },
cls=self.Encoder,
).replace('\n', ' ')
def __bytes__(self): def __bytes__(self):
""" """
@ -105,7 +128,7 @@ class Message(object):
if isinstance(msg, cls): if isinstance(msg, cls):
msg = str(msg) msg = str(msg)
if isinstance(msg, bytes) or isinstance(msg, bytearray): if isinstance(msg, (bytes, bytearray)):
msg = msg.decode('utf-8') msg = msg.decode('utf-8')
if isinstance(msg, str): if isinstance(msg, str):
try: try:

View file

@ -1,5 +1,6 @@
import copy import copy
import json import json
import logging
import random import random
import re import re
import sys import sys
@ -11,6 +12,8 @@ from platypush.config import Config
from platypush.message import Message from platypush.message import Message
from platypush.utils import get_event_class_by_type from platypush.utils import get_event_class_by_type
logger = logging.getLogger('platypush')
class Event(Message): class Event(Message):
"""Event message class""" """Event message class"""
@ -26,7 +29,7 @@ class Event(Message):
origin=None, origin=None,
id=None, id=None,
timestamp=None, timestamp=None,
disable_logging=False, logging_level=logging.INFO,
disable_web_clients_notification=False, disable_web_clients_notification=False,
**kwargs **kwargs
): ):
@ -36,15 +39,16 @@ class Event(Message):
origin -- Origin node (default: current node) [String] origin -- Origin node (default: current node) [String]
id -- Event ID (default: auto-generated) id -- Event ID (default: auto-generated)
kwargs -- Additional arguments for the event [kwDict] kwargs -- Additional arguments for the event [kwDict]
logging_level -- Logging level that should be applied to these
events (default: INFO).
""" """
super().__init__(timestamp=timestamp) super().__init__(timestamp=timestamp, logging_level=logging_level)
self.id = id if id else self._generate_id() self.id = id if id else self._generate_id()
self.target = target if target else Config.get('device_id') self.target = target if target else Config.get('device_id')
self.origin = origin if origin else Config.get('device_id') self.origin = origin if origin else Config.get('device_id')
self.type = '{}.{}'.format(self.__class__.__module__, self.__class__.__name__) self.type = '{}.{}'.format(self.__class__.__module__, self.__class__.__name__)
self.args = kwargs self.args = kwargs
self.disable_logging = disable_logging
self.disable_web_clients_notification = disable_web_clients_notification self.disable_web_clients_notification = disable_web_clients_notification
for arg, value in self.args.items(): for arg, value in self.args.items():
@ -55,7 +59,7 @@ class Event(Message):
'target', 'target',
'type', 'type',
'timestamp', 'timestamp',
'disable_logging', 'logging_level',
] and not arg.startswith('_'): ] and not arg.startswith('_'):
self.__setattr__(arg, value) self.__setattr__(arg, value)
@ -152,7 +156,7 @@ class Event(Message):
result.score += 1.5 result.score += 1.5
elif re.search(condition_token, event_token): elif re.search(condition_token, event_token):
m = re.search('({})'.format(condition_token), event_token) m = re.search('({})'.format(condition_token), event_token)
if m.group(1): if m and m.group(1):
event_tokens.pop(0) event_tokens.pop(0)
result.score += 1.25 result.score += 1.25
@ -214,7 +218,7 @@ class EventMatchResult:
the match is - in case of multiple event matches, the ones with the the match is - in case of multiple event matches, the ones with the
highest score will win""" highest score will win"""
def __init__(self, is_match, score=0, parsed_args=None): def __init__(self, is_match, score=0.0, parsed_args=None):
self.is_match = is_match self.is_match = is_match
self.score = score self.score = score
self.parsed_args = parsed_args or {} self.parsed_args = parsed_args or {}

View file

@ -1,8 +1,10 @@
import logging
from platypush.message.event import Event from platypush.message.event import Event
class CameraEvent(Event): class CameraEvent(Event):
""" Base class for camera events """ """Base class for camera events"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -49,13 +51,13 @@ class CameraFrameCapturedEvent(CameraEvent):
Event triggered when a camera frame has been captured Event triggered when a camera frame has been captured
""" """
disable_logging = True
def __init__(self, filename=None, *args, **kwargs): def __init__(self, filename=None, *args, **kwargs):
super().__init__(*args, filename=filename, super().__init__(
disable_logging=kwargs.pop('disable_logging') *args,
if 'disable_logging' in kwargs else self.disable_logging, filename=filename,
**kwargs) logging_level=kwargs.pop('logging_level', logging.DEBUG),
**kwargs
)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -1,3 +1,5 @@
import logging
from platypush.message.event import Event from platypush.message.event import Event
@ -5,9 +7,16 @@ class DistanceSensorEvent(Event):
""" """
Event triggered when a new value is processed by a distance sensor. Event triggered when a new value is processed by a distance sensor.
""" """
def __init__(self, distance: float, unit: str = 'mm', *args, **kwargs): def __init__(self, distance: float, unit: str = 'mm', *args, **kwargs):
super().__init__(*args, distance=distance, unit=unit, disable_logging=True, super().__init__(
disable_web_clients_notification=True, **kwargs) *args,
distance=distance,
unit=unit,
logging_level=logging.DEBUG,
disable_web_clients_notification=True,
**kwargs
)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -1,3 +1,4 @@
import logging
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from platypush.message.event import Event from platypush.message.event import Event
@ -12,7 +13,9 @@ class ZwaveNetworkReadyEvent(ZwaveEvent):
""" """
Triggered when the network started on a Z-Wave adapter becomes ready. Triggered when the network started on a Z-Wave adapter becomes ready.
""" """
def __init__(self,
def __init__(
self,
ozw_library_version: str, ozw_library_version: str,
python_library_version: str, python_library_version: str,
zwave_library: str, zwave_library: str,
@ -21,8 +24,11 @@ class ZwaveNetworkReadyEvent(ZwaveEvent):
home_id: int, home_id: int,
nodes_count: int, nodes_count: int,
device: Optional[str] = None, device: Optional[str] = None,
*args, **kwargs): *args,
super().__init__(*args, **kwargs
):
super().__init__(
*args,
device=device, device=device,
ozw_library_version=ozw_library_version, ozw_library_version=ozw_library_version,
python_library_version=python_library_version, python_library_version=python_library_version,
@ -31,13 +37,15 @@ class ZwaveNetworkReadyEvent(ZwaveEvent):
node_id=node_id, node_id=node_id,
node_version=node_version, node_version=node_version,
nodes_count=nodes_count, nodes_count=nodes_count,
**kwargs) **kwargs
)
class ZwaveNetworkStoppedEvent(ZwaveEvent): class ZwaveNetworkStoppedEvent(ZwaveEvent):
""" """
Triggered when a Z-Wave network is stopped. Triggered when a Z-Wave network is stopped.
""" """
pass pass
@ -45,6 +53,7 @@ class ZwaveNetworkErrorEvent(ZwaveEvent):
""" """
Triggered when an error occurs on the Z-Wave network. Triggered when an error occurs on the Z-Wave network.
""" """
pass pass
@ -52,6 +61,7 @@ class ZwaveNetworkResetEvent(ZwaveEvent):
""" """
Triggered when a Z-Wave network is reset. Triggered when a Z-Wave network is reset.
""" """
pass pass
@ -59,6 +69,7 @@ class ZwaveNodeEvent(ZwaveEvent):
""" """
Generic Z-Wave node event class. Generic Z-Wave node event class.
""" """
def __init__(self, node: Dict[str, Any], *args, **kwargs): def __init__(self, node: Dict[str, Any], *args, **kwargs):
super().__init__(*args, node=node, **kwargs) super().__init__(*args, node=node, **kwargs)
@ -67,6 +78,7 @@ class ZwaveNodeAddedEvent(ZwaveNodeEvent):
""" """
Triggered when a node is added to the network. Triggered when a node is added to the network.
""" """
pass pass
@ -74,6 +86,7 @@ class ZwaveNodeRemovedEvent(ZwaveNodeEvent):
""" """
Triggered when a node is removed from the network. Triggered when a node is removed from the network.
""" """
pass pass
@ -81,6 +94,7 @@ class ZwaveNodeRenamedEvent(ZwaveNodeEvent):
""" """
Triggered when a node is renamed. Triggered when a node is renamed.
""" """
pass pass
@ -88,6 +102,7 @@ class ZwaveNodeReadyEvent(ZwaveNodeEvent):
""" """
Triggered when a node is ready. Triggered when a node is ready.
""" """
pass pass
@ -95,6 +110,7 @@ class ZwaveNodeAsleepEvent(ZwaveNodeEvent):
""" """
Triggered when a node goes in sleep mode. Triggered when a node goes in sleep mode.
""" """
pass pass
@ -102,6 +118,7 @@ class ZwaveNodeAwakeEvent(ZwaveNodeEvent):
""" """
Triggered when a node goes back into awake mode. Triggered when a node goes back into awake mode.
""" """
pass pass
@ -109,8 +126,11 @@ class ZwaveNodeGroupEvent(ZwaveNodeEvent):
""" """
Triggered when a node is associated/de-associated to a group. Triggered when a node is associated/de-associated to a group.
""" """
def __init__(self, group_index: Optional[int] = None, *args, **kwargs): def __init__(self, group_index: Optional[int] = None, *args, **kwargs):
kwargs['disable_logging'] = True # ZwaveNodeGroupEvent can be quite verbose, so we only report them if
# debug logging is enabled
kwargs['logging_level'] = logging.DEBUG
super().__init__(*args, group_index=group_index, **kwargs) super().__init__(*args, group_index=group_index, **kwargs)
@ -118,6 +138,7 @@ class ZwaveNodeSceneEvent(ZwaveNodeEvent):
""" """
Triggered when a scene is activated on a node. Triggered when a scene is activated on a node.
""" """
def __init__(self, scene_id: int, *args, **kwargs): def __init__(self, scene_id: int, *args, **kwargs):
super().__init__(*args, scene_id=scene_id, **kwargs) super().__init__(*args, scene_id=scene_id, **kwargs)
@ -126,6 +147,7 @@ class ZwaveNodePollingEnabledEvent(ZwaveNodeEvent):
""" """
Triggered when the polling of a node is successfully turned on. Triggered when the polling of a node is successfully turned on.
""" """
pass pass
@ -133,6 +155,7 @@ class ZwaveNodePollingDisabledEvent(ZwaveNodeEvent):
""" """
Triggered when the polling of a node is successfully turned off. Triggered when the polling of a node is successfully turned off.
""" """
pass pass
@ -140,6 +163,7 @@ class ZwaveButtonCreatedEvent(ZwaveNodeEvent):
""" """
Triggered when a button is added to the network. Triggered when a button is added to the network.
""" """
pass pass
@ -147,6 +171,7 @@ class ZwaveButtonRemovedEvent(ZwaveNodeEvent):
""" """
Triggered when a button is removed from the network. Triggered when a button is removed from the network.
""" """
pass pass
@ -154,6 +179,7 @@ class ZwaveButtonOnEvent(ZwaveNodeEvent):
""" """
Triggered when a button is pressed. Triggered when a button is pressed.
""" """
pass pass
@ -161,6 +187,7 @@ class ZwaveButtonOffEvent(ZwaveNodeEvent):
""" """
Triggered when a button is released. Triggered when a button is released.
""" """
pass pass
@ -168,8 +195,11 @@ class ZwaveValueEvent(ZwaveEvent):
""" """
Abstract class for Z-Wave value events. Abstract class for Z-Wave value events.
""" """
def __init__(self, node: Dict[str, Any], value: Dict[str, Any], *args, **kwargs): def __init__(self, node: Dict[str, Any], value: Dict[str, Any], *args, **kwargs):
kwargs['disable_logging'] = True # ZwaveValueEvent can be quite verbose, so we only report them if debug
# logging is enabled
kwargs['logging_level'] = logging.DEBUG
super().__init__(*args, node=node, value=value, **kwargs) super().__init__(*args, node=node, value=value, **kwargs)
@ -177,6 +207,7 @@ class ZwaveValueAddedEvent(ZwaveValueEvent):
""" """
Triggered when a value is added to a node on the network. Triggered when a value is added to a node on the network.
""" """
pass pass
@ -184,6 +215,7 @@ class ZwaveValueChangedEvent(ZwaveValueEvent):
""" """
Triggered when a value of a node on the network changes. Triggered when a value of a node on the network changes.
""" """
pass pass
@ -191,6 +223,7 @@ class ZwaveValueRefreshedEvent(ZwaveValueEvent):
""" """
Triggered when a value of a node on the network is refreshed. Triggered when a value of a node on the network is refreshed.
""" """
pass pass
@ -198,6 +231,7 @@ class ZwaveValueRemovedEvent(ZwaveValueEvent):
""" """
Triggered when a value of a node on the network is removed. Triggered when a value of a node on the network is removed.
""" """
pass pass
@ -205,6 +239,7 @@ class ZwaveNodeQueryCompletedEvent(ZwaveEvent):
""" """
Triggered when all the nodes on the network have been queried. Triggered when all the nodes on the network have been queried.
""" """
pass pass
@ -212,16 +247,33 @@ class ZwaveCommandEvent(ZwaveEvent):
""" """
Triggered when a command is received on the network. Triggered when a command is received on the network.
""" """
def __init__(self, state: str, state_description: str, error: Optional[str] = None,
error_description: Optional[str] = None, node: Optional[Dict[str, Any]] = None, *args, **kwargs): def __init__(
super().__init__(*args, state=state, state_description=state_description, self,
error=error, error_description=error_description, node=node, **kwargs) state: str,
state_description: str,
error: Optional[str] = None,
error_description: Optional[str] = None,
node: Optional[Dict[str, Any]] = None,
*args,
**kwargs
):
super().__init__(
*args,
state=state,
state_description=state_description,
error=error,
error_description=error_description,
node=node,
**kwargs
)
class ZwaveCommandWaitingEvent(ZwaveCommandEvent): class ZwaveCommandWaitingEvent(ZwaveCommandEvent):
""" """
Triggered when a command is waiting for a message to proceed. Triggered when a command is waiting for a message to proceed.
""" """
pass pass

View file

@ -254,6 +254,7 @@ class Request(Message):
action action
) )
plugin = get_plugin(module_name) plugin = get_plugin(module_name)
assert plugin, f'No such plugin: {module_name}'
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
msg = 'Uncaught pre-processing exception from action [{}]: {}'.format( msg = 'Uncaught pre-processing exception from action [{}]: {}'.format(
@ -286,12 +287,8 @@ class Request(Message):
'Response processed with errors from ' + 'action {}: {}' 'Response processed with errors from ' + 'action {}: {}'
).format(action, str(response)) ).format(action, str(response))
) )
elif not response.disable_logging: else:
logger.info( response.log(action)
'Processed response from action {}: {}'.format(
action, str(response)
)
)
except (AssertionError, TimeoutError) as e: except (AssertionError, TimeoutError) as e:
logger.warning( logger.warning(
'%s from action [%s]: %s', e.__class__.__name__, action, str(e) '%s from action [%s]: %s', e.__class__.__name__, action, str(e)

View file

@ -1,14 +1,23 @@
import json import json
import logging
import time import time
from platypush.message import Message from platypush.message import Message
class Response(Message): class Response(Message):
""" Response message class """ """Response message class"""
def __init__(self, target=None, origin=None, id=None, output=None, errors=None, def __init__(
timestamp=None, disable_logging=False): self,
target=None,
origin=None,
id=None,
output=None,
errors=None,
timestamp=None,
logging_level=logging.INFO,
):
""" """
:param target: Target :param target: Target
:type target: str :type target: str
@ -22,21 +31,20 @@ class Response(Message):
:type timestamp: float :type timestamp: float
""" """
super().__init__(timestamp=timestamp) super().__init__(timestamp=timestamp, logging_level=logging_level)
self.target = target self.target = target
self.output = self._parse_msg(output) self.output = self._parse_msg(output)
self.errors = self._parse_msg(errors or []) self.errors = self._parse_msg(errors or [])
self.origin = origin self.origin = origin
self.id = id self.id = id
self.disable_logging = disable_logging
def is_error(self): def is_error(self):
""" Returns True if the response has errors """ """Returns True if the response has errors"""
return len(self.errors) != 0 return len(self.errors) != 0
@classmethod @classmethod
def _parse_msg(cls, msg): def _parse_msg(cls, msg):
if isinstance(msg, bytes) or isinstance(msg, bytearray): if isinstance(msg, (bytes, bytearray)):
msg = msg.decode('utf-8') msg = msg.decode('utf-8')
if isinstance(msg, str): if isinstance(msg, str):
try: try:
@ -54,7 +62,7 @@ class Response(Message):
'output': msg['response']['output'], 'output': msg['response']['output'],
'errors': msg['response']['errors'], 'errors': msg['response']['errors'],
'timestamp': msg['_timestamp'] if '_timestamp' in msg else time.time(), 'timestamp': msg['_timestamp'] if '_timestamp' in msg else time.time(),
'disable_logging': msg.get('_disable_logging', False), 'logging_level': msg.get('_logging_level', logging.INFO),
} }
if 'id' in msg: if 'id' in msg:
@ -69,9 +77,9 @@ class Response(Message):
Overrides the ``str()`` operator and converts Overrides the ``str()`` operator and converts
the message into a UTF-8 JSON string the message into a UTF-8 JSON string
""" """
output = self.output if self.output is not None else { output = (
'success': True if not self.errors else False self.output if self.output is not None else {'success': bool(self.errors)}
} )
response_dict = { response_dict = {
'id': self.id, 'id': self.id,
@ -85,10 +93,19 @@ class Response(Message):
}, },
} }
if self.disable_logging: if self.logging_level:
response_dict['_disable_logging'] = self.disable_logging response_dict['_logging_level'] = self.logging_level
return json.dumps(response_dict, cls=self.Encoder) return json.dumps(response_dict, cls=self.Encoder)
def log(self, action=None):
prefix = (
f'Processed response from action {action}: '
if action
else 'Received response: '
)
super().log(prefix)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: