aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/logger
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/logger
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/logger')
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/__init__.py135
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_buffer.py54
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_capture.py25
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_file.py77
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_filter.py211
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_flatten.py175
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_format.py373
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_global.py226
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_interfaces.py63
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_io.py187
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_json.py285
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_legacy.py147
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_levels.py81
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_logger.py269
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_observer.py112
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_stdlib.py131
-rw-r--r--contrib/python/Twisted/py3/twisted/logger/_util.py51
17 files changed, 2602 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/logger/__init__.py b/contrib/python/Twisted/py3/twisted/logger/__init__.py
new file mode 100644
index 0000000000..62f2f71f4e
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/__init__.py
@@ -0,0 +1,135 @@
+# -*- test-case-name: twisted.logger.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Logger: Classes and functions to do granular logging.
+
+Example usage in a module C{some.module}::
+
+ from twisted.logger import Logger
+ log = Logger()
+
+ def handleData(data):
+ log.debug("Got data: {data!r}.", data=data)
+
+Or in a class::
+
+ from twisted.logger import Logger
+
+ class Foo:
+ log = Logger()
+
+ def oops(self, data):
+ self.log.error("Oops! Invalid data from server: {data!r}",
+ data=data)
+
+C{Logger}s have namespaces, for which logging can be configured independently.
+Namespaces may be specified by passing in a C{namespace} argument to L{Logger}
+when instantiating it, but if none is given, the logger will derive its own
+namespace by using the module name of the callable that instantiated it, or, in
+the case of a class, by using the fully qualified name of the class.
+
+In the first example above, the namespace would be C{some.module}, and in the
+second example, it would be C{some.module.Foo}.
+
+@var globalLogPublisher: The L{LogPublisher} that all L{Logger} instances that
+ are not otherwise parameterized will publish events to by default.
+@var globalLogBeginner: The L{LogBeginner} used to activate the main log
+ observer, whether it's a log file, or an observer pointing at stderr.
+"""
+
+__all__ = [
+ # From ._levels
+ "InvalidLogLevelError",
+ "LogLevel",
+ # From ._format
+ "formatEvent",
+ "formatEventAsClassicLogText",
+ "formatTime",
+ "timeFormatRFC3339",
+ "eventAsText",
+ # From ._flatten
+ "extractField",
+ # From ._interfaces
+ "ILogObserver",
+ "LogEvent",
+ # From ._logger
+ "Logger",
+ "_loggerFor",
+ # From ._observer
+ "LogPublisher",
+ # From ._buffer
+ "LimitedHistoryLogObserver",
+ # From ._file
+ "FileLogObserver",
+ "textFileLogObserver",
+ # From ._filter
+ "PredicateResult",
+ "ILogFilterPredicate",
+ "FilteringLogObserver",
+ "LogLevelFilterPredicate",
+ # From ._stdlib
+ "STDLibLogObserver",
+ # From ._io
+ "LoggingFile",
+ # From ._legacy
+ "LegacyLogObserverWrapper",
+ # From ._global
+ "globalLogPublisher",
+ "globalLogBeginner",
+ "LogBeginner",
+ # From ._json
+ "eventAsJSON",
+ "eventFromJSON",
+ "jsonFileLogObserver",
+ "eventsFromJSONLogFile",
+ # From ._capture
+ "capturedLogs",
+]
+
+from ._levels import InvalidLogLevelError, LogLevel
+
+from ._flatten import extractField
+
+from ._format import (
+ formatEvent,
+ formatEventAsClassicLogText,
+ formatTime,
+ timeFormatRFC3339,
+ eventAsText,
+)
+
+from ._interfaces import ILogObserver, LogEvent
+
+from ._logger import Logger, _loggerFor
+
+from ._observer import LogPublisher
+
+from ._buffer import LimitedHistoryLogObserver
+
+from ._file import FileLogObserver, textFileLogObserver
+
+from ._filter import (
+ PredicateResult,
+ ILogFilterPredicate,
+ FilteringLogObserver,
+ LogLevelFilterPredicate,
+)
+
+from ._stdlib import STDLibLogObserver
+
+from ._io import LoggingFile
+
+from ._legacy import LegacyLogObserverWrapper
+
+from ._global import globalLogPublisher, globalLogBeginner, LogBeginner
+
+from ._json import (
+ eventAsJSON,
+ eventFromJSON,
+ jsonFileLogObserver,
+ eventsFromJSONLogFile,
+)
+
+from ._capture import capturedLogs
diff --git a/contrib/python/Twisted/py3/twisted/logger/_buffer.py b/contrib/python/Twisted/py3/twisted/logger/_buffer.py
new file mode 100644
index 0000000000..d5e514f18b
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_buffer.py
@@ -0,0 +1,54 @@
+# -*- test-case-name: twisted.logger.test.test_buffer -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Log observer that maintains a buffer.
+"""
+
+from collections import deque
+from typing import Deque, Optional
+
+from zope.interface import implementer
+
+from ._interfaces import ILogObserver, LogEvent
+
+_DEFAULT_BUFFER_MAXIMUM = 64 * 1024
+
+
+@implementer(ILogObserver)
+class LimitedHistoryLogObserver:
+ """
+ L{ILogObserver} that stores events in a buffer of a fixed size::
+
+ >>> from twisted.logger import LimitedHistoryLogObserver
+ >>> history = LimitedHistoryLogObserver(5)
+ >>> for n in range(10): history({'n': n})
+ ...
+ >>> repeats = []
+ >>> history.replayTo(repeats.append)
+ >>> len(repeats)
+ 5
+ >>> repeats
+ [{'n': 5}, {'n': 6}, {'n': 7}, {'n': 8}, {'n': 9}]
+ >>>
+ """
+
+ def __init__(self, size: Optional[int] = _DEFAULT_BUFFER_MAXIMUM) -> None:
+ """
+ @param size: The maximum number of events to buffer. If L{None}, the
+ buffer is unbounded.
+ """
+ self._buffer: Deque[LogEvent] = deque(maxlen=size)
+
+ def __call__(self, event: LogEvent) -> None:
+ self._buffer.append(event)
+
+ def replayTo(self, otherObserver: ILogObserver) -> None:
+ """
+ Re-play the buffered events to another log observer.
+
+ @param otherObserver: An observer to replay events to.
+ """
+ for event in self._buffer:
+ otherObserver(event)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_capture.py b/contrib/python/Twisted/py3/twisted/logger/_capture.py
new file mode 100644
index 0000000000..9d3ce0e3ab
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_capture.py
@@ -0,0 +1,25 @@
+# -*- test-case-name: twisted.logger.test.test_capture -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Context manager for capturing logs.
+"""
+
+from contextlib import contextmanager
+from typing import Iterator, List, Sequence, cast
+
+from twisted.logger import globalLogPublisher
+from ._interfaces import ILogObserver, LogEvent
+
+
+@contextmanager
+def capturedLogs() -> Iterator[Sequence[LogEvent]]:
+ events: List[LogEvent] = []
+ observer = cast(ILogObserver, events.append)
+
+ globalLogPublisher.addObserver(observer)
+
+ yield events
+
+ globalLogPublisher.removeObserver(observer)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_file.py b/contrib/python/Twisted/py3/twisted/logger/_file.py
new file mode 100644
index 0000000000..43ae32cd29
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_file.py
@@ -0,0 +1,77 @@
+# -*- test-case-name: twisted.logger.test.test_file -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+File log observer.
+"""
+
+from typing import IO, Any, Callable, Optional
+
+from zope.interface import implementer
+
+from twisted.python.compat import ioType
+from ._format import formatEventAsClassicLogText, formatTime, timeFormatRFC3339
+from ._interfaces import ILogObserver, LogEvent
+
+
+@implementer(ILogObserver)
+class FileLogObserver:
+ """
+ Log observer that writes to a file-like object.
+ """
+
+ def __init__(
+ self, outFile: IO[Any], formatEvent: Callable[[LogEvent], Optional[str]]
+ ) -> None:
+ """
+ @param outFile: A file-like object. Ideally one should be passed which
+ accepts text data. Otherwise, UTF-8 L{bytes} will be used.
+ @param formatEvent: A callable that formats an event.
+ """
+ if ioType(outFile) is not str:
+ self._encoding: Optional[str] = "utf-8"
+ else:
+ self._encoding = None
+
+ self._outFile = outFile
+ self.formatEvent = formatEvent
+
+ def __call__(self, event: LogEvent) -> None:
+ """
+ Write event to file.
+
+ @param event: An event.
+ """
+ text = self.formatEvent(event)
+
+ if text:
+ if self._encoding is None:
+ self._outFile.write(text)
+ else:
+ self._outFile.write(text.encode(self._encoding))
+ self._outFile.flush()
+
+
+def textFileLogObserver(
+ outFile: IO[Any], timeFormat: Optional[str] = timeFormatRFC3339
+) -> FileLogObserver:
+ """
+ Create a L{FileLogObserver} that emits text to a specified (writable)
+ file-like object.
+
+ @param outFile: A file-like object. Ideally one should be passed which
+ accepts text data. Otherwise, UTF-8 L{bytes} will be used.
+ @param timeFormat: The format to use when adding timestamp prefixes to
+ logged events. If L{None}, or for events with no C{"log_timestamp"}
+ key, the default timestamp prefix of C{"-"} is used.
+
+ @return: A file log observer.
+ """
+
+ def formatEvent(event: LogEvent) -> Optional[str]:
+ return formatEventAsClassicLogText(
+ event, formatTime=lambda e: formatTime(e, timeFormat)
+ )
+
+ return FileLogObserver(outFile, formatEvent)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_filter.py b/contrib/python/Twisted/py3/twisted/logger/_filter.py
new file mode 100644
index 0000000000..fa4220ea3e
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_filter.py
@@ -0,0 +1,211 @@
+# -*- test-case-name: twisted.logger.test.test_filter -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Filtering log observer.
+"""
+
+from functools import partial
+from typing import Dict, Iterable
+
+from zope.interface import Interface, implementer
+
+from constantly import NamedConstant, Names # type: ignore[import]
+
+from ._interfaces import ILogObserver, LogEvent
+from ._levels import InvalidLogLevelError, LogLevel
+from ._observer import bitbucketLogObserver
+
+
+class PredicateResult(Names):
+ """
+ Predicate results.
+
+ @see: L{LogLevelFilterPredicate}
+
+ @cvar yes: Log the specified event. When this value is used,
+ L{FilteringLogObserver} will always log the message, without
+ evaluating other predicates.
+
+ @cvar no: Do not log the specified event. When this value is used,
+ L{FilteringLogObserver} will I{not} log the message, without
+ evaluating other predicates.
+
+ @cvar maybe: Do not have an opinion on the event. When this value is used,
+ L{FilteringLogObserver} will consider subsequent predicate results;
+ if returned by the last predicate being considered, then the event will
+ be logged.
+ """
+
+ yes = NamedConstant()
+ no = NamedConstant()
+ maybe = NamedConstant()
+
+
+class ILogFilterPredicate(Interface):
+ """
+ A predicate that determined whether an event should be logged.
+ """
+
+ def __call__(event: LogEvent) -> NamedConstant:
+ """
+ Determine whether an event should be logged.
+
+ @returns: a L{PredicateResult}.
+ """
+
+
+def shouldLogEvent(predicates: Iterable[ILogFilterPredicate], event: LogEvent) -> bool:
+ """
+ Determine whether an event should be logged, based on the result of
+ C{predicates}.
+
+ By default, the result is C{True}; so if there are no predicates,
+ everything will be logged.
+
+ If any predicate returns C{yes}, then we will immediately return C{True}.
+
+ If any predicate returns C{no}, then we will immediately return C{False}.
+
+ As predicates return C{maybe}, we keep calling the next predicate until we
+ run out, at which point we return C{True}.
+
+ @param predicates: The predicates to use.
+ @param event: An event
+
+ @return: True if the message should be forwarded on, C{False} if not.
+ """
+ for predicate in predicates:
+ result = predicate(event)
+ if result == PredicateResult.yes:
+ return True
+ if result == PredicateResult.no:
+ return False
+ if result == PredicateResult.maybe:
+ continue
+ raise TypeError(f"Invalid predicate result: {result!r}")
+ return True
+
+
+@implementer(ILogObserver)
+class FilteringLogObserver:
+ """
+ L{ILogObserver} that wraps another L{ILogObserver}, but filters out events
+ based on applying a series of L{ILogFilterPredicate}s.
+ """
+
+ def __init__(
+ self,
+ observer: ILogObserver,
+ predicates: Iterable[ILogFilterPredicate],
+ negativeObserver: ILogObserver = bitbucketLogObserver,
+ ) -> None:
+ """
+ @param observer: An observer to which this observer will forward
+ events when C{predictates} yield a positive result.
+ @param predicates: Predicates to apply to events before forwarding to
+ the wrapped observer.
+ @param negativeObserver: An observer to which this observer will
+ forward events when C{predictates} yield a negative result.
+ """
+ self._observer = observer
+ self._shouldLogEvent = partial(shouldLogEvent, list(predicates))
+ self._negativeObserver = negativeObserver
+
+ def __call__(self, event: LogEvent) -> None:
+ """
+ Forward to next observer if predicate allows it.
+ """
+ if self._shouldLogEvent(event):
+ if "log_trace" in event:
+ event["log_trace"].append((self, self._observer))
+ self._observer(event)
+ else:
+ self._negativeObserver(event)
+
+
+@implementer(ILogFilterPredicate)
+class LogLevelFilterPredicate:
+ """
+ L{ILogFilterPredicate} that filters out events with a log level lower than
+ the log level for the event's namespace.
+
+ Events that not not have a log level or namespace are also dropped.
+ """
+
+ def __init__(self, defaultLogLevel: NamedConstant = LogLevel.info) -> None:
+ """
+ @param defaultLogLevel: The default minimum log level.
+ """
+ self._logLevelsByNamespace: Dict[str, NamedConstant] = {}
+ self.defaultLogLevel = defaultLogLevel
+ self.clearLogLevels()
+
+ def logLevelForNamespace(self, namespace: str) -> NamedConstant:
+ """
+ Determine an appropriate log level for the given namespace.
+
+ This respects dots in namespaces; for example, if you have previously
+ invoked C{setLogLevelForNamespace("mypackage", LogLevel.debug)}, then
+ C{logLevelForNamespace("mypackage.subpackage")} will return
+ C{LogLevel.debug}.
+
+ @param namespace: A logging namespace. Use C{""} for the default
+ namespace.
+
+ @return: The log level for the specified namespace.
+ """
+ if not namespace:
+ return self._logLevelsByNamespace[""]
+
+ if namespace in self._logLevelsByNamespace:
+ return self._logLevelsByNamespace[namespace]
+
+ segments = namespace.split(".")
+ index = len(segments) - 1
+
+ while index > 0:
+ namespace = ".".join(segments[:index])
+ if namespace in self._logLevelsByNamespace:
+ return self._logLevelsByNamespace[namespace]
+ index -= 1
+
+ return self._logLevelsByNamespace[""]
+
+ def setLogLevelForNamespace(self, namespace: str, level: NamedConstant) -> None:
+ """
+ Sets the log level for a logging namespace.
+
+ @param namespace: A logging namespace.
+ @param level: The log level for the given namespace.
+ """
+ if level not in LogLevel.iterconstants():
+ raise InvalidLogLevelError(level)
+
+ if namespace:
+ self._logLevelsByNamespace[namespace] = level
+ else:
+ self._logLevelsByNamespace[""] = level
+
+ def clearLogLevels(self) -> None:
+ """
+ Clears all log levels to the default.
+ """
+ self._logLevelsByNamespace.clear()
+ self._logLevelsByNamespace[""] = self.defaultLogLevel
+
+ def __call__(self, event: LogEvent) -> NamedConstant:
+ eventLevel = event.get("log_level", None)
+ if eventLevel is None:
+ return PredicateResult.no
+
+ namespace = event.get("log_namespace", "")
+ if not namespace:
+ return PredicateResult.no
+
+ namespaceLevel = self.logLevelForNamespace(namespace)
+ if eventLevel < namespaceLevel:
+ return PredicateResult.no
+
+ return PredicateResult.maybe
diff --git a/contrib/python/Twisted/py3/twisted/logger/_flatten.py b/contrib/python/Twisted/py3/twisted/logger/_flatten.py
new file mode 100644
index 0000000000..b79476aa24
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_flatten.py
@@ -0,0 +1,175 @@
+# -*- test-case-name: twisted.logger.test.test_flatten -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Code related to "flattening" events; that is, extracting a description of all
+relevant fields from the format string and persisting them for later
+examination.
+"""
+
+from collections import defaultdict
+from string import Formatter
+from typing import Any, Dict, Optional
+
+from ._interfaces import LogEvent
+
+aFormatter = Formatter()
+
+
+class KeyFlattener:
+ """
+ A L{KeyFlattener} computes keys for the things within curly braces in
+ PEP-3101-style format strings as parsed by L{string.Formatter.parse}.
+ """
+
+ def __init__(self) -> None:
+ """
+ Initialize a L{KeyFlattener}.
+ """
+ self.keys: Dict[str, int] = defaultdict(lambda: 0)
+
+ def flatKey(
+ self, fieldName: str, formatSpec: Optional[str], conversion: Optional[str]
+ ) -> str:
+ """
+ Compute a string key for a given field/format/conversion.
+
+ @param fieldName: A format field name.
+ @param formatSpec: A format spec.
+ @param conversion: A format field conversion type.
+
+ @return: A key specific to the given field, format and conversion, as
+ well as the occurrence of that combination within this
+ L{KeyFlattener}'s lifetime.
+ """
+ if formatSpec is None:
+ formatSpec = ""
+
+ if conversion is None:
+ conversion = ""
+
+ result = "{fieldName}!{conversion}:{formatSpec}".format(
+ fieldName=fieldName,
+ formatSpec=formatSpec,
+ conversion=conversion,
+ )
+ self.keys[result] += 1
+ n = self.keys[result]
+ if n != 1:
+ result += "/" + str(self.keys[result])
+ return result
+
+
+def flattenEvent(event: LogEvent) -> None:
+ """
+ Flatten the given event by pre-associating format fields with specific
+ objects and callable results in a L{dict} put into the C{"log_flattened"}
+ key in the event.
+
+ @param event: A logging event.
+ """
+ if event.get("log_format", None) is None:
+ return
+
+ if "log_flattened" in event:
+ fields = event["log_flattened"]
+ else:
+ fields = {}
+
+ keyFlattener = KeyFlattener()
+
+ for literalText, fieldName, formatSpec, conversion in aFormatter.parse(
+ event["log_format"]
+ ):
+ if fieldName is None:
+ continue
+
+ if conversion != "r":
+ conversion = "s"
+
+ flattenedKey = keyFlattener.flatKey(fieldName, formatSpec, conversion)
+ structuredKey = keyFlattener.flatKey(fieldName, formatSpec, "")
+
+ if flattenedKey in fields:
+ # We've already seen and handled this key
+ continue
+
+ if fieldName.endswith("()"):
+ fieldName = fieldName[:-2]
+ callit = True
+ else:
+ callit = False
+
+ field = aFormatter.get_field(fieldName, (), event)
+ fieldValue = field[0]
+
+ if conversion == "r":
+ conversionFunction = repr
+ else: # Above: if conversion is not "r", it's "s"
+ conversionFunction = str
+
+ if callit:
+ fieldValue = fieldValue()
+
+ flattenedValue = conversionFunction(fieldValue)
+ fields[flattenedKey] = flattenedValue
+ fields[structuredKey] = fieldValue
+
+ if fields:
+ event["log_flattened"] = fields
+
+
+def extractField(field: str, event: LogEvent) -> Any:
+ """
+ Extract a given format field from the given event.
+
+ @param field: A string describing a format field or log key. This is the
+ text that would normally fall between a pair of curly braces in a
+ format string: for example, C{"key[2].attribute"}. If a conversion is
+ specified (the thing after the C{"!"} character in a format field) then
+ the result will always be str.
+ @param event: A log event.
+
+ @return: A value extracted from the field.
+
+ @raise KeyError: if the field is not found in the given event.
+ """
+ keyFlattener = KeyFlattener()
+
+ [[literalText, fieldName, formatSpec, conversion]] = aFormatter.parse(
+ "{" + field + "}"
+ )
+
+ assert fieldName is not None
+
+ key = keyFlattener.flatKey(fieldName, formatSpec, conversion)
+
+ if "log_flattened" not in event:
+ flattenEvent(event)
+
+ return event["log_flattened"][key]
+
+
+def flatFormat(event: LogEvent) -> str:
+ """
+ Format an event which has been flattened with L{flattenEvent}.
+
+ @param event: A logging event.
+
+ @return: A formatted string.
+ """
+ fieldValues = event["log_flattened"]
+ keyFlattener = KeyFlattener()
+ s = []
+
+ for literalText, fieldName, formatSpec, conversion in aFormatter.parse(
+ event["log_format"]
+ ):
+ s.append(literalText)
+
+ if fieldName is not None:
+ key = keyFlattener.flatKey(fieldName, formatSpec, conversion or "s")
+ s.append(str(fieldValues[key]))
+
+ return "".join(s)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_format.py b/contrib/python/Twisted/py3/twisted/logger/_format.py
new file mode 100644
index 0000000000..4bc06ec40c
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_format.py
@@ -0,0 +1,373 @@
+# -*- test-case-name: twisted.logger.test.test_format -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tools for formatting logging events.
+"""
+
+from datetime import datetime as DateTime
+from typing import Any, Callable, Iterator, Mapping, Optional, Union, cast
+
+from constantly import NamedConstant # type: ignore[import]
+
+from twisted.python._tzhelper import FixedOffsetTimeZone
+from twisted.python.failure import Failure
+from twisted.python.reflect import safe_repr
+from ._flatten import aFormatter, flatFormat
+from ._interfaces import LogEvent
+
+timeFormatRFC3339 = "%Y-%m-%dT%H:%M:%S%z"
+
+
+def formatEvent(event: LogEvent) -> str:
+ """
+ Formats an event as text, using the format in C{event["log_format"]}.
+
+ This implementation should never raise an exception; if the formatting
+ cannot be done, the returned string will describe the event generically so
+ that a useful message is emitted regardless.
+
+ @param event: A logging event.
+
+ @return: A formatted string.
+ """
+ return eventAsText(
+ event,
+ includeTraceback=False,
+ includeTimestamp=False,
+ includeSystem=False,
+ )
+
+
+def formatUnformattableEvent(event: LogEvent, error: BaseException) -> str:
+ """
+ Formats an event as text that describes the event generically and a
+ formatting error.
+
+ @param event: A logging event.
+ @param error: The formatting error.
+
+ @return: A formatted string.
+ """
+ try:
+ return "Unable to format event {event!r}: {error}".format(
+ event=event, error=error
+ )
+ except BaseException:
+ # Yikes, something really nasty happened.
+ #
+ # Try to recover as much formattable data as possible; hopefully at
+ # least the namespace is sane, which will help you find the offending
+ # logger.
+ failure = Failure()
+
+ text = ", ".join(
+ " = ".join((safe_repr(key), safe_repr(value)))
+ for key, value in event.items()
+ )
+
+ return (
+ "MESSAGE LOST: unformattable object logged: {error}\n"
+ "Recoverable data: {text}\n"
+ "Exception during formatting:\n{failure}".format(
+ error=safe_repr(error), failure=failure, text=text
+ )
+ )
+
+
+def formatTime(
+ when: Optional[float],
+ timeFormat: Optional[str] = timeFormatRFC3339,
+ default: str = "-",
+) -> str:
+ """
+ Format a timestamp as text.
+
+ Example::
+
+ >>> from time import time
+ >>> from twisted.logger import formatTime
+ >>>
+ >>> t = time()
+ >>> formatTime(t)
+ u'2013-10-22T14:19:11-0700'
+ >>> formatTime(t, timeFormat="%Y/%W") # Year and week number
+ u'2013/42'
+ >>>
+
+ @param when: A timestamp.
+ @param timeFormat: A time format.
+ @param default: Text to return if C{when} or C{timeFormat} is L{None}.
+
+ @return: A formatted time.
+ """
+ if timeFormat is None or when is None:
+ return default
+ else:
+ tz = FixedOffsetTimeZone.fromLocalTimeStamp(when)
+ datetime = DateTime.fromtimestamp(when, tz)
+ return str(datetime.strftime(timeFormat))
+
+
+def formatEventAsClassicLogText(
+ event: LogEvent, formatTime: Callable[[Optional[float]], str] = formatTime
+) -> Optional[str]:
+ """
+ Format an event as a line of human-readable text for, e.g. traditional log
+ file output.
+
+ The output format is C{"{timeStamp} [{system}] {event}\\n"}, where:
+
+ - C{timeStamp} is computed by calling the given C{formatTime} callable
+ on the event's C{"log_time"} value
+
+ - C{system} is the event's C{"log_system"} value, if set, otherwise,
+ the C{"log_namespace"} and C{"log_level"}, joined by a C{"#"}. Each
+ defaults to C{"-"} is not set.
+
+ - C{event} is the event, as formatted by L{formatEvent}.
+
+ Example::
+
+ >>> from time import time
+ >>> from twisted.logger import formatEventAsClassicLogText
+ >>> from twisted.logger import LogLevel
+ >>>
+ >>> formatEventAsClassicLogText(dict()) # No format, returns None
+ >>> formatEventAsClassicLogText(dict(log_format="Hello!"))
+ u'- [-#-] Hello!\\n'
+ >>> formatEventAsClassicLogText(dict(
+ ... log_format="Hello!",
+ ... log_time=time(),
+ ... log_namespace="my_namespace",
+ ... log_level=LogLevel.info,
+ ... ))
+ u'2013-10-22T17:30:02-0700 [my_namespace#info] Hello!\\n'
+ >>> formatEventAsClassicLogText(dict(
+ ... log_format="Hello!",
+ ... log_time=time(),
+ ... log_system="my_system",
+ ... ))
+ u'2013-11-11T17:22:06-0800 [my_system] Hello!\\n'
+ >>>
+
+ @param event: an event.
+ @param formatTime: A time formatter
+
+ @return: A formatted event, or L{None} if no output is appropriate.
+ """
+ eventText = eventAsText(event, formatTime=formatTime)
+ if not eventText:
+ return None
+ eventText = eventText.replace("\n", "\n\t")
+ return eventText + "\n"
+
+
+class CallMapping(Mapping[str, Any]):
+ """
+ Read-only mapping that turns a C{()}-suffix in key names into an invocation
+ of the key rather than a lookup of the key.
+
+ Implementation support for L{formatWithCall}.
+ """
+
+ def __init__(self, submapping: Mapping[str, Any]) -> None:
+ """
+ @param submapping: Another read-only mapping which will be used to look
+ up items.
+ """
+ self._submapping = submapping
+
+ def __iter__(self) -> Iterator[Any]:
+ return iter(self._submapping)
+
+ def __len__(self) -> int:
+ return len(self._submapping)
+
+ def __getitem__(self, key: str) -> Any:
+ """
+ Look up an item in the submapping for this L{CallMapping}, calling it
+ if C{key} ends with C{"()"}.
+ """
+ callit = key.endswith("()")
+ realKey = key[:-2] if callit else key
+ value = self._submapping[realKey]
+ if callit:
+ value = value()
+ return value
+
+
+def formatWithCall(formatString: str, mapping: Mapping[str, Any]) -> str:
+ """
+ Format a string like L{str.format}, but:
+
+ - taking only a name mapping; no positional arguments
+
+ - with the additional syntax that an empty set of parentheses
+ correspond to a formatting item that should be called, and its result
+ C{str}'d, rather than calling C{str} on the element directly as
+ normal.
+
+ For example::
+
+ >>> formatWithCall("{string}, {function()}.",
+ ... dict(string="just a string",
+ ... function=lambda: "a function"))
+ 'just a string, a function.'
+
+ @param formatString: A PEP-3101 format string.
+ @param mapping: A L{dict}-like object to format.
+
+ @return: The string with formatted values interpolated.
+ """
+ return str(aFormatter.vformat(formatString, (), CallMapping(mapping)))
+
+
+def _formatEvent(event: LogEvent) -> str:
+ """
+ Formats an event as a string, using the format in C{event["log_format"]}.
+
+ This implementation should never raise an exception; if the formatting
+ cannot be done, the returned string will describe the event generically so
+ that a useful message is emitted regardless.
+
+ @param event: A logging event.
+
+ @return: A formatted string.
+ """
+ try:
+ if "log_flattened" in event:
+ return flatFormat(event)
+
+ format = cast(Optional[Union[str, bytes]], event.get("log_format", None))
+ if format is None:
+ return ""
+
+ # Make sure format is text.
+ if isinstance(format, str):
+ pass
+ elif isinstance(format, bytes):
+ format = format.decode("utf-8")
+ else:
+ raise TypeError(f"Log format must be str, not {format!r}")
+
+ return formatWithCall(format, event)
+
+ except BaseException as e:
+ return formatUnformattableEvent(event, e)
+
+
+def _formatTraceback(failure: Failure) -> str:
+ """
+ Format a failure traceback, assuming UTF-8 and using a replacement
+ strategy for errors. Every effort is made to provide a usable
+ traceback, but should not that not be possible, a message and the
+ captured exception are logged.
+
+ @param failure: The failure to retrieve a traceback from.
+
+ @return: The formatted traceback.
+ """
+ try:
+ traceback = failure.getTraceback()
+ except BaseException as e:
+ traceback = "(UNABLE TO OBTAIN TRACEBACK FROM EVENT):" + str(e)
+ return traceback
+
+
+def _formatSystem(event: LogEvent) -> str:
+ """
+ Format the system specified in the event in the "log_system" key if set,
+ otherwise the C{"log_namespace"} and C{"log_level"}, joined by a C{"#"}.
+ Each defaults to C{"-"} is not set. If formatting fails completely,
+ "UNFORMATTABLE" is returned.
+
+ @param event: The event containing the system specification.
+
+ @return: A formatted string representing the "log_system" key.
+ """
+ system = cast(Optional[str], event.get("log_system", None))
+ if system is None:
+ level = cast(Optional[NamedConstant], event.get("log_level", None))
+ if level is None:
+ levelName = "-"
+ else:
+ levelName = level.name
+
+ system = "{namespace}#{level}".format(
+ namespace=cast(str, event.get("log_namespace", "-")),
+ level=levelName,
+ )
+ else:
+ try:
+ system = str(system)
+ except Exception:
+ system = "UNFORMATTABLE"
+ return system
+
+
+def eventAsText(
+ event: LogEvent,
+ includeTraceback: bool = True,
+ includeTimestamp: bool = True,
+ includeSystem: bool = True,
+ formatTime: Callable[[float], str] = formatTime,
+) -> str:
+ r"""
+ Format an event as text. Optionally, attach timestamp, traceback, and
+ system information.
+
+ The full output format is:
+ C{"{timeStamp} [{system}] {event}\n{traceback}\n"} where:
+
+ - C{timeStamp} is the event's C{"log_time"} value formatted with
+ the provided C{formatTime} callable.
+
+ - C{system} is the event's C{"log_system"} value, if set, otherwise,
+ the C{"log_namespace"} and C{"log_level"}, joined by a C{"#"}. Each
+ defaults to C{"-"} is not set.
+
+ - C{event} is the event, as formatted by L{formatEvent}.
+
+ - C{traceback} is the traceback if the event contains a
+ C{"log_failure"} key. In the event the original traceback cannot
+ be formatted, a message indicating the failure will be substituted.
+
+ If the event cannot be formatted, and no traceback exists, an empty string
+ is returned, even if includeSystem or includeTimestamp are true.
+
+ @param event: A logging event.
+ @param includeTraceback: If true and a C{"log_failure"} key exists, append
+ a traceback.
+ @param includeTimestamp: If true include a formatted timestamp before the
+ event.
+ @param includeSystem: If true, include the event's C{"log_system"} value.
+ @param formatTime: A time formatter
+
+ @return: A formatted string with specified options.
+
+ @since: Twisted 18.9.0
+ """
+ eventText = _formatEvent(event)
+ if includeTraceback and "log_failure" in event:
+ f = event["log_failure"]
+ traceback = _formatTraceback(f)
+ eventText = "\n".join((eventText, traceback))
+
+ if not eventText:
+ return eventText
+
+ timeStamp = ""
+ if includeTimestamp:
+ timeStamp = "".join([formatTime(cast(float, event.get("log_time", None))), " "])
+
+ system = ""
+ if includeSystem:
+ system = "".join(["[", _formatSystem(event), "]", " "])
+
+ return "{timeStamp}{system}{eventText}".format(
+ timeStamp=timeStamp,
+ system=system,
+ eventText=eventText,
+ )
diff --git a/contrib/python/Twisted/py3/twisted/logger/_global.py b/contrib/python/Twisted/py3/twisted/logger/_global.py
new file mode 100644
index 0000000000..8ae89baf72
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_global.py
@@ -0,0 +1,226 @@
+# -*- test-case-name: twisted.logger.test.test_global -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module includes process-global state associated with the logging system,
+and implementation of logic for managing that global state.
+"""
+
+import sys
+import warnings
+from typing import IO, Any, Iterable, Optional, Type
+
+from twisted.python.compat import currentframe
+from twisted.python.reflect import qual
+from ._buffer import LimitedHistoryLogObserver
+from ._file import FileLogObserver
+from ._filter import FilteringLogObserver, LogLevelFilterPredicate
+from ._format import eventAsText
+from ._interfaces import ILogObserver
+from ._io import LoggingFile
+from ._levels import LogLevel
+from ._logger import Logger
+from ._observer import LogPublisher
+
+MORE_THAN_ONCE_WARNING = (
+ "Warning: primary log target selected twice at <{fileNow}:{lineNow}> - "
+ "previously selected at <{fileThen}:{lineThen}>. Remove one of the calls "
+ "to beginLoggingTo."
+)
+
+
+class LogBeginner:
+ """
+ A L{LogBeginner} holds state related to logging before logging has begun,
+ and begins logging when told to do so. Logging "begins" when someone has
+ selected a set of observers, like, for example, a L{FileLogObserver} that
+ writes to a file on disk, or to standard output.
+
+ Applications will not typically need to instantiate this class, except
+ those which intend to initialize the global logging system themselves,
+ which may wish to instantiate this for testing. The global instance for
+ the current process is exposed as
+ L{twisted.logger.globalLogBeginner}.
+
+ Before logging has begun, a L{LogBeginner} will:
+
+ 1. Log any critical messages (e.g.: unhandled exceptions) to the given
+ file-like object.
+
+ 2. Save (a limited number of) log events in a
+ L{LimitedHistoryLogObserver}.
+
+ @cvar _DEFAULT_BUFFER_SIZE: The default size for the initial log events
+ buffer.
+
+ @ivar _initialBuffer: A buffer of messages logged before logging began.
+ @ivar _publisher: The log publisher passed in to L{LogBeginner}'s
+ constructor.
+ @ivar _log: The logger used to log messages about the operation of the
+ L{LogBeginner} itself.
+ @ivar _stdio: An object with C{stderr} and C{stdout} attributes (like the
+ L{sys} module) which will be replaced when redirecting standard I/O.
+ @ivar _temporaryObserver: If not L{None}, an L{ILogObserver} that observes
+ events on C{_publisher} for this L{LogBeginner}.
+ """
+
+ _DEFAULT_BUFFER_SIZE = 200
+
+ def __init__(
+ self,
+ publisher: LogPublisher,
+ errorStream: IO[Any],
+ stdio: object,
+ warningsModule: Any,
+ initialBufferSize: Optional[int] = None,
+ ) -> None:
+ """
+ Initialize this L{LogBeginner}.
+
+ @param initialBufferSize: The size of the event buffer into which
+ events are collected until C{beginLoggingTo} is called. Or
+ C{None} to use the default size.
+ """
+ if initialBufferSize is None:
+ initialBufferSize = self._DEFAULT_BUFFER_SIZE
+ self._initialBuffer = LimitedHistoryLogObserver(size=initialBufferSize)
+ self._publisher = publisher
+ self._log = Logger(observer=publisher)
+ self._stdio = stdio
+ self._warningsModule = warningsModule
+ self._temporaryObserver: Optional[ILogObserver] = LogPublisher(
+ self._initialBuffer,
+ FilteringLogObserver(
+ FileLogObserver(
+ errorStream,
+ lambda event: eventAsText(
+ event,
+ includeTimestamp=False,
+ includeSystem=False,
+ )
+ + "\n",
+ ),
+ [LogLevelFilterPredicate(defaultLogLevel=LogLevel.critical)],
+ ),
+ )
+ self._previousBegin = ("", 0)
+ publisher.addObserver(self._temporaryObserver)
+ self._oldshowwarning = warningsModule.showwarning
+
+ def beginLoggingTo(
+ self,
+ observers: Iterable[ILogObserver],
+ discardBuffer: bool = False,
+ redirectStandardIO: bool = True,
+ ) -> None:
+ """
+ Begin logging to the given set of observers. This will:
+
+ 1. Add all the observers given in C{observers} to the
+ L{LogPublisher} associated with this L{LogBeginner}.
+
+ 2. Optionally re-direct standard output and standard error streams
+ to the logging system.
+
+ 3. Re-play any messages that were previously logged to that
+ publisher to the new observers, if C{discardBuffer} is not set.
+
+ 4. Stop logging critical errors from the L{LogPublisher} as strings
+ to the C{errorStream} associated with this L{LogBeginner}, and
+ allow them to be logged normally.
+
+ 5. Re-direct warnings from the L{warnings} module associated with
+ this L{LogBeginner} to log messages.
+
+ @note: Since a L{LogBeginner} is designed to encapsulate the transition
+ between process-startup and log-system-configuration, this method
+ is intended to be invoked I{once}.
+
+ @param observers: The observers to register.
+ @param discardBuffer: Whether to discard the buffer and not re-play it
+ to the added observers. (This argument is provided mainly for
+ compatibility with legacy concerns.)
+ @param redirectStandardIO: If true, redirect standard output and
+ standard error to the observers.
+ """
+ caller = currentframe(1)
+ filename = caller.f_code.co_filename
+ lineno = caller.f_lineno
+
+ for observer in observers:
+ self._publisher.addObserver(observer)
+
+ if self._temporaryObserver is not None:
+ self._publisher.removeObserver(self._temporaryObserver)
+ if not discardBuffer:
+ self._initialBuffer.replayTo(self._publisher)
+ self._temporaryObserver = None
+ self._warningsModule.showwarning = self.showwarning
+ else:
+ previousFile, previousLine = self._previousBegin
+ self._log.warn(
+ MORE_THAN_ONCE_WARNING,
+ fileNow=filename,
+ lineNow=lineno,
+ fileThen=previousFile,
+ lineThen=previousLine,
+ )
+
+ self._previousBegin = (filename, lineno)
+ if redirectStandardIO:
+ streams = [("stdout", LogLevel.info), ("stderr", LogLevel.error)]
+ else:
+ streams = []
+
+ for stream, level in streams:
+ oldStream = getattr(self._stdio, stream)
+ loggingFile = LoggingFile(
+ logger=Logger(namespace=stream, observer=self._publisher),
+ level=level,
+ encoding=getattr(oldStream, "encoding", None),
+ )
+ setattr(self._stdio, stream, loggingFile)
+
+ def showwarning(
+ self,
+ message: str,
+ category: Type[Warning],
+ filename: str,
+ lineno: int,
+ file: Optional[IO[Any]] = None,
+ line: Optional[str] = None,
+ ) -> None:
+ """
+ Twisted-enabled wrapper around L{warnings.showwarning}.
+
+ If C{file} is L{None}, the default behaviour is to emit the warning to
+ the log system, otherwise the original L{warnings.showwarning} Python
+ function is called.
+
+ @param message: A warning message to emit.
+ @param category: A warning category to associate with C{message}.
+ @param filename: A file name for the source code file issuing the
+ warning.
+ @param lineno: A line number in the source file where the warning was
+ issued.
+ @param file: A file to write the warning message to. If L{None},
+ write to L{sys.stderr}.
+ @param line: A line of source code to include with the warning message.
+ If L{None}, attempt to read the line from C{filename} and
+ C{lineno}.
+ """
+ if file is None:
+ self._log.warn(
+ "{filename}:{lineno}: {category}: {warning}",
+ warning=message,
+ category=qual(category),
+ filename=filename,
+ lineno=lineno,
+ )
+ else:
+ self._oldshowwarning(message, category, filename, lineno, file, line)
+
+
+globalLogPublisher = LogPublisher()
+globalLogBeginner = LogBeginner(globalLogPublisher, sys.stderr, sys, warnings)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_interfaces.py b/contrib/python/Twisted/py3/twisted/logger/_interfaces.py
new file mode 100644
index 0000000000..496de1de54
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_interfaces.py
@@ -0,0 +1,63 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Logger interfaces.
+"""
+
+from typing import TYPE_CHECKING, Any, Dict, List, Tuple
+
+from zope.interface import Interface
+
+if TYPE_CHECKING:
+ from ._logger import Logger
+
+
+LogEvent = Dict[str, Any]
+LogTrace = List[Tuple["Logger", "ILogObserver"]]
+
+
+class ILogObserver(Interface):
+ """
+ An observer which can handle log events.
+
+ Unlike most interfaces within Twisted, an L{ILogObserver} I{must be
+ thread-safe}. Log observers may be called indiscriminately from many
+ different threads, as any thread may wish to log a message at any time.
+ """
+
+ def __call__(event: LogEvent) -> None:
+ """
+ Log an event.
+
+ @param event: A dictionary with arbitrary keys as defined by the
+ application emitting logging events, as well as keys added by the
+ logging system. The logging system reserves the right to set any
+ key beginning with the prefix C{"log_"}; applications should not
+ use any key so named. Currently, the following keys are used by
+ the logging system in some way, if they are present (they are all
+ optional):
+
+ - C{"log_format"}: a PEP-3101-style format string which draws
+ upon the keys in the event as its values, used to format the
+ event for human consumption.
+
+ - C{"log_flattened"}: a dictionary mapping keys derived from
+ the names and format values used in the C{"log_format"}
+ string to their values. This is used to preserve some
+ structured information for use with
+ L{twisted.logger.extractField}.
+
+ - C{"log_trace"}: A L{list} designed to capture information
+ about which L{LogPublisher}s have observed the event.
+
+ - C{"log_level"}: a L{log level
+ <twisted.logger.LogLevel>} constant, indicating the
+ importance of and audience for this event.
+
+ - C{"log_namespace"}: a namespace for the emitter of the event,
+ given as a L{str}.
+
+ - C{"log_system"}: a string indicating the network event or
+ method call which resulted in the message being logged.
+ """
diff --git a/contrib/python/Twisted/py3/twisted/logger/_io.py b/contrib/python/Twisted/py3/twisted/logger/_io.py
new file mode 100644
index 0000000000..7a49718db7
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_io.py
@@ -0,0 +1,187 @@
+# -*- test-case-name: twisted.logger.test.test_io -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+File-like object that logs.
+"""
+
+import sys
+from typing import AnyStr, Iterable, Optional
+
+from constantly import NamedConstant # type: ignore[import]
+from incremental import Version
+
+from twisted.python.deprecate import deprecatedProperty
+from ._levels import LogLevel
+from ._logger import Logger
+
+
+class LoggingFile:
+ """
+ File-like object that turns C{write()} calls into logging events.
+
+ Note that because event formats are L{str}, C{bytes} received via C{write()}
+ are converted to C{str}, which is the opposite of what C{file} does.
+
+ @ivar softspace: Attribute to make this class more file-like under Python 2;
+ value is zero or one. Do not use.
+ """
+
+ _softspace = 0
+
+ @deprecatedProperty(Version("Twisted", 21, 2, 0))
+ def softspace(self):
+ return self._softspace
+
+ @softspace.setter # type: ignore[no-redef]
+ def softspace(self, value):
+ self._softspace = value
+
+ def __init__(
+ self,
+ logger: Logger,
+ level: NamedConstant = LogLevel.info,
+ encoding: Optional[str] = None,
+ ) -> None:
+ """
+ @param logger: the logger to log through.
+ @param level: the log level to emit events with.
+ @param encoding: The encoding to expect when receiving bytes via
+ C{write()}. If L{None}, use C{sys.getdefaultencoding()}.
+ """
+ self.level = level
+ self.log = logger
+
+ if encoding is None:
+ self._encoding = sys.getdefaultencoding()
+ else:
+ self._encoding = encoding
+
+ self._buffer = ""
+ self._closed = False
+
+ @property
+ def closed(self) -> bool:
+ """
+ Read-only property. Is the file closed?
+
+ @return: true if closed, otherwise false.
+ """
+ return self._closed
+
+ @property
+ def encoding(self) -> str:
+ """
+ Read-only property. File encoding.
+
+ @return: an encoding.
+ """
+ return self._encoding
+
+ @property
+ def mode(self) -> str:
+ """
+ Read-only property. File mode.
+
+ @return: "w"
+ """
+ return "w"
+
+ @property
+ def newlines(self) -> None:
+ """
+ Read-only property. Types of newlines encountered.
+
+ @return: L{None}
+ """
+ return None
+
+ @property
+ def name(self) -> str:
+ """
+ The name of this file; a repr-style string giving information about its
+ namespace.
+
+ @return: A file name.
+ """
+ return "<{} {}#{}>".format(
+ self.__class__.__name__,
+ self.log.namespace,
+ self.level.name,
+ )
+
+ def close(self) -> None:
+ """
+ Close this file so it can no longer be written to.
+ """
+ self._closed = True
+
+ def flush(self) -> None:
+ """
+ No-op; this file does not buffer.
+ """
+ pass
+
+ def fileno(self) -> int:
+ """
+ Returns an invalid file descriptor, since this is not backed by an FD.
+
+ @return: C{-1}
+ """
+ return -1
+
+ def isatty(self) -> bool:
+ """
+ A L{LoggingFile} is not a TTY.
+
+ @return: C{False}
+ """
+ return False
+
+ def write(self, message: AnyStr) -> None:
+ """
+ Log the given message.
+
+ @param message: The message to write.
+ """
+ if self._closed:
+ raise ValueError("I/O operation on closed file")
+
+ if isinstance(message, bytes):
+ text = message.decode(self._encoding)
+ else:
+ text = message
+
+ lines = (self._buffer + text).split("\n")
+ self._buffer = lines[-1]
+ lines = lines[0:-1]
+
+ for line in lines:
+ self.log.emit(self.level, format="{log_io}", log_io=line)
+
+ def writelines(self, lines: Iterable[AnyStr]) -> None:
+ """
+ Log each of the given lines as a separate message.
+
+ @param lines: Data to write.
+ """
+ for line in lines:
+ self.write(line)
+
+ def _unsupported(self, *args: object) -> None:
+ """
+ Template for unsupported operations.
+
+ @param args: Arguments.
+ """
+ raise OSError("unsupported operation")
+
+ read = _unsupported
+ next = _unsupported
+ readline = _unsupported
+ readlines = _unsupported
+ xreadlines = _unsupported
+ seek = _unsupported
+ tell = _unsupported
+ truncate = _unsupported
diff --git a/contrib/python/Twisted/py3/twisted/logger/_json.py b/contrib/python/Twisted/py3/twisted/logger/_json.py
new file mode 100644
index 0000000000..2ecdd43045
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_json.py
@@ -0,0 +1,285 @@
+# -*- test-case-name: twisted.logger.test.test_json -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tools for saving and loading log events in a structured format.
+"""
+
+from json import dumps, loads
+from typing import IO, Any, AnyStr, Dict, Iterable, Optional, Union, cast
+from uuid import UUID
+
+from constantly import NamedConstant # type: ignore[import]
+
+from twisted.python.failure import Failure
+from ._file import FileLogObserver
+from ._flatten import flattenEvent
+from ._interfaces import LogEvent
+from ._levels import LogLevel
+from ._logger import Logger
+
+log = Logger()
+
+
+JSONDict = Dict[str, Any]
+
+
+def failureAsJSON(failure: Failure) -> JSONDict:
+ """
+ Convert a failure to a JSON-serializable data structure.
+
+ @param failure: A failure to serialize.
+
+ @return: a mapping of strings to ... stuff, mostly reminiscent of
+ L{Failure.__getstate__}
+ """
+ return dict(
+ failure.__getstate__(),
+ type=dict(
+ __module__=failure.type.__module__,
+ __name__=failure.type.__name__,
+ ),
+ )
+
+
+def failureFromJSON(failureDict: JSONDict) -> Failure:
+ """
+ Load a L{Failure} from a dictionary deserialized from JSON.
+
+ @param failureDict: a JSON-deserialized object like one previously returned
+ by L{failureAsJSON}.
+
+ @return: L{Failure}
+ """
+ f = Failure.__new__(Failure)
+ typeInfo = failureDict["type"]
+ failureDict["type"] = type(typeInfo["__name__"], (), typeInfo)
+ f.__dict__ = failureDict
+ return f
+
+
+classInfo = [
+ (
+ lambda level: (
+ isinstance(level, NamedConstant)
+ and getattr(LogLevel, level.name, None) is level
+ ),
+ UUID("02E59486-F24D-46AD-8224-3ACDF2A5732A"),
+ lambda level: dict(name=level.name),
+ lambda level: getattr(LogLevel, level["name"], None),
+ ),
+ (
+ lambda o: isinstance(o, Failure),
+ UUID("E76887E2-20ED-49BF-A8F8-BA25CC586F2D"),
+ failureAsJSON,
+ failureFromJSON,
+ ),
+]
+
+
+uuidToLoader = {uuid: loader for (predicate, uuid, saver, loader) in classInfo}
+
+
+def objectLoadHook(aDict: JSONDict) -> object:
+ """
+ Dictionary-to-object-translation hook for certain value types used within
+ the logging system.
+
+ @see: the C{object_hook} parameter to L{json.load}
+
+ @param aDict: A dictionary loaded from a JSON object.
+
+ @return: C{aDict} itself, or the object represented by C{aDict}
+ """
+ if "__class_uuid__" in aDict:
+ return uuidToLoader[UUID(aDict["__class_uuid__"])](aDict)
+ return aDict
+
+
+def objectSaveHook(pythonObject: object) -> JSONDict:
+ """
+ Object-to-serializable hook for certain value types used within the logging
+ system.
+
+ @see: the C{default} parameter to L{json.dump}
+
+ @param pythonObject: Any object.
+
+ @return: If the object is one of the special types the logging system
+ supports, a specially-formatted dictionary; otherwise, a marker
+ dictionary indicating that it could not be serialized.
+ """
+ for predicate, uuid, saver, loader in classInfo:
+ if predicate(pythonObject):
+ result = saver(pythonObject)
+ result["__class_uuid__"] = str(uuid)
+ return result
+ return {"unpersistable": True}
+
+
+def eventAsJSON(event: LogEvent) -> str:
+ """
+ Encode an event as JSON, flattening it if necessary to preserve as much
+ structure as possible.
+
+ Not all structure from the log event will be preserved when it is
+ serialized.
+
+ @param event: A log event dictionary.
+
+ @return: A string of the serialized JSON; note that this will contain no
+ newline characters, and may thus safely be stored in a line-delimited
+ file.
+ """
+
+ def default(unencodable: object) -> Union[JSONDict, str]:
+ """
+ Serialize an object not otherwise serializable by L{dumps}.
+
+ @param unencodable: An unencodable object.
+
+ @return: C{unencodable}, serialized
+ """
+ if isinstance(unencodable, bytes):
+ return unencodable.decode("charmap")
+ return objectSaveHook(unencodable)
+
+ flattenEvent(event)
+ return dumps(event, default=default, skipkeys=True)
+
+
+def eventFromJSON(eventText: str) -> JSONDict:
+ """
+ Decode a log event from JSON.
+
+ @param eventText: The output of a previous call to L{eventAsJSON}
+
+ @return: A reconstructed version of the log event.
+ """
+ return cast(JSONDict, loads(eventText, object_hook=objectLoadHook))
+
+
+def jsonFileLogObserver(
+ outFile: IO[Any], recordSeparator: str = "\x1e"
+) -> FileLogObserver:
+ """
+ Create a L{FileLogObserver} that emits JSON-serialized events to a
+ specified (writable) file-like object.
+
+ Events are written in the following form::
+
+ RS + JSON + NL
+
+ C{JSON} is the serialized event, which is JSON text. C{NL} is a newline
+ (C{"\\n"}). C{RS} is a record separator. By default, this is a single
+ RS character (C{"\\x1e"}), which makes the default output conform to the
+ IETF draft document "draft-ietf-json-text-sequence-13".
+
+ @param outFile: A file-like object. Ideally one should be passed which
+ accepts L{str} data. Otherwise, UTF-8 L{bytes} will be used.
+ @param recordSeparator: The record separator to use.
+
+ @return: A file log observer.
+ """
+ return FileLogObserver(
+ outFile, lambda event: f"{recordSeparator}{eventAsJSON(event)}\n"
+ )
+
+
+def eventsFromJSONLogFile(
+ inFile: IO[Any],
+ recordSeparator: Optional[str] = None,
+ bufferSize: int = 4096,
+) -> Iterable[LogEvent]:
+ """
+ Load events from a file previously saved with L{jsonFileLogObserver}.
+ Event records that are truncated or otherwise unreadable are ignored.
+
+ @param inFile: A (readable) file-like object. Data read from C{inFile}
+ should be L{str} or UTF-8 L{bytes}.
+ @param recordSeparator: The expected record separator.
+ If L{None}, attempt to automatically detect the record separator from
+ one of C{"\\x1e"} or C{""}.
+ @param bufferSize: The size of the read buffer used while reading from
+ C{inFile}.
+
+ @return: Log events as read from C{inFile}.
+ """
+
+ def asBytes(s: AnyStr) -> bytes:
+ if isinstance(s, bytes):
+ return s
+ else:
+ return s.encode("utf-8")
+
+ def eventFromBytearray(record: bytearray) -> Optional[LogEvent]:
+ try:
+ text = bytes(record).decode("utf-8")
+ except UnicodeDecodeError:
+ log.error(
+ "Unable to decode UTF-8 for JSON record: {record!r}",
+ record=bytes(record),
+ )
+ return None
+
+ try:
+ return eventFromJSON(text)
+ except ValueError:
+ log.error("Unable to read JSON record: {record!r}", record=bytes(record))
+ return None
+
+ if recordSeparator is None:
+ first = asBytes(inFile.read(1))
+
+ if first == b"\x1e":
+ # This looks json-text-sequence compliant.
+ recordSeparatorBytes = first
+ else:
+ # Default to simpler newline-separated stream, which does not use
+ # a record separator.
+ recordSeparatorBytes = b""
+
+ else:
+ recordSeparatorBytes = asBytes(recordSeparator)
+ first = b""
+
+ if recordSeparatorBytes == b"":
+ recordSeparatorBytes = b"\n" # Split on newlines below
+
+ eventFromRecord = eventFromBytearray
+
+ else:
+
+ def eventFromRecord(record: bytearray) -> Optional[LogEvent]:
+ if record[-1] == ord("\n"):
+ return eventFromBytearray(record)
+ else:
+ log.error(
+ "Unable to read truncated JSON record: {record!r}",
+ record=bytes(record),
+ )
+ return None
+
+ buffer = bytearray(first)
+
+ while True:
+ newData = inFile.read(bufferSize)
+
+ if not newData:
+ if len(buffer) > 0:
+ event = eventFromRecord(buffer)
+ if event is not None:
+ yield event
+ break
+
+ buffer += asBytes(newData)
+ records = buffer.split(recordSeparatorBytes)
+
+ for record in records[:-1]:
+ if len(record) > 0:
+ event = eventFromRecord(record)
+ if event is not None:
+ yield event
+
+ buffer = records[-1]
diff --git a/contrib/python/Twisted/py3/twisted/logger/_legacy.py b/contrib/python/Twisted/py3/twisted/logger/_legacy.py
new file mode 100644
index 0000000000..2847bc7a40
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_legacy.py
@@ -0,0 +1,147 @@
+# -*- test-case-name: twisted.logger.test.test_legacy -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Integration with L{twisted.python.log}.
+"""
+
+from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
+
+from zope.interface import implementer
+
+from ._format import formatEvent
+from ._interfaces import ILogObserver, LogEvent
+from ._levels import LogLevel
+from ._stdlib import StringifiableFromEvent, fromStdlibLogLevelMapping
+
+if TYPE_CHECKING:
+ from twisted.python.log import ILogObserver as ILegacyLogObserver
+
+
+@implementer(ILogObserver)
+class LegacyLogObserverWrapper:
+ """
+ L{ILogObserver} that wraps a L{twisted.python.log.ILogObserver}.
+
+ Received (new-style) events are modified prior to forwarding to
+ the legacy observer to ensure compatibility with observers that
+ expect legacy events.
+ """
+
+ def __init__(self, legacyObserver: "ILegacyLogObserver") -> None:
+ """
+ @param legacyObserver: a legacy observer to which this observer will
+ forward events.
+ """
+ self.legacyObserver = legacyObserver
+
+ def __repr__(self) -> str:
+ return "{self.__class__.__name__}({self.legacyObserver})".format(self=self)
+
+ def __call__(self, event: LogEvent) -> None:
+ """
+ Forward events to the legacy observer after editing them to
+ ensure compatibility.
+
+ @param event: an event
+ """
+
+ # The "message" key is required by textFromEventDict()
+ if "message" not in event:
+ event["message"] = ()
+
+ if "time" not in event:
+ event["time"] = event["log_time"]
+
+ if "system" not in event:
+ event["system"] = event.get("log_system", "-")
+
+ # Format new style -> old style
+ if "format" not in event and event.get("log_format", None) is not None:
+ # Create an object that implements __str__() in order to defer the
+ # work of formatting until it's needed by a legacy log observer.
+ event["format"] = "%(log_legacy)s"
+ event["log_legacy"] = StringifiableFromEvent(event.copy())
+
+ # In the old-style system, the 'message' key always holds a tuple
+ # of messages. If we find the 'message' key here to not be a
+ # tuple, it has been passed as new-style parameter. We drop it
+ # here because we render it using the old-style 'format' key,
+ # which otherwise doesn't get precedence, and the original event
+ # has been copied above.
+ if not isinstance(event["message"], tuple):
+ event["message"] = ()
+
+ # From log.failure() -> isError blah blah
+ if "log_failure" in event:
+ if "failure" not in event:
+ event["failure"] = event["log_failure"]
+ if "isError" not in event:
+ event["isError"] = 1
+ if "why" not in event:
+ event["why"] = formatEvent(event)
+ elif "isError" not in event:
+ if event["log_level"] in (LogLevel.error, LogLevel.critical):
+ event["isError"] = 1
+ else:
+ event["isError"] = 0
+
+ self.legacyObserver(event)
+
+
+def publishToNewObserver(
+ observer: ILogObserver,
+ eventDict: Dict[str, Any],
+ textFromEventDict: Callable[[Dict[str, Any]], Optional[str]],
+) -> None:
+ """
+ Publish an old-style (L{twisted.python.log}) event to a new-style
+ (L{twisted.logger}) observer.
+
+ @note: It's possible that a new-style event was sent to a
+ L{LegacyLogObserverWrapper}, and may now be getting sent back to a
+ new-style observer. In this case, it's already a new-style event,
+ adapted to also look like an old-style event, and we don't need to
+ tweak it again to be a new-style event, hence this checks for
+ already-defined new-style keys.
+
+ @param observer: A new-style observer to handle this event.
+ @param eventDict: An L{old-style <twisted.python.log>}, log event.
+ @param textFromEventDict: callable that can format an old-style event as a
+ string. Passed here rather than imported to avoid circular dependency.
+ """
+
+ if "log_time" not in eventDict:
+ eventDict["log_time"] = eventDict["time"]
+
+ if "log_format" not in eventDict:
+ text = textFromEventDict(eventDict)
+ if text is not None:
+ eventDict["log_text"] = text
+ eventDict["log_format"] = "{log_text}"
+
+ if "log_level" not in eventDict:
+ if "logLevel" in eventDict:
+ try:
+ level = fromStdlibLogLevelMapping[eventDict["logLevel"]]
+ except KeyError:
+ level = None
+ elif "isError" in eventDict:
+ if eventDict["isError"]:
+ level = LogLevel.critical
+ else:
+ level = LogLevel.info
+ else:
+ level = LogLevel.info
+
+ if level is not None:
+ eventDict["log_level"] = level
+
+ if "log_namespace" not in eventDict:
+ eventDict["log_namespace"] = "log_legacy"
+
+ if "log_system" not in eventDict and "system" in eventDict:
+ eventDict["log_system"] = eventDict["system"]
+
+ observer(eventDict)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_levels.py b/contrib/python/Twisted/py3/twisted/logger/_levels.py
new file mode 100644
index 0000000000..800a549f88
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_levels.py
@@ -0,0 +1,81 @@
+# -*- test-case-name: twisted.logger.test.test_levels -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Log levels.
+"""
+
+from constantly import NamedConstant, Names # type: ignore[import]
+
+
+class InvalidLogLevelError(Exception):
+ """
+ Someone tried to use a L{LogLevel} that is unknown to the logging system.
+ """
+
+ def __init__(self, level: NamedConstant) -> None:
+ """
+ @param level: A log level from L{LogLevel}.
+ """
+ super().__init__(str(level))
+ self.level = level
+
+
+class LogLevel(Names):
+ """
+ Constants describing log levels.
+
+ @cvar debug: Debugging events: Information of use to a developer of the
+ software, not generally of interest to someone running the software
+ unless they are attempting to diagnose a software issue.
+
+ @cvar info: Informational events: Routine information about the status of
+ an application, such as incoming connections, startup of a subsystem,
+ etc.
+
+ @cvar warn: Warning events: Events that may require greater attention than
+ informational events but are not a systemic failure condition, such as
+ authorization failures, bad data from a network client, etc. Such
+ events are of potential interest to system administrators, and should
+ ideally be phrased in such a way, or documented, so as to indicate an
+ action that an administrator might take to mitigate the warning.
+
+ @cvar error: Error conditions: Events indicating a systemic failure, such
+ as programming errors in the form of unhandled exceptions, loss of
+ connectivity to an external system without which no useful work can
+ proceed, such as a database or API endpoint, or resource exhaustion.
+ Similarly to warnings, errors that are related to operational
+ parameters may be actionable to system administrators and should
+ provide references to resources which an administrator might use to
+ resolve them.
+
+ @cvar critical: Critical failures: Errors indicating systemic failure (ie.
+ service outage), data corruption, imminent data loss, etc. which must
+ be handled immediately. This includes errors unanticipated by the
+ software, such as unhandled exceptions, wherein the cause and
+ consequences are unknown.
+ """
+
+ debug = NamedConstant()
+ info = NamedConstant()
+ warn = NamedConstant()
+ error = NamedConstant()
+ critical = NamedConstant()
+
+ @classmethod
+ def levelWithName(cls, name: str) -> NamedConstant:
+ """
+ Get the log level with the given name.
+
+ @param name: The name of a log level.
+
+ @return: The L{LogLevel} with the specified C{name}.
+
+ @raise InvalidLogLevelError: if the C{name} does not name a valid log
+ level.
+ """
+ try:
+ return cls.lookupByName(name)
+ except ValueError:
+ raise InvalidLogLevelError(name)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_logger.py b/contrib/python/Twisted/py3/twisted/logger/_logger.py
new file mode 100644
index 0000000000..cc428d87af
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_logger.py
@@ -0,0 +1,269 @@
+# -*- test-case-name: twisted.logger.test.test_logger -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Logger class.
+"""
+
+from time import time
+from typing import Any, Optional, cast
+
+from twisted.python.compat import currentframe
+from twisted.python.failure import Failure
+from ._interfaces import ILogObserver, LogTrace
+from ._levels import InvalidLogLevelError, LogLevel
+
+
+class Logger:
+ """
+ A L{Logger} emits log messages to an observer. You should instantiate it
+ as a class or module attribute, as documented in L{this module's
+ documentation <twisted.logger>}.
+
+ @ivar namespace: the namespace for this logger
+ @ivar source: The object which is emitting events via this logger
+ @ivar observer: The observer that this logger will send events to.
+ """
+
+ @staticmethod
+ def _namespaceFromCallingContext() -> str:
+ """
+ Derive a namespace from the module containing the caller's caller.
+
+ @return: the fully qualified python name of a module.
+ """
+ try:
+ return cast(str, currentframe(2).f_globals["__name__"])
+ except KeyError:
+ return "<unknown>"
+
+ def __init__(
+ self,
+ namespace: Optional[str] = None,
+ source: Optional[object] = None,
+ observer: Optional["ILogObserver"] = None,
+ ) -> None:
+ """
+ @param namespace: The namespace for this logger. Uses a dotted
+ notation, as used by python modules. If not L{None}, then the name
+ of the module of the caller is used.
+ @param source: The object which is emitting events via this
+ logger; this is automatically set on instances of a class
+ if this L{Logger} is an attribute of that class.
+ @param observer: The observer that this logger will send events to.
+ If L{None}, use the L{global log publisher <globalLogPublisher>}.
+ """
+ if namespace is None:
+ namespace = self._namespaceFromCallingContext()
+
+ self.namespace = namespace
+ self.source = source
+
+ if observer is None:
+ from ._global import globalLogPublisher
+
+ self.observer: ILogObserver = globalLogPublisher
+ else:
+ self.observer = observer
+
+ def __get__(self, instance: object, owner: Optional[type] = None) -> "Logger":
+ """
+ When used as a descriptor, i.e.::
+
+ # File: athing.py
+ class Something:
+ log = Logger()
+ def hello(self):
+ self.log.info("Hello")
+
+ a L{Logger}'s namespace will be set to the name of the class it is
+ declared on. In the above example, the namespace would be
+ C{athing.Something}.
+
+ Additionally, its source will be set to the actual object referring to
+ the L{Logger}. In the above example, C{Something.log.source} would be
+ C{Something}, and C{Something().log.source} would be an instance of
+ C{Something}.
+ """
+ assert owner is not None
+
+ if instance is None:
+ source: Any = owner
+ else:
+ source = instance
+
+ return self.__class__(
+ ".".join([owner.__module__, owner.__name__]),
+ source,
+ observer=self.observer,
+ )
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} {self.namespace!r}>"
+
+ def emit(
+ self, level: LogLevel, format: Optional[str] = None, **kwargs: object
+ ) -> None:
+ """
+ Emit a log event to all log observers at the given level.
+
+ @param level: a L{LogLevel}
+ @param format: a message format using new-style (PEP 3101)
+ formatting. The logging event (which is a L{dict}) is
+ used to render this format string.
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ if level not in LogLevel.iterconstants():
+ self.failure(
+ "Got invalid log level {invalidLevel!r} in {logger}.emit().",
+ Failure(InvalidLogLevelError(level)),
+ invalidLevel=level,
+ logger=self,
+ )
+ return
+
+ event = kwargs
+ event.update(
+ log_logger=self,
+ log_level=level,
+ log_namespace=self.namespace,
+ log_source=self.source,
+ log_format=format,
+ log_time=time(),
+ )
+
+ if "log_trace" in event:
+ cast(LogTrace, event["log_trace"]).append((self, self.observer))
+
+ self.observer(event)
+
+ def failure(
+ self,
+ format: str,
+ failure: Optional[Failure] = None,
+ level: LogLevel = LogLevel.critical,
+ **kwargs: object,
+ ) -> None:
+ """
+ Log a failure and emit a traceback.
+
+ For example::
+
+ try:
+ frob(knob)
+ except Exception:
+ log.failure("While frobbing {knob}", knob=knob)
+
+ or::
+
+ d = deferredFrob(knob)
+ d.addErrback(lambda f: log.failure("While frobbing {knob}",
+ f, knob=knob))
+
+ This method is generally meant to capture unexpected exceptions in
+ code; an exception that is caught and handled somehow should be logged,
+ if appropriate, via L{Logger.error} instead. If some unknown exception
+ occurs and your code doesn't know how to handle it, as in the above
+ example, then this method provides a means to describe the failure in
+ nerd-speak. This is done at L{LogLevel.critical} by default, since no
+ corrective guidance can be offered to an user/administrator, and the
+ impact of the condition is unknown.
+
+ @param format: a message format using new-style (PEP 3101) formatting.
+ The logging event (which is a L{dict}) is used to render this
+ format string.
+ @param failure: a L{Failure} to log. If L{None}, a L{Failure} is
+ created from the exception in flight.
+ @param level: a L{LogLevel} to use.
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ if failure is None:
+ failure = Failure()
+
+ self.emit(level, format, log_failure=failure, **kwargs)
+
+ def debug(self, format: Optional[str] = None, **kwargs: object) -> None:
+ """
+ Emit a log event at log level L{LogLevel.debug}.
+
+ @param format: a message format using new-style (PEP 3101) formatting.
+ The logging event (which is a L{dict}) is used to render this
+ format string.
+
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ self.emit(LogLevel.debug, format, **kwargs)
+
+ def info(self, format: Optional[str] = None, **kwargs: object) -> None:
+ """
+ Emit a log event at log level L{LogLevel.info}.
+
+ @param format: a message format using new-style (PEP 3101) formatting.
+ The logging event (which is a L{dict}) is used to render this
+ format string.
+
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ self.emit(LogLevel.info, format, **kwargs)
+
+ def warn(self, format: Optional[str] = None, **kwargs: object) -> None:
+ """
+ Emit a log event at log level L{LogLevel.warn}.
+
+ @param format: a message format using new-style (PEP 3101) formatting.
+ The logging event (which is a L{dict}) is used to render this
+ format string.
+
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ self.emit(LogLevel.warn, format, **kwargs)
+
+ def error(self, format: Optional[str] = None, **kwargs: object) -> None:
+ """
+ Emit a log event at log level L{LogLevel.error}.
+
+ @param format: a message format using new-style (PEP 3101) formatting.
+ The logging event (which is a L{dict}) is used to render this
+ format string.
+
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ self.emit(LogLevel.error, format, **kwargs)
+
+ def critical(self, format: Optional[str] = None, **kwargs: object) -> None:
+ """
+ Emit a log event at log level L{LogLevel.critical}.
+
+ @param format: a message format using new-style (PEP 3101) formatting.
+ The logging event (which is a L{dict}) is used to render this
+ format string.
+
+ @param kwargs: additional key/value pairs to include in the event.
+ Note that values which are later mutated may result in
+ non-deterministic behavior from observers that schedule work for
+ later execution.
+ """
+ self.emit(LogLevel.critical, format, **kwargs)
+
+
+_log = Logger()
+_loggerFor = lambda obj: _log.__get__(obj, obj.__class__)
diff --git a/contrib/python/Twisted/py3/twisted/logger/_observer.py b/contrib/python/Twisted/py3/twisted/logger/_observer.py
new file mode 100644
index 0000000000..86f89c37b4
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_observer.py
@@ -0,0 +1,112 @@
+# -*- 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.
+ """
diff --git a/contrib/python/Twisted/py3/twisted/logger/_stdlib.py b/contrib/python/Twisted/py3/twisted/logger/_stdlib.py
new file mode 100644
index 0000000000..030b643883
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_stdlib.py
@@ -0,0 +1,131 @@
+# -*- test-case-name: twisted.logger.test.test_stdlib -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Integration with Python standard library logging.
+"""
+
+import logging as stdlibLogging
+from typing import Mapping, Tuple
+
+from zope.interface import implementer
+
+from constantly import NamedConstant # type: ignore[import]
+
+from twisted.python.compat import currentframe
+from ._format import formatEvent
+from ._interfaces import ILogObserver, LogEvent
+from ._levels import LogLevel
+
+# Mappings to Python's logging module
+toStdlibLogLevelMapping: Mapping[NamedConstant, int] = {
+ LogLevel.debug: stdlibLogging.DEBUG,
+ LogLevel.info: stdlibLogging.INFO,
+ LogLevel.warn: stdlibLogging.WARNING,
+ LogLevel.error: stdlibLogging.ERROR,
+ LogLevel.critical: stdlibLogging.CRITICAL,
+}
+
+
+def _reverseLogLevelMapping() -> Mapping[int, NamedConstant]:
+ """
+ Reverse the above mapping, adding both the numerical keys used above and
+ the corresponding string keys also used by python logging.
+ @return: the reversed mapping
+ """
+ mapping = {}
+ for logLevel, pyLogLevel in toStdlibLogLevelMapping.items():
+ mapping[pyLogLevel] = logLevel
+ mapping[stdlibLogging.getLevelName(pyLogLevel)] = logLevel
+ return mapping
+
+
+fromStdlibLogLevelMapping = _reverseLogLevelMapping()
+
+
+@implementer(ILogObserver)
+class STDLibLogObserver:
+ """
+ Log observer that writes to the python standard library's C{logging}
+ module.
+
+ @note: Warning: specific logging configurations (example: network) can lead
+ to this observer blocking. Nothing is done here to prevent that, so be
+ sure to not to configure the standard library logging module to block
+ when used in conjunction with this module: code within Twisted, such as
+ twisted.web, assumes that logging does not block.
+
+ @cvar defaultStackDepth: This is the default number of frames that it takes
+ to get from L{STDLibLogObserver} through the logging module, plus one;
+ in other words, the number of frames if you were to call a
+ L{STDLibLogObserver} directly. This is useful to use as an offset for
+ the C{stackDepth} parameter to C{__init__}, to add frames for other
+ publishers.
+ """
+
+ defaultStackDepth = 4
+
+ def __init__(
+ self, name: str = "twisted", stackDepth: int = defaultStackDepth
+ ) -> None:
+ """
+ @param name: logger identifier.
+ @param stackDepth: The depth of the stack to investigate for caller
+ metadata.
+ """
+ self.logger = stdlibLogging.getLogger(name)
+ self.logger.findCaller = self._findCaller # type: ignore[assignment]
+ self.stackDepth = stackDepth
+
+ def _findCaller(
+ self, stackInfo: bool = False, stackLevel: int = 1
+ ) -> Tuple[str, int, str, None]:
+ """
+ Based on the stack depth passed to this L{STDLibLogObserver}, identify
+ the calling function.
+
+ @param stackInfo: Whether or not to construct stack information.
+ (Currently ignored.)
+ @param stackLevel: The number of stack frames to skip when determining
+ the caller (currently ignored; use stackDepth on the instance).
+
+ @return: Depending on Python version, either a 3-tuple of (filename,
+ lineno, name) or a 4-tuple of that plus stack information.
+ """
+ f = currentframe(self.stackDepth)
+ co = f.f_code
+ extra = (None,)
+ return (co.co_filename, f.f_lineno, co.co_name) + extra
+
+ def __call__(self, event: LogEvent) -> None:
+ """
+ Format an event and bridge it to Python logging.
+ """
+ level = event.get("log_level", LogLevel.info)
+ failure = event.get("log_failure")
+ if failure is None:
+ excInfo = None
+ else:
+ excInfo = (failure.type, failure.value, failure.getTracebackObject())
+ stdlibLevel = toStdlibLogLevelMapping.get(level, stdlibLogging.INFO)
+ self.logger.log(stdlibLevel, StringifiableFromEvent(event), exc_info=excInfo)
+
+
+class StringifiableFromEvent:
+ """
+ An object that implements C{__str__()} in order to defer the work of
+ formatting until it's converted into a C{str}.
+ """
+
+ def __init__(self, event: LogEvent) -> None:
+ """
+ @param event: An event.
+ """
+ self.event = event
+
+ def __str__(self) -> str:
+ return formatEvent(self.event)
+
+ def __bytes__(self) -> bytes:
+ return str(self).encode("utf-8")
diff --git a/contrib/python/Twisted/py3/twisted/logger/_util.py b/contrib/python/Twisted/py3/twisted/logger/_util.py
new file mode 100644
index 0000000000..e8f02ddd22
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/logger/_util.py
@@ -0,0 +1,51 @@
+# -*- test-case-name: twisted.logger.test.test_util -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Logging utilities.
+"""
+
+from typing import List
+
+from ._interfaces import LogTrace
+from ._logger import Logger
+
+
+def formatTrace(trace: LogTrace) -> str:
+ """
+ Format a trace (that is, the contents of the C{log_trace} key of a log
+ event) as a visual indication of the message's propagation through various
+ observers.
+
+ @param trace: the contents of the C{log_trace} key from an event.
+
+ @return: A multi-line string with indentation and arrows indicating the
+ flow of the message through various observers.
+ """
+
+ def formatWithName(obj: object) -> str:
+ if hasattr(obj, "name"):
+ return f"{obj} ({obj.name})"
+ else:
+ return f"{obj}"
+
+ result = []
+ lineage: List[Logger] = []
+
+ for parent, child in trace:
+ if not lineage or lineage[-1] is not parent:
+ if parent in lineage:
+ while lineage[-1] is not parent:
+ lineage.pop()
+
+ else:
+ if not lineage:
+ result.append(f"{formatWithName(parent)}\n")
+
+ lineage.append(parent)
+
+ result.append(" " * len(lineage))
+ result.append(f"-> {formatWithName(child)}\n")
+
+ return "".join(result)