diff --git a/docs/running.rst b/docs/running.rst index 0bc6f92252..3c9882f4c0 100644 --- a/docs/running.rst +++ b/docs/running.rst @@ -650,10 +650,14 @@ Common Log Handler Attributes All handlers accept the following set of attributes (keys) in their configuration: * ``type``: (required) the type of the handler. - There are two types of handlers used for standard logging in ReFrame + There are several types of handlers used for logging in ReFrame. + Some of them are only relevant for performance logging: 1. ``file``: a handler that writes log records in file. 2. ``stream``: a handler that writes log records in a file stream. + 3. ``syslog``: a handler that sends log records to Unix syslog. + 4. ``filelog``: a handler for writing performance logs (relevant only for `performance logging <#performance-logging>`__). + 5. ``graylog``: a handler for sending performance logs to a Graylog server (relevant only for `performance logging <#performance-logging>`__). * ``level``: (default: ``DEBUG``) The lowest level of log records that this handler can process. @@ -709,6 +713,25 @@ In addition to the common log handler attributes, file log handlers accept the f Available values: ``stdout`` for standard output and ``stderr`` for standard error. +Syslog log handler +"""""""""""""""""" + +In addition to the common log handler attributes, file log handlers accept the following: + +* ``socktype``: The type of socket where the handler will send log records to. There are two socket types: + + 1. ``udp``: (default) This opens a UDP datagram socket. + 2. ``tcp``: This opens a TCP stream socket. + +* ``facility``: (default: ``user``) The Syslog facility to send records to. + The list of supported facilities can be found `here `__. +* ``address``: (required) The address where the handler will connect to. + This can either be of the form ``:`` or simply a path that refers to a Unix domain socket. + + +.. note:: + .. versionadded:: 2.17 + Performance Logging ^^^^^^^^^^^^^^^^^^^ diff --git a/reframe/core/logging.py b/reframe/core/logging.py index b36843c78f..171ad8714f 100644 --- a/reframe/core/logging.py +++ b/reframe/core/logging.py @@ -8,6 +8,7 @@ import shutil import sys import warnings +import socket from datetime import datetime import reframe @@ -197,6 +198,37 @@ def _create_filelog_handler(handler_config): return MultiFileHandler(filename_patt, mode='a+' if append else 'w+') +def _create_syslog_handler(handler_config): + address = handler_config.get('address', None) + if address is None: + raise ConfigError('syslog handler: no address specified') + + # Check if address is in `host:port` format + try: + host, port = address.split(':', maxsplit=1) + except ValueError: + pass + else: + address = (host, port) + + facility = handler_config.get('facility', 'user') + try: + facility_type = logging.handlers.SysLogHandler.facility_names[facility] + except KeyError: + raise ConfigError('syslog handler: ' + 'unknown facility: %s' % facility) from None + + socktype = handler_config.get('socktype', 'udp') + if socktype == 'udp': + socket_type = socket.SOCK_DGRAM + elif socktype == 'tcp': + socket_type = socket.SOCK_STREAM + else: + raise ConfigError('syslog handler: unknown socket type: %s' % socktype) + + return logging.handlers.SysLogHandler(address, facility_type, socket_type) + + def _create_stream_handler(handler_config): stream = handler_config.get('name', 'stdout') if stream == 'stdout': @@ -258,6 +290,8 @@ def _extract_handlers(handlers_list): hdlr = _create_file_handler(handler_config) elif handler_type == 'filelog': hdlr = _create_filelog_handler(handler_config) + elif handler_type == 'syslog': + hdlr = _create_syslog_handler(handler_config) elif handler_type == 'stream': hdlr = _create_stream_handler(handler_config) elif handler_type == 'graylog': diff --git a/unittests/test_logging.py b/unittests/test_logging.py index 513856caa8..11ff3f470f 100644 --- a/unittests/test_logging.py +++ b/unittests/test_logging.py @@ -296,11 +296,12 @@ def test_multiple_handlers(self): 'level': 'INFO', 'handlers': [ {'type': 'stream', 'name': 'stderr'}, - {'type': 'file', 'name': self.logfile} + {'type': 'file', 'name': self.logfile}, + {'type': 'syslog', 'address': '/dev/log'} ], } rlog.configure_logging(self.logging_config) - self.assertEqual(len(rlog.getlogger().logger.handlers), 2) + self.assertEqual(len(rlog.getlogger().logger.handlers), 3) def test_file_handler_timestamp(self): self.logging_config['handlers'][0]['timestamp'] = '%F' @@ -330,6 +331,46 @@ def test_stream_handler_unknown_stream(self): self.assertRaises(ConfigError, rlog.configure_logging, self.logging_config) + def test_syslog_handler(self): + import platform + + if platform.system() == 'Linux': + addr = '/dev/log' + elif platform.system() == 'Darwin': + addr = '/dev/run/syslog' + else: + self.skipTest() + + self.logging_config = { + 'level': 'INFO', + 'handlers': [{'type': 'syslog', 'address': addr}] + } + rlog.getlogger().info('foo') + + def test_syslog_handler_no_address(self): + self.logging_config = { + 'level': 'INFO', + 'handlers': [{'type': 'syslog'}] + } + self.assertRaises(ConfigError, rlog.configure_logging, + self.logging_config) + + def test_syslog_handler_unknown_facility(self): + self.logging_config = { + 'level': 'INFO', + 'handlers': [{'type': 'syslog', 'facility': 'foo'}] + } + self.assertRaises(ConfigError, rlog.configure_logging, + self.logging_config) + + def test_syslog_handler_unknown_socktype(self): + self.logging_config = { + 'level': 'INFO', + 'handlers': [{'type': 'syslog', 'socktype': 'foo'}] + } + self.assertRaises(ConfigError, rlog.configure_logging, + self.logging_config) + def test_global_noconfig(self): # This is to test the case when no configuration is set, but since the # order the unit tests are invoked is arbitrary, we emulate the