Merge lp:~allenap/maas/normalise-logging--bug-1639182 into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 5541
Proposed branch: lp:~allenap/maas/normalise-logging--bug-1639182
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 410 lines (+148/-113)
4 files modified
src/provisioningserver/logger/_common.py (+1/-1)
src/provisioningserver/logger/_twisted.py (+55/-24)
src/provisioningserver/logger/tests/test__twisted.py (+87/-83)
src/provisioningserver/logger/tests/test_logger.py (+5/-5)
To merge this branch: bzr merge lp:~allenap/maas/normalise-logging--bug-1639182
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Blake Rouse (community) Needs Information
Review via email: mp+310188@code.launchpad.net

Commit message

Change the logging format to be similar between regiond.log, rackd.log, and maas.log.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

This monkey-patches the legacy log emitter to use DEFAULT_LOG_FORMAT, so future changes to logging format will be easier.

Having said that... because we're in a transnational state in Twisted between the legacy and modern systems... this code completely doesn't have the intended effect in Twisted 16.4 (which is found in Yakkety).

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. I tried to get this to work on Friday and was lost. I really dont think twisted could have made logging any more complicated.

This is good enough for the SRU to Xenial, but we need to get this fixes in yacketty as well. ETA on that, possible to do in the branch. So we can get it done in one pass. We need to release 2.1.1 today.

review: Needs Information
Revision history for this message
Gavin Panella (allenap) wrote :

> Looks good. I tried to get this to work on Friday and was lost. I
> really dont think twisted could have made logging any more
> complicated.

I'm right with you on this. My only hope for this is that it'll get a
lot simpler once t.p.log is gone.

> This is good enough for the SRU to Xenial, but we need to get this
> fixes in yacketty as well. ETA on that, possible to do in the branch.
> So we can get it done in one pass. We need to release 2.1.1 today.

I've been hitting my head against the wall with doing this in Yakkety.
I'll land this but we're just going to have to live with funny log for a
bit longer in Yakkety.

On the other hand, do we need to do this at all? The message format has
not changed through the recent logging changes. Do we actually need to
change it now before we can release?

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Yes we need to get this correct before release of 2.1.1. As we are SRU'ing 2.1.1 to Xenial. Mark wants the SRU finished by the end of this week. So we need to release ASAP. This 2.1.1 will also require an SRU to yakketty so we should make this work in yakketty as well.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

The format has changed between 2.1.0 and what will be 2.1.1. So I'm fine landing this for xenial and we will address it for yakkety afterwards.

We need to have a serious discussion how we can overall improve the logging system.

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

> We need to have a serious discussion how we can overall improve the
> logging system.

Let me kick it off.

There has been pain in all parts of the stack: Django, Twisted, and the
standard library all do stupid things that we need to work around. I've
been able to contain the ugliness to the p.logger package.

The logs that Django and `logging` produce and those that Twisted
produce are integrated only when they hit the log file. Logs from one
subsystem are not shipped from `logging` to Twisted or vice-versa. There
is an adapter that can pipe Twisted's logs over to the standard library
but it's too lossy right now, and it's not clear that it'll help much
anyway.

Twisted has just one idea of what a log should look like. It doesn't
care about customising the textual output, though it does provide the
means to write your own log event[1] observer and so emit rot-13 logs to
a thermal printer on a pidgeon. It's neither right nor wrong, just a
different approach to that of the stdlib's `logging` package.

This bug in particular has been painful for a couple of reasons:

1. I'm trying to do it the "wrong" way with Twisted. When invoking a
   plugin via twistd the "right" way to customise logging output is to
   specify an observer via `--logger=fully.qualified.callable`.

2. As I've mentioned before, Twisted is in a transitional state between
   its "legacy" and "modern" logging subsystems. There's a lot of
   complexity behind the scenes to make this work, and significant
   change between Twisted in Xenial and Yakkety.

I've been trying to avoid #1 so far, to keep the details hidden, but no
longer. The changes between Twisted 16.0 (Xenial) and 16.4 (Yakkety)
means this is likely to be the only sane way to achieve what we want,
and this is what I'll work on now.

