9

Given the need to log only in debug mode the easiest way would be to use if conditions:

def test(x, debug=False):
    if debug:
        print(x)

    # ...Some more code

    if debug:
        print("Something else")

    # ...More conditional logging

    return x * 2

This leads to the cluttering of the actual function logic. The best I could think of to improve this was to hide it behind a function to make it less cluttered:

def _log_if_debug(info, debug):
    if debug:
        print(info)

def test(x, debug=False):
    _log_if_debug(x, debug)

    # ...Some more code

    _log_if_debug("Something else", debug)

    # ...More conditional logging
    return x * 2

Is there a better pattern to do this?

3
  • 1
    Any special reason why your log function does not use a global debug variable instead of expecting the same parameter to get passed in over and over again, from every place where it is used in your program?
    – Doc Brown
    Commented May 26, 2018 at 9:24
  • I've seen global debug variable used in a large code base. The problem is that the meaning of debug becomes really murky - It is used in different functions for different reasons: logging, mocking rest APIs with local JSON, changing the REST endpoint, etc. This could be mitigated with calling the variable logging_enabled though. Commented May 26, 2018 at 10:09
  • debug is a pretty terrible name for a global variable, it's quite meaningless, as you say. For a big system you should definitely use more fine-grained logging with multiple logging levels, as shown in my answer. There is some general agreement on what different log levels mean.
    – Frax
    Commented May 26, 2018 at 17:55

3 Answers 3

11

Use standard logging library

In Python there is only one proper pattern: use standard logging module according to official HowTo. Or, for Python 3, this standard logging module and this HowTo. There are no important differences between these versions¹.

If you have reasons to dislike the standard library, you can alternatively use one of the existing replacements. They have different configuration and some extensions, but the basic usage is similar or identical. I'll get to them later. It should be relatively easy to switch to them at any time, so logging is at least a good starting point.

Usage in your module

Your code looks like this:

import logging

logger = logging.getLogger(__name__)  # you can use other name

logger.info('Loading my cool module with cool logging')

    def coolFunction(x):
        logger.debug('Most of the time this message is really irrelevant')
        ...
        if something_suspicious:
            logger.warning('Something suspicious happened: nothing broken yet, but you probably want to know')
        ...
        if everything_is_wrong:
            logger.error('Everything is broken and this program just went nuts')

Or alternatively, you can use simpler:

logging.info(...)
logging.debug(...)

instead of creating a logger, but it seems simple enough to add this single line everywhere.

If you are not sure what logging levels to use for different messages, you can refer to this SO question with some decent answers.

Configuration

Then somewhere in your cofig you say:

logging.basicConfig(filename='example.log', level=logging.INFO)

or

logging.basicConfig(filename='example.log', level=logging.DEBUG)

to set the output and filter the errors appropriately. If you want to use stderr, say

logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

instead.

Alternative handlers, logging to external services

There is also a number of libraries that you can but in place of basic logger, like raven for Sentry. You don't need to change anything in your code to tap into such a service, you just change the configuration.

Alternative libraries

There are some libraries that that you may want to use to completely replace logging. They follow similar pattern, central logging configuration, then access a logger in each file and log using log levels. Logbook seems to be a mostly drop-in replacement for logging, with some nice features added. Some other are mentioned in this SO question.

Removing logging from optimized build

For extreme cases where debug logging might significantly affect performance or leak sensitive data in production environment, built-in __debug__ constant can be used:

if __debug__:
    debug_info = some_complex_computation()
    logger.debug()

In this case, the whole statement would be erased when compiling the code with -o option. However, this solution is ugly and somewhat brittle, it should be used with care and only where really necessary. For most applications that means "nowhere, never".

Other languages

For other languages, it may look just the same (e.g. in Java) or slightly different, and vary somewhat (not every language has one standard way). But in general, you will find very similar patters, stuff like

LOG(DEBUG, "Your message")

or

LOG_ERROR("Error message")

or

LOG(INFO) << "Some people like C++ streams' syntax"

¹ AFAIK the only difference worth knowing about is that Python 3 has a "last resort" logger logging to stderror, so if you omit config completely, you still get something. In Python 2 you get just a warning about misconfigured logging.

7
  • A down-vote? Seriously? Heck, I'm not even trying to understand that.
    – Frax
    Commented May 26, 2018 at 11:05
  • While I think the advice to use logging is good, I despise the standard logging library - it's just hard to use. I find logbook much better to use, but miss something like Serilog Commented May 28, 2018 at 7:24
  • @ChristianSauer I added some information about alternatives.
    – Frax
    Commented May 28, 2018 at 9:38
  • Thx for doing so! Commented May 28, 2018 at 10:55
  • Coming from JS, we have a debug package that lets you prefix the debug messages. So that DEBUG="myapp:ui" node myapp.js will only print messages debug("myapp:ui")("My message)" but not debug("myapp:backend")("My other message") for instance. Is there anything similar in Python? The app I update is bloated with debug logs that are not categorized, some are computations, some licence related... etc.
    – Eric Burel
    Commented Oct 6, 2021 at 13:13
5

Have you heard of closures?

log = buildLog(debug)

...

log("Something ya wanna log")

Now you can control logging globally or your can control it locally by only passing the enabled log function to the few things that need it right now. This little scheme combines well with default arguments.

4

Sure, call your method just log().

Sounds silly, but it's twice as readable to have a short method name sprinkled throughout your code than a long one.

The next level of readability would be to separate your cross-cutting concerns completely from the business logic and weave them in with some sort of aspect-oriented tool. But that is massively more effort (admittedly, for much better results).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.