Delgan/loguru

How to set the log level?

cyberfox1 opened this issue Β· 27 comments

It's strange for something that is supposed to be stupidly simple there is no setLevel() function available, as on the plain old logger I get back from getLogger().

The docs show a heavy handed approach of adding a whole new handler just to set the level like:

logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")

But really?

I you would like to use another level in place of the default "DEBUG", you can just set the LOGURU_LEVEL environment variable to the severity level your prefer.

Alternatively, you can just re-add the stderr handler with the appropriate level, you don't need to modify the format and filter attributes:

logger.remove()
logger.add(sys.stderr, level="INFO")

The thing to understand is that, contrary to standard logging, the Loguru's logger is not associated to any level: only handlers are. So, there is no setLevel() function because it would not make sense. The handlers are the sole master of the logs severity they accept.

Feel free to re-open this issue if you have others concerns.

ab-10 commented

Even though I set the LOGURU_LEVEL=INFO in .env logger still prints debug statements, could someone please suggest what could be going wrong here?

from loguru import logger
from dynaconf import settings

# init configs
var = settings.get("var")

logger.info(f"LOGURU_LEVEL: {os.environ['LOGURU_LEVEL']}") >> LOGURU_LEVEL: INFO
logger.debug("This shouldn't appear") >> This shouldn't appear

when I run the script as LOGURU_LEVEL=INFO python -m test_loguru the debug statement doesn't appear.

I would think this is a dynaconf issue, but the os.environ reports the correct environment variable for LOGURU_LEVEL.

@ab-10 Try importing dynaconf before loguru maybe?

Loguru setups logging values at import time using environment variables. If the environment variables have not been configured yet by dynaconf, Loguru will use the default values (which is "DEBUG" for logging level).

ab-10 commented

@ab-10 Try importing dynaconf before loguru maybe?

Loguru setups logging values at import time using environment variables. If the environment variables have not been configured yet by dynaconf, Loguru will use the default values (which is "DEBUG" for logging level).

That makes sense, however could you please advise why does importing loguru after the os environment variable has been set doesn't resolve the issue?

@ab-10 What do you mean? Are you saying that inverting dynaconf with louru import did not solve the problem?

ab-10 commented

yes, exactly

@ab-10 According to the dynaconf documentation, it seems environment variables are lazily loaded. The os.environ is not populated until settings.get() is called. So, if loguru is imported before settings being used, it will not have access to the configured LOGURU_LEVEL value.

ab-10 commented

Thanks @Delgan, this really looks like a dynaconf issue. I ended up using conda for setting the environment variable (in etc/conda/activate.d/env_vars.sh) and it works as expected now.

Back to the original issue:
Is there a chance to add API to manipulate handlers for usages liks set_all_handlers_level?
That is, make logger._core.handlers more "public".

@YuvalAdler This is not something I'm planning. To keep things simple, handlers are not exposed publicly. Some possible workarounds are listed in the documentation.

I can't set loguru level dynamically with dynaconf or os.environ via enviroment variables, it works only when I set variable directly in terminal. I want to set different levels due the "development layer" inside the code but it seems there isn't any way to do this πŸ˜•

@deknowny Modifying os.envrion or dynaconf will only affect the default level at the time the handlers are added. This will have no effect on existing handlers.

If you need to change the level of a handler, you will have to use one another workaround (see link to documentation in my previous post).

I guess filter is the solution. Thanks!

environ*

logger.remove()
logger.add(sys.stderr, level="INFO")

@Delgan

Why not provide a logger.set_level() method that just gets the last handler (like remove does), and sets the level there?

Having to remove and add, is a little clunky imho for such a common operation.

@erezsh A function that only acts on the last handler would be incomplete and would not cover all use cases. I don't consider that changing the level at runtime is a common enough use case to justify adding a new function to the API, especially since it would be a special case and there may be many other reasons to change the handler.

You can define a dynamic filter to achieve the same result:

import sys
from loguru import logger

min_level = "INFO"

