Merge lp:~allenap/maas/overhaul-logging-move-legacy-observer into lp:~maas-committers/maas/trunk
- overhaul-logging-move-legacy-observer
- Merge into trunk
Proposed by
Gavin Panella
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5509 |
Proposed branch: | lp:~allenap/maas/overhaul-logging-move-legacy-observer |
Merge into: | lp:~maas-committers/maas/trunk |
Prerequisite: | lp:~allenap/maas/overhaul-logging-more-01 |
Diff against target: |
668 lines (+293/-267) 6 files modified
src/provisioningserver/logger/__init__.py (+2/-2) src/provisioningserver/logger/_twisted.py (+50/-0) src/provisioningserver/logger/tests/test__twisted.py (+240/-0) src/provisioningserver/monkey.py (+0/-7) src/provisioningserver/utils/tests/test_twisted.py (+0/-215) src/provisioningserver/utils/twisted.py (+1/-43) |
To merge this branch: | bzr merge lp:~allenap/maas/overhaul-logging-move-legacy-observer |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Review via email: mp+309123@code.launchpad.net |
Commit message
Move the customised legacy log observer into p.logger.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/provisioningserver/logger/__init__.py' |
2 | --- src/provisioningserver/logger/__init__.py 2016-10-20 14:45:06 +0000 |
3 | +++ src/provisioningserver/logger/__init__.py 2016-10-24 14:36:58 +0000 |
4 | @@ -66,9 +66,9 @@ |
5 | |
6 | import crochet |
7 | from provisioningserver.config import is_dev_environment |
8 | +from provisioningserver.logger._twisted import LegacyLogObserverWrapper |
9 | from provisioningserver.logger.log import get_maas_logger |
10 | from provisioningserver.utils import typed |
11 | -from provisioningserver.utils.twisted import LegacyLogObserverWrapperForMAAS |
12 | from twisted import logger as twistedModern |
13 | from twisted.python import log as twistedLegacy |
14 | import twisted.python.usage |
15 | @@ -236,7 +236,7 @@ |
16 | # namespace and level by the legacy logger in `t.python.log`. This needs |
17 | # to be injected into the `t.p.log` module in order to process events as |
18 | # they move from the legacy to the modern systems. |
19 | - LegacyLogObserverWrapperForMAAS.install() |
20 | + LegacyLogObserverWrapper.install() |
21 | |
22 | # Prevent `crochet` from initialising Twisted's logging. |
23 | warn_unless(hasattr(crochet._main, "_startLoggingWithObserver"), ( |
24 | |
25 | === added file 'src/provisioningserver/logger/_twisted.py' |
26 | --- src/provisioningserver/logger/_twisted.py 1970-01-01 00:00:00 +0000 |
27 | +++ src/provisioningserver/logger/_twisted.py 2016-10-24 14:36:58 +0000 |
28 | @@ -0,0 +1,50 @@ |
29 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
30 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
31 | + |
32 | +"""Twisted-specific logging stuff.""" |
33 | + |
34 | +__all__ = [ |
35 | + 'LegacyLogObserverWrapper', |
36 | +] |
37 | + |
38 | +from twisted import logger |
39 | +from twisted.python import log |
40 | + |
41 | + |
42 | +class LegacyLogObserverWrapper(logger.LegacyLogObserverWrapper): |
43 | + """Ensure that `log_system` is set in the event. |
44 | + |
45 | + This mimics what `twisted.logger.formatEventAsClassicLogText` does when |
46 | + `log_system` is not set, and constructs it from `log_namespace` and |
47 | + `log_level`. |
48 | + |
49 | + This `log_system` value is then seen by `LegacyLogObserverWrapper` and |
50 | + copied into the `system` key and then printed out in the logs by Twisted's |
51 | + legacy logging (`t.python.log`) machinery. This still used by `twistd`, so |
52 | + the net effect is that the logger's namespace and level are printed to the |
53 | + `twistd` log. |
54 | + """ |
55 | + |
56 | + @classmethod |
57 | + def install(cls): |
58 | + """Install this wrapper in place of `log.LegacyLogObserverWrapper`. |
59 | + |
60 | + Inject this wrapper into the `t.python.log` module then remove and |
61 | + re-add all the legacy observers so that they're re-wrapped. |
62 | + """ |
63 | + log.LegacyLogObserverWrapper = cls |
64 | + for observer in log.theLogPublisher.observers: |
65 | + log.theLogPublisher.removeObserver(observer) |
66 | + log.theLogPublisher.addObserver(observer) |
67 | + |
68 | + def __call__(self, event): |
69 | + # Be defensive: `system` could be missing or could have a value of |
70 | + # None. Same goes for `log_system`, `log_namespace`, and `log_level`. |
71 | + if event.get("system") is None and event.get("log_system") is None: |
72 | + namespace = event.get("log_namespace") |
73 | + level = event.get("log_level") |
74 | + event["log_system"] = "{namespace}#{level}".format( |
75 | + namespace=("-" if namespace is None else namespace), |
76 | + level=("-" if level is None else level.name)) |
77 | + # Up-call, which will apply some more transformations. |
78 | + return super().__call__(event) |
79 | |
80 | === added file 'src/provisioningserver/logger/tests/test__twisted.py' |
81 | --- src/provisioningserver/logger/tests/test__twisted.py 1970-01-01 00:00:00 +0000 |
82 | +++ src/provisioningserver/logger/tests/test__twisted.py 2016-10-24 14:36:58 +0000 |
83 | @@ -0,0 +1,240 @@ |
84 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
85 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
86 | + |
87 | +"""Tests for Twisted-specific logging stuff.""" |
88 | + |
89 | +__all__ = [] |
90 | + |
91 | +from functools import partial |
92 | +import io |
93 | +from unittest.mock import ( |
94 | + patch, |
95 | + sentinel, |
96 | +) |
97 | + |
98 | +from maastesting.factory import factory |
99 | +from maastesting.matchers import DocTestMatches |
100 | +from maastesting.testcase import MAASTestCase |
101 | +from provisioningserver.logger._twisted import LegacyLogObserverWrapper |
102 | +from testtools.matchers import ( |
103 | + AllMatch, |
104 | + Contains, |
105 | + ContainsAll, |
106 | + ContainsDict, |
107 | + Equals, |
108 | + HasLength, |
109 | + Is, |
110 | + IsInstance, |
111 | + MatchesAll, |
112 | + MatchesSetwise, |
113 | + Not, |
114 | +) |
115 | +from twisted import logger |
116 | +from twisted.python import log |
117 | + |
118 | + |
119 | +def ContainsDictByEquality(expected): |
120 | + return ContainsDict( |
121 | + {key: Equals(value) for key, value in expected.items()}) |
122 | + |
123 | + |
124 | +def setLegacyObservers(observers): |
125 | + """Remove existing legacy log observers, add those given.""" |
126 | + for observer in log.theLogPublisher.observers: |
127 | + log.theLogPublisher.removeObserver(observer) |
128 | + for observer in observers: |
129 | + log.theLogPublisher.addObserver(observer) |
130 | + |
131 | + |
132 | +class TestLegacyLogObserverWrapper(MAASTestCase): |
133 | + """Scenario tests for `LegacyLogObserverWrapper`.""" |
134 | + |
135 | + scenarios = tuple( |
136 | + (log_level.name, dict(log_level=log_level)) |
137 | + for log_level in logger.LogLevel.iterconstants() |
138 | + ) |
139 | + |
140 | + def processEvent(self, event): |
141 | + events = [] |
142 | + observer = LegacyLogObserverWrapper(events.append) |
143 | + observer(event) |
144 | + self.assertThat(events, HasLength(1)) |
145 | + return events[0] |
146 | + |
147 | + def test__adds_log_system_and_system_to_event(self): |
148 | + self.assertThat( |
149 | + # This is a `twisted.logger` event, not legacy, and requires |
150 | + # values for `log_time` and `log_level` at a minimum. |
151 | + self.processEvent({ |
152 | + "log_time": sentinel.log_time, |
153 | + "log_level": self.log_level, |
154 | + }), |
155 | + ContainsDictByEquality({ |
156 | + "log_system": "-#" + self.log_level.name, |
157 | + "system": "-#" + self.log_level.name, |
158 | + }), |
159 | + ) |
160 | + |
161 | + def test__adds_log_system_and_system_to_event_with_namespace(self): |
162 | + log_namespace = factory.make_name("log_namespace") |
163 | + self.assertThat( |
164 | + self.processEvent({ |
165 | + "log_time": sentinel.log_time, |
166 | + "log_level": self.log_level, |
167 | + "log_namespace": log_namespace, |
168 | + }), |
169 | + ContainsDictByEquality({ |
170 | + "log_system": log_namespace + "#" + self.log_level.name, |
171 | + "system": log_namespace + "#" + self.log_level.name, |
172 | + }), |
173 | + ) |
174 | + |
175 | + def test__adds_log_system_and_system_to_legacy_event(self): |
176 | + self.assertThat( |
177 | + # This is a `twisted.python.log` event, i.e. legacy, and requires |
178 | + # values for `time` and `isError` at a minimum. |
179 | + self.processEvent({ |
180 | + "time": sentinel.time, |
181 | + "isError": factory.pick_bool(), |
182 | + }), |
183 | + ContainsDictByEquality({ |
184 | + "log_system": "-#-", |
185 | + "system": "-#-", |
186 | + }), |
187 | + ) |
188 | + |
189 | + def test__preserves_log_system_in_event(self): |
190 | + log_system = factory.make_name("log_system") |
191 | + self.assertThat( |
192 | + self.processEvent({ |
193 | + "log_time": sentinel.time, |
194 | + "log_level": self.log_level, |
195 | + "log_system": log_system, |
196 | + }), |
197 | + # `log_system` is not modified; `system` is set to match. |
198 | + ContainsDictByEquality({ |
199 | + "log_system": log_system, |
200 | + "system": log_system, |
201 | + }), |
202 | + ) |
203 | + |
204 | + def test__preserves_system_in_legacy_event(self): |
205 | + system = factory.make_name("system") |
206 | + self.assertThat( |
207 | + self.processEvent({ |
208 | + "time": sentinel.time, |
209 | + "isError": factory.pick_bool(), |
210 | + "system": system, |
211 | + }), |
212 | + MatchesAll( |
213 | + # `log_system` is not added when `system` already exists. |
214 | + Not(Contains("log_system")), |
215 | + ContainsDictByEquality({ |
216 | + "system": system, |
217 | + }), |
218 | + ), |
219 | + ) |
220 | + |
221 | + def test__namespace_and_level_is_printed_in_legacy_log(self): |
222 | + # Restore existing observers at the end. This must be careful with |
223 | + # ordering of clean-ups, hence the use of unittest.mock.patch.object |
224 | + # as a context manager. |
225 | + self.addCleanup(setLegacyObservers, log.theLogPublisher.observers) |
226 | + # The global non-legacy `LogBeginner` emits critical messages straight |
227 | + # to stderr, so temporarily put aside its observer to avoid seeing the |
228 | + # critical log messages we're going to generate. |
229 | + self.patch(logger.globalLogPublisher, "_observers", []) |
230 | + |
231 | + logbuffer = io.StringIO() |
232 | + observer = log.FileLogObserver(logbuffer) |
233 | + observer.formatTime = lambda when: "<timestamp>" |
234 | + |
235 | + oldlog = log.msg |
236 | + # Deliberately use the default global observer in the new logger |
237 | + # because we want to see how it behaves in a typical environment where |
238 | + # logs are being emitted by the legacy logging infrastructure, for |
239 | + # example running under `twistd`. |
240 | + newlog = partial(logger.Logger().emit, self.log_level) |
241 | + |
242 | + with patch.object( |
243 | + log, "LegacyLogObserverWrapper", |
244 | + log.LegacyLogObserverWrapper): |
245 | + setLegacyObservers([observer.emit]) |
246 | + oldlog("Before (legacy)") |
247 | + newlog("Before (new)") |
248 | + LegacyLogObserverWrapper.install() |
249 | + oldlog("After (legacy)") |
250 | + newlog("After (new)") |
251 | + |
252 | + self.assertThat( |
253 | + logbuffer.getvalue(), DocTestMatches("""\ |
254 | + <timestamp> [-] Before (legacy) |
255 | + <timestamp> [-] Before (new) |
256 | + <timestamp> [-] After (legacy) |
257 | + <timestamp> [%s#%s] After (new) |
258 | + """ % (__name__, self.log_level.name))) |
259 | + |
260 | + |
261 | +class TestLegacyLogObserverWrapper_Installation(MAASTestCase): |
262 | + """Tests for `LegacyLogObserverWrapper`.""" |
263 | + |
264 | + def setUp(self): |
265 | + super().setUp() |
266 | + # Restore existing observers at the end. Tests must be careful with |
267 | + # ordering of clean-ups, hence the use of unittest.mock.patch.object |
268 | + # as a context manager in the tests themselves. |
269 | + self.addCleanup(setLegacyObservers, log.theLogPublisher.observers) |
270 | + |
271 | + def test__installs_wrapper_to_log_module(self): |
272 | + with patch.object(log, "LegacyLogObserverWrapper", sentinel.unchanged): |
273 | + self.assertThat( |
274 | + log.LegacyLogObserverWrapper, |
275 | + Is(sentinel.unchanged)) |
276 | + LegacyLogObserverWrapper.install() |
277 | + self.assertThat( |
278 | + log.LegacyLogObserverWrapper, |
279 | + Is(LegacyLogObserverWrapper)) |
280 | + |
281 | + def test__rewraps_existing_observers(self): |
282 | + |
283 | + class OldWrapper: |
284 | + |
285 | + def __init__(self, observer): |
286 | + self.legacyObserver = observer |
287 | + |
288 | + def __call__(self, event): |
289 | + return self.legacyObserver(event) |
290 | + |
291 | + with patch.object(log, "LegacyLogObserverWrapper", OldWrapper): |
292 | + |
293 | + observers = (lambda event: event), (lambda event: event) |
294 | + setLegacyObservers(observers) |
295 | + |
296 | + # Our legacy observers are all registered. |
297 | + self.assertThat( |
298 | + log.theLogPublisher.observers, |
299 | + MatchesSetwise(*map(Is, observers))) |
300 | + # Behind the scenes they're all wrapped with OldWrapper. |
301 | + self.assertThat( |
302 | + log.theLogPublisher._legacyObservers, |
303 | + AllMatch(IsInstance(OldWrapper))) |
304 | + # They're registered with the new global log publisher too. |
305 | + self.assertThat( |
306 | + logger.globalLogPublisher._observers, |
307 | + ContainsAll(log.theLogPublisher._legacyObservers)) |
308 | + |
309 | + # Install! |
310 | + LegacyLogObserverWrapper.install() |
311 | + |
312 | + # Our legacy observers are all still registered. |
313 | + self.assertThat( |
314 | + log.theLogPublisher.observers, |
315 | + MatchesSetwise(*map(Is, observers))) |
316 | + # Behind the scenes they're now all wrapped with our wrapper. |
317 | + self.assertThat( |
318 | + log.theLogPublisher._legacyObservers, |
319 | + AllMatch(IsInstance(LegacyLogObserverWrapper))) |
320 | + # They're registered with the new global log publisher too. |
321 | + self.assertThat( |
322 | + logger.globalLogPublisher._observers, |
323 | + ContainsAll(log.theLogPublisher._legacyObservers)) |
324 | |
325 | === modified file 'src/provisioningserver/monkey.py' |
326 | --- src/provisioningserver/monkey.py 2016-10-13 23:43:14 +0000 |
327 | +++ src/provisioningserver/monkey.py 2016-10-24 14:36:58 +0000 |
328 | @@ -327,12 +327,6 @@ |
329 | threads.deferToThreadPool = new_deferToThreadPool |
330 | |
331 | |
332 | -def install_twisted_legacy_log_observer_wrapper(): |
333 | - """Install `LegacyLogObserverWrapperForMAAS`.""" |
334 | - from provisioningserver.utils import twisted |
335 | - twisted.LegacyLogObserverWrapperForMAAS.install() |
336 | - |
337 | - |
338 | def add_patches_to_txtftp(): |
339 | add_term_error_code_to_tftp() |
340 | fix_tftp_requests() |
341 | @@ -344,4 +338,3 @@ |
342 | fix_twisted_web_server_addressToTuple() |
343 | fix_twisted_internet_tcp() |
344 | augment_twisted_deferToThreadPool() |
345 | - install_twisted_legacy_log_observer_wrapper() |
346 | |
347 | === modified file 'src/provisioningserver/utils/tests/test_twisted.py' |
348 | --- src/provisioningserver/utils/tests/test_twisted.py 2016-10-13 23:43:14 +0000 |
349 | +++ src/provisioningserver/utils/tests/test_twisted.py 2016-10-24 14:36:58 +0000 |
350 | @@ -24,7 +24,6 @@ |
351 | from unittest.mock import ( |
352 | ANY, |
353 | Mock, |
354 | - patch, |
355 | sentinel, |
356 | ) |
357 | |
358 | @@ -62,7 +61,6 @@ |
359 | FOREVER, |
360 | IAsynchronous, |
361 | ISynchronous, |
362 | - LegacyLogObserverWrapperForMAAS, |
363 | LONGTIME, |
364 | makeDeferredWithProcessProtocol, |
365 | pause, |
366 | @@ -79,10 +77,7 @@ |
367 | from testtools.deferredruntest import assert_fails_with |
368 | from testtools.matchers import ( |
369 | AfterPreprocessing, |
370 | - AllMatch, |
371 | Contains, |
372 | - ContainsAll, |
373 | - ContainsDict, |
374 | Equals, |
375 | HasLength, |
376 | Is, |
377 | @@ -90,13 +85,11 @@ |
378 | MatchesAll, |
379 | MatchesException, |
380 | MatchesListwise, |
381 | - MatchesSetwise, |
382 | MatchesStructure, |
383 | Not, |
384 | Raises, |
385 | ) |
386 | from testtools.testcase import ExpectedException |
387 | -from twisted import logger |
388 | from twisted.internet import reactor |
389 | from twisted.internet.defer import ( |
390 | AlreadyCalledError, |
391 | @@ -118,7 +111,6 @@ |
392 | ) |
393 | from twisted.python import ( |
394 | context, |
395 | - log, |
396 | threadable, |
397 | ) |
398 | from twisted.python.failure import Failure |
399 | @@ -1887,210 +1879,3 @@ |
400 | transport = Mock(pid=randrange(99999, 9999999)) |
401 | protocol.makeConnection(transport) |
402 | self.assertThat(setpgid, MockCalledOnceWith(transport.pid, 0)) |
403 | - |
404 | - |
405 | -def ContainsDictByEquality(expected): |
406 | - return ContainsDict( |
407 | - {key: Equals(value) for key, value in expected.items()}) |
408 | - |
409 | - |
410 | -def setLegacyObservers(observers): |
411 | - """Remove existing legacy log observers, add those given.""" |
412 | - for observer in log.theLogPublisher.observers: |
413 | - log.theLogPublisher.removeObserver(observer) |
414 | - for observer in observers: |
415 | - log.theLogPublisher.addObserver(observer) |
416 | - |
417 | - |
418 | -class TestLegacyLogObserverWrapperForMAAS(MAASTestCase): |
419 | - """Scenario tests for `LegacyLogObserverWrapperForMAAS`.""" |
420 | - |
421 | - scenarios = tuple( |
422 | - (log_level.name, dict(log_level=log_level)) |
423 | - for log_level in logger.LogLevel.iterconstants() |
424 | - ) |
425 | - |
426 | - def processEvent(self, event): |
427 | - events = [] |
428 | - observer = LegacyLogObserverWrapperForMAAS(events.append) |
429 | - observer(event) |
430 | - self.assertThat(events, HasLength(1)) |
431 | - return events[0] |
432 | - |
433 | - def test__adds_log_system_and_system_to_event(self): |
434 | - self.assertThat( |
435 | - # This is a `twisted.logger` event, not legacy, and requires |
436 | - # values for `log_time` and `log_level` at a minimum. |
437 | - self.processEvent({ |
438 | - "log_time": sentinel.log_time, |
439 | - "log_level": self.log_level, |
440 | - }), |
441 | - ContainsDictByEquality({ |
442 | - "log_system": "-#" + self.log_level.name, |
443 | - "system": "-#" + self.log_level.name, |
444 | - }), |
445 | - ) |
446 | - |
447 | - def test__adds_log_system_and_system_to_event_with_namespace(self): |
448 | - log_namespace = factory.make_name("log_namespace") |
449 | - self.assertThat( |
450 | - self.processEvent({ |
451 | - "log_time": sentinel.log_time, |
452 | - "log_level": self.log_level, |
453 | - "log_namespace": log_namespace, |
454 | - }), |
455 | - ContainsDictByEquality({ |
456 | - "log_system": log_namespace + "#" + self.log_level.name, |
457 | - "system": log_namespace + "#" + self.log_level.name, |
458 | - }), |
459 | - ) |
460 | - |
461 | - def test__adds_log_system_and_system_to_legacy_event(self): |
462 | - self.assertThat( |
463 | - # This is a `twisted.python.log` event, i.e. legacy, and requires |
464 | - # values for `time` and `isError` at a minimum. |
465 | - self.processEvent({ |
466 | - "time": sentinel.time, |
467 | - "isError": factory.pick_bool(), |
468 | - }), |
469 | - ContainsDictByEquality({ |
470 | - "log_system": "-#-", |
471 | - "system": "-#-", |
472 | - }), |
473 | - ) |
474 | - |
475 | - def test__preserves_log_system_in_event(self): |
476 | - log_system = factory.make_name("log_system") |
477 | - self.assertThat( |
478 | - self.processEvent({ |
479 | - "log_time": sentinel.time, |
480 | - "log_level": self.log_level, |
481 | - "log_system": log_system, |
482 | - }), |
483 | - # `log_system` is not modified; `system` is set to match. |
484 | - ContainsDictByEquality({ |
485 | - "log_system": log_system, |
486 | - "system": log_system, |
487 | - }), |
488 | - ) |
489 | - |
490 | - def test__preserves_system_in_legacy_event(self): |
491 | - system = factory.make_name("system") |
492 | - self.assertThat( |
493 | - self.processEvent({ |
494 | - "time": sentinel.time, |
495 | - "isError": factory.pick_bool(), |
496 | - "system": system, |
497 | - }), |
498 | - MatchesAll( |
499 | - # `log_system` is not added when `system` already exists. |
500 | - Not(Contains("log_system")), |
501 | - ContainsDictByEquality({ |
502 | - "system": system, |
503 | - }), |
504 | - ), |
505 | - ) |
506 | - |
507 | - def test__namespace_and_level_is_printed_in_legacy_log(self): |
508 | - # Restore existing observers at the end. This must be careful with |
509 | - # ordering of clean-ups, hence the use of unittest.mock.patch.object |
510 | - # as a context manager. |
511 | - self.addCleanup(setLegacyObservers, log.theLogPublisher.observers) |
512 | - # The global non-legacy `LogBeginner` emits critical messages straight |
513 | - # to stderr, so temporarily put aside its observer to avoid seeing the |
514 | - # critical log messages we're going to generate. |
515 | - self.patch(logger.globalLogPublisher, "_observers", []) |
516 | - |
517 | - logbuffer = io.StringIO() |
518 | - observer = log.FileLogObserver(logbuffer) |
519 | - observer.formatTime = lambda when: "<timestamp>" |
520 | - |
521 | - oldlog = log.msg |
522 | - # Deliberately use the default global observer in the new logger |
523 | - # because we want to see how it behaves in a typical environment where |
524 | - # logs are being emitted by the legacy logging infrastructure, for |
525 | - # example running under `twistd`. |
526 | - newlog = partial(logger.Logger().emit, self.log_level) |
527 | - |
528 | - with patch.object( |
529 | - log, "LegacyLogObserverWrapper", |
530 | - log.LegacyLogObserverWrapper): |
531 | - setLegacyObservers([observer.emit]) |
532 | - oldlog("Before (legacy)") |
533 | - newlog("Before (new)") |
534 | - LegacyLogObserverWrapperForMAAS.install() |
535 | - oldlog("After (legacy)") |
536 | - newlog("After (new)") |
537 | - |
538 | - self.assertThat( |
539 | - logbuffer.getvalue(), DocTestMatches("""\ |
540 | - <timestamp> [-] Before (legacy) |
541 | - <timestamp> [-] Before (new) |
542 | - <timestamp> [-] After (legacy) |
543 | - <timestamp> [%s#%s] After (new) |
544 | - """ % (__name__, self.log_level.name))) |
545 | - |
546 | - |
547 | -class TestLegacyLogObserverWrapperForMAAS_Installation(MAASTestCase): |
548 | - """Tests for `LegacyLogObserverWrapperForMAAS`.""" |
549 | - |
550 | - def setUp(self): |
551 | - super().setUp() |
552 | - # Restore existing observers at the end. Tests must be careful with |
553 | - # ordering of clean-ups, hence the use of unittest.mock.patch.object |
554 | - # as a context manager in the tests themselves. |
555 | - self.addCleanup(setLegacyObservers, log.theLogPublisher.observers) |
556 | - |
557 | - def test__installs_wrapper_to_log_module(self): |
558 | - with patch.object(log, "LegacyLogObserverWrapper", sentinel.unchanged): |
559 | - self.assertThat( |
560 | - log.LegacyLogObserverWrapper, |
561 | - Is(sentinel.unchanged)) |
562 | - LegacyLogObserverWrapperForMAAS.install() |
563 | - self.assertThat( |
564 | - log.LegacyLogObserverWrapper, |
565 | - Is(LegacyLogObserverWrapperForMAAS)) |
566 | - |
567 | - def test__rewraps_existing_observers(self): |
568 | - |
569 | - class OldWrapper: |
570 | - |
571 | - def __init__(self, observer): |
572 | - self.legacyObserver = observer |
573 | - |
574 | - def __call__(self, event): |
575 | - return self.legacyObserver(event) |
576 | - |
577 | - with patch.object(log, "LegacyLogObserverWrapper", OldWrapper): |
578 | - |
579 | - observers = (lambda event: event), (lambda event: event) |
580 | - setLegacyObservers(observers) |
581 | - |
582 | - # Our legacy observers are all registered. |
583 | - self.assertThat( |
584 | - log.theLogPublisher.observers, |
585 | - MatchesSetwise(*map(Is, observers))) |
586 | - # Behind the scenes they're all wrapped with OldWrapper. |
587 | - self.assertThat( |
588 | - log.theLogPublisher._legacyObservers, |
589 | - AllMatch(IsInstance(OldWrapper))) |
590 | - # They're registered with the new global log publisher too. |
591 | - self.assertThat( |
592 | - logger.globalLogPublisher._observers, |
593 | - ContainsAll(log.theLogPublisher._legacyObservers)) |
594 | - |
595 | - # Install! |
596 | - LegacyLogObserverWrapperForMAAS.install() |
597 | - |
598 | - # Our legacy observers are all still registered. |
599 | - self.assertThat( |
600 | - log.theLogPublisher.observers, |
601 | - MatchesSetwise(*map(Is, observers))) |
602 | - # Behind the scenes they're now all wrapped with our wrapper. |
603 | - self.assertThat( |
604 | - log.theLogPublisher._legacyObservers, |
605 | - AllMatch(IsInstance(LegacyLogObserverWrapperForMAAS))) |
606 | - # They're registered with the new global log publisher too. |
607 | - self.assertThat( |
608 | - logger.globalLogPublisher._observers, |
609 | - ContainsAll(log.theLogPublisher._legacyObservers)) |
610 | |
611 | === modified file 'src/provisioningserver/utils/twisted.py' |
612 | --- src/provisioningserver/utils/twisted.py 2016-10-13 23:43:14 +0000 |
613 | +++ src/provisioningserver/utils/twisted.py 2016-10-24 14:36:58 +0000 |
614 | @@ -63,10 +63,7 @@ |
615 | from twisted.internet.error import ProcessDone |
616 | from twisted.internet.protocol import ProcessProtocol |
617 | from twisted.internet.threads import deferToThread |
618 | -from twisted.logger import ( |
619 | - LegacyLogObserverWrapper, |
620 | - Logger, |
621 | -) |
622 | +from twisted.logger import Logger |
623 | from twisted.python import ( |
624 | context, |
625 | log, |
626 | @@ -1058,42 +1055,3 @@ |
627 | _os_setpgid(self.transport.pid, 0) |
628 | except ProcessLookupError: |
629 | pass # Already exited. |
630 | - |
631 | - |
632 | -class LegacyLogObserverWrapperForMAAS(LegacyLogObserverWrapper): |
633 | - """Ensure that `log_system` is set in the event. |
634 | - |
635 | - This mimics what `twisted.logger.formatEventAsClassicLogText` does when |
636 | - `log_system` is not set, and constructs it from `log_namespace` and |
637 | - `log_level`. |
638 | - |
639 | - This `log_system` value is then seen by `LegacyLogObserverWrapper` and |
640 | - copied into the `system` key and then printed out in the logs by Twisted's |
641 | - legacy logging (`t.python.log`) machinery. This still used by `twistd`, so |
642 | - the net effect is that the logger's namespace and level are printed to the |
643 | - `twistd` log. |
644 | - """ |
645 | - |
646 | - @classmethod |
647 | - def install(cls): |
648 | - """Install this wrapper in place of `log.LegacyLogObserverWrapper`. |
649 | - |
650 | - Inject this wrapper into the `t.python.log` module then remove and |
651 | - re-add all the legacy observers so that they're re-wrapped. |
652 | - """ |
653 | - log.LegacyLogObserverWrapper = cls |
654 | - for observer in log.theLogPublisher.observers: |
655 | - log.theLogPublisher.removeObserver(observer) |
656 | - log.theLogPublisher.addObserver(observer) |
657 | - |
658 | - def __call__(self, event): |
659 | - # Be defensive: `system` could be missing or could have a value of |
660 | - # None. Same goes for `log_system`, `log_namespace`, and `log_level`. |
661 | - if event.get("system") is None and event.get("log_system") is None: |
662 | - namespace = event.get("log_namespace") |
663 | - level = event.get("log_level") |
664 | - event["log_system"] = "{namespace}#{level}".format( |
665 | - namespace=("-" if namespace is None else namespace), |
666 | - level=("-" if level is None else level.name)) |
667 | - # Up-call, which will apply some more transformations. |
668 | - return super().__call__(event) |
This is a mechanical move, so self-reviewing. It comes after lp:~allenap/maas/overhaul-logging-more-01 which needs a full review so there's plenty of time to say something if you spot a problem.