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