Skip to content

Commit a00fa32

Browse files
authored
Merge pull request #3274 from dmargala/add-plugin-log-handler
[feat] Allow users define custom log handlers and attach them to the framework
2 parents fe588bd + 701c230 commit a00fa32

File tree

3 files changed

+92
-33
lines changed

3 files changed

+92
-33
lines changed

docs/howto.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,3 +1162,63 @@ If you use a Python-based configuration file, you can define your custom launche
11621162
.. note::
11631163

11641164
In versions prior to 4.0, launchers could only be implemented inside the source code tree of ReFrame.
1165+
1166+
1167+
.. _custom-loggers:
1168+
1169+
Implementing a custom log handler
1170+
---------------------------------
1171+
1172+
.. versionadded:: 4.7
1173+
1174+
ReFrame allows you to define custom log handlers and attach them to the framework.
1175+
Here's an example implementation of a custom log handler and how it can be used in a Python-based configuration file.
1176+
1177+
Define a custom log handler class based on :class:`~logging.Handler` which uses a custom logging API:
1178+
1179+
.. code-block:: python
1180+
1181+
import logging
1182+
import mylogger
1183+
1184+
class MyLoggerHandler(logging.Handler):
1185+
def __init__(self, key):
1186+
super().__init__()
1187+
self.key = key
1188+
1189+
def emit(self, record):
1190+
myrecord = {
1191+
'value': record.check_perf_value,
1192+
}
1193+
mylogger.log(self.key, myrecord)
1194+
1195+
Applying the :func:`@register_log_handler <reframe.core.logging.register_log_handler>` decorator to a function returns an instance of the custom log handler:
1196+
1197+
.. code-block:: python
1198+
1199+
from reframe.core.logging import register_log_handler
1200+
1201+
@register_log_handler("mylogger")
1202+
def _create_mylogger_handler(site_config, config_prefix):
1203+
key = site_config.get(f'{config_prefix}/key')
1204+
return MyLoggerHandler(key)
1205+
1206+
1207+
Finally, add a handler entry with type matching the registered name for the custom log handler to the site config:
1208+
1209+
.. code-block:: python
1210+
1211+
site_configuration = {
1212+
'logging': [
1213+
{
1214+
'handlers': [
1215+
{
1216+
'type': 'mylogger',
1217+
'key': 'abc',
1218+
},
1219+
...
1220+
]
1221+
}
1222+
],
1223+
...
1224+
}

reframe/core/logging.py

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,20 @@ def stream_handler_kind(handler):
412412
return logger
413413

414414

415+
# Registry for log handler creation functions
416+
_create_handlers = {}
417+
418+
419+
def register_log_handler(name):
420+
'''Register the decorated log handler creation function'''
421+
def _create_handler_wrapper(fn):
422+
_create_handlers[name] = fn
423+
return fn
424+
425+
return _create_handler_wrapper
426+
427+
428+
@register_log_handler('file')
415429
def _create_file_handler(site_config, config_prefix):
416430
filename = os.path.expandvars(site_config.get(f'{config_prefix}/name'))
417431
if not filename:
@@ -431,6 +445,7 @@ def _create_file_handler(site_config, config_prefix):
431445
mode='a+' if append else 'w+')
432446

433447

448+
@register_log_handler('filelog')
434449
def _create_filelog_handler(site_config, config_prefix):
435450
basedir = os.path.abspath(os.path.join(
436451
site_config.get('systems/0/prefix'),
@@ -447,6 +462,7 @@ def _create_filelog_handler(site_config, config_prefix):
447462
ignore_keys=ignore_keys)
448463

449464

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

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

487503

504+
@register_log_handler('stream')
488505
def _create_stream_handler(site_config, config_prefix):
489506
stream = site_config.get(f'{config_prefix}/name')
490507
if stream == 'stdout':
@@ -496,6 +513,7 @@ def _create_stream_handler(site_config, config_prefix):
496513
raise AssertionError(f'unknown stream: {stream}')
497514

498515

516+
@register_log_handler('graylog')
499517
def _create_graylog_handler(site_config, config_prefix):
500518
try:
501519
import pygelf
@@ -528,6 +546,7 @@ def _create_graylog_handler(site_config, config_prefix):
528546
json_default=jsonext.encode)
529547

530548

549+
@register_log_handler('httpjson')
531550
def _create_httpjson_handler(site_config, config_prefix):
532551
url = site_config.get(f'{config_prefix}/url')
533552
extras = site_config.get(f'{config_prefix}/extras')
@@ -684,35 +703,18 @@ def _extract_handlers(site_config, handlers_group):
684703
handlers = []
685704
for i, handler_config in enumerate(handlers_list):
686705
handler_type = handler_config['type']
687-
if handler_type == 'file':
688-
hdlr = _create_file_handler(site_config, f'{handler_prefix}/{i}')
689-
elif handler_type == 'filelog':
690-
hdlr = _create_filelog_handler(
691-
site_config, f'{handler_prefix}/{i}'
692-
)
693-
elif handler_type == 'syslog':
694-
hdlr = _create_syslog_handler(site_config, f'{handler_prefix}/{i}')
695-
elif handler_type == 'stream':
696-
hdlr = _create_stream_handler(site_config, f'{handler_prefix}/{i}')
697-
elif handler_type == 'graylog':
698-
hdlr = _create_graylog_handler(
699-
site_config, f'{handler_prefix}/{i}'
700-
)
701-
if hdlr is None:
702-
getlogger().warning('could not initialize the '
703-
'graylog handler; ignoring ...')
704-
continue
705-
elif handler_type == 'httpjson':
706-
hdlr = _create_httpjson_handler(
707-
site_config, f'{handler_prefix}/{i}'
708-
)
709-
if hdlr is None:
710-
getlogger().warning('could not initialize the '
711-
'httpjson handler; ignoring ...')
712-
continue
713-
else:
714-
# Should not enter here
715-
raise AssertionError(f'unknown handler type: {handler_type}')
706+
707+
try:
708+
create_handler = _create_handlers[handler_type]
709+
except KeyError:
710+
raise ConfigError(
711+
f'unknown handler type: {handler_type}') from None
712+
713+
hdlr = create_handler(site_config, f'{handler_prefix}/{i}')
714+
if hdlr is None:
715+
getlogger().warning('could not initialize the '
716+
f'{handler_type} handler; ignoring ...')
717+
continue
716718

717719
level = site_config.get(f'{handler_prefix}/{i}/level')
718720
fmt = site_config.get(f'{handler_prefix}/{i}/format')

reframe/schemas/config.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,7 @@
4949
"handler_common": {
5050
"type": "object",
5151
"properties": {
52-
"type": {
53-
"type": "string",
54-
"enum": ["file", "filelog", "graylog", "stream", "syslog", "httpjson"]
55-
},
52+
"type": {"type": "string"},
5653
"level": {"$ref": "#/defs/loglevel"},
5754
"format": {"type": "string"},
5855
"format_perfvars": {"type": "string"},

0 commit comments

Comments
 (0)