Coverage for tests/test_log.py: 10%
367 statements
« prev ^ index » next coverage.py v6.4, created at 2022-06-02 03:26 -0700
« prev ^ index » next coverage.py v6.4, created at 2022-06-02 03:26 -0700
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 testBasicFormat(self):
118 """
119 Test basic log output with default configuration but using
120 the f variants.
121 Since the default threshold is INFO, the DEBUG or TRACE
122 message is not emitted.
123 """
124 with TestLog.StdoutCapture(self.outputFilename):
125 log.configure()
126 log.logf(log.getDefaultLogger(), log.INFO,
127 "This is {{INFO}} Item 1: {item[1]}",
128 item=["a", "b", "c"])
129 log.infof(u"This is {unicode} INFO")
130 log.tracef("This is TRACE")
131 log.debugf("This is DEBUG")
132 log.warnf("This is WARN {city}", city="Tucson")
133 log.errorf("This is ERROR {1}->{0}", 2, 1)
134 log.fatalf("This is FATAL {1} out of {0} times for {place}",
135 4, 3, place="LSST")
136 log.warnf("Format {} {} {}", 3, 2.71828, "foo")
137 self.check("""
138root INFO: This is {INFO} Item 1: b
139root INFO: This is {unicode} INFO
140root WARN: This is WARN Tucson
141root ERROR: This is ERROR 1->2
142root FATAL: This is FATAL 3 out of 4 times for LSST
143root WARN: Format 3 2.71828 foo
144""")
146 def testPattern(self):
147 """
148 Test a complex pattern for log messages, including Mapped
149 Diagnostic Context (MDC).
150 """
151 with TestLog.StdoutCapture(self.outputFilename):
152 self.configure("""
153log4j.rootLogger=DEBUG, CA
154log4j.appender.CA=ConsoleAppender
155log4j.appender.CA.layout=PatternLayout
156log4j.appender.CA.layout.ConversionPattern=%-5p %c %C %M (%F:%L) %l - %m - %X%n
157""")
158 log.trace("This is TRACE")
159 log.info("This is INFO")
160 log.debug("This is DEBUG")
162 log.MDC("x", 3)
163 log.MDC("y", "foo")
164 log.MDC("z", TestLog)
166 log.trace("This is TRACE 2")
167 log.info("This is INFO 2")
168 log.debug("This is DEBUG 2")
169 log.MDCRemove("z")
171 log.trace("This is TRACE 3")
172 log.info("This is INFO 3")
173 log.debug("This is DEBUG 3")
174 log.MDCRemove("x")
175 log.trace("This is TRACE 4")
176 log.info("This is INFO 4")
177 log.debug("This is DEBUG 4")
179 log.trace("This is TRACE 5")
180 log.info("This is INFO 5")
181 log.debug("This is DEBUG 5")
183 log.MDCRemove("y")
185 # Use format to make line numbers easier to change.
186 self.check("""
187INFO root testPattern (test_log.py:{0[0]}) test_log.py({0[0]}) - This is INFO - {{}}
188DEBUG root testPattern (test_log.py:{0[1]}) test_log.py({0[1]}) - This is DEBUG - {{}}
189INFO root testPattern (test_log.py:{0[2]}) test_log.py({0[2]}) - This is INFO 2 - {{{{x,3}}{{y,foo}}{{z,<class '{1}.TestLog'>}}}}
190DEBUG root testPattern (test_log.py:{0[3]}) test_log.py({0[3]}) - This is DEBUG 2 - {{{{x,3}}{{y,foo}}{{z,<class '{1}.TestLog'>}}}}
191INFO root testPattern (test_log.py:{0[4]}) test_log.py({0[4]}) - This is INFO 3 - {{{{x,3}}{{y,foo}}}}
192DEBUG root testPattern (test_log.py:{0[5]}) test_log.py({0[5]}) - This is DEBUG 3 - {{{{x,3}}{{y,foo}}}}
193INFO root testPattern (test_log.py:{0[6]}) test_log.py({0[6]}) - This is INFO 4 - {{{{y,foo}}}}
194DEBUG root testPattern (test_log.py:{0[7]}) test_log.py({0[7]}) - This is DEBUG 4 - {{{{y,foo}}}}
195INFO root testPattern (test_log.py:{0[8]}) test_log.py({0[8]}) - This is INFO 5 - {{{{y,foo}}}}
196DEBUG root testPattern (test_log.py:{0[9]}) test_log.py({0[9]}) - This is DEBUG 5 - {{{{y,foo}}}}
197""".format([x + 159 for x in (0, 1, 8, 9, 13, 14, 17, 18, 21, 22)], __name__)) # noqa E501 line too long
199 def testMDCPutPid(self):
200 """
201 Test add of PID Mapped Diagnostic Context (MDC).
202 """
203 pid = os.fork()
204 try:
206 log.MDC("PID", os.getpid())
207 self.configure("""
208log4j.rootLogger=DEBUG, CA
209log4j.appender.CA=ConsoleAppender
210log4j.appender.CA.layout=PatternLayout
211log4j.appender.CA.layout.ConversionPattern=%-5p PID:%X{{PID}} %c %C %M (%F:%L) %l - %m%n
212""") # noqa E501 line too long
213 self.assertGreaterEqual(pid, 0, "Failed to fork")
215 msg = "This is INFO"
216 if pid == 0:
217 self.tempDir = tempfile.mkdtemp()
218 self.outputFilename = os.path.join(self.tempDir,
219 "log-child.out")
220 msg += " in child process"
221 elif pid > 0:
222 child_pid, child_status = os.wait()
223 self.assertEqual(child_status, 0,
224 "Child returns incorrect code")
225 msg += " in parent process"
227 with TestLog.StdoutCapture(self.outputFilename):
228 log.info(msg)
229 line = 228 # line number for previous line
230 finally:
231 log.MDCRemove("PID")
233 # Use format to make line numbers easier to change.
234 self.check("""
235INFO PID:{1} root testMDCPutPid (test_log.py:{0}) test_log.py({0}) - {2}
236""".format(line, os.getpid(), msg))
238 # don't pass other tests in child process
239 if pid == 0:
240 os._exit(0)
242 def testFileAppender(self):
243 """Test configuring logging to go to a file."""
244 self.configure("""
245log4j.rootLogger=DEBUG, FA
246log4j.appender.FA=FileAppender
247log4j.appender.FA.file={0}
248log4j.appender.FA.layout=SimpleLayout
249""")
250 log.MDC("x", 3)
251 log.trace("This is TRACE")
252 log.info("This is INFO")
253 log.debug("This is DEBUG")
254 log.MDCRemove("x")
256 self.check("""
257INFO - This is INFO
258DEBUG - This is DEBUG
259""")
261 def testPythonLogging(self):
262 """Test logging through the Python logging interface."""
263 with TestLog.StdoutCapture(self.outputFilename):
264 lgr = logging.getLogger()
265 lgr.setLevel(logging.INFO)
266 log.configure()
267 with self.assertLogs(level="INFO") as cm:
268 # Force the lsst.log handler to be applied as well as the
269 # unittest log handler
270 lgr.addHandler(log.LogHandler())
271 lgr.info("This is INFO")
272 lgr.debug("This is DEBUG")
273 lgr.warning("This is %s", "WARNING")
274 # message can be arbitrary Python object
275 lgr.info(((1, 2), (3, 4)))
276 lgr.info({1: 2})
278 # Confirm that Python logging also worked
279 self.assertEqual(len(cm.output), 4, f"Got output: {cm.output}")
280 logging.shutdown()
282 self.check("""
283root INFO: This is INFO
284root WARN: This is WARNING
285root INFO: ((1, 2), (3, 4))
286root INFO: {1: 2}
287""")
289 def testMdcInit(self):
291 expected_msg = \
292 "INFO - main thread {{MDC_INIT,OK}}\n" + \
293 "INFO - thread 1 {{MDC_INIT,OK}}\n" + \
294 "INFO - thread 2 {{MDC_INIT,OK}}\n"
296 with TestLog.StdoutCapture(self.outputFilename):
298 self.configure("""
299log4j.rootLogger=DEBUG, CA
300log4j.appender.CA=ConsoleAppender
301log4j.appender.CA.layout=PatternLayout
302log4j.appender.CA.layout.ConversionPattern=%-5p - %m %X%n
303""")
305 def fun():
306 log.MDC("MDC_INIT", "OK")
307 log.MDCRegisterInit(fun)
309 log.info("main thread")
311 thread = threading.Thread(target=lambda: log.info("thread 1"))
312 thread.start()
313 thread.join()
315 thread = threading.Thread(target=lambda: log.info("thread 2"))
316 thread.start()
317 thread.join()
319 self.check(expected_msg)
321 log.MDCRemove("MDC_INIT")
323 def testMdcUpdate(self):
324 """Test for overwriting MDC.
325 """
327 expected_msg = \
328 "INFO - Message one {}\n" \
329 "INFO - Message two {{LABEL,123456}}\n" \
330 "INFO - Message three {{LABEL,654321}}\n" \
331 "INFO - Message four {}\n"
333 with TestLog.StdoutCapture(self.outputFilename):
335 self.configure("""
336log4j.rootLogger=DEBUG, CA
337log4j.appender.CA=ConsoleAppender
338log4j.appender.CA.layout=PatternLayout
339log4j.appender.CA.layout.ConversionPattern=%-5p - %m %X%n
340""")
342 log.info("Message one")
344 log.MDC("LABEL", "123456")
345 log.info("Message two")
347 log.MDC("LABEL", "654321")
348 log.info("Message three")
350 log.MDCRemove("LABEL")
351 log.info("Message four")
353 self.check(expected_msg)
355 def testLwpID(self):
356 """Test log.lwpID() method."""
357 lwp1 = log.lwpID()
358 lwp2 = log.lwpID()
360 self.assertEqual(lwp1, lwp2)
362 def testLogger(self):
363 """
364 Test log object.
365 """
366 with TestLog.StdoutCapture(self.outputFilename):
367 log.configure()
368 logger = log.Log.getLogger("b")
369 self.assertEqual(logger.getName(), "b")
370 self.assertEqual(logger.name, logger.getName())
371 logger.trace("This is TRACE")
372 logger.info("This is INFO")
373 logger.debug("This is DEBUG")
374 logger.warn("This is WARN")
375 logger.error("This is ERROR")
376 logger.fatal("This is FATAL")
377 logger.warn("Format %d %g %s", 3, 2.71828, "foo")
378 self.check("""
379b INFO: This is INFO
380b WARN: This is WARN
381b ERROR: This is ERROR
382b FATAL: This is FATAL
383b WARN: Format 3 2.71828 foo
384""")
386 def testLoggerLevel(self):
387 """
388 Test levels of Log objects
389 """
390 with TestLog.StdoutCapture(self.outputFilename):
391 self.configure("""
392log4j.rootLogger=TRACE, CA
393log4j.appender.CA=ConsoleAppender
394log4j.appender.CA.layout=PatternLayout
395log4j.appender.CA.layout.ConversionPattern=%-5p %c (%F)- %m%n
396""")
397 self.assertEqual(log.Log.getLevel(log.Log.getDefaultLogger()),
398 log.TRACE)
399 self.assertEqual(log.Log.getDefaultLogger().getEffectiveLevel(), log.TRACE)
400 logger = log.Log.getLogger("a.b")
401 self.assertEqual(logger.getName(), "a.b")
402 self.assertEqual(logger.getEffectiveLevel(), log.TRACE)
403 self.assertEqual(log.getEffectiveLevel(logger.name), log.TRACE)
404 logger.trace("This is TRACE")
405 logger.setLevel(log.INFO)
406 self.assertEqual(logger.getLevel(), log.INFO)
407 self.assertEqual(logger.getEffectiveLevel(), log.INFO)
408 self.assertEqual(logger.level, logger.getLevel())
409 self.assertEqual(log.Log.getLevel(logger), log.INFO)
410 logger.debug("This is DEBUG")
411 logger.info("This is INFO")
412 logger.fatal("Format %d %g %s", 3, 2.71828, "foo")
414 logger = log.Log.getLogger("a.b.c")
415 self.assertEqual(logger.getName(), "a.b.c")
416 logger.trace("This is TRACE")
417 logger.debug("This is DEBUG")
418 logger.warn("This is WARN")
419 logger.error("This is ERROR")
420 logger.fatal("This is FATAL")
421 logger.info("Format %d %g %s", 3, 2.71828, "foo")
422 self.check("""
423TRACE a.b (test_log.py)- This is TRACE
424INFO a.b (test_log.py)- This is INFO
425FATAL a.b (test_log.py)- Format 3 2.71828 foo
426WARN a.b.c (test_log.py)- This is WARN
427ERROR a.b.c (test_log.py)- This is ERROR
428FATAL a.b.c (test_log.py)- This is FATAL
429INFO a.b.c (test_log.py)- Format 3 2.71828 foo
430""")
432 def testMsgWithPercentS(self):
433 """Test logging messages containing %s (DM-7509)
434 """
435 with TestLog.StdoutCapture(self.outputFilename):
436 log.configure()
437 logger = log.Log()
438 logger.info("INFO with %s")
439 logger.trace("TRACE with %s")
440 logger.debug("DEBUG with %s")
441 logger.warn("WARN with %s")
442 logger.error("ERROR with %s")
443 logger.fatal("FATAL with %s")
444 logger.logMsg(log.DEBUG, "foo", "bar", 5, "DEBUG with %s")
445 self.check("""
446root INFO: INFO with %s
447root WARN: WARN with %s
448root ERROR: ERROR with %s
449root FATAL: FATAL with %s
450root DEBUG: DEBUG with %s
451""")
453 def testForwardToPython(self):
454 """Test that `lsst.log` log messages can be forwarded to `logging`."""
455 log.configure()
457 # Without forwarding we only get python logger messages captured
458 with self.assertLogs(level="WARNING") as cm:
459 log.warn("lsst.log warning message that will not be forwarded to Python")
460 logging.warning("Python logging message that will be captured")
461 self.assertEqual(len(cm.output), 1)
463 log.usePythonLogging()
465 # With forwarding we get 2 logging messages captured
466 with self.assertLogs(level="WARNING") as cm:
467 log.warn("This is a warning from lsst log meant for python logging")
468 logging.warning("Python warning log message to be captured")
469 self.assertEqual(len(cm.output), 2)
471 loggername = "newlogger"
472 log2 = log.Log.getLogger(loggername)
473 with self.assertLogs(level="INFO", logger=loggername):
474 log2.info("Info message to non-root lsst logger")
476 # Check that debug and info are working properly
477 # This test should return a single log message
478 with self.assertLogs(level="INFO", logger=loggername) as cm:
479 log2.info("Second INFO message to non-root lsst logger")
480 log.debug("Debug message to root lsst logger")
482 self.assertEqual(len(cm.output), 1, f"Got output: {cm.output}")
484 logging.shutdown()
486 def testLogLoop(self):
487 """Test that Python log forwarding works even if Python logging has
488 been forwarded to lsst.log"""
490 log.configure()
492 # Note that assertLogs causes a specialists Python logging handler
493 # to be added.
495 # Set up some Python loggers
496 loggername = "testLogLoop"
497 lgr = logging.getLogger(loggername)
498 lgr.setLevel(logging.INFO)
499 rootlgr = logging.getLogger()
500 rootlgr.setLevel(logging.INFO)
502 # Declare that we are using the Python logger and that this will
503 # not cause a log loop if we also are forwarding Python logging to
504 # lsst.log
505 log.usePythonLogging()
507 # Ensure that we can log both in lsst.log and Python
508 rootlgr.addHandler(log.LogHandler())
510 # All three of these messages go through LogHandler
511 # The first two because they have the handler added explicitly, the
512 # the final one because the lsst.log logger is forwarded to the
513 # ROOT Python logger which has the LogHandler registered.
515 with open(self.outputFilename, "w") as fd:
516 # Adding a StreamHandler will cause the LogHandler to no-op
517 streamHandler = logging.StreamHandler(stream=fd)
518 rootlgr.addHandler(streamHandler)
520 # Do not use assertLogs since that messes with handlers
521 lgr.info("INFO message: Python child logger, lsst.log.LogHandler + PythonLogging")
522 rootlgr.info("INFO message: Python root logger, lsst.log.logHandler + PythonLogging")
524 # This will use a ROOT python logger which has a LogHandler attached
525 log.info("INFO message: lsst.log root logger, PythonLogging")
527 rootlgr.removeHandler(streamHandler)
529 self.check("""
530INFO message: Python child logger, lsst.log.LogHandler + PythonLogging
531INFO message: Python root logger, lsst.log.logHandler + PythonLogging
532INFO message: lsst.log root logger, PythonLogging""")
534 with open(self.outputFilename, "w") as fd:
535 # Adding a StreamHandler will cause the LogHandler to no-op
536 streamHandler = logging.StreamHandler(stream=fd)
537 rootlgr.addHandler(streamHandler)
539 # Do not use assertLogs since that messes with handlers
540 lgr.info("INFO message: Python child logger, lsst.log.LogHandler + PythonLogging")
541 rootlgr.info("INFO message: Python root logger, lsst.log.logHandler + PythonLogging")
543 # This will use a ROOT python logger which has a LogHandler attached
544 log.info("INFO message: lsst.log root logger, PythonLogging")
546 rootlgr.removeHandler(streamHandler)
548 self.check("""
549INFO message: Python child logger, lsst.log.LogHandler + PythonLogging
550INFO message: Python root logger, lsst.log.logHandler + PythonLogging
551INFO message: lsst.log root logger, PythonLogging""")
553 with self.assertLogs(level="INFO") as cm:
554 rootlgr.info("Python log message forward to lsst.log")
555 log.info("lsst.log message forwarded to Python")
557 self.assertEqual(len(cm.output), 2, f"Got output: {cm.output}")
559 logging.shutdown()
561 def testForwardToPythonContextManager(self):
562 """Test that `lsst.log` log messages can be forwarded to `logging`
563 using context manager"""
564 log.configure()
566 # Without forwarding we only get python logger messages captured
567 with self.assertLogs(level="WARNING") as cm:
568 log.warning("lsst.log: not forwarded")
569 logging.warning("Python logging: captured")
570 self.assertEqual(len(cm.output), 1)
572 # Temporarily turn on forwarding
573 with log.UsePythonLogging():
574 with self.assertLogs(level="WARNING") as cm:
575 log.warn("lsst.log: forwarded")
576 logging.warning("Python logging: also captured")
577 self.assertEqual(len(cm.output), 2)
579 # Verify that forwarding is disabled
580 self.assertFalse(log.Log.UsePythonLogging)
582 def testForwardToPythonAppender(self):
583 """Test that `log4cxx` appender forwards it all to logging"""
584 self.configure("""
585log4j.rootLogger=DEBUG, PyLog
586log4j.appender.PyLog = PyLogAppender
587""")
588 with self.assertLogs(level="WARNING") as cm:
589 log.warn("lsst.log: forwarded")
590 logging.warning("Python logging: also captured")
591 self.assertEqual(len(cm.output), 2)
593 # check that MDC is stored in LogRecord
594 log.MDC("LABEL", "some.task")
595 with self.assertLogs(level="WARNING") as cm:
596 log.warn("lsst.log: forwarded")
597 log.MDCRemove("LABEL")
598 self.assertEqual(len(cm.records), 1)
599 self.assertEqual(cm.records[0].MDC, {"LABEL": "some.task"})
600 self.assertEqual(cm.records[0].msg, "lsst.log: forwarded")
602 def testForwardToPythonAppenderWithMDC(self):
603 """Test that `log4cxx` appender forwards it all to logging and modifies
604 message with MDC info"""
605 self.configure("""
606log4j.rootLogger=DEBUG, PyLog
607log4j.appender.PyLog = PyLogAppender
608log4j.appender.PyLog.MessagePattern = %m (LABEL=%X{{LABEL}})
609""")
610 log.MDC("LABEL", "some.task")
611 with self.assertLogs(level="WARNING") as cm:
612 log.warn("lsst.log: forwarded")
613 log.MDCRemove("LABEL")
614 self.assertEqual(len(cm.records), 1)
615 self.assertEqual(cm.records[0].MDC, {"LABEL": "some.task"})
616 self.assertEqual(cm.records[0].msg, "lsst.log: forwarded (LABEL=some.task)")
618 def testForwardToPythonAppenderFormatMDC(self):
619 """Test that we can format `log4cxx` MDC on Python side"""
621 # remember old record factory
622 old_factory = logging.getLogRecordFactory()
624 # configure things using convenience method
625 log.configure_pylog_MDC("INFO")
627 with self.assertLogs(level="WARNING") as cm:
628 log.warn("lsst.log: forwarded 1")
629 log.MDC("LABEL", "task1")
630 log.warn("lsst.log: forwarded 2")
631 log.MDC("LABEL-X", "task2")
632 log.warn("lsst.log: forwarded 3")
633 logging.warning("Python logging: also captured")
634 log.MDCRemove("LABEL")
635 log.MDCRemove("LABEL-X")
636 self.assertEqual(len(cm.records), 4)
638 # restore factory
639 logging.setLogRecordFactory(old_factory)
641 # %-style formatting, only works on whole MDC
642 formatter = logging.Formatter(fmt="%(levelname)s:%(name)s:%(message)s", style="%")
643 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1")
644 formatter = logging.Formatter(fmt="%(levelname)s:%(name)s:%(message)s:%(MDC)s", style="%")
645 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1:{}")
646 self.assertEqual(formatter.format(cm.records[1]),
647 "WARNING:root:lsst.log: forwarded 2:{LABEL=task1}")
648 self.assertEqual(formatter.format(cm.records[2]),
649 "WARNING:root:lsst.log: forwarded 3:{LABEL=task1, LABEL-X=task2}")
650 self.assertEqual(formatter.format(cm.records[3]), "WARNING:root:Python logging: also captured:{}")
652 # format-style formatting, without MDC first
653 formatter = logging.Formatter(fmt="{levelname}:{name}:{message}", style="{")
654 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1")
656 # format-style formatting, with full MDC
657 formatter = logging.Formatter(fmt="{levelname}:{name}:{message}:{MDC}", style="{")
658 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1:{}")
659 self.assertEqual(formatter.format(cm.records[1]),
660 "WARNING:root:lsst.log: forwarded 2:{LABEL=task1}")
661 self.assertEqual(formatter.format(cm.records[2]),
662 "WARNING:root:lsst.log: forwarded 3:{LABEL=task1, LABEL-X=task2}")
663 self.assertEqual(formatter.format(cm.records[3]), "WARNING:root:Python logging: also captured:{}")
665 # format-style, using index access to MDC items, works for almost any
666 # item names
667 formatter = logging.Formatter(fmt="{levelname}:{name}:{message}:{MDC[LABEL-X]}", style="{")
668 self.assertEqual(formatter.format(cm.records[0]), "WARNING:root:lsst.log: forwarded 1:")
669 self.assertEqual(formatter.format(cm.records[1]), "WARNING:root:lsst.log: forwarded 2:")
670 self.assertEqual(formatter.format(cm.records[2]),
671 "WARNING:root:lsst.log: forwarded 3:task2")
672 self.assertEqual(formatter.format(cm.records[3]), "WARNING:root:Python logging: also captured:")
674 def testLevelTranslator(self):
675 """Test LevelTranslator class
676 """
677 # correspondence between levels, logging has no TRACE but we accept
678 # small integer in its place
679 levelMap = ((log.TRACE, 5),
680 (log.DEBUG, logging.DEBUG),
681 (log.INFO, logging.INFO),
682 (log.WARN, logging.WARNING),
683 (log.WARNING, logging.WARNING),
684 (log.ERROR, logging.ERROR),
685 (log.CRITICAL, logging.CRITICAL),
686 (log.FATAL, logging.FATAL))
687 for logLevel, loggingLevel in levelMap:
688 self.assertEqual(log.LevelTranslator.lsstLog2logging(logLevel), loggingLevel)
689 self.assertEqual(log.LevelTranslator.logging2lsstLog(loggingLevel), logLevel)
691 def testLevelName(self):
692 self.assertEqual(log.getLevelName(log.INFO), "INFO")
693 self.assertEqual(log.getLevelName(3253), "Level 3253")
695 def testRepr(self):
696 logger = log.getLogger("a.b")
697 logger.setLevel(log.DEBUG)
698 self.assertEqual(repr(logger), "<lsst.log.Log 'a.b' (DEBUG)>")
700 def testChildLogger(self):
701 """Check the getChild logger method."""
702 logger = log.getDefaultLogger()
703 self.assertEqual(logger.getName(), "")
704 logger1 = logger.getChild("child1")
705 self.assertEqual(logger1.getName(), "child1")
706 logger2 = logger1.getChild("child2")
707 self.assertEqual(logger2.getName(), "child1.child2")
708 logger2a = logger1.getChild(".child2")
709 self.assertEqual(logger2a.getName(), "child1.child2")
710 logger3 = logger2.getChild(" .. child3")
711 self.assertEqual(logger3.getName(), "child1.child2.child3")
712 logger3a = logger1.getChild("child2.child3")
713 self.assertEqual(logger3a.getName(), "child1.child2.child3")
714 self.assertEqual(logger2.parent.name, logger1.name)
715 self.assertEqual(logger1.parent.name, logger.name)
716 self.assertIsNone(logger.parent)
719if __name__ == "__main__": 719 ↛ 720line 719 didn't jump to line 720, because the condition on line 719 was never true
720 unittest.main()