def my_filter(record):
    return record["level"].no >= logger.level(min_level).no

logger.remove()
logger.add(sys.stderr, filter=my_filter)

logger.debug("Not logged")
min_level = "DEBUG"
logger.debug("Logged")

Sorry to ping on a closed issue, I was just hoping for some clarification on the accepted solutions.

One thing I'm slightly concerned about with the suggestion of "just remove and add a handler"...

However, if your work is intended to be used as a library, you usually should not add any handler. This is user responsibility to configure logging according to its preferences, and it’s better not to interfere with that. Indeed, since Loguru is based on a single common logger, handlers added by a library will also receive user logs, which is generally not desirable.

Is this meant in the context of importing and using my python library from within other python code?
(ie. CLI usage is unaffected?)
Or do I need to consider not remove-then-add a handler in my case (command line usage only)?

Delgan commented

Hi @glass-ships.

The statement you quoted only applies to cases where your library is intended to be used as a package or library that is imported by other Python code. For example:

import some_lib

some_lib.do_something()

If your Python code is standalone and is only intended to be run as a CLI tool (what I call "script"), then you can safely add and remove handlers to the logger as needed.

awesome, thanks so much delgan!!

nav9 commented

It's important to provide some context as to what those commands do, so here's my code snippet (which I also updated here):

import sys
from loguru import logger as log
log.remove() #remove the old handler. Else, the old one will work (and continue printing DEBUG logs) along with the new handler added below'
log.add(sys.stdout, level="INFO") #add a new handler which has INFO as the default
log.debug("debug message")
log.info("info message")
log.warning("warning message")
log.error("error message") 

My two cents:

I really like loguru, especially for small/medium scripts, where i just want to log things instead of print() them. I like the simplicity of just importing the lib and using it without jumping through hoops. I appreciate that we can have multiple handlers and all, but I guess a large share of use cases are like mine: single handler logger where i just want things to look good out of the box. The handler abstraction itself is a distraction for this use case: I just want to import a lib and start logging stuff.

My two cents: Why not add a set_level in the top level to deal with this simple use case? The function could check if theres only a single default handler, and then perform the remove/add dance. If there are multiple handlers then just error out, as the programmer should then decide what to do.

from loguru import logger
logger.set_level("ERROR")
logger.log("Hey")
logger.error("Hey")

looks better than

from loguru import logger
logger.remove() # for someone not familiar with the lib, whats going on here?
logger.add(sys.stdout, level="ERROR"))

logger.log("Hey")
logger.error("Hey")
Delgan commented

@polvoazul I appreciate your input, but from my point of view, such set_level() method wouldn't align with the overall design of the Loguru API. While it may appear more user-friendly, it introduces a special case that makes the relationship between the logger and handlers less straightforward. I don't want to compromise this concept just to save one line of configuration.

I agree that the "logger.remove() then logger.add()" pattern may not be immediately obvious. You could potentially replace it with logger.configure():

handler = {"sink": sys.stdout, "level": "ERROR"}
logger.configure(handlers=[handler])

Fair enough. I understand that adding a special case is indeed a bold ask (although sometimes pragmatism is valuable enough to warrant it).

Didn't know about logger.configure. This looks much better to my eye than remove/add:

import sys
logger.configure(handlers=[{"sink": sys.stdout, "level": "ERROR"}])

almost a 1 liner.

Maybe add this twoliner to the README? I guarantee it will get ctrl-c many times :)

I you would like to use another level in place of the default "DEBUG", you can just set the LOGURU_LEVEL environment variable to the severity level your prefer.

Could this please be in the documentation, like at the very top of the README? The very first thing I want to know when debugging somebody else's code is "how to I increase the logging level?" And I searched for about 10 minutes in the documentation and did not find the answer until I landed on this issue.

For anyone else who uses richuru, you'll have to make sure you do the richuru.install() after you log.remove(); log.add(...)

A minor inconvenience, but one that I was annoyed at:)

And, I think there is a desire to have rich and loguru "properly married" so this might not be a very useful comment, at all.