forked from platypush/platypush
Improved logging for requests, responses and events.
- The following logging namespaces are now used, to make it easier to filter only log lines related to the logged application message: - `platypush:events` - `platypush:requests` - `platypush:responses` - Those messages are always logged as JSON, with no prefixes nor suffixes. - Requests are always logged when executed - no more delegation to the upstream backend. - Responses are always logged when fully populated (including `id`, `origin`, `target` etc.), instead of being logged when still partially populated. This makes it particularly easy to link request/response IDs directly from the logs.
This commit is contained in:
parent
cb04af0bbd
commit
fa1364d854
4 changed files with 32 additions and 65 deletions
|
@ -21,20 +21,18 @@ def execute_route():
|
||||||
try:
|
try:
|
||||||
msg = json.loads(request.data.decode('utf-8'))
|
msg = json.loads(request.data.decode('utf-8'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger().error(
|
logger().error('Unable to parse JSON from request %s: %s', request.data, e)
|
||||||
'Unable to parse JSON from request {}: {}'.format(request.data, str(e))
|
|
||||||
)
|
|
||||||
abort(400, str(e))
|
abort(400, str(e))
|
||||||
|
|
||||||
logger().info('Received message on the HTTP backend: {}'.format(msg))
|
logger().debug(
|
||||||
|
'Received message on the HTTP backend from %s: %s', request.remote_addr, msg
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = send_message(msg)
|
response = send_message(msg)
|
||||||
return Response(str(response or {}), mimetype='application/json')
|
return Response(str(response or {}), mimetype='application/json')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger().error(
|
logger().error('Error while running HTTP action: %s. Request: %s', e, msg)
|
||||||
'Error while running HTTP action: {}. Request: {}'.format(str(e), msg)
|
|
||||||
)
|
|
||||||
abort(500, str(e))
|
abort(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ class Event(Message):
|
||||||
self.type = f'{self.__class__.__module__}.{self.__class__.__name__}'
|
self.type = f'{self.__class__.__module__}.{self.__class__.__name__}'
|
||||||
self.args = kwargs
|
self.args = kwargs
|
||||||
self.disable_web_clients_notification = disable_web_clients_notification
|
self.disable_web_clients_notification = disable_web_clients_notification
|
||||||
|
self._logger = logging.getLogger('platypush:events')
|
||||||
|
|
||||||
for arg, value in self.args.items():
|
for arg, value in self.args.items():
|
||||||
if arg not in [
|
if arg not in [
|
||||||
|
|
|
@ -57,6 +57,7 @@ class Request(Message):
|
||||||
self.args = args if args else {}
|
self.args = args if args else {}
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.token = token
|
self.token = token
|
||||||
|
self._logger = logging.getLogger('platypush:requests')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, msg):
|
def build(cls, msg):
|
||||||
|
@ -77,16 +78,11 @@ class Request(Message):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_id():
|
def _generate_id():
|
||||||
_id = ''
|
return ''.join(f'{random.randint(0, 255):02x}' for _ in range(0, 16))
|
||||||
for _ in range(0, 16):
|
|
||||||
_id += '%.2x' % random.randint(0, 255)
|
|
||||||
return _id
|
|
||||||
|
|
||||||
def _execute_procedure(self, *args, **kwargs):
|
def _execute_procedure(self, *args, **kwargs):
|
||||||
from platypush.config import Config
|
|
||||||
from platypush.procedure import Procedure
|
from platypush.procedure import Procedure
|
||||||
|
|
||||||
logger.info('Executing procedure request: {}'.format(self.action))
|
|
||||||
procedures = Config.get_procedures()
|
procedures = Config.get_procedures()
|
||||||
proc_name = '.'.join(self.action.split('.')[1:])
|
proc_name = '.'.join(self.action.split('.')[1:])
|
||||||
if proc_name not in procedures:
|
if proc_name not in procedures:
|
||||||
|
@ -120,7 +116,7 @@ class Request(Message):
|
||||||
|
|
||||||
constants = Config.get_constants()
|
constants = Config.get_constants()
|
||||||
context['constants'] = {}
|
context['constants'] = {}
|
||||||
for (name, value) in constants.items():
|
for name, value in constants.items():
|
||||||
context['constants'][name] = value
|
context['constants'][name] = value
|
||||||
|
|
||||||
keys = []
|
keys = []
|
||||||
|
@ -143,23 +139,20 @@ class Request(Message):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def expand_value_from_context(cls, _value, **context):
|
def expand_value_from_context(cls, _value, **context):
|
||||||
for (k, v) in context.items():
|
for k, v in context.items():
|
||||||
if isinstance(v, Message):
|
if isinstance(v, Message):
|
||||||
v = json.loads(str(v))
|
v = json.loads(str(v))
|
||||||
try:
|
try:
|
||||||
exec('{}={}'.format(k, v))
|
exec('{}={}'.format(k, v))
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.debug(str(e))
|
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
try:
|
try:
|
||||||
exec('{}="{}"'.format(k, re.sub(r'(^|[^\\])"', '\1\\"', v)))
|
exec('{}="{}"'.format(k, re.sub(r'(^|[^\\])"', '\1\\"', v)))
|
||||||
except Exception as e:
|
except Exception as e2:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Could not set context variable {}={}: {}'.format(
|
'Could not set context variable %s=%s: %s', k, v, e2
|
||||||
k, v, str(e)
|
|
||||||
)
|
)
|
||||||
)
|
logger.debug('Context: %s', context)
|
||||||
logger.debug('Context: {}'.format(context))
|
|
||||||
|
|
||||||
parsed_value = ''
|
parsed_value = ''
|
||||||
if not isinstance(_value, str):
|
if not isinstance(_value, str):
|
||||||
|
@ -205,6 +198,7 @@ class Request(Message):
|
||||||
response.id = self.id
|
response.id = self.id
|
||||||
response.target = self.origin
|
response.target = self.origin
|
||||||
response.origin = Config.get('device_id')
|
response.origin = Config.get('device_id')
|
||||||
|
response.log()
|
||||||
|
|
||||||
if self.backend and self.origin:
|
if self.backend and self.origin:
|
||||||
self.backend.send_response(response=response, request=self)
|
self.backend.send_response(response=response, request=self)
|
||||||
|
@ -235,6 +229,7 @@ class Request(Message):
|
||||||
from platypush.plugins import RunnablePlugin
|
from platypush.plugins import RunnablePlugin
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
|
self.log()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.action.startswith('procedure.'):
|
if self.action.startswith('procedure.'):
|
||||||
|
@ -243,25 +238,20 @@ class Request(Message):
|
||||||
if response is not None:
|
if response is not None:
|
||||||
self._send_response(response)
|
self._send_response(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# utils.get_context is a special action that simply returns the current context
|
# utils.get_context is a special action that simply returns the current context
|
||||||
elif self.action == 'utils.get_context':
|
if self.action == 'utils.get_context':
|
||||||
response = Response(output=context)
|
response = Response(output=context)
|
||||||
self._send_response(response)
|
self._send_response(response)
|
||||||
return response
|
return response
|
||||||
else:
|
|
||||||
action = self.expand_value_from_context(self.action, **context)
|
action = self.expand_value_from_context(self.action, **context)
|
||||||
(module_name, method_name) = get_module_and_method_from_action(
|
(module_name, method_name) = get_module_and_method_from_action(action)
|
||||||
action
|
|
||||||
)
|
|
||||||
plugin = get_plugin(module_name)
|
plugin = get_plugin(module_name)
|
||||||
assert plugin, f'No such 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(
|
response = Response(output=None, errors=[str(e)])
|
||||||
self.action, str(e)
|
|
||||||
)
|
|
||||||
logger.warning(msg)
|
|
||||||
response = Response(output=None, errors=[msg])
|
|
||||||
self._send_response(response)
|
self._send_response(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -276,41 +266,23 @@ class Request(Message):
|
||||||
else:
|
else:
|
||||||
response = plugin.run(method_name, args)
|
response = plugin.run(method_name, args)
|
||||||
|
|
||||||
if not response:
|
if response is None:
|
||||||
logger.warning(
|
response = Response()
|
||||||
'Received null response from action {}'.format(action)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if response.is_error():
|
|
||||||
logger.warning(
|
|
||||||
(
|
|
||||||
'Response processed with errors from ' + 'action {}: {}'
|
|
||||||
).format(action, str(response))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
response.log(action)
|
|
||||||
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, e
|
||||||
)
|
)
|
||||||
response = Response(output=None, errors=[str(e)])
|
response = Response(output=None, errors=[str(e)])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Retry mechanism
|
# Retry mechanism
|
||||||
plugin.logger.exception(e)
|
plugin.logger.exception(e)
|
||||||
logger.warning(
|
|
||||||
(
|
|
||||||
'Uncaught exception while processing response '
|
|
||||||
+ 'from action [{}]: {}'
|
|
||||||
).format(action, str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
errors = errors or []
|
errors = errors or []
|
||||||
if str(e) not in errors:
|
if str(e) not in errors:
|
||||||
errors.append(str(e))
|
errors.append(str(e))
|
||||||
|
|
||||||
response = Response(output=None, errors=errors)
|
response = Response(output=None, errors=errors)
|
||||||
if _n_tries - 1 > 0:
|
if _n_tries - 1 > 0:
|
||||||
logger.info('Reloading plugin {} and retrying'.format(module_name))
|
logger.info('Reloading plugin %s and retrying', module_name)
|
||||||
plugin = get_plugin(module_name, reload=True)
|
plugin = get_plugin(module_name, reload=True)
|
||||||
if isinstance(plugin, RunnablePlugin):
|
if isinstance(plugin, RunnablePlugin):
|
||||||
plugin.bus = get_bus()
|
plugin.bus = get_bus()
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Response(Message):
|
||||||
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._logger = logging.getLogger('platypush:responses')
|
||||||
|
|
||||||
def is_error(self):
|
def is_error(self):
|
||||||
"""Returns True if the response has errors"""
|
"""Returns True if the response has errors"""
|
||||||
|
@ -98,14 +99,9 @@ class Response(Message):
|
||||||
|
|
||||||
return json.dumps(response_dict, cls=self.Encoder)
|
return json.dumps(response_dict, cls=self.Encoder)
|
||||||
|
|
||||||
def log(self, action=None):
|
def log(self, *args, **kwargs):
|
||||||
prefix = (
|
self.logging_level = logging.WARNING if self.is_error() else logging.INFO
|
||||||
f'Processed response from action {action}: '
|
super().log(*args, **kwargs)
|
||||||
if action
|
|
||||||
else 'Received response: '
|
|
||||||
)
|
|
||||||
|
|
||||||
super().log(prefix)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
Loading…
Reference in a new issue