python-poetry / cleo

Cleo allows you to create beautiful and testable command-line interfaces.
https://cleo.readthedocs.io
MIT License
1.28k stars 91 forks source link

use cleo in combination with logging #49

Open flokli opened 7 years ago

flokli commented 7 years ago

Hey,

do you have any plans on how to use cleo's self.line and verbosity levels in combination with logger, so log entries might get written (and colored) by a custom cleo handler?

flokli commented 7 years ago

I had something like click-log in mind.

kam1sh commented 5 years ago

It's easy to write logging.Handler: just take clikit's IO and check verbosity before calling write_line:

from clikit.api.io import flags as verbosity

_levels = {
    logging.CRITICAL: verbosity.NORMAL,
    logging.ERROR: verbosity.NORMAL,
    logging.WARNING: verbosity.NORMAL,
    logging.INFO: verbosity.VERY_VERBOSE,
    logging.DEBUG: verbosity.DEBUG,
}

class ClikitHandler(logging.Handler):
    """Logging handler that redirects all messages to clikit io object."""

    def __init__(self, io, level=logging.NOTSET):
        super().__init__(level=level)
        self.io = io

    def emit(self, record: logging.LogRecord):
        level = _levels[record.levelno]
        if record.levelno >= logging.WARNING:
            text = record.getMessage()
            self.io.error_line(text, flags=level)
        elif self.io.verbosity >= level:
            text = record.getMessage()
            self.io.write_line(text)

    @classmethod
    def setup_for(cls, name, io):
        log = logging.getLogger(name)
        log.setLevel(logging.DEBUG)
        log.handlers = [cls(io)]
        log.debug("Logger initialized.")

Much harder, IMO, is to get that IO between IO initialization and running command. I solved this by calling ClikitHandler.setup_for right after ApplicationConfig.create_io():

from cleo.config.application_config import ApplicationConfig
from hypai.logging import ClikitHandler

class App(cleo.Application):
    def __init__(self, config=None):
        super().__init__(config=config or AppConfig())
        self.add(BuildDebian())

class AppConfig(ApplicationConfig):
    def __init__(self):
        super().__init__("hypai", version=__version__)

class LoggingAppConfig(AppConfig):
    def create_io(self, *args, **kwargs):
        io = super().create_io(*args, **kwargs)
        ClikitHandler.setup_for("hypai", io)
        return io

def main():
    config = LoggingAppConfig()
    app = App(config=config)
    app.run()

P.S.: this is not necroposting, I just want to help others struggling with lack of documentation about CliKit's IO and Cleo-Clikit integration.

esciara commented 5 years ago

Actually it is exactly what I was looking for. Thanks @kam1sh !