~blake-rouse/maas/is-importing-2.1

« back to all changes in this revision

Viewing changes to src/provisioningserver/logger/tests/test__twisted.py

  • Committer: LaMont Jones
  • Date: 2016-12-07 12:59:10 UTC
  • mfrom: (5561 2.1)
  • mto: This revision was merged to the branch mainline in revision 5563.
  • Revision ID: lamont@canonical.com-20161207125910-fow7gg8v9bo0s1iq
merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
 
6
6
__all__ = []
7
7
 
8
 
from functools import partial
9
8
import io
10
 
from unittest.mock import (
11
 
    patch,
12
 
    sentinel,
13
 
)
14
9
 
15
10
from maastesting.factory import factory
16
 
from maastesting.matchers import DocTestMatches
17
11
from maastesting.testcase import MAASTestCase
18
12
from maastesting.twisted import TwistedLoggerFixture
 
13
from provisioningserver.logger import _twisted
 
14
from provisioningserver.logger._common import DEFAULT_LOG_FORMAT_DATE
19
15
from provisioningserver.logger._twisted import (
20
 
    LegacyFileLogObserver,
 
16
    _formatModernEvent,
 
17
    _getSystemName,
 
18
    EventLogger,
21
19
    LegacyLogger,
22
 
    LegacyLogObserverWrapper,
23
20
    observe_twisted_internet_tcp,
24
21
    observe_twisted_internet_udp,
25
22
    observe_twisted_internet_unix,
26
23
)
27
 
from provisioningserver.logger.testing import make_event
 
24
from provisioningserver.logger.testing import (
 
25
    find_log_lines,
 
26
    make_event,
 
27
    pick_log_time,
 
28
)
28
29
from testtools.matchers import (
29
30
    AfterPreprocessing,
30
 
    AllMatch,
31
31
    Contains,
32
 
    ContainsAll,
33
32
    ContainsDict,
34
33
    Equals,
35
34
    HasLength,
37
36
    IsInstance,
38
37
    MatchesAll,
39
38
    MatchesDict,
40
 
    MatchesSetwise,
41
 
    Not,
42
39
)
43
40
from twisted import logger
44
 
from twisted.python import log
45
41
from twisted.python.failure import Failure
46
42
 
47
43
 
50
46
        {key: Equals(value) for key, value in expected.items()})
51
47
 
52
48
 
53
 
def setLegacyObservers(observers):
54
 
    """Remove existing legacy log observers, add those given."""
55
 
    for observer in log.theLogPublisher.observers:
56
 
        log.theLogPublisher.removeObserver(observer)
57
 
    for observer in observers:
58
 
        log.theLogPublisher.addObserver(observer)
59
 
 
60
 
 
61
 
class 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
 
 
104
 
class 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
 
 
135
 
class TestLegacyLogObserverWrapper(MAASTestCase):
136
 
    """Scenario tests for `LegacyLogObserverWrapper`."""
137
 
 
138
 
    scenarios = tuple(
139
 
        (log_level.name, dict(log_level=log_level))
140
 
        for log_level in logger.LogLevel.iterconstants()
141
 
    )
142
 
 
143
 
    def processEvent(self, event):
144
 
        events = []
145
 
        observer = LegacyLogObserverWrapper(events.append)
146
 
        observer(event)
147
 
        self.assertThat(events, HasLength(1))
148
 
        return events[0]
149
 
 
150
 
    def test__adds_system_to_event(self):
151
 
        self.assertThat(
152
 
            # This is a `twisted.logger` event, not legacy, and requires
153
 
            # values for `log_time` and `log_level` at a minimum.
154
 
            self.processEvent({
155
 
                "log_time": sentinel.log_time,
156
 
                "log_level": self.log_level,
157
 
            }),
158
 
            MatchesAll(
159
 
                Not(Contains("log_system")),
160
 
                ContainsDictByEquality({"system": "-"}),
161
 
            ),
162
 
        )
163
 
 
164
 
    def test__adds_log_system_and_system_to_event_with_namespace(self):
165
 
        log_namespace = factory.make_name("log_namespace")
166
 
        self.assertThat(
167
 
            self.processEvent({
168
 
                "log_time": sentinel.log_time,
169
 
                "log_level": self.log_level,
170
 
                "log_namespace": log_namespace,
171
 
            }),
172
 
            ContainsDictByEquality({
173
 
                "log_system": log_namespace,
174
 
                "system": log_namespace,
175
 
            }),
176
 
        )
