6

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?

  • 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 May 26 '18 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. – Veneet Reddy May 26 '18 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 May 26 '18 at 17:55
8

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.

  • A down-vote? Seriously? Heck, I'm not even trying to understand that. – Frax May 26 '18 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 – Christian Sauer May 28 '18 at 7:24
  • @ChristianSauer I added some information about alternatives. – Frax May 28 '18 at 9:38
  • Thx for doing so! – Christian Sauer May 28 '18 at 10:55
3

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).

3

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.

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.