ez_wsi_dicomweb/ez_wsi_logging_factory.py (77 lines of code) (raw):
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Abstract and reference python logging interfaces for local DICOM slide cache."""
from __future__ import annotations
import abc
import collections
import copy
import logging
from typing import Any, Mapping, MutableMapping, Optional, Union
OptionalStructureElements = Union[Exception, Mapping[str, Any], None]
DEFAULT_EZ_WSI_PYTHON_LOGGER_NAME = 'ez-wsi-DICOMweb'
class AbstractLoggingInterface(metaclass=abc.ABCMeta):
"""Logging interface for local DICOM slide cache."""
@abc.abstractmethod
def debug(self, msg: str, *args: OptionalStructureElements) -> None:
"""Logs debug message.
Args:
msg: Message to log.
*args: Optional arguments to log as structured logs or additional msg.
Returns:
None
"""
@abc.abstractmethod
def info(self, msg: str, *args: OptionalStructureElements) -> None:
"""Logs info message.
Args:
msg: Message to log.
*args: Optional arguments to log as structured logs or additional msg.
Returns:
None
"""
@abc.abstractmethod
def warning(self, msg: str, *args: OptionalStructureElements) -> None:
"""Logs warning message.
Args:
msg: Message to log.
*args: Optional arguments to log as structured logs or additional msg.
Returns:
None
"""
@abc.abstractmethod
def error(self, msg: str, *args: OptionalStructureElements) -> None:
"""Logs error message.
Args:
msg: Message to log.
*args: Optional arguments to log as structured logs or additional msg.
Returns:
None
"""
@abc.abstractmethod
def critical(self, msg: str, *args: OptionalStructureElements) -> None:
"""Logs critical message.
Args:
msg: Message to log.
*args: Optional arguments to log as structured logs or additional msg.
Returns:
None
"""
class AbstractLoggingInterfaceFactory(metaclass=abc.ABCMeta):
@abc.abstractmethod
def create_logger(
self, signature: Optional[Mapping[str, Any]] = None
) -> AbstractLoggingInterface:
"""Creates an instance of the logger.
Args:
signature: Optional signature element to include as structure or message
elements in all logs.
"""
def _merge_dict_sorted_by_key(
merge_dict: MutableMapping[str, Any], element: Mapping[str, Any]
) -> None:
for key in sorted(element):
merge_dict[key] = element[key]
class _BasePythonLogger(AbstractLoggingInterface):
"""Reference implementation for python logging interface."""
def __init__(
self,
pylogger: logging.Logger,
signature: Optional[Mapping[str, Any]] = None,
):
self._logger = pylogger
self._signature = {} if signature is None else copy.copy(signature)
def _build_msg(self, msg: str, *args: OptionalStructureElements) -> str:
"""Builds log msg from msg and structure elements."""
optional_args = collections.OrderedDict()
found_exception = None
for element in args:
if element is None or not element:
continue
if isinstance(element, Mapping):
_merge_dict_sorted_by_key(optional_args, element)
elif isinstance(element, Exception):
found_exception = element
if self._signature:
_merge_dict_sorted_by_key(optional_args, self._signature)
if found_exception is not None:
optional_args['EXCEPTION'] = found_exception
structure = '; '.join(
[f'{key}: {value}' for key, value in optional_args.items()]
)
if structure:
return f'{msg}; {structure}'
return msg
def debug(self, msg: str, *args: OptionalStructureElements) -> None:
self._logger.debug(self._build_msg(msg, *args))
def info(self, msg: str, *args: OptionalStructureElements) -> None:
self._logger.info(self._build_msg(msg, *args))
def warning(self, msg: str, *args: OptionalStructureElements) -> None:
self._logger.warning(self._build_msg(msg, *args))
def error(self, msg: str, *args: OptionalStructureElements) -> None:
self._logger.error(self._build_msg(msg, *args))
def critical(self, msg: str, *args: OptionalStructureElements) -> None:
self._logger.critical(self._build_msg(msg, *args))
class BasePythonLoggerFactory(AbstractLoggingInterfaceFactory):
"""Factory class to contruct Python logger for DICOM Slide Cache."""
def __init__(
self,
name: Optional[str] = None,
):
self._name = name
def create_logger(
self, signature: Optional[Mapping[str, Any]] = None
) -> _BasePythonLogger:
return _BasePythonLogger(logging.getLogger(self._name), signature)