Source code for ewokscore.events.global_state

"""Manage the EWOKS event logger which is a global object"""

import logging
import os
from contextlib import contextmanager
from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
from typing import Optional
from typing import Tuple

from ewoksutils.logging_utils.asyncwrapper import AsyncHandlerWrapper
from ewoksutils.logging_utils.cleanup import cleanup_handler
from ewoksutils.logging_utils.cleanup import cleanup_logger
from ewoksutils.logging_utils.cleanup import protect_logging_state

from .handlers import instantiate_handler
from .handlers import is_ewoks_event_handler

_app_logger = logging.getLogger(__name__)
EWOKS_EVENT_LOGGER_NAME = f"__{__name__}__"
ENABLE_EWOKS_EVENTS_BY_DEFAULT = True


[docs] def send( *args, handlers: Optional[List[Dict[str, Any]]] = None, asynchronous: Optional[bool] = None, **kw, ) -> None: """Log an EWOKS event with the EWOKS event handlers and the application handlers.""" with _ewoks_event_logger( handlers=handlers, asynchronous=asynchronous, cleanup_on_different_handlers=True ) as logger: extra = kw.get("extra", {}) level = logging.ERROR if extra.get("error", False) else logging.INFO # Send to the EWOKS event handlers logger.log(level, *args, **kw) # Send to the application log handlers _app_logger.log(level, *args, **kw)
[docs] def add_handler( handler: logging.Handler, asynchronous: Optional[bool] = None, ) -> None: """Add a handler to the global EWOKS event logger.""" with _ewoks_event_logger() as logger: # If logging was disable, re-enable it. logger.disabled = False if _has_handler_instance(logger, handler): return if asynchronous is None and is_ewoks_event_handler(handler): asynchronous = handler.BLOCKING if asynchronous: handler = AsyncHandlerWrapper(handler) logger.addHandler(handler)
[docs] def remove_handler(handler: logging.Handler) -> None: """Remove a handler from all loggers that receive EWOKS events.""" with _ewoks_event_logger() as logger: for linstance, hinstance in _iter_handler_owners(logger, handler): linstance.removeHandler(hinstance) cleanup_handler(hinstance)
[docs] def cleanup(): """Pending events will be dropped""" with protect_logging_state(): _cleanup_ewoks_event_logger()
def _after_fork_in_child(): cleanup() if hasattr(os, "register_at_fork"): os.register_at_fork(after_in_child=_after_fork_in_child) @contextmanager def _ewoks_event_logger( handlers: Optional[List[Dict[str, Any]]] = None, asynchronous: Optional[bool] = None, cleanup_on_different_handlers: bool = False, ) -> Iterator[logging.Logger]: """Initialize and yield the EWOKS event logger""" # Issue with logging and forking: # https://pythonspeed.com/articles/python-multiprocessing/ with protect_logging_state(): if _ewoks_event_logger_requires_cleanup( handlers=handlers, cleanup_on_different_handlers=cleanup_on_different_handlers, ): _cleanup_ewoks_event_logger() if _ewoks_event_logger_requires_init(): _init_ewoks_event_logger(handlers, asynchronous) yield logging.getLogger(EWOKS_EVENT_LOGGER_NAME) def _cleanup_ewoks_event_logger(): """Cleanup and delete the global EWOKS event logger""" cleanup_logger(EWOKS_EVENT_LOGGER_NAME) def _ewoks_event_logger_requires_init() -> bool: logger = logging.getLogger(EWOKS_EVENT_LOGGER_NAME) return not hasattr(logger, "ewoks_pid") def _ewoks_event_logger_requires_cleanup( handlers: Optional[List[Dict[str, Any]]] = None, cleanup_on_different_handlers: bool = False, ) -> bool: logger = logging.getLogger(EWOKS_EVENT_LOGGER_NAME) ewoks_pid = getattr(logger, "ewoks_pid", None) if ewoks_pid is not None and ewoks_pid != os.getpid(): # Process forked return True if cleanup_on_different_handlers: ewoks_handlers = getattr(logger, "ewoks_handlers", None) if ewoks_handlers != handlers: return True return False def _init_ewoks_event_logger( handlers: Optional[List[Dict[str, Any]]], asynchronous: Optional[bool] ): logger = logging.getLogger(EWOKS_EVENT_LOGGER_NAME) logger.setLevel(logging.DEBUG) logger.ewoks_pid = os.getpid() logger.ewoks_handlers = handlers logger.propagate = False if not handlers: # No handlers -> so no event logging. # If logger is not disabled `logging.lastResort` will be used # as a default handler and we do not want that. logger.disabled = True return for desc in handlers: try: kwargs = { arg["name"]: arg["value"] for arg in desc.get("arguments", list()) } handler = instantiate_handler(desc["class"], **kwargs) except Exception as e: raise RuntimeError( "cannot create an EWOKS event handler from the description" ) from e asynchronous_handler = desc.get("asynchronous", asynchronous) add_handler(handler, asynchronous=asynchronous_handler) def _iter_loggers(logger: logging.Logger) -> Iterator[logging.Logger]: """Yield all loggers which will receive EWOKS events.""" _logger = logger while _logger is not None: yield _logger if not _logger.propagate: return _logger = _logger.parent def _iter_handler_owners( logger: logging.Logger, instance: logging.Handler ) -> Iterator[Tuple[logging.Logger, logging.Handler]]: """Yield all loggers which have a specific handler (or a handler that wraps the specific event handler).""" for _logger in _iter_loggers(logger): for handler in _logger.handlers: if handler is instance: yield _logger, instance elif isinstance(handler, AsyncHandlerWrapper): instance2 = handler.wrapped_handler if instance2 is instance: yield _logger, instance2 def _has_handler_instance(logger: logging.Logger, instance: logging.Handler) -> bool: """Is this handler registered with a logger or it's parents?""" for _ in _iter_handler_owners(logger, instance): return True return False