From 66e7a4cea37dffd9f9a7fb88c43ae63453bd4c50 Mon Sep 17 00:00:00 2001 From: Daniel Margala Date: Thu, 26 Sep 2024 09:09:29 -0700 Subject: [PATCH 1/7] Add support for plugin log handler --- reframe/core/logging.py | 44 +++++++++++++++++-------------------- reframe/schemas/config.json | 2 +- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/reframe/core/logging.py b/reframe/core/logging.py index d91b067fbe..997be8d4f0 100644 --- a/reframe/core/logging.py +++ b/reframe/core/logging.py @@ -678,39 +678,35 @@ def emit(self, record): raise LoggingError('logging failed') from e +_create_handler_registry = { + 'file': _create_file_handler, + 'filelog': _create_filelog_handler, + 'syslog': _create_syslog_handler, + 'stream': _create_stream_handler, + 'graylog': _create_graylog_handler, + 'httpjson': _create_httpjson_handler, +} + + +def register_plugin_handler(create_plugin_handler): + _create_handler_registry["plugin"] = create_plugin_handler + + def _extract_handlers(site_config, handlers_group): handler_prefix = f'logging/0/{handlers_group}' handlers_list = site_config.get(handler_prefix) 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}' - ) + + try: + create_handler = _create_handler_registry[handler_type] + hdlr = create_handler(site_config, f'{handler_prefix}/{i}') if hdlr is None: getlogger().warning('could not initialize the ' - 'httpjson handler; ignoring ...') + f'{handler_type} handler; ignoring ...') continue - else: + except KeyError: # Should not enter here raise AssertionError(f'unknown handler type: {handler_type}') diff --git a/reframe/schemas/config.json b/reframe/schemas/config.json index a3b9eccd1a..7bee4836f9 100644 --- a/reframe/schemas/config.json +++ b/reframe/schemas/config.json @@ -51,7 +51,7 @@ "properties": { "type": { "type": "string", - "enum": ["file", "filelog", "graylog", "stream", "syslog", "httpjson"] + "enum": ["file", "filelog", "graylog", "stream", "syslog", "httpjson", "plugin"] }, "level": {"$ref": "#/defs/loglevel"}, "format": {"type": "string"}, From db99ca345e6278de98e4b096859d81ab8baa1742 Mon Sep 17 00:00:00 2001 From: Daniel Margala Date: Wed, 16 Oct 2024 21:03:04 -0700 Subject: [PATCH 2/7] drop type enum from log handler config schema --- reframe/schemas/config.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/reframe/schemas/config.json b/reframe/schemas/config.json index 7bee4836f9..4e8b868bef 100644 --- a/reframe/schemas/config.json +++ b/reframe/schemas/config.json @@ -49,10 +49,7 @@ "handler_common": { "type": "object", "properties": { - "type": { - "type": "string", - "enum": ["file", "filelog", "graylog", "stream", "syslog", "httpjson", "plugin"] - }, + "type": {"type": "string"}, "level": {"$ref": "#/defs/loglevel"}, "format": {"type": "string"}, "format_perfvars": {"type": "string"}, From 4db5993993db3303b3cbe3c1f227482effa48409 Mon Sep 17 00:00:00 2001 From: Daniel Margala Date: Wed, 16 Oct 2024 21:04:20 -0700 Subject: [PATCH 3/7] apply suggestions for registering log handlers --- reframe/core/logging.py | 48 ++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/reframe/core/logging.py b/reframe/core/logging.py index 997be8d4f0..673dd7648f 100644 --- a/reframe/core/logging.py +++ b/reframe/core/logging.py @@ -412,6 +412,19 @@ 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: @@ -431,6 +444,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'), @@ -447,6 +461,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') @@ -485,6 +500,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': @@ -496,6 +512,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 @@ -528,6 +545,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') @@ -678,20 +696,6 @@ def emit(self, record): raise LoggingError('logging failed') from e -_create_handler_registry = { - 'file': _create_file_handler, - 'filelog': _create_filelog_handler, - 'syslog': _create_syslog_handler, - 'stream': _create_stream_handler, - 'graylog': _create_graylog_handler, - 'httpjson': _create_httpjson_handler, -} - - -def register_plugin_handler(create_plugin_handler): - _create_handler_registry["plugin"] = create_plugin_handler - - def _extract_handlers(site_config, handlers_group): handler_prefix = f'logging/0/{handlers_group}' handlers_list = site_config.get(handler_prefix) @@ -700,15 +704,15 @@ def _extract_handlers(site_config, handlers_group): handler_type = handler_config['type'] try: - create_handler = _create_handler_registry[handler_type] - 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 + create_handler = _create_handlers[handler_type] except KeyError: - # Should not enter here - raise AssertionError(f'unknown handler type: {handler_type}') + 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') From 6c52643554a76a2ca3c205a80262241930c570fb Mon Sep 17 00:00:00 2001 From: Daniel Margala Date: Wed, 16 Oct 2024 21:05:49 -0700 Subject: [PATCH 4/7] add example doc for registering a custom log handler --- docs/howto.rst | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/howto.rst b/docs/howto.rst index cdc4c3d083..b5c6408871 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -1162,3 +1162,60 @@ 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 +--------------------------------- + +Here's an example implementation of a custom log handler defined 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) + +Apply the :func:`~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 registered name for the custom log handler to the site config: + +.. code-block:: python + + site_configuration = { + 'logging': [ + { + 'handlers': [ + { + 'type': 'mylogger', + 'key': 'abc', + }, + ... + ] + } + ], + ... + } From 8d6e000778396a22f13a830a8aaee11842155e45 Mon Sep 17 00:00:00 2001 From: Daniel Margala Date: Thu, 17 Oct 2024 16:05:33 -0700 Subject: [PATCH 5/7] update doc suggestions --- docs/howto.rst | 53 ++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/docs/howto.rst b/docs/howto.rst index b5c6408871..97c72bf55b 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -1169,7 +1169,10 @@ If you use a Python-based configuration file, you can define your custom launche Implementing a custom log handler --------------------------------- -Here's an example implementation of a custom log handler defined in a Python-based configuration file. +.. 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: @@ -1179,17 +1182,17 @@ Define a custom log handler class based on :class:`~logging.Handler` which uses import mylogger class MyLoggerHandler(logging.Handler): - def __init__(self, key): - super().__init__() - self.key = key + def __init__(self, key): + super().__init__() + self.key = key - def emit(self, record): - myrecord = { - 'value': record.check_perf_value, - } - mylogger.log(self.key, myrecord) + def emit(self, record): + myrecord = { + 'value': record.check_perf_value, + } + mylogger.log(self.key, myrecord) -Apply the :func:`~reframe.core.logging.register_log_handler` decorator to a function returns an instance of the custom log handler: +Applying the :func:`@register_log_handler ` decorator to a function returns an instance of the custom log handler: .. code-block:: python @@ -1197,25 +1200,25 @@ Apply the :func:`~reframe.core.logging.register_log_handler` decorator to a func @register_log_handler("mylogger") def _create_mylogger_handler(site_config, config_prefix): - key = site_config.get(f'{config_prefix}/key') - return MyLoggerHandler(key) + key = site_config.get(f'{config_prefix}/key') + return MyLoggerHandler(key) -Finally, add a handler entry with type matching registered name for the custom log handler to the site config: +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', - }, - ... - ] - } - ], - ... + 'logging': [ + { + 'handlers': [ + { + 'type': 'mylogger', + 'key': 'abc', + }, + ... + ] + } + ], + ... } From 1efc43e2e738ff7541a9bca8230845439afbda51 Mon Sep 17 00:00:00 2001 From: Daniel Margala Date: Thu, 17 Oct 2024 16:06:10 -0700 Subject: [PATCH 6/7] add blank line after return --- reframe/core/logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reframe/core/logging.py b/reframe/core/logging.py index 673dd7648f..0f464dc60b 100644 --- a/reframe/core/logging.py +++ b/reframe/core/logging.py @@ -421,6 +421,7 @@ def register_log_handler(name): def _create_handler_wrapper(fn): _create_handlers[name] = fn return fn + return _create_handler_wrapper From 701c2300fa6a4656fde9165209a0d10cce1deb06 Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Fri, 18 Oct 2024 10:08:44 +0200 Subject: [PATCH 7/7] Minor code style fix --- reframe/core/logging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reframe/core/logging.py b/reframe/core/logging.py index 0f464dc60b..804bd103bd 100644 --- a/reframe/core/logging.py +++ b/reframe/core/logging.py @@ -707,7 +707,8 @@ def _extract_handlers(site_config, handlers_group): try: create_handler = _create_handlers[handler_type] except KeyError: - raise ConfigError(f'unknown handler type: {handler_type}') from None + raise ConfigError( + f'unknown handler type: {handler_type}') from None hdlr = create_handler(site_config, f'{handler_prefix}/{i}') if hdlr is None: