Source code for ska_ser_logging.configuration

# -*- coding: utf-8 -*-

"""This module provides the standard logging configuration."""
import copy
import logging
import logging.config
import time


class _UTCFormatter(logging.Formatter):
    converter = time.gmtime


# Format defined here:
#   https://developer.skatelescope.org/en/latest/development/logging-format.html
# VERSION "|" TIMESTAMP "|" SEVERITY "|" [THREAD-ID] "|" [FUNCTION] "|" [LINE-LOC] "|"
#  [TAGS] "|" MESSAGE LF

_FORMAT_STR_NO_TAGS = (
    "1|"
    "%(asctime)s.%(msecs)03dZ|"
    "%(levelname)s|"
    "%(threadName)s|"
    "%(funcName)s|"
    "%(filename)s#%(lineno)d|"
    "|"
    "%(message)s"
)

_FORMAT_STR_WITH_TAGS = (
    "1|"
    "%(asctime)s.%(msecs)03dZ|"
    "%(levelname)s|"
    "%(threadName)s|"
    "%(funcName)s|"
    "%(filename)s#%(lineno)d|"
    "%(tags)s|"
    "%(message)s"
)

_FORMAT_STR_DATE = "%Y-%m-%dT%H:%M:%S"

_LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "()": _UTCFormatter,
            "format": _FORMAT_STR_NO_TAGS,
            "datefmt": _FORMAT_STR_DATE,
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
            "stream": "ext://sys.stdout",
        }
    },
    "root": {"handlers": ["console"], "filters": [], "level": "INFO"},
}


[docs]def configure_logging(level=None, tags_filter=None, overrides=None): """Configure Python logging for SKA applications. This function should be used to configure the application's logging system as early as possible at start up. .. note:: For Python TANGO devices that inherit from `ska_tango_base.SKABaseDevice` this is already done in that base class, so it does not need to be done again. Example usage is shown in this repo's ``README.md`` file. Parameters ---------- level : str or int, optional Set the logging level to this instead of the default. Use the string representations like ""INFO" and "DEBUG", or integer values like `logging.INFO` and `logging.DEBUG`. tags_filter : type derived from `logging.Filter`, optional If this type (not instance) is provided, the default formatter will include a "%(tags)s" specifier. The filter must inject the `tags` attribute in each record. overrides : dict, optional The default logging configuration can be modified by passing in this dict. It will be recursively merged with the default. This allows existing values to be modified, or even removed. It also allows additional loggers, handlers, filters and formatters to be added. See the `_override` function for more details on the merging process. The end result must be compatible with the `logging.config.dictConfig` schema. Note that the `level` and `tags_filter` parameter are applied after merging the overrides. """ config = copy.deepcopy(_LOGGING_CONFIG) if overrides: config = _override(config, copy.deepcopy(overrides)) if level: config["root"]["level"] = level if tags_filter: config["filters"] = {"tags": {"()": tags_filter}} for handler in config["handlers"].values(): filters = handler.get("filters", []) if "tags" not in filters: filters.append("tags") handler["filters"] = filters config["formatters"]["default"]["format"] = _FORMAT_STR_WITH_TAGS logging.config.dictConfig(config)
[docs]def get_default_formatter(tags=False): """Return a formatter configured with the standard logging format. Parameters ---------- tags : bool, optional If true, then include the "tags" field in the format string. This requires a tags filter to be linked to the corresponding handler. Returns ------- logging.Formatter A new default formatter. """ if tags: format_str = _FORMAT_STR_WITH_TAGS else: format_str = _FORMAT_STR_NO_TAGS return _UTCFormatter(fmt=format_str, datefmt=_FORMAT_STR_DATE)
def _override(config, overrides): """Update a config dictionary with overrides, merging dictionaries. Where both sides have a dictionary, the override is done recursively. Where `overrides` contains a value of `None`, the corresponding entry is deleted from the return value. .. note:: this does not validate the result, which should be done by the caller. Code based on: https://github.com/ska-sa/katsdpcontroller/blob/ 5d988d2a7a00921924739bba1ec3f6a7d6293eae/katsdpcontroller/product_config.py#L91 Copyright (c) 2013-2019, National Research Foundation (SARAO) All rights reserved. (BSD-3-Clause license) Parameters ---------- config : dict The input configuration dictionary. overrides : dict The dictionary which will be used to override the original configuration. Returns ------- dict The updated configuration. """ if isinstance(config, dict) and isinstance(overrides, dict): new_config = dict(config) for key, value in overrides.items(): if value is None: new_config.pop(key, None) else: new_config[key] = _override(config.get(key), value) return new_config return overrides