<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>chris' random ramblings (Posts about mozharness)</title><link>https://atlee.ca/</link><description></description><atom:link href="https://atlee.ca/categories/mozharness.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Sat, 22 Feb 2025 20:04:33 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Diving into python logging</title><link>https://atlee.ca/posts/diving-into-python-logging/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;Python has a very rich
&lt;a href="https://docs.python.org/3/library/logging.html"&gt;logging&lt;/a&gt; system. It's very
easy to add structured or unstructured log output to your python code, and
have it written to a file, or output to the console, or sent to syslog, or
to customize the output format.&lt;/p&gt;
&lt;p&gt;We're in the middle of re-examining how logging works in
&lt;a href="https://wiki.mozilla.org/ReleaseEngineering/Mozharness"&gt;mozharness&lt;/a&gt; to
make it easier to factor-out code and have fewer
&lt;a href="https://mgerva.wordpress.com/2015/02/25/on-mixins/"&gt;mixins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are a few tips and tricks that have really helped me with python
logging:&lt;/p&gt;
&lt;h2 id="there-can-be-only-more-than-one"&gt;There can be &lt;s&gt;only&lt;/s&gt; more than one&lt;/h2&gt;
&lt;p&gt;Well, there can be only one logger with a given name. There is a special
"root" logger with no name. Multiple
&lt;code&gt;getLogger(name)&lt;/code&gt; calls with the same name will return the same logger
object.  This is an important property because it means you don't need to
explicitly pass logger objects around in your code. You can retrieve them
by name if you wish. The logging module is maintaining a global registry of
logging objects.&lt;/p&gt;
&lt;p&gt;You can have multiple loggers active, each specific to its own module or
even class or instance.&lt;/p&gt;
&lt;p&gt;Each logger has a name, typically the name of the module it's being used
from. A common pattern you see in python modules is this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c1"&gt;# in module foo.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This works because inside &lt;code&gt;foo.py&lt;/code&gt;, &lt;code&gt;__name__&lt;/code&gt; is equal to "foo". So inside
this module the &lt;code&gt;log&lt;/code&gt; object is specific to this module.&lt;/p&gt;
&lt;h2 id="loggers-are-hierarchical"&gt;Loggers are hierarchical&lt;/h2&gt;
&lt;p&gt;The names of the loggers form their own namespace, with "." separating
levels. This means that if you have have loggers called &lt;code&gt;foo.bar&lt;/code&gt;, and &lt;code&gt;foo.baz&lt;/code&gt;,
you can do things on logger &lt;code&gt;foo&lt;/code&gt; that will impact both of the children. In
particular, you can set the logging level of &lt;code&gt;foo&lt;/code&gt; to show or ignore debug
messages for both submodules.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c1"&gt;# Let's enable all the debug logging for all the foo modules&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="log-messages-are-like-events-that-flow-up-through-the-hierarchy"&gt;Log messages are like events that flow up through the hierarchy&lt;/h2&gt;
&lt;p&gt;Let's say we have a module foo.bar:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# __name__ is "foo.bar" here&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_widget&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"made a widget!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When we call &lt;code&gt;make_widget()&lt;/code&gt;, the code generates a debug log message. Each
logger in the hierarchy has a chance to output something for the message,
ignore it, or pass the message along to its parent.&lt;/p&gt;
&lt;p&gt;The default
configuration for loggers is to have their levels unset (or set to &lt;a href="https://docs.python.org/3/library/logging.html#logging-levels"&gt;&lt;code&gt;NOTSET&lt;/code&gt;&lt;/a&gt;). This means the logger will just pass the message on up to its parent. Rinse &amp;amp; repeat until you get up to the root logger.&lt;/p&gt;
&lt;p&gt;So if the &lt;code&gt;foo.bar&lt;/code&gt; logger hasn't specified a level, the message will
continue up to the &lt;code&gt;foo&lt;/code&gt; logger. If the &lt;code&gt;foo&lt;/code&gt; logger hasn't specified a
level, the message will continue up to the root logger.&lt;/p&gt;
&lt;p&gt;This is why you typically configure the logging output on the root logger;
it typically gets ALL THE MESSAGES!!! Because this is so common, there's a
dedicated method for configuring the root logger:
&lt;a href="https://docs.python.org/3/library/logging.html#logging.basicConfig"&gt;&lt;code&gt;logging.basicConfig()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This also allows us to use mixed levels of log output depending on where
the message are coming from:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="c1"&gt;# Enable debug logging for all the foo modules&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Configure the root logger to log only INFO calls, and output to the console&lt;/span&gt;
&lt;span class="c1"&gt;# (the default)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This will output the debug message&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo.bar"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ohai!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you comment out the &lt;code&gt;setLevel(logging.DEBUG)&lt;/code&gt; call, you won't see the
message at all.&lt;/p&gt;
&lt;h2 id="exc_info-is-teh-awesome"&gt;exc_info is teh awesome&lt;/h2&gt;
&lt;p&gt;All the built-in logging calls support a keyword called &lt;code&gt;exc_info&lt;/code&gt;, which
if isn't false, causes the current exception information to be logged in
addition to the log message.
e.g.:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"surprise! got an exception!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There's a special case for this, &lt;code&gt;log.exception()&lt;/code&gt;, which is equivalent to
&lt;code&gt;log.error(..., exc_info=True)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Python 3.2 introduced a new keyword, &lt;code&gt;stack_info&lt;/code&gt;, which will output the
current stack to the current code. Very handy to figure out &lt;em&gt;how&lt;/em&gt; you got
to a certain point in the code, even if no exceptions have occurred!&lt;/p&gt;
&lt;h2 id="no-handlers-found"&gt;"No handlers found..."&lt;/h2&gt;
&lt;p&gt;You've probably come across this message, especially when working with 3rd
party modules. What this means is that you don't have any logging handlers
configured, and something is trying to log a message. The message has gone
all the way up the logging hierarchy and fallen off the...top of the chain
(maybe I need a better metaphor).&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"no log for you!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;outputs:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nv"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;handlers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;could&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are two things that can be done here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Configure logging in your module with &lt;code&gt;basicConfig()&lt;/code&gt; or similar&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Library authors should add a
   &lt;a href="https://docs.python.org/3/library/logging.handlers.html#logging.NullHandler"&gt;NullHandler&lt;/a&gt; at the root of their module to
   prevent this. See the
   &lt;a href="https://docs.python.org/3/howto/logging.html#library-config"&gt;cookbook&lt;/a&gt;
   and &lt;a href="http://pythonsweetness.tumblr.com/post/67394619015/use-of-logging-package-from-within-a-library"&gt;this
   blog&lt;/a&gt;
   for more details here.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="want-more"&gt;Want more?&lt;/h2&gt;
&lt;p&gt;I really recommend that you read the &lt;a href="https://docs.python.org/3/library/logging.html"&gt;logging
documentation&lt;/a&gt; and
&lt;a href="https://docs.python.org/3/howto/logging-cookbook.html"&gt;cookbook&lt;/a&gt; which
have a lot more great information (and are also very well written!) There's
a lot more you can do, with custom log handlers, different output formats,
outputting to many locations at once, etc. Have fun!&lt;/p&gt;</description><category>mozharness</category><category>mozilla</category><category>python</category><guid>https://atlee.ca/posts/diving-into-python-logging/</guid><pubDate>Fri, 27 Feb 2015 21:09:33 GMT</pubDate></item></channel></rss>