Coverage for tests/test_log.py: 10%
355 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-20 10:52 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-20 10:52 +0000
2# LSST Data Management System
3# Copyright 2014-2017 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
22"""
23This tests the logging system in a variety of ways.
24"""
27import os
28import shutil
29import tempfile
30import threading
31import unittest
32import logging
33import lsst.log as log
36class TestLog(unittest.TestCase):
38 class StdoutCapture(object):
39 """
40 Context manager to redirect stdout to a file.
41 """
43 def __init__(self, filename):
44 self.stdout = None
45 self.outputFilename = filename
47 def __enter__(self):
48 self.stdout = os.dup(1)
49 os.close(1)
50 os.open(self.outputFilename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
52 def __exit__(self, type, value, traceback):
53 if self.stdout is not None:
54 os.close(1)
55 os.dup(self.stdout)
56 os.close(self.stdout)
57 self.stdout = None
59 def setUp(self):
60 """Make a temporary directory and a log file in it."""
61 self.tempDir = tempfile.mkdtemp()
62 self.outputFilename = os.path.join(self.tempDir, "log.out")
63 self.stdout = None
65 def tearDown(self):
66 """Remove the temporary directory and clean up Python forwarding."""
67 log.doNotUsePythonLogging()
68 shutil.rmtree(self.tempDir)
70 def configure(self, configuration):
71 """
72 Create a configuration file in the temporary directory and populate
73 it with the provided string.
74 """
75 log.configure_prop(configuration.format(self.outputFilename))
77 def check(self, reference):
78 """Compare the log file with the provided reference text."""
79 with open(self.outputFilename, 'r') as f:
80 # strip everything up to first ] to remove timestamp and thread ID
81 lines = [line.split(']')[-1].rstrip("\n") for line in f.readlines()]
82 reflines = [line for line in reference.split("\n") if line != ""]
83 self.maxDiff = None
84 self.assertListEqual(lines, reflines)
86 def testDefaultLogger(self):
87 """Check the default root logger name."""
88 self.assertEqual(log.getDefaultLogger().getName(), "")
90 def testBasic(self):
91 """
92 Test basic log output with default configuration.
93 Since the default threshold is INFO, the DEBUG or TRACE
94 message is not emitted.
95 """
96 with TestLog.StdoutCapture(self.outputFilename):
97 log.configure()
98 log.log(log.getDefaultLogger(), log.INFO, "This is INFO")
99 log.info(u"This is unicode INFO")
100 log.trace("This is TRACE")
101 log.debug("This is DEBUG")
102 log.warn("This is WARN")
103 log.error("This is ERROR")
104 log.fatal("This is FATAL")
105 log.critical("This is CRITICAL")
106 log.warning("Format %d %g %s", 3, 2.71828, "foo")
107 self.check("""
108root INFO: This is INFO
109root INFO: This is unicode INFO
110root WARN: This is WARN
111root ERROR: This is ERROR
112root FATAL: This is FATAL
113root FATAL: This is CRITICAL
114root WARN: Format 3 2.71828 foo
115""")
117 def testPattern(self):
118 """
119 Test a complex pattern for log messages, including Mapped
120 Diagnostic Context (MDC).
121 """
122 with TestLog.StdoutCapture(self.outputFilename):
123 self.configure("""
124log4j.rootLogger=DEBUG, CA
125log4j.appender.CA=ConsoleAppender
126log4j.appender.CA.layout=PatternLayout
127log4j.appender.CA.layout.ConversionPattern=%-5p %c %C %M (%F:%L) %l - %m - %X%n
128""")
129 log.trace("This is TRACE")
130 log.info("This is INFO")
131 log.debug("This is DEBUG")
133 log.MDC("x", 3)
134 log.MDC("y", "foo")
135 log.MDC("z", TestLog)
137 log.trace("This is TRACE 2")
138 log.info("This is INFO 2")
139 log.debug("This is DEBUG 2")
140 log.MDCRemove("z")
142 log.trace("This is TRACE 3")
143 log.info("This is INFO 3")
144 log.debug("This is DEBUG 3")
145 log.MDCRemove("x")
146 log.trace("This is TRACE 4")
147 log.info("This is INFO 4")
148 log.debug("This is DEBUG 4")
150 log.trace("This is TRACE 5")
151 log.info("This is INFO 5")
152 log.debug("This is DEBUG 5")
154 log.MDCRemove("y")
156 # Use format to make line numbers easier to change.
157 self.check("""
158INFO root testPattern (test_log.py:{0[0]}) test_log.py({0[0]}) - This is INFO - {{}}
159DEBUG root testPattern (test_log.py:{0[1]}) test_log.py({0[1]}) - This is DEBUG - {{}}
160INFO root testPattern (test_log.py:{0[2]}) test_log.py({0[2]}) - This is INFO 2 - {{{{x,3}}{{y,foo}}{{z,<class '{1}.TestLog'>}}}}
161DEBUG root testPattern (test_log.py:{0[3]}) test_log.py({0[3]}) - This is DEBUG 2 - {{{{x,3}}{{y,foo}}{{z,<class '{1}.TestLog'>}}}}
162INFO root testPattern (test_log.py:{0[4]}) test_log.py({0[4]}) - This is INFO 3 - {{{{x,3}}{{y,foo}}}}
163DEBUG root testPattern (test_log.py:{0[5]}) test_log.py({0[5]}) - This is DEBUG 3 - {{{{x,3}}{{y,foo}}}}
164INFO root testPattern (test_log.py:{0[6]}) test_log.py({0[6]}) - This is INFO 4 - {{{{y,foo}}}}
165DEBUG root testPattern (test_log.py:{0[7]}) test_log.py({0[7]}) - This is DEBUG 4 - {{{{y,foo}}}}
166INFO root testPattern (test_log.py:{0[8]}) test_log.py({0[8]}) - This is INFO 5 - {{{{y,foo}}}}
167DEBUG root testPattern (test_log.py:{0[9]}) test_log.py({0[9]}) - This is DEBUG 5 - {{{{y,foo}}}}
168""".format([x + 130 for x in (0, 1, 8, 9, 13, 14, 17, 18, 21, 22)], __name__)) # noqa E501 line too long
170 def testMDCPutPid(self):
171 """
172 Test add of PID Mapped Diagnostic Context (MDC).
173 """
174 pid = os.fork()
175 try:
177 log.MDC("PID", os.getpid())
178 self.configure("""
179log4j.rootLogger=DEBUG, CA
180log4j.appender.CA=ConsoleAppender
181log4j.appender.CA.layout=PatternLayout
182log4j.appender.CA.layout.ConversionPattern=%-5p PID:%X{{PID}} %c %C %M (%F:%L) %l - %m%n
183""") # noqa E501 line too long
184 self.assertGreaterEqual(pid, 0, "Failed to fork")
186 msg = "This is INFO"
187 if pid == 0:
188 self.tempDir = tempfile.mkdtemp()
189 self.outputFilename = os.path.join(self.tempDir,
190 "log-child.out")
191 msg += " in child process"
192 elif pid > 0:
193 child_pid, child_status = os.wait()
194 self.assertEqual(child_status, 0,
195 "Child returns incorrect code")
196 msg += " in parent process"
198 with TestLog.StdoutCapture(self.outputFilename):
199 log.info(msg)
200 line = 199 # line number for previous line
201 finally:
202 log.MDCRemove("PID")
204 # Use format to make line numbers easier to change.
205 self.check("""
206INFO PID:{1} root testMDCPutPid (test_log.py:{0}) test_log.py({0}) - {2}
207""".format(line, os.getpid(), msg))
209 # don't pass other tests in child process
210 if pid == 0:
211 os._exit(0)
213 def testFileAppender(self):
214 """Test configuring logging to go to a file."""
215 self.configure("""
216log4j.rootLogger=DEBUG, FA
217log4j.appender.FA=FileAppender
218log4j.appender.FA.file={0}
219log4j.appender.FA.layout=SimpleLayout
220""")
221 log.MDC("x", 3)
222 log.trace("This is TRACE")
223 log.info("This is INFO")
224 log.debug("This is DEBUG")
225 log.MDCRemove("x")
227 self.check("""
228INFO - This is INFO
229DEBUG - This is DEBUG
230""")
232 def testPythonLogging(self):
233 """Test logging through the Python logging interface."""
234 with TestLog.StdoutCapture(self.outputFilename):
235 lgr = logging.getLogger()
236 lgr.setLevel(logging.INFO)
237 log.configure()
238 with self.assertLogs(level="INFO") as cm:
239 # Force the lsst.log handler to be applied as well as the
240 # unittest log handler
241 lgr.addHandler(log.LogHandler())
242 lgr.info("This is INFO")
243 lgr.debug("This is DEBUG")
244 lgr.warning("This is %s", "WARNING")
245 # message can be arbitrary Python object
246 lgr.info(((1, 2), (3, 4)))
247 lgr.info({1: 2})
249 # Confirm that Python logging also worked
250 self.assertEqual(len(cm.output), 4, f"Got output: {cm.output}")
251 logging.shutdown()
253 self.check("""
254root INFO: This is INFO
255root WARN: This is WARNING
256root INFO: ((1, 2), (3, 4))
257root INFO: {1: 2}
258""")
260 def testMdcInit(self):
262 expected_msg = \
263 "INFO - main thread {{MDC_INIT,OK}}\n" + \
264 "INFO - thread 1 {{MDC_INIT,OK}}\n" + \
265 "INFO - thread 2 {{MDC_INIT,OK}}\n"
267 with TestLog.StdoutCapture(self.outputFilename):
269 self.configure("""
270log4j.rootLogger=DEBUG, CA
271log4j.appender.CA=ConsoleAppender
272log4j.appender.CA.layout=PatternLayout
273log4j.appender.CA.layout.ConversionPattern=%-5p - %m %X%n
274""")
276 def fun():
277 log.MDC("MDC_INIT", "OK")
278 log.MDCRegisterInit(fun)
280 log.info("main thread")
282 thread = threading.Thread(target=lambda: log.info("thread 1"))
283 thread.start()
284 thread.join()
286 thread = threading.Thread(target=lambda: log.info("thread 2"))
287 thread.start()
288 thread.join()
290 self.check(expected_msg)
292 log.MDCRemove("MDC_INIT")
294 def testMdcUpdate(self):
295 """Test for overwriting MDC.
296 """
298 expected_msg = \
299 "INFO - Message one {}\n" \
300 "INFO - Message two {{LABEL,123456}}\n" \
301 "INFO - Message three {{LABEL,654321}}\n" \
302 "INFO - Message four {}\n"
304 with TestLog.StdoutCapture(self.outputFilename):
306 self.configure("""
307log4j.rootLogger=DEBUG, CA
308log4j.appender.CA=ConsoleAppender
309log4j.appender.CA.layout=PatternLayout
310log4j.appender.CA.layout.ConversionPattern=%-5p - %m %X%n
311""")
313 log.info("Message one")
315 log.MDC("LABEL", "123456")
316 log.info("Message two")
318 log.MDC("LABEL", "654321")
319 log.info("Message three")
321 log.MDCRemove("LABEL")
322 log.info("Message four")
324 self.check(expected_msg)
326 def testLwpID(self):
327 """Test log.lwpID() method."""
328 lwp1 = log.lwpID()
329 lwp2 = log.lwpID()
331 self.assertEqual(lwp1, lwp2)
333 def testLogger(self):
334 """
335 Test log object.
336 """
337 with TestLog.StdoutCapture(self.outputFilename):
338 log.configure()
339 logger = log.Log.getLogger("b")
340 self.assertEqual(logger.getName(), "b")
341 self.assertEqual(logger.name, logger.getName())
342 logger.trace("This is TRACE")
343 logger.info("This is INFO")
344 logger.debug("This is DEBUG")
345 logger.warn("This is WARN")
346 logger.error("This is ERROR")
347 logger.fatal("This is FATAL")
348 logger.warn("Format %d %g %s", 3, 2.71828, "foo")
349 self.check("""
350b INFO: This is INFO
351b WARN: This is WARN
352b ERROR: This is ERROR
353b FATAL: This is FATAL
354b WARN: Format 3 2.71828 foo
355""")
357 def testLoggerLevel(self):
358 """
359 Test levels of Log objects
360 """
361 with TestLog.StdoutCapture(self.outputFilename):
362 self.configure("""
363log4j.rootLogger=TRACE, CA
364log4j.appender.CA=ConsoleAppender
365log4j.appender.CA.layout=PatternLayout
366log4j.appender.CA.layout.ConversionPattern=%-5p %c (%F)- %m%n
367""")
368 self.assertEqual(log.Log.getLevel(log.Log.getDefaultLogger()),
369 log.TRACE)
370 self.assertEqual(log.Log.getDefaultLogger().getEffectiveLevel(), log.TRACE)
371 logger = log.Log.getLogger("a.b")
372 self.assertEqual(logger.getName(), "a.b")
373 self.assertEqual(logger.getEffectiveLevel(), log.TRACE)
374 self.assertEqual(log.getEffectiveLevel(logger.name), log.TRACE)
375 logger.trace("This is TRACE")
376 logger.setLevel(log.INFO)
377 self.assertEqual(logger.getLevel(), log.INFO)
378 self.assertEqual(logger.getEffectiveLevel(), log.INFO)
379 self.assertEqual(logger.level, logger.getLevel())
380 self.assertEqual(log.Log.getLevel(logger), log.INFO)
381 logger.debug("This is DEBUG")
382 logger.info("This is INFO")
383 logger.fatal("Format %d %g %s", 3, 2.71828, "foo")
385 logger = log.Log.getLogger("a.b.c")
386 self.assertEqual(logger.getName(), "a.b.c")
387 logger.trace("This is TRACE")
388 logger.debug("This is DEBUG")
389 logger.warn("This is WARN")
390 logger.error("This is ERROR")
391 logger.fatal("This is FATAL")
392 logger.info("Format %d %g %s", 3, 2.71828, "foo")
393 self.check("""
394TRACE a.b (test_log.py)- This is TRACE
395INFO a.b (test_log.py)- This is INFO
396FATAL a.b (test_log.py)- Format 3 2.71828 foo
397WARN a.b.c (test_log.py)- This is WARN
398ERROR a.b.c (test_log.py)- This is ERROR
399FATAL a.b.c (test_log.py)- This is FATAL
400INFO a.b.c (test_log.py)- Format 3 2.71828 foo
401""")
403 def testMsgWithPercentS(self):
404 """Test logging messages containing %s (DM-7509)
405 """
406 with TestLog.StdoutCapture(self.outputFilename):
407 log.configure()
408 logger = log.Log()
409 logger.info("INFO with %s")
410 logger.trace("TRACE with %s")
411 logger.debug("DEBUG with %s")
412 logger.warn("WARN with %s")
413 logger.error("ERROR with %s")
414 logger.fatal("FATAL with %s")
415 logger.logMsg(log.DEBUG, "foo", "bar", 5, "DEBUG with %s")
416 self.check("""
417root INFO: INFO with %s
418root WARN: WARN with %s
419root ERROR: ERROR with %s
420root FATAL: FATAL with %s
421root DEBUG: DEBUG with %s
422""")
424 def testForwardToPython(self):
425 """Test that `lsst.log` log messages can be forwarded to `logging`."""
426 log.configure()
428 # Without forwarding we only get python logger messages captured
429 with self.assertLogs(level="WARNING") as cm:
430 log.warn("lsst.log warning message that will not be forwarded to Python")
431 logging.warning("Python logging message that will be captured")
432 self.assertEqual(len(cm.output), 1)
434 log.usePythonLogging()
436 # With forwarding we get 2 logging messages captured
437 with self.assertLogs(level="WARNING") as cm:
438 log.warn("This is a warning from lsst log meant for python logging")
439 logging.warning("Python warning log message to be captured")
440 self.assertEqual(len(cm.output), 2)
442 loggername = "newlogger"
443 log2 = log.Log.getLogger(loggername)
444 with self.assertLogs(level="INFO", logger=loggername):
445 log2.info("Info message to non-root lsst logger")
447 # Check that debug and info are working properly
448 # This test should return a single log message
449 with self.assertLogs(level="INFO", logger=loggername) as cm:
450 log2.info("Second INFO message to non-root lsst logger")
451 log.debug("Debug message to root lsst logger")
453 self.assertEqual(len(cm.output), 1, f"Got output: {cm.output}")
455 logging.shutdown()
457 def testLogLoop(self):
458 """Test that Python log forwarding works even if Python logging has
459 been forwarded to lsst.log"""
461 log.configure()
463 # Note that assertLogs causes a specialists Python logging handler
464 # to be added.
466 # Set up some Python loggers
467 loggername = "testLogLoop"
468 lgr = logging.getLogger(loggername)
469 lgr.setLevel(logging.INFO)
470 rootlgr = logging.getLogger()
471 rootlgr.setLevel(logging.INFO)
473 # Declare that we are using the Python logger and that this will
474 # not cause a log loop if we also are forwarding Python logging to
475 # lsst.log
476 log.usePythonLogging()
478 # Ensure that we can log both in lsst.log and Python
479 rootlgr.addHandler(log.LogHandler())
481 # All three of these messages go through LogHandler
482 # The first two because they have the handler added explicitly, the
483 # the final one because the lsst.log logger is forwarded to the
484 # ROOT Python logger which has the LogHandler registered.
486 with open(self.outputFilename, "w") as fd:
487 # Adding a StreamHandler will cause the LogHandler to no-op
488 streamHandler = logging.StreamHandler(stream=fd)
489 rootlgr.addHandler(streamHandler)
491 # Do not use assertLogs since that messes with handlers
492 lgr.info("INFO message: Python child logger, lsst.log.LogHandler + PythonLogging")
493 rootlgr.info("INFO message: Python root logger, lsst.log.logHandler + PythonLogging")
495 # This will use a ROOT python logger which has a LogHandler attached
496 log.info("INFO message: lsst.log root logger, PythonLogging")
498 rootlgr.removeHandler(streamHandler)
500 self.check("""
501INFO message: Python child logger, lsst.log.LogHandler + PythonLogging
502INFO message: Python root logger, lsst.log.logHandler + PythonLogging
503INFO message: lsst.log root logger, PythonLogging""")
505 with open(self.outputFilename, "w") as fd:
506 # Adding a StreamHandler will cause the LogHandler to no-op
507 streamHandler = logging.StreamHandler(stream=fd)
508 rootlgr.addHandler(streamHandler)
510 # Do not use assertLogs since that messes with handlers
511 lgr.info("INFO message: Python child logger, lsst.log.LogHandler + PythonLogging")
512 rootlgr.info("INFO message: Python root logger, lsst.log.logHandler + PythonLogging")
514 # This will use a ROOT python logger which has a LogHandler attached
515 log.info("INFO message: lsst.log root logger, PythonLogging")
517 rootlgr.removeHandler(streamHandler)
519 self.check("""
520INFO message: Python child logger, lsst.log.LogHandler + PythonLogging
521INFO message: Python root logger, lsst.log.logHandler + PythonLogging
522INFO message: lsst.log root logger, PythonLogging""")
524 with self.assertLogs(level="INFO") as cm:
525 rootlgr.info("Python log message forward to lsst.log")
526 log.info("lsst.log message forwarded to Python")
528 self.assertEqual(len(cm.output), 2, f"Got output: {cm.output}")
530 logging.shutdown()
532 def testForwardToPythonContextManager(self):
533 """Test that `lsst.log` log messages can be forwarded to `logging`
534 using context manager"""
535 log.configure()
537 # Without forwarding we only get python logger messages captured
538 with self.assertLogs(level="WARNING") as cm:
539 log.warning("lsst.log: not forwarded")
540 logging.warning("Python logging: captured")
541 self.assertEqual(len(cm.output), 1)
543 # Temporarily turn on forwarding
544 with log.UsePythonLogging():
545 with self.assertLogs(level="WARNING") as cm:
546 log.warn("lsst.log: forwarded")
547 logging.warning("Python logging: also captured")
548 self.assertEqual(len(cm.output), 2)
550 # Verify that forwarding is disabled
551 self.assertFalse(log.Log.UsePythonLogging)
553 def testForwardToPythonAppender(self):
554 """Test that `log4cxx` appender forwards it all to logging"""
555 self.configure("""
556log4j.rootLogger=DEBUG, PyLog
557log4j.appender.PyLog = PyLogAppender
558""")
559 with self.assertLogs(level="WARNING") as cm:
560 log.warn("lsst.log: forwarded")
561 logging.warning("Python logging: also captured")
562 self.assertEqual(len(cm.output), 2)
564 # check that MDC is stored in LogRecord
565 log.MDC("LABEL", "some.task")
566 with self.assertLogs(level="WARNING") as cm:
567 log.warn("lsst.log: forwarded")
568 log.MDCRemove("LABEL")
569 self.assertEqual(len(cm.records), 1)
570 self.assertEqual(cm.records[0].MDC, {"LABEL": "some.task"})
571 self.assertEqual(cm.records[0].msg, "lsst.log: forwarded")
573 def testForwardToPythonAppenderWithMDC(self):
574 """Test that `log4cxx` appender forwards it all to logging and modifies
575 message with MDC info"""
576 self.configure("""
577log4j.rootLogger=DEBUG, PyLog
578log4j.appender.PyLog = PyLogAppender
579log4j.appender.PyLog.MessagePattern = %m (LABEL=%X{{LABEL}})
580""")
581 log.MDC("LABEL", "some.task")
582 with self.assertLogs(level="WARNING") as cm:
583 log.warn("lsst.log: forwarded")
584 log.MDCRemove("LABEL")
585 self.assertEqual(len(cm.records), 1)
586 self.assertEqual(cm.records[0].MDC, {"LABEL": "some.task"})
587 self.assertEqual(cm.records[0].msg, "lsst.log: forwarded (LABEL=some.task)")
589 def testForwardToPythonAppenderFormatMDC(self):
590 """Test that we can format `log4cxx` MDC on Python side"""
592 # remember old record factory
593 old_factory = logging.getLogRecordFactory()
595 # configure things using convenience method
596 log.configure_pylog_MDC("INFO")
598 with self.assertLogs(level="WARNING") as cm:
599 log.warn("lsst.log: forwarded 1")
600 log.MDC("LABEL", "task1")
601 log.warn("lsst.log: forwarded 2")
602 log.MDC("LABEL-X", "task2")
603 log.warn("lsst.log: forwarded 3")
604 logging.warning("Python logging: also captured")
605 log.MDCRemove("LABEL")
606 log.MDCRemove("LABEL-X")
607 self.assertEqual(len(cm.records), 4)
609 # restore factory
610 logging.setLogRecordFactory(old_factory)
612 # %-style formatting, only works on whole MDC
613 formatter = logging.Formatter(fmt="%(levelname)s:%(name)s:%(message)s", style="%")
614 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1")
615 formatter = logging.Formatter(fmt="%(levelname)s:%(name)s:%(message)s:%(MDC)s", style="%")
616 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1:{}")
617 self.assertEqual(formatter.format(cm.records[1]),
618 "WARNING:root:lsst.log: forwarded 2:{LABEL=task1}")
619 self.assertEqual(formatter.format(cm.records[2]),
620 "WARNING:root:lsst.log: forwarded 3:{LABEL=task1, LABEL-X=task2}")
621 self.assertEqual(formatter.format(cm.records[3]), "WARNING:root:Python logging: also captured:{}")
623 # format-style formatting, without MDC first
624 formatter = logging.Formatter(fmt="{levelname}:{name}:{message}", style="{")
625 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1")
627 # format-style formatting, with full MDC
628 formatter = logging.Formatter(fmt="{levelname}:{name}:{message}:{MDC}", style="{")
629 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1:{}")
630 self.assertEqual(formatter.format(cm.records[1]),
631 "WARNING:root:lsst.log: forwarded 2:{LABEL=task1}")
632 self.assertEqual(formatter.format(cm.records[2]),
633 "WARNING:root:lsst.log: forwarded 3:{LABEL=task1, LABEL-X=task2}")
634 self.assertEqual(formatter.format(cm.records[3]), "WARNING:root:Python logging: also captured:{}")
636 # format-style, using index access to MDC items, works for almost any
637 # item names
638 formatter = logging.Formatter(fmt="{levelname}:{name}:{message}:{MDC[LABEL-X]}", style="{")
639 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1:")
640 self.assertEqual(formatter.format(cm.records[1]), "WARNING:root:lsst.log: forwarded 2:")
641 self.assertEqual(formatter.format(cm.records[2]),
642 "WARNING:root:lsst.log: forwarded 3:task2")
643 self.assertEqual(formatter.format(cm.records[3]), "WARNING:root:Python logging: also captured:")
645 def testLevelTranslator(self):
646 """Test LevelTranslator class
647 """
648 # correspondence between levels, logging has no TRACE but we accept
649 # small integer in its place
650 levelMap = ((log.TRACE, 5),
651 (log.DEBUG, logging.DEBUG),
652 (log.INFO, logging.INFO),
653 (log.WARN, logging.WARNING),
654 (log.WARNING, logging.WARNING),
655 (log.ERROR, logging.ERROR),
656 (log.CRITICAL, logging.CRITICAL),
657 (log.FATAL, logging.FATAL))
658 for logLevel, loggingLevel in levelMap:
659 self.assertEqual(log.LevelTranslator.lsstLog2logging(logLevel), loggingLevel)
660 self.assertEqual(log.LevelTranslator.logging2lsstLog(loggingLevel), logLevel)
662 def testLevelName(self):
663 self.assertEqual(log.getLevelName(log.INFO), "INFO")
664 self.assertEqual(log.getLevelName(3253), "Level 3253")
666 def testRepr(self):
667 logger = log.getLogger("a.b")
668 logger.setLevel(log.DEBUG)
669 self.assertEqual(repr(logger), "<lsst.log.Log 'a.b' (DEBUG)>")
671 def testChildLogger(self):
672 """Check the getChild logger method."""
673 logger = log.getDefaultLogger()
674 self.assertEqual(logger.getName(), "")
675 logger1 = logger.getChild("child1")
676 self.assertEqual(logger1.getName(), "child1")
677 logger2 = logger1.getChild("child2")
678 self.assertEqual(logger2.getName(), "child1.child2")
679 logger2a = logger1.getChild(".child2")
680 self.assertEqual(logger2a.getName(), "child1.child2")
681 logger3 = logger2.getChild(" .. child3")
682 self.assertEqual(logger3.getName(), "child1.child2.child3")
683 logger3a = logger1.getChild("child2.child3")
684 self.assertEqual(logger3a.getName(), "child1.child2.child3")
685 self.assertEqual(logger2.parent.name, logger1.name)
686 self.assertEqual(logger1.parent.name, logger.name)
687 self.assertIsNone(logger.parent)
690if __name__ == "__main__": 690 ↛ 691line 690 didn't jump to line 691, because the condition on line 690 was never true
691 unittest.main()