Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_stdlib.py 4.4KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. # -*- test-case-name: twisted.logger.test.test_stdlib -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Integration with Python standard library logging.
  6. """
  7. import logging as stdlibLogging
  8. from typing import Mapping, Tuple
  9. from zope.interface import implementer
  10. from constantly import NamedConstant # type: ignore[import]
  11. from twisted.python.compat import currentframe
  12. from ._format import formatEvent
  13. from ._interfaces import ILogObserver, LogEvent
  14. from ._levels import LogLevel
  15. # Mappings to Python's logging module
  16. toStdlibLogLevelMapping: Mapping[NamedConstant, int] = {
  17. LogLevel.debug: stdlibLogging.DEBUG,
  18. LogLevel.info: stdlibLogging.INFO,
  19. LogLevel.warn: stdlibLogging.WARNING,
  20. LogLevel.error: stdlibLogging.ERROR,
  21. LogLevel.critical: stdlibLogging.CRITICAL,
  22. }
  23. def _reverseLogLevelMapping() -> Mapping[int, NamedConstant]:
  24. """
  25. Reverse the above mapping, adding both the numerical keys used above and
  26. the corresponding string keys also used by python logging.
  27. @return: the reversed mapping
  28. """
  29. mapping = {}
  30. for logLevel, pyLogLevel in toStdlibLogLevelMapping.items():
  31. mapping[pyLogLevel] = logLevel
  32. mapping[stdlibLogging.getLevelName(pyLogLevel)] = logLevel
  33. return mapping
  34. fromStdlibLogLevelMapping = _reverseLogLevelMapping()
  35. @implementer(ILogObserver)
  36. class STDLibLogObserver:
  37. """
  38. Log observer that writes to the python standard library's C{logging}
  39. module.
  40. @note: Warning: specific logging configurations (example: network) can lead
  41. to this observer blocking. Nothing is done here to prevent that, so be
  42. sure to not to configure the standard library logging module to block
  43. when used in conjunction with this module: code within Twisted, such as
  44. twisted.web, assumes that logging does not block.
  45. @cvar defaultStackDepth: This is the default number of frames that it takes
  46. to get from L{STDLibLogObserver} through the logging module, plus one;
  47. in other words, the number of frames if you were to call a
  48. L{STDLibLogObserver} directly. This is useful to use as an offset for
  49. the C{stackDepth} parameter to C{__init__}, to add frames for other
  50. publishers.
  51. """
  52. defaultStackDepth = 4
  53. def __init__(
  54. self, name: str = "twisted", stackDepth: int = defaultStackDepth
  55. ) -> None:
  56. """
  57. @param name: logger identifier.
  58. @param stackDepth: The depth of the stack to investigate for caller
  59. metadata.
  60. """
  61. self.logger = stdlibLogging.getLogger(name)
  62. self.logger.findCaller = self._findCaller # type: ignore[assignment]
  63. self.stackDepth = stackDepth
  64. def _findCaller(
  65. self, stackInfo: bool = False, stackLevel: int = 1
  66. ) -> Tuple[str, int, str, None]:
  67. """
  68. Based on the stack depth passed to this L{STDLibLogObserver}, identify
  69. the calling function.
  70. @param stackInfo: Whether or not to construct stack information.
  71. (Currently ignored.)
  72. @param stackLevel: The number of stack frames to skip when determining
  73. the caller (currently ignored; use stackDepth on the instance).
  74. @return: Depending on Python version, either a 3-tuple of (filename,
  75. lineno, name) or a 4-tuple of that plus stack information.
  76. """
  77. f = currentframe(self.stackDepth)
  78. co = f.f_code
  79. extra = (None,)
  80. return (co.co_filename, f.f_lineno, co.co_name) + extra
  81. def __call__(self, event: LogEvent) -> None:
  82. """
  83. Format an event and bridge it to Python logging.
  84. """
  85. level = event.get("log_level", LogLevel.info)
  86. failure = event.get("log_failure")
  87. if failure is None:
  88. excInfo = None
  89. else:
  90. excInfo = (failure.type, failure.value, failure.getTracebackObject())
  91. stdlibLevel = toStdlibLogLevelMapping.get(level, stdlibLogging.INFO)
  92. self.logger.log(stdlibLevel, StringifiableFromEvent(event), exc_info=excInfo)
  93. class StringifiableFromEvent:
  94. """
  95. An object that implements C{__str__()} in order to defer the work of
  96. formatting until it's converted into a C{str}.
  97. """
  98. def __init__(self, event: LogEvent) -> None:
  99. """
  100. @param event: An event.
  101. """
  102. self.event = event
  103. def __str__(self) -> str:
  104. return formatEvent(self.event)
  105. def __bytes__(self) -> bytes:
  106. return str(self).encode("utf-8")