8
\$\begingroup\$

I tend to include something like the following in small and medium-sized Python projects, either at the top of the executable or as logger.py:

import logging
import sys

logger = logging.getLogger(__name__)
stoh = logging.StreamHandler(sys.stderr)
fmth = logging.Formatter("[%(levelname)s] %(asctime)s %(message)s")
stoh.setFormatter(fmth)
logger.addHandler(stoh)
logger.setLevel(logging.DEBUG)

Then import logger from logger (as needed), and proceed to make calls throughout the project like logger.level('message').

Are there any obvious (or non-obvious) red flags or anti-patterns with this approach?

\$\endgroup\$
8
\$\begingroup\$

Setting up logging

You might be interested in logging.basicConfig, which can setup everything you setup there as well. It's not sufficient if you want for example both a file and a stream logger, but for a small, static configuration like this it's perfectly suitable. Your code up there becomes a simple:

logging.basicConfig(format="[%(levelname)s] %(asctime)s %(message)s", level=logging.DEBUG)
logger = logging.getLogger(__name__)

And you're done. (The default stream is sys.stderr.)

Do note that in no way, shape or form is the long form wrong - it's easier extendable, for one. However, for a quick script it might be easier to use basicConfig.

Module-level logging

I'd recommend you use a different logger for every different file, and have all of them be named the way you did that one. However, if you then also include the %(name) parameter in your format string, your logging messages will each contain the filename of the file they're logging from. This will help in debugging if you don't know exactly where the problems come from. I personally also include %(lineno) for the line number. Personally, I always use the .format method for my logging strings, which makes it look like this:

logging.basicConfig(format="{levelname: <8}:{asctime}:{name: <30}:{lineno: <4}:{message}", level=logging.DEBUG, style="{")
# Or with % formatting:
logging.basicConfig(format="%(levelname)-8s:%(asctime)s:%(name)-30s:%(lineno)-4s:%(message)s", level=logging.DEBUG)

Which tells me exactly where a logging statement was made as well. You won't have to search for your own exact wording anymore when you resume debugging your code for that one weird issue 3 months from now. You can just grab filename and linenumber and you're at the right location.

\$\endgroup\$
  • 1
    \$\begingroup\$ Left fill with %-formatting is %-Ns, where N is a number. E.g. {: <8} => %-8s, {levelname: <8} => %(levelname)-8s. \$\endgroup\$ – wjandrea Sep 23 at 14:58
  • \$\begingroup\$ logging prints to stderr by default, no? \$\endgroup\$ – wjandrea Sep 23 at 15:03
  • \$\begingroup\$ Thanks, improved the answer with the left-fill. I....actually couldn't find in the docs where it'd log by default, but if it leaves it up to the StreamHandler, that one defaults to sys.stderr. Since it's not that important a part of the answer, I just removed the sentence. \$\endgroup\$ – Gloweye Sep 23 at 15:40
  • \$\begingroup\$ Thanks! I'm submitting an edit to further improve it. I looked myself and saw in the documentation for basicConfig it says it sets up a StreamHandler, and StreamHandler defaults to sys.stderr. \$\endgroup\$ – wjandrea Sep 23 at 16:08
  • \$\begingroup\$ Ah, that's ok. I didn't know and didn't want to bet it didn't set a different stream when init-ed through basicConfig. \$\endgroup\$ – Gloweye Sep 24 at 6:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.