Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

[Suggestion/Discussion] Support for logging.getLogger(__name__) #389

Open
HappyEinara opened this issue Mar 30, 2021 · 2 comments
Open

[Suggestion/Discussion] Support for logging.getLogger(__name__) #389

HappyEinara opened this issue Mar 30, 2021 · 2 comments

Comments

@HappyEinara
Copy link

HappyEinara commented Mar 30, 2021

This isn't an issue, and not necessarily a feature request, but more of a request for feedback.

💡 tldr: I propose a refactor that puts most of logzero into a class implementing the interface expected by logging.setLoggerClass and reduce setup_logger() to a function that instantiates, configures and returns an instance of that class. This is to permit libraries unaware of logzero to benefit from all that makes logzero great but using the defacto standard pattern of logger = logging.getLogger(__name__). But please also read the last line. :)

If I'm writing an application where the logging is under my control, including in libraries I wrote, logzero is a beautiful solution. But consider the following situations:

  • I write a library that I depend on but others might want to also.
  • I depend on external libraries who have no idea about my application or its logging.

In these cases the recommended pattern is for code to call logging.getLogger(__name__), where the stdlib logging library will return a logger likely configured by the parent application.

My idea is to implement this pattern but also leverage the sane and beautiful defaults of Logzero and the very easy implementation.

Now, you know more about logging than I do, Chris, but it seems passing an appropriately implemented Logger class to logging.setLoggerClass(klass) would achieve this. In the following I will call that class Klass both as the metavar for a class and to acknowledge your home in Austria, but it could be called logzero.DefaultLogger or something.

It seems there are three options:

  1. Refactor logzero so it continues to do exactly what it does as well as it does but also offers the same default setup as a Klass that implements the interface described in the link above, offering the logzero experience to a wider set of use-cases.
  2. Fork logzero entirely, copying and pasting the details necessary to implement my idea in a new project. This would mean limited hassle for you if you aren't interested, but means manual work to keep your improvements merged in; it would also mean yet another logging library that wouldn't be as discoverable as this.
  3. Refactor logzero's code to encapsulate more things like the default formatter, default handlers etc in a way that offers an API for a wrapper project to provide a Klass, or for an advanced user to implement themselves. If this is the solution you may as well just do 1) imho.

In the current Logzero implementation, while some things like the LogFormatter from Tornado are written in classes that could be imported and reused in other ways, a lot of logic is in functions like setup_logger() which provides the documented Logzero interface but also handles setting up the instances the Logzero Way. I think those tasks should be separated.

Tricky bits:

  • The interface setLoggerClass defines: the constructor of Klass may only accept a name. However most configuration on logzero is done by calling module-level functions. If the defaults were implemented on the Klass and the config functions continued to manipulate the default logzero.logger, then the only problem would be finding the right way for a class-based user to achieve beautiful logzero results with logzero ease where configuration is needed. My suggestion is that the module-level config functions be implemented as methods on the Klass which update a number of class-level variables, and the module interface reduced to wrappers that call these methods on logzero.logger which becomes an instance of Klass.

I realise that this proposal would require a substantial rewrite of logzero, but on the other hand it's not a big file and it's mostly just a refactor and testing. On the other hand, I think there's an opportunity to maintain the current interface and expose the benefits of Logzero to a much wider set of use-cases which need the standard logging pattern but would love the ease and beauty of the Logzero interface and defaults.

Happy to hear your thoughts. If this doesn't appeal, I might attempt to implement this myself to demonstrate it and we can look at ways to collaborate after that if I can prove it's workable.

An alternative: just monkeypatch logging.getLogger in the parent app to logzero.setup_logger and be done with it. I don't know if that's a genius idea or a risky hack tbh.

@metachris
Copy link
Owner

Just a note that this issue is noticed. I'm just having a hard time finding the time to think about it! Will get back to you soon :)

@HappyEinara
Copy link
Author

@metachris I've been thinking about it a bit more too, and I'm starting to think:

  1. Logzero does a particular kind of job very well and is suited for smaller applications where logging doesn't need over-thinking it just needs to be easy.
  2. Setting up and using "traditional" standard library approaches like getLogger(__name__) isn't actually that hard for simple cases. But the defaults aren't great and the massive flexibility means things can seem hard or get overcomplicated easily.
  3. Very complex applications should be out of scope for logzero anyway.

But: logzero provides defaults that could make configuring a logger with basicConfig or a dict config nicer for intermediate-level projects that are more than a quick cli and might involve logging from libraries. So now I'm thinking perhaps logzero could:
4. Refactor its implementation deliberately into two layers:
- A layer that makes available the "Lego bricks" (the colourful streams, a well configured rotating file handler, the json formatter etc) with a bunch of beautiful defaults and
- A layer that provides the easy, one-step logzero interface we love to use for small projects.

That way we can choose to use a lot of the cool stuff easily, but also take advantage of a bit more flexibility by using basicConfig or a dictionary config with standard library patterns. So I might want to build my own logging config, but still use your nicely formatted output in my cli and your json formatter in Lambda, and be able to do that very quickly and easily compared with hand-rolled approaches.

The good news is, I think Logzero is a bit a long that journey already. The LogFormatter is, I think, already importable. And you already have _get_json_formatter() to set up a Formatter, and .json() to apply that to the Logzero default interface. Really all I'm suggesting is that you promote things like _get_json_formatter() to a first-class interface we can reuse in other ways. But: make the API for that "advanced" use just as well considered and beautiful as the basic one.

Finally, then all you would need is:

  1. Provide some more documentation to describe how to use these bricks. i.e. how to use my imaginary function "logzero.get_json_formatter()" with logging.basicConfig, or how to set up a nice friendly logzero.RotatingFileHandler in a dictConfig. (I haven't decided whether classes or module-level functions or both is the right interface for this layer)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants