# -*- test-case-name: twisted.logger.test.test_observer -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Basic log observers. """ from typing import Callable, Optional from zope.interface import implementer from twisted.python.failure import Failure from ._interfaces import ILogObserver, LogEvent from ._logger import Logger OBSERVER_DISABLED = ( "Temporarily disabling observer {observer} due to exception: {log_failure}" ) @implementer(ILogObserver) class LogPublisher: """ I{ILogObserver} that fans out events to other observers. Keeps track of a set of L{ILogObserver} objects and forwards events to each. """ def __init__(self, *observers: ILogObserver) -> None: self._observers = list(observers) self.log = Logger(observer=self) def addObserver(self, observer: ILogObserver) -> None: """ Registers an observer with this publisher. @param observer: An L{ILogObserver} to add. """ if not callable(observer): raise TypeError(f"Observer is not callable: {observer!r}") if observer not in self._observers: self._observers.append(observer) def removeObserver(self, observer: ILogObserver) -> None: """ Unregisters an observer with this publisher. @param observer: An L{ILogObserver} to remove. """ try: self._observers.remove(observer) except ValueError: pass def __call__(self, event: LogEvent) -> None: """ Forward events to contained observers. """ if "log_trace" not in event: trace: Optional[Callable[[ILogObserver], None]] = None else: def trace(observer: ILogObserver) -> None: """ Add tracing information for an observer. @param observer: an observer being forwarded to """ event["log_trace"].append((self, observer)) brokenObservers = [] for observer in self._observers: if trace is not None: trace(observer) try: observer(event) except Exception: brokenObservers.append((observer, Failure())) for brokenObserver, failure in brokenObservers: errorLogger = self._errorLoggerForObserver(brokenObserver) errorLogger.failure( OBSERVER_DISABLED, failure=failure, observer=brokenObserver, ) def _errorLoggerForObserver(self, observer: ILogObserver) -> Logger: """ Create an error-logger based on this logger, which does not contain the given bad observer. @param observer: The observer which previously had an error. @return: A L{Logger} without the given observer. """ errorPublisher = LogPublisher( *(obs for obs in self._observers if obs is not observer) ) return Logger(observer=errorPublisher) @implementer(ILogObserver) def bitbucketLogObserver(event: LogEvent) -> None: """ I{ILogObserver} that does nothing with the events it sees. """