1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# -*- 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 zope.interface import implementer
from twisted.python.compat import _PY3, currentframe, unicode
from ._levels import LogLevel
from ._format import formatEvent
from ._observer import ILogObserver
# Mappings to Python's logging module
toStdlibLogLevelMapping = {
LogLevel.debug: stdlibLogging.DEBUG,
LogLevel.info: stdlibLogging.INFO,
LogLevel.warn: stdlibLogging.WARNING,
LogLevel.error: stdlibLogging.ERROR,
LogLevel.critical: stdlibLogging.CRITICAL,
}
def _reverseLogLevelMapping():
"""
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(object):
"""
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="twisted", stackDepth=defaultStackDepth):
"""
@param loggerName: logger identifier.
@type loggerName: C{str}
@param stackDepth: The depth of the stack to investigate for caller
metadata.
@type stackDepth: L{int}
"""
self.logger = stdlibLogging.getLogger(name)
self.logger.findCaller = self._findCaller
self.stackDepth = stackDepth
def _findCaller(self, stackInfo=False, stackLevel=1):
"""
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.)
@type stackInfo: L{bool}
@param stackLevel: The number of stack frames to skip when determining
the caller (currently ignored; use stackDepth on the instance).
@type stackLevel: L{int}
@return: Depending on Python version, either a 3-tuple of (filename,
lineno, name) or a 4-tuple of that plus stack information.
@rtype: L{tuple}
"""
f = currentframe(self.stackDepth)
co = f.f_code
if _PY3:
extra = (None,)
else:
extra = ()
return (co.co_filename, f.f_lineno, co.co_name) + extra
def __call__(self, event):
"""
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(object):
"""
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):
"""
@param event: An event.
@type event: L{dict}
"""
self.event = event
def __unicode__(self):
return formatEvent(self.event)
def __bytes__(self):
return unicode(self).encode("utf-8")
if _PY3:
__str__ = __unicode__
else:
__str__ = __bytes__
|