How to output logs with python logging in a click cli?
Asked Answered
P

3

16

I've written a python cli with the 'click' library. I'd like to also use python's built-in logging module for logging to the console. But I've struggled getting the logging messages to the console. I tried a very simple approach:

logger = logging.getLogger(__name__)

@click.command()
def cli():
    logger.setLevel("INFO")
    logger.info("Does this work?")
    print("done.")

The logger content doesn't appear in my console. Maybe it needs a handler to explicitly send log messages to stdout?

logger = logging.getLogger(__name__)

@click.command()
def cli():
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
    handler.setLevel("INFO")
    logger.addHandler(handler)
    logger.info("Does this work?")
    print("done.")

Unfortunately this also doesn't work.

A third option--creating a handler and setting loglevels for the handler and the logger-- works:

logger = logging.getLogger(__name__)

@click.command()
def cli():
    logger.setLevel("INFO")
    handler = logging.StreamHandler(sys.stderr)
    handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
    handler.setLevel("INFO")
    logger.addHandler(handler)
    logger.info("Does this work?")
    print("done.")

It seems like:

  • if creating a logger with logging.getLogger, I have to create a handler for my logger explicitly.
  • and I have to set a loglevel on both the logger and the handler?

Is that right? It seems silly to set the level twice. What's the point?

Or am I still misunderstanding the right way to do this?

Thanks for your help!

Pilfer answered 23/4, 2020 at 12:38 Comment(0)
Z
10

I had to manually set

logging.basicConfig(level=logging.INFO)

Example

import click
import logging

logging.basicConfig(level=logging.INFO)

@click.command()
def cli():
    logging.info("it works")

gives

$ mycli
INFO:root:it works
Zootoxin answered 28/1, 2021 at 11:23 Comment(1)
Use logging.basicConfig(level=logging.INFO, format='%(message)s') to get output without "INFO:root:"Crusado
B
9

I personally like the loguru library for handling logs. I think it's simpler.

Here's an example of how I usually do it:

import click
from loguru import logger


@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        logger.info(f"That's it, beautiful and simple logging! - Counter: {x}")
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()
❯ python loguru-click-cli-test.py --count=3
Your name: Geraldo
2020-06-10 20:02:48.297 | INFO     | __main__:hello:11 - That's it, beautiful and simple logging! - Counter: 0
Hello Geraldo!
2020-06-10 20:02:48.297 | INFO     | __main__:hello:11 - That's it, beautiful and simple logging! - Counter: 1
Hello Geraldo!
2020-06-10 20:02:48.297 | INFO     | __main__:hello:11 - That's it, beautiful and simple logging! - Counter: 2
Hello Geraldo!

Loguru: https://github.com/Delgan/loguru

Bombe answered 10/6, 2020 at 23:6 Comment(0)
A
2

I was looking to get logs when a click handler is called (command name and parameters). I end up with this snippet:

from functools import wraps
import logging
import click

logger = logging.getLogger(__name__)

def click_log(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        context = click.get_current_context()
        logger.info(f"{context.command.name}(**{context.params})")
        return fn(*args, **kwargs)
    return wrapper

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
@click_log
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo(f"Hello {name}!")  # you can use logger.info


def configure_logger():
    logging.basicConfig(level="DEBUG")

configure_logger()

if __name__ == '__main__':
    hello()

It gives you the following output:

python test_click.py --count 3
Your name: ok
INFO:__main__:hello(**{'count': 3, 'name': 'ok'})
Hello ok!
Hello ok!
Hello ok!
Alterable answered 30/6, 2021 at 9:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.