177
 
 
178
 
    def test__adds_system_to_legacy_event(self):
179
 
        self.assertThat(
180
 
            # This is a `twisted.python.log` event, i.e. legacy, and requires
181
 
            # values for `time` and `isError` at a minimum.
182
 
            self.processEvent({
183
 
                "time": sentinel.time,
184
 
                "isError": factory.pick_bool(),
185
 
            }),
186
 
            MatchesAll(
187
 
                Not(Contains("log_system")),
188
 
                ContainsDictByEquality({"system": "-"}),
189
 
            ),
190
 
        )
191
 
 
192
 
    def test__preserves_log_system_in_event(self):
193
 
        log_system = factory.make_name("log_system")
194
 
        self.assertThat(
195
 
            self.processEvent({
196
 
                "log_time": sentinel.time,
197
 
                "log_level": self.log_level,
198
 
                "log_system": log_system,
199
 
            }),
200
 
            # `log_system` is not modified; `system` is set to match.
201
 
            ContainsDictByEquality({
202
 
                "log_system": log_system,
203
 
                "system": log_system,
204
 
            }),
205
 
        )
206
 
 
207
 
    def test__preserves_system_in_legacy_event(self):
208
 
        system = factory.make_name("system")
209
 
        self.assertThat(
210
 
            self.processEvent({
211
 
                "time": sentinel.time,
212
 
                "isError": factory.pick_bool(),
213
 
                "system": system,
214
 
            }),
215
 
            MatchesAll(
216
 
                # `log_system` is not added when `system` already exists.
217
 
                Not(Contains("log_system")),
218
 
                ContainsDictByEquality({
219
 
                    "system": system,
220
 
                }),
221
 
            ),
222
 
        )
223
 
 
224
 
 
225
 
class TestLegacyLogObserverWrapper_Installation(MAASTestCase):
226
 
    """Tests for `LegacyLogObserverWrapper`."""
227
 
 
228
 
    def setUp(self):
229
 
        super().setUp()
230
 
        # Restore existing observers at the end. Tests must be careful with
231
 
        # ordering of clean-ups, hence the use of unittest.mock.patch.object
232
 
        # as a context manager in the tests themselves.
233
 
        self.addCleanup(setLegacyObservers, log.theLogPublisher.observers)
234
 
 
235
 
    def test__installs_wrapper_to_log_module(self):
236
 
        with patch.object(log, "LegacyLogObserverWrapper", sentinel.unchanged):
237
 
            self.assertThat(
238
 
                log.LegacyLogObserverWrapper,
239
 
                Is(sentinel.unchanged))
240
 
            LegacyLogObserverWrapper.install()
241
 
            self.assertThat(
242
 
                log.LegacyLogObserverWrapper,
243
 
                Is(LegacyLogObserverWrapper))
244
 
 
245
 
    def test__rewraps_existing_observers(self):
246
 
 
247
 
        class OldWrapper:
248
 
 
249
 
            def __init__(self, observer):
250
 
                self.legacyObserver = observer
251
 
 
252
 
            def __call__(self, event):
253
 
                return self.legacyObserver(event)
254
 
 
255
 
        with patch.object(log, "LegacyLogObserverWrapper", OldWrapper):
256
 
 
257
 
            observers = (lambda event: event), (lambda event: event)
258
 
            setLegacyObservers(observers)
259
 
 
260
 
            # Our legacy observers are all registered.
261
 
            self.assertThat(
262
 
                log.theLogPublisher.observers,
263
 
                MatchesSetwise(*map(Is, observers)))
264
 
            # Behind the scenes they're all wrapped with OldWrapper.
265
 
            self.assertThat(
266
 
                log.theLogPublisher._legacyObservers,
267
 
                AllMatch(IsInstance(OldWrapper)))
268
 
            # They're registered with the new global log publisher too.
269
 
            self.assertThat(
270
 
                logger.globalLogPublisher._observers,
271
 
                ContainsAll(log.theLogPublisher._legacyObservers))
272
 
 
273
 
            # Install!
274
 
            LegacyLogObserverWrapper.install()
275
 
 
276
 
            # Our legacy observers are all still registered.
277
 
            self.assertThat(
278
 
                log.theLogPublisher.observers,
279
 
                MatchesSetwise(*map(Is, observers)))
