Skip to content

Commit

Permalink
Merge pull request #3274 from dmargala/add-plugin-log-handler
Browse files Browse the repository at this point in the history
[feat] Allow users define custom log handlers and attach them to the framework
  • Loading branch information
vkarak authored Oct 18, 2024
2 parents fe588bd + 701c230 commit a00fa32
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 33 deletions.
60 changes: 60 additions & 0 deletions docs/howto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1162,3 +1162,63 @@ If you use a Python-based configuration file, you can define your custom launche
.. note::

In versions prior to 4.0, launchers could only be implemented inside the source code tree of ReFrame.


.. _custom-loggers:

Implementing a custom log handler
---------------------------------

.. versionadded:: 4.7

ReFrame allows you to define custom log handlers and attach them to the framework.
Here's an example implementation of a custom log handler and how it can be used in a Python-based configuration file.

Define a custom log handler class based on :class:`~logging.Handler` which uses a custom logging API:

.. code-block:: python
import logging
import mylogger
class MyLoggerHandler(logging.Handler):
def __init__(self, key):
super().__init__()
self.key = key
def emit(self, record):
myrecord = {
'value': record.check_perf_value,
}
mylogger.log(self.key, myrecord)
Applying the :func:`@register_log_handler <reframe.core.logging.register_log_handler>` decorator to a function returns an instance of the custom log handler:

.. code-block:: python
from reframe.core.logging import register_log_handler
@register_log_handler("mylogger")
def _create_mylogger_handler(site_config, config_prefix):
key = site_config.get(f'{config_prefix}/key')
return MyLoggerHandler(key)
Finally, add a handler entry with type matching the registered name for the custom log handler to the site config:

.. code-block:: python
site_configuration = {
'logging': [
{
'handlers': [
{
'type': 'mylogger',
'key': 'abc',
},
...
]
}
],
...
}
60 changes: 31 additions & 29 deletions reframe/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,20 @@ def stream_handler_kind(handler):
return logger


# Registry for log handler creation functions
_create_handlers = {}


def register_log_handler(name):
'''Register the decorated log handler creation function'''
def _create_handler_wrapper(fn):
_create_handlers[name] = fn
return fn

return _create_handler_wrapper


@register_log_handler('file')
def _create_file_handler(site_config, config_prefix):
filename = os.path.expandvars(site_config.get(f'{config_prefix}/name'))
if not filename:
Expand All @@ -431,6 +445,7 @@ def _create_file_handler(site_config, config_prefix):
mode='a+' if append else 'w+')


@register_log_handler('filelog')
def _create_filelog_handler(site_config, config_prefix):
basedir = os.path.abspath(os.path.join(
site_config.get('systems/0/prefix'),
Expand All @@ -447,6 +462,7 @@ def _create_filelog_handler(site_config, config_prefix):
ignore_keys=ignore_keys)


@register_log_handler('syslog')
def _create_syslog_handler(site_config, config_prefix):
address = site_config.get(f'{config_prefix}/address')

Expand Down Expand Up @@ -485,6 +501,7 @@ def _create_syslog_handler(site_config, config_prefix):
return logging.handlers.SysLogHandler(address, facility_type, socket_type)


@register_log_handler('stream')
def _create_stream_handler(site_config, config_prefix):
stream = site_config.get(f'{config_prefix}/name')
if stream == 'stdout':
Expand All @@ -496,6 +513,7 @@ def _create_stream_handler(site_config, config_prefix):
raise AssertionError(f'unknown stream: {stream}')


@register_log_handler('graylog')
def _create_graylog_handler(site_config, config_prefix):
try:
import pygelf
Expand Down Expand Up @@ -528,6 +546,7 @@ def _create_graylog_handler(site_config, config_prefix):
json_default=jsonext.encode)


@register_log_handler('httpjson')
def _create_httpjson_handler(site_config, config_prefix):
url = site_config.get(f'{config_prefix}/url')
extras = site_config.get(f'{config_prefix}/extras')
Expand Down Expand Up @@ -684,35 +703,18 @@ def _extract_handlers(site_config, handlers_group):
handlers = []
for i, handler_config in enumerate(handlers_list):
handler_type = handler_config['type']
if handler_type == 'file':
hdlr = _create_file_handler(site_config, f'{handler_prefix}/{i}')
elif handler_type == 'filelog':
hdlr = _create_filelog_handler(
site_config, f'{handler_prefix}/{i}'
)
elif handler_type == 'syslog':
hdlr = _create_syslog_handler(site_config, f'{handler_prefix}/{i}')
elif handler_type == 'stream':
hdlr = _create_stream_handler(site_config, f'{handler_prefix}/{i}')
elif handler_type == 'graylog':
hdlr = _create_graylog_handler(
site_config, f'{handler_prefix}/{i}'
)
if hdlr is None:
getlogger().warning('could not initialize the '
'graylog handler; ignoring ...')
continue
elif handler_type == 'httpjson':
hdlr = _create_httpjson_handler(
site_config, f'{handler_prefix}/{i}'
)
if hdlr is None:
getlogger().warning('could not initialize the '
'httpjson handler; ignoring ...')
continue
else:
# Should not enter here
raise AssertionError(f'unknown handler type: {handler_type}')

try:
create_handler = _create_handlers[handler_type]
except KeyError:
raise ConfigError(
f'unknown handler type: {handler_type}') from None

hdlr = create_handler(site_config, f'{handler_prefix}/{i}')
if hdlr is None:
getlogger().warning('could not initialize the '
f'{handler_type} handler; ignoring ...')
continue

level = site_config.get(f'{handler_prefix}/{i}/level')
fmt = site_config.get(f'{handler_prefix}/{i}/format')
Expand Down
5 changes: 1 addition & 4 deletions reframe/schemas/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@
"handler_common": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["file", "filelog", "graylog", "stream", "syslog", "httpjson"]
},
"type": {"type": "string"},
"level": {"$ref": "#/defs/loglevel"},
"format": {"type": "string"},
"format_perfvars": {"type": "string"},
Expand Down

0 comments on commit a00fa32

Please sign in to comment.