[1] A log *event* is a dict containing some standard keys and any number
of other pieces of information, passed between log observers. One such
observer might filter events, one might forward events to multiple
observers, one might format the event and write it out to a log file.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/provisioningserver/logger/_common.py'
--- src/provisioningserver/logger/_common.py 2016-10-28 15:58:32 +0000
+++ src/provisioningserver/logger/_common.py 2016-11-07 14:38:23 +0000
@@ -21,7 +21,7 @@
2121
22# This format roughly matches Twisted's default, so that combined Twisted and22# This format roughly matches Twisted's default, so that combined Twisted and
23# Django logs are consistent with one another.23# Django logs are consistent with one another.
24DEFAULT_LOG_FORMAT = "%(asctime)s [%(name)s#%(levelname)s] %(message)s"24DEFAULT_LOG_FORMAT = "%(asctime)s %(name)s: [%(levelname)s] %(message)s"
25DEFAULT_LOG_FORMAT_DATE = "%Y-%m-%d %H:%M:%S"25DEFAULT_LOG_FORMAT_DATE = "%Y-%m-%d %H:%M:%S"
26DEFAULT_LOG_VERBOSITY_LEVELS = {0, 1, 2, 3}26DEFAULT_LOG_VERBOSITY_LEVELS = {0, 1, 2, 3}
27DEFAULT_LOG_VERBOSITY = 227DEFAULT_LOG_VERBOSITY = 2
2828
=== modified file 'src/provisioningserver/logger/_twisted.py'
--- src/provisioningserver/logger/_twisted.py 2016-11-03 21:11:05 +0000
+++ src/provisioningserver/logger/_twisted.py 2016-11-07 14:38:23 +0000
@@ -15,6 +15,7 @@
1515
16import crochet16import crochet
17from provisioningserver.logger._common import (17from provisioningserver.logger._common import (
18 DEFAULT_LOG_FORMAT,
18 DEFAULT_LOG_FORMAT_DATE,19 DEFAULT_LOG_FORMAT_DATE,
19 DEFAULT_LOG_VERBOSITY,20 DEFAULT_LOG_VERBOSITY,
20 DEFAULT_LOG_VERBOSITY_LEVELS,21 DEFAULT_LOG_VERBOSITY_LEVELS,
@@ -28,6 +29,7 @@
28 log as twistedLegacy,29 log as twistedLegacy,
29 usage,30 usage,
30)31)
32from twisted.python.util import untilConcludes
3133
32# Map verbosity numbers to `twisted.logger` levels.34# Map verbosity numbers to `twisted.logger` levels.
33DEFAULT_TWISTED_VERBOSITY_LEVELS = {35DEFAULT_TWISTED_VERBOSITY_LEVELS = {
@@ -117,12 +119,10 @@
117 twistedModern.globalLogBeginner.showwarning = warnings.showwarning119 twistedModern.globalLogBeginner.showwarning = warnings.showwarning
118 twistedLegacy.theLogPublisher.showwarning = warnings.showwarning120 twistedLegacy.theLogPublisher.showwarning = warnings.showwarning
119121
120 # Globally override Twisted's log date format. It's tricky to get to the122 # Globally override Twisted's legacy logging format.
121 # FileLogObserver that twistd installs so that we can modify its config123 warn_unless(hasattr(twistedLegacy, "FileLogObserver"), (
122 # alone, but we actually do want to make a global change anyway.124 "No t.p.log.FileLogObserver attribute found; please investigate!"))
123 warn_unless(hasattr(twistedLegacy.FileLogObserver, "timeFormat"), (125 LegacyFileLogObserver.install()
124 "No FileLogObserver.timeFormat attribute found; please investigate!"))
125 twistedLegacy.FileLogObserver.timeFormat = DEFAULT_LOG_FORMAT_DATE
126126
127 # Install a wrapper so that log events from `t.logger` are logged with a127 # Install a wrapper so that log events from `t.logger` are logged with a
128 # namespace and level by the legacy logger in `t.python.log`. This needs128 # namespace and level by the legacy logger in `t.python.log`. This needs
@@ -157,18 +157,55 @@
157 twistedLegacy.startLogging(sys.__stdout__, setStdout=False)157 twistedLegacy.startLogging(sys.__stdout__, setStdout=False)
158158
159159
160class LegacyFileLogObserver(twistedLegacy.FileLogObserver):
161 """Log legacy/mixed events to a file, formatting the MAAS way."""
162
163 timeFormat = DEFAULT_LOG_FORMAT_DATE
164 lineFormat = DEFAULT_LOG_FORMAT + "\n"
165
166 @classmethod
167 def install(cls):
168 """Install this wrapper in place of `log.FileLogObserver`."""
169 twistedLegacy.FileLogObserver = cls
170
171 def emit(self, event):
172 """Format and write out the given `event`."""
173 text = twistedLegacy.textFromEventDict(event)
174 if text is None:
175 return
176
177 system = event["system"] if "system" in event else None
178 if system is None and "log_namespace" in event:
179 system = event["log_namespace"]
180 # Logs written directly to `t.p.log.logfile` and `.logerr` get a
181 # namespace of "twisted.python.log". This is not interesting.
182 if system == twistedLegacy.__name__:
183 system = None
184
185 level = event["log_level"] if "log_level" in event else None
186 if level is None:
187 if "isError" in event and event["isError"]:
188 level = twistedModern.LogLevel.critical
189 else:
190 level = twistedModern.LogLevel.info
191
192 line = twistedLegacy._safeFormat(self.lineFormat, {
193 "asctime": self.formatTime(event["time"]),
194 "levelname": "-" if level is None else level.name,
195 "message": text.replace("\n", "\n\t"),
196 "name": "-" if system is None else system,
197 })
198
199 untilConcludes(self.write, line)
200 untilConcludes(self.flush)
201
202
160class LegacyLogObserverWrapper(twistedModern.LegacyLogObserverWrapper):203class LegacyLogObserverWrapper(twistedModern.LegacyLogObserverWrapper):
161 """Ensure that `log_system` is set in the event.204 """Ensure that `log_system` is set in the event.
162205
163 This mimics what `twisted.logger.formatEventAsClassicLogText` does when206 A newly populated `log_system` value is seen by `LegacyLogObserverWrapper`
164 `log_system` is not set, and constructs it from `log_namespace` and207 (the superclass) and copied into the `system` key, later used when emitting
165 `log_level`.208 formatted log lines.
166
167 This `log_system` value is then seen by `LegacyLogObserverWrapper` and
168 copied into the `system` key and then printed out in the logs by Twisted's
169 legacy logging (`t.python.log`) machinery. This still used by `twistd`, so
170 the net effect is that the logger's namespace and level are printed to the
171 `twistd` log.
172 """209 """
173210
174 @classmethod211 @classmethod
@@ -185,17 +222,11 @@
185222
186 def __call__(self, event):223 def __call__(self, event):
187 # Be defensive: `system` could be missing or could have a value of224 # Be defensive: `system` could be missing or could have a value of
188 # None. Same goes for `log_system`, `log_namespace`, and `log_level`.225 # None. Same goes for `log_system` and `log_namespace`.
189 if event.get("system") is None and event.get("log_system") is None:226 if event.get("system") is None and event.get("log_system") is None:
190 namespace = event.get("log_namespace")227 namespace = event.get("log_namespace")
191 # Logs written directly to `t.p.log.logfile` and `.logerr` get a228 if namespace is not None:
192 # namespace of "twisted.python.log". This is not interesting.229 event["log_system"] = namespace
193 if namespace == twistedLegacy.__name__:
194 namespace = None
195 level = event.get("log_level")
196 event["log_system"] = "{namespace}#{level}".format(
197 namespace=("-" if namespace is None else namespace),
198 level=("-" if level is None else level.name))
199 # Up-call, which will apply some more transformations.230 # Up-call, which will apply some more transformations.
200 return super().__call__(event)231 return super().__call__(event)
201232
202233
=== modified file 'src/provisioningserver/logger/tests/test__twisted.py'
--- src/provisioningserver/logger/tests/test__twisted.py 2016-11-02 10:01:16 +0000
+++ src/provisioningserver/logger/tests/test__twisted.py 2016-11-07 14:38:23 +0000
@@ -17,6 +17,7 @@
17from maastesting.testcase import MAASTestCase17from maastesting.testcase import MAASTestCase
18from maastesting.twisted import TwistedLoggerFixture18from maastesting.twisted import TwistedLoggerFixture
19from provisioningserver.logger._twisted import (19from provisioningserver.logger._twisted import (
20 LegacyFileLogObserver,
20 LegacyLogger,21 LegacyLogger,
21 LegacyLogObserverWrapper,22 LegacyLogObserverWrapper,
22 observe_twisted_internet_tcp,23 observe_twisted_internet_tcp,
@@ -57,6 +58,80 @@
57 log.theLogPublisher.addObserver(observer)58 log.theLogPublisher.addObserver(observer)
5859
5960
61class TestLegacyFileLogObserver(MAASTestCase):
62 """Scenario tests for `LegacyFileLogObserver`."""
63
64 scenarios = tuple(
65 (log_level.name, dict(log_level=log_level))
66 for log_level in logger.LogLevel.iterconstants()
67 )
68
69 def test__namespace_and_level_is_printed_in_legacy_log(self):
70 # Restore existing observers at the end. This must be careful with
71 # ordering of clean-ups, hence the use of unittest.mock.patch.object
72 # as a context manager.
73 self.addCleanup(setLegacyObservers, log.theLogPublisher.observers)
74 # The global non-legacy `LogBeginner` emits critical messages straight
75 # to stderr, so temporarily put aside its observer to avoid seeing the
76 # critical log messages we're going to generate.
77 self.patch(logger.globalLogPublisher, "_observers", [])
78
79 logbuffer = io.StringIO()
80 observer = LegacyFileLogObserver(logbuffer)
81 observer.formatTime = lambda when: "<timestamp>"
82
83 oldlog = log.msg
84 # Deliberately use the default global observer in the new logger
85 # because we want to see how it behaves in a typical environment where
86 # logs are being emitted by the legacy logging infrastructure, for
87 # example running under `twistd`.
88 newlog = partial(logger.Logger().emit, self.log_level)
89
90 with patch.object(
91 log, "LegacyLogObserverWrapper",
92 log.LegacyLogObserverWrapper):
93 setLegacyObservers([observer.emit])
94 oldlog("Message from legacy", system="legacy")
95 newlog("Message from modern", log_system="modern")
96
97 self.assertThat(
98 logbuffer.getvalue(), DocTestMatches("""\
99 <timestamp> legacy: [info] Message from legacy
100 <timestamp> modern: [%s] Message from modern
101 """ % self.log_level.name))
102
103
104class TestLegacyFileLogObserver_Other(MAASTestCase):
105 """Other tests for `LegacyFileLogObserver`."""
106
107 def test__namespace_is_not_emitted_via_logfile_logerr(self):
108 # Restore existing observers at the end. This must be careful with
109 # ordering of clean-ups, hence the use of unittest.mock.patch.object
110 # as a context manager.
111 self.addCleanup(setLegacyObservers, log.theLogPublisher.observers)
112 # The global non-legacy `LogBeginner` emits critical messages straight
113 # to stderr, so temporarily put aside its observer to avoid seeing the
114 # critical log messages we're going to generate.
115 self.patch(logger.globalLogPublisher, "_observers", [])
116
117 logbuffer = io.StringIO()
118 observer = LegacyFileLogObserver(logbuffer)
119 observer.formatTime = lambda when: "<timestamp>"
120
121 with patch.object(
122 log, "LegacyLogObserverWrapper",
123 log.LegacyLogObserverWrapper):
124 setLegacyObservers([observer.emit])
125 log.logfile.write("Via log.logfile\n")
126 log.logerr.write("Via log.logerr\n")
127
128 self.assertThat(
129 logbuffer.getvalue(), DocTestMatches("""\
130 <timestamp> -: [info] Via log.logfile
131 <timestamp> -: [error] Via log.logerr
132 """))
133
134
60class TestLegacyLogObserverWrapper(MAASTestCase):135class TestLegacyLogObserverWrapper(MAASTestCase):
61 """Scenario tests for `LegacyLogObserverWrapper`."""136 """Scenario tests for `LegacyLogObserverWrapper`."""
62137
@@ -72,7 +147,7 @@
72 self.assertThat(events, HasLength(1))147 self.assertThat(events, HasLength(1))
73 return events[0]148 return events[0]
74149
75 def test__adds_log_system_and_system_to_event(self):150 def test__adds_system_to_event(self):
76 self.assertThat(151 self.assertThat(
77 # This is a `twisted.logger` event, not legacy, and requires152 # This is a `twisted.logger` event, not legacy, and requires
78 # values for `log_time` and `log_level` at a minimum.153 # values for `log_time` and `log_level` at a minimum.
@@ -80,10 +155,10 @@
80 "log_time": sentinel.log_time,155 "log_time": sentinel.log_time,
81 "log_level": self.log_level,156 "log_level": self.log_level,
82 }),157 }),
83 ContainsDictByEquality({158 MatchesAll(
84 "log_system": "-#" + self.log_level.name,159 Not(Contains("log_system")),
85 "system": "-#" + self.log_level.name,160 ContainsDictByEquality({"system": "-"}),
86 }),161 ),
87 )162 )
88163
89 def test__adds_log_system_and_system_to_event_with_namespace(self):164 def test__adds_log_system_and_system_to_event_with_namespace(self):
@@ -95,12 +170,12 @@
95 "log_namespace": log_namespace,170 "log_namespace": log_namespace,
96 }),171 }),
97 ContainsDictByEquality({172 ContainsDictByEquality({
98 "log_system": log_namespace + "#" + self.log_level.name,173 "log_system": log_namespace,
99 "system": log_namespace + "#" + self.log_level.name,174 "system": log_namespace,
100 }),175 }),
101 )176 )
102177
103 def test__adds_log_system_and_system_to_legacy_event(self):178 def test__adds_system_to_legacy_event(self):
104 self.assertThat(179 self.assertThat(
105 # This is a `twisted.python.log` event, i.e. legacy, and requires180 # This is a `twisted.python.log` event, i.e. legacy, and requires
106 # values for `time` and `isError` at a minimum.181 # values for `time` and `isError` at a minimum.
@@ -108,10 +183,10 @@
108 "time": sentinel.time,183 "time": sentinel.time,
109 "isError": factory.pick_bool(),184 "isError": factory.pick_bool(),
110 }),185 }),
111 ContainsDictByEquality({186 MatchesAll(
112 "log_system": "-#-",187 Not(Contains("log_system")),
113 "system": "-#-",188 ContainsDictByEquality({"system": "-"}),
114 }),189 ),
115 )190 )
116191
117 def test__preserves_log_system_in_event(self):192 def test__preserves_log_system_in_event(self):
@@ -146,77 +221,6 @@
146 ),221 ),
147 )222 )
148223
149 def test__namespace_and_level_is_printed_in_legacy_log(self):
150 # Restore existing observers at the end. This must be careful with
151 # ordering of clean-ups, hence the use of unittest.mock.patch.object
152 # as a context manager.
153 self.addCleanup(setLegacyObservers, log.theLogPublisher.observers)
154 # The global non-legacy `LogBeginner` emits critical messages straight
155 # to stderr, so temporarily put aside its observer to avoid seeing the
156 # critical log messages we're going to generate.
157 self.patch(logger.globalLogPublisher, "_observers", [])
158
159 logbuffer = io.StringIO()
160 observer = log.FileLogObserver(logbuffer)
161 observer.formatTime = lambda when: "<timestamp>"
162
163 oldlog = log.msg
164 # Deliberately use the default global observer in the new logger
165 # because we want to see how it behaves in a typical environment where
166 # logs are being emitted by the legacy logging infrastructure, for
167 # example running under `twistd`.
168 newlog = partial(logger.Logger().emit, self.log_level)
169
170 with patch.object(
171 log, "LegacyLogObserverWrapper",
172 log.LegacyLogObserverWrapper):
173 setLegacyObservers([observer.emit])
174 oldlog("Before (legacy)")
175 newlog("Before (new)")
176 LegacyLogObserverWrapper.install()
177 oldlog("After (legacy)")
178 newlog("After (new)")
179
180 self.assertThat(
181 logbuffer.getvalue(), DocTestMatches("""\
182 <timestamp> [-] Before (legacy)
183 <timestamp> [-] Before (new)
184 <timestamp> [-] After (legacy)
185 <timestamp> [%s#%s] After (new)
186 """ % (__name__, self.log_level.name)))
187
188 def test__namespace_and_level_are_elided_via_logfile_logerr(self):
189 # Restore existing observers at the end. This must be careful with
190 # ordering of clean-ups, hence the use of unittest.mock.patch.object
191 # as a context manager.
192 self.addCleanup(setLegacyObservers, log.theLogPublisher.observers)
193 # The global non-legacy `LogBeginner` emits critical messages straight
194 # to stderr, so temporarily put aside its observer to avoid seeing the
195 # critical log messages we're going to generate.
196 self.patch(logger.globalLogPublisher, "_observers", [])
197
198 logbuffer = io.StringIO()
199 observer = log.FileLogObserver(logbuffer)
200 observer.formatTime = lambda when: "<timestamp>"
201
202 with patch.object(
203 log, "LegacyLogObserverWrapper",
204 log.LegacyLogObserverWrapper):
205 setLegacyObservers([observer.emit])
206 log.logfile.write("Before (via log.logfile)\n")
207 log.logerr.write("Before (via log.logerr)\n")
208 LegacyLogObserverWrapper.install()
209 log.logfile.write("After (via log.logfile)\n")
210 log.logerr.write("After (via log.logerr)\n")
211
212 self.assertThat(
213 logbuffer.getvalue(), DocTestMatches("""\
214 <timestamp> [-] Before (via log.logfile)
215 <timestamp> [-] Before (via log.logerr)
216 <timestamp> [-#info] After (via log.logfile)
217 <timestamp> [-#error] After (via log.logerr)
218 """))
219
220224
221class TestLegacyLogObserverWrapper_Installation(MAASTestCase):225class TestLegacyLogObserverWrapper_Installation(MAASTestCase):
222 """Tests for `LegacyLogObserverWrapper`."""226 """Tests for `LegacyLogObserverWrapper`."""
223227
=== modified file 'src/provisioningserver/logger/tests/test_logger.py'
--- src/provisioningserver/logger/tests/test_logger.py 2016-11-03 21:39:45 +0000
+++ src/provisioningserver/logger/tests/test_logger.py 2016-11-07 14:38:23 +0000
@@ -42,7 +42,7 @@
4242
43# Matches lines like: 2016-10-18 14:23:55 [namespace#level] message43# Matches lines like: 2016-10-18 14:23:55 [namespace#level] message
44find_log_lines_re = re.compile(44find_log_lines_re = re.compile(
45 r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) [[](.*?)(?:#(.*))?[]] (.*)$",45 r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.*?): [[](.*)[]] (.*)$",
46 re.MULTILINE)46 re.MULTILINE)
4747
4848
@@ -106,7 +106,7 @@
106 (name, 'info', 'From `twisted.logger`.'),106 (name, 'info', 'From `twisted.logger`.'),
107 (name, 'warn', 'From `twisted.logger`.'),107 (name, 'warn', 'From `twisted.logger`.'),
108 (name, 'error', 'From `twisted.logger`.'),108 (name, 'error', 'From `twisted.logger`.'),
109 (name, '', 'From `twisted.python.log`.'),109 (name, 'info', 'From `twisted.python.log`.'),
110 (name, 'info', 'From `logging`.'),110 (name, 'info', 'From `logging`.'),
111 (name, 'warn', 'From `logging`.'),111 (name, 'warn', 'From `logging`.'),
112 (name, 'error', 'From `logging`.'),112 (name, 'error', 'From `logging`.'),
@@ -132,7 +132,7 @@
132 (name, 'info', 'From `twisted.logger`.'),132 (name, 'info', 'From `twisted.logger`.'),
133 (name, 'warn', 'From `twisted.logger`.'),133 (name, 'warn', 'From `twisted.logger`.'),
134 (name, 'error', 'From `twisted.logger`.'),134 (name, 'error', 'From `twisted.logger`.'),
135 (name, '', 'From `twisted.python.log`.'),135 (name, 'info', 'From `twisted.python.log`.'),
136 (name, 'debug', 'From `logging`.'),136 (name, 'debug', 'From `logging`.'),
137 (name, 'info', 'From `logging`.'),137 (name, 'info', 'From `logging`.'),
138 (name, 'warn', 'From `logging`.'),138 (name, 'warn', 'From `logging`.'),
@@ -195,7 +195,7 @@
195 (name, 'info', 'From `twisted.logger`.'),195 (name, 'info', 'From `twisted.logger`.'),
196 (name, 'warn', 'From `twisted.logger`.'),196 (name, 'warn', 'From `twisted.logger`.'),
197 (name, 'error', 'From `twisted.logger`.'),197 (name, 'error', 'From `twisted.logger`.'),
198 (name, '', 'From `twisted.python.log`.'),198 (name, 'info', 'From `twisted.python.log`.'),
199 (name, 'info', 'From `logging`.'),199 (name, 'info', 'From `logging`.'),
200 (name, 'warn', 'From `logging`.'),200 (name, 'warn', 'From `logging`.'),
201 (name, 'error', 'From `logging`.'),201 (name, 'error', 'From `logging`.'),
@@ -224,7 +224,7 @@
224 (name, 'info', 'From `twisted.logger`.'),224 (name, 'info', 'From `twisted.logger`.'),
225 (name, 'warn', 'From `twisted.logger`.'),225 (name, 'warn', 'From `twisted.logger`.'),
226 (name, 'error', 'From `twisted.logger`.'),226 (name, 'error', 'From `twisted.logger`.'),
227 (name, '', 'From `twisted.python.log`.'),227 (name, 'info', 'From `twisted.python.log`.'),
228 (name, 'debug', 'From `logging`.'),228 (name, 'debug', 'From `logging`.'),
229 (name, 'info', 'From `logging`.'),229 (name, 'info', 'From `logging`.'),
230 (name, 'warn', 'From `logging`.'),230 (name, 'warn', 'From `logging`.'),