280
 
            # Behind the scenes they're now all wrapped with our wrapper.
281
 
            self.assertThat(
282
 
                log.theLogPublisher._legacyObservers,
283
 
                AllMatch(IsInstance(LegacyLogObserverWrapper)))
284
 
            # They're registered with the new global log publisher too.
285
 
            self.assertThat(
286
 
                logger.globalLogPublisher._observers,
287
 
                ContainsAll(log.theLogPublisher._legacyObservers))
288
 
 
289
 
 
290
49
def formatTimeStatic(when):
291
50
    """Just return <when>."""
292
51
    return "<when>"
443
202
    """Tests for `observe_twisted_internet_unix`."""
444
203
 
445
204
    def test__ignores_port_closed_events(self):
446
 
        event = make_event("(Port %d Closed)" % factory.pick_port())
 
205
        event = make_event("(Port %r Closed)" % factory.make_name("port"))
447
206
        with TwistedLoggerFixture() as logger:
448
207
            observe_twisted_internet_unix(event)
449
208
        self.assertThat(logger.events, HasLength(0))
460
219
        with TwistedLoggerFixture() as logger:
461
220
            observe_twisted_internet_unix(event)
462
221
        self.assertThat(logger.events, Contains(event))
 
222
 
 
223
 
 
224
class TestGetSystemName(MAASTestCase):
 
225
    """Tests for `_getSystemName`."""
 
226
 
 
227
    expectations = {
 
228
        "foo.bar.baz": "foo.bar.baz",
 
229
        "f_o.bar.baz": "f_o.bar.baz",
 
230
        "foo.b_r.baz": "foo.b_r.baz",
 
231
        "foo.bar.b_z": "foo.bar.b_z",
 
232
        "foo.bar._az": "foo.bar",
 
233
        "foo._ar.baz": "foo",
 
234
        "foo._ar._az": "foo",
 
235
        "_oo.bar.baz": None,
 
236
        "_": None,
 
237
        "": None,
 
238
        None: None,
 
239
    }
 
240
 
 
241
    scenarios = tuple(
 
242
        ("%s => %s" % (
 
243
            string_in or repr(string_in),
 
244
            string_out or repr(string_out)),
 
245
         {"string_in": string_in, "string_out": string_out})
 
246
        for string_in, string_out in expectations.items()
 
247
    )
 
248
 
 
249
    def test(self):
 
250
        self.assertThat(
 
251
            _getSystemName(self.string_in),
 
252
            Equals(self.string_out))
 
253
 
 
254
 
 
255
class TestFormatModernEvent(MAASTestCase):
 
256
    """Tests for `_formatModernEvent`."""
 
257
 
 
258
    scenarios = tuple(
 
259
        (level.name, {"log_level": level})
 
260
        for level in logger.LogLevel.iterconstants()
 
261
    )
 
262
 
 
263
    def test_format_basics(self):
 
264
        thing1 = factory.make_name("thing")
 
265
        thing2 = factory.make_name("thing")
 
266
        log_system = factory.make_name("system")
 
267
        log_format = ">{thing1}< >{thing2}<"
 
268
        log_time = pick_log_time()
 
269
        self.assertThat(
 
270
            _formatModernEvent({
 
271
                "log_time": log_time,
 
272
                "log_format": log_format,
 
273
                "log_system": log_system,
 
274
                "log_level": self.log_level,
 
275
                "thing1": thing1,
 
276
                "thing2": thing2,
 
277
            }),
 
278
            Equals(
 
279
                "%s %s: [%s] >%s< >%s<\n" % (
 
280
                    logger.formatTime(log_time, DEFAULT_LOG_FORMAT_DATE),
 
281
                    log_system, self.log_level.name, thing1, thing2),
 
282
            ),
 
283
        )
 
284
 
 
285
    def test_formats_without_format(self):
 
286
        self.assertThat(
 
287
            _formatModernEvent({
 
288
                "log_level": self.log_level,
 
289
            }),
 
290
            Equals("- -: [%s] \n" % self.log_level.name),
 
291
        )
 
292
 
 
293
    def test_formats_with_null_format(self):
 
294
        self.assertThat(
 
295
            _formatModernEvent({
 
296
                "log_format": None,
 
297
                "log_level": self.log_level,
 
298
            }),
 
299
            Equals("- -: [%s] \n" % self.log_level.name),
 
300
        )
 
301
 
 
302
    def test_formats_without_time(self):
 
303
        self.assertThat(
 
304
            _formatModernEvent({
 
305
                "log_level": self.log_level,
 
306
            }),
 
307
            Equals("- -: [%s] \n" % self.log_level.name),
 
308
        )
 
309
 
 
310
    def test_formats_with_null_time(self):
 
311
        self.assertThat(
 
312
            _formatModernEvent({
 
313
                "log_time": None,
 
314
                "log_level": self.log_level,
 
315
            }),
 
316
            Equals("- -: [%s] \n" % self.log_level.name),
 
317
        )
 
318
 
 
319
    def test_uses_namespace_if_system_missing(self):
 
320
        log_namespace = factory.make_name("namespace")
 
321
        self.assertThat(
 
322
            _formatModernEvent({
 
323
                "log_level": self.log_level,
 
324
                "log_namespace": log_namespace,
 
325
            }),
 
326
            Equals(
 
327
                "- %s: [%s] \n" % (
 
328
                    log_namespace, self.log_level.name),
 
329
            ),
 
330
        )
 
331
 
 
332
    def test_uses_namespace_if_system_null(self):
 
333
        log_namespace = factory.make_name("namespace")
 
334
        self.assertThat(
 
335
            _formatModernEvent({
 
336
                "log_level": self.log_level,
 
337
                "log_namespace": log_namespace,
 
338
                "log_system": None,
 
339
            }),
 
340
            Equals(
 
341
                "- %s: [%s] \n" % (
 
342
                    log_namespace, self.log_level.name),
 
343
            ),
 
344
        )
 
345
 
 
346
 
 
347
class TestEventLogger(MAASTestCase):
 
348
    """Tests for `EventLogger`."""
 
349
 
 
350
    scenarios = tuple(
 
351
        (level.name, {"log_level": level})
 
352
        for level in logger.LogLevel.iterconstants()
 
353
    )
 
354
 
 
355
    def setUp(self):
 
356
        super(TestEventLogger, self).setUp()
 
357
        self.output = io.StringIO()
 
358
        self.log = EventLogger(self.output)
 
359
        self.get_logs = lambda: find_log_lines(self.output.getvalue())
 
360
 
 
361
    def setLogLevel(self, log_level):
 
362
        """Set the level at which events will be logged.
 
363
 
 
364
        This is not a minimum level, it is an absolute level.
 
365
        """
 
366
        self.patch(_twisted, "_filterByLevels", {self.log_level})
 
367
 
 
368
    def test_basics(self):
 
369
        self.setLogLevel(self.log_level)
 
370
        event = make_event(log_level=self.log_level)
 
371
        event["log_system"] = factory.make_name("system")
 
372
        self.log(event)
 
373
        self.assertSequenceEqual(
 
374
            [(event["log_system"], self.log_level.name, event["log_text"])],
 
375
            self.get_logs(),
 
376
        )
 
377
 
 
378
    def test_filters_by_level(self):
 
379
        self.setLogLevel(self.log_level)
 
380
        events = {
 
381
            log_level: make_event(log_level=log_level)
 
382
            for log_level in logger.LogLevel.iterconstants()
 
383
        }
 
384
        for event in events.values():
 
385
            self.log(event)
 
386
        # Only the log at the current level will get through.
 
387
        self.assertSequenceEqual(
 
388
            [("-", self.log_level.name, events[self.log_level]["log_text"])],
 
389
            self.get_logs(),
 
390
        )
 
391
 
 
392
    def test_filters_by_noise(self):
 
393
        self.setLogLevel(self.log_level)
 
394
        common = dict(log_namespace="log_legacy", log_system="-")
 
395
        noisy = [
 
396
            make_event("Log opened.", **common),
 
397
            make_event("Main loop terminated.", **common),
 
398
        ]
 
399
        for event in noisy:
 
400
            self.log(event)
 
401
        okay = [
 
402
            make_event(log_level=self.log_level, **common),
 
403
        ]
 
404
        for event in okay:
 
405
            self.log(event)
 
406
        # Only the `okay` logs will get through.
 
407
        expected = [
 
408
            ("-", self.log_level.name, event["log_text"])
 
409
            for event in okay
 
410
        ]
 
411
        self.assertSequenceEqual(expected, self.get_logs())