Skip to content

How to set the log level? #138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cyberfox1 opened this issue Aug 28, 2019 · 32 comments
Closed

How to set the log level? #138

cyberfox1 opened this issue Aug 28, 2019 · 32 comments
Labels
question Further information is requested

Comments

@cyberfox1
Copy link

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?

@Delgan
Copy link
Owner

Delgan commented Aug 28, 2019

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

@Delgan
Copy link
Owner

Delgan commented Aug 28, 2019

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.

@Delgan Delgan added the question Further information is requested label Oct 8, 2019
@Delgan
Copy link
Owner

Delgan commented Oct 8, 2019

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

@Delgan Delgan closed this as completed Oct 8, 2019
@ab-10
Copy link

ab-10 commented Apr 7, 2020

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.

@Delgan
Copy link
Owner

Delgan commented Apr 7, 2020

@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
Copy link

ab-10 commented Apr 10, 2020

@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?

@Delgan
Copy link
Owner

Delgan commented Apr 10, 2020

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

@ab-10
Copy link

ab-10 commented Apr 10, 2020

yes, exactly

@Delgan
Copy link
Owner

Delgan commented Apr 10, 2020

@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
Copy link

ab-10 commented Apr 13, 2020

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.

@YuvalAdler
Copy link

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

@Delgan
Copy link
Owner

Delgan commented Mar 8, 2021

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

@deknowny
Copy link

deknowny commented May 5, 2021

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 😕

@Delgan
Copy link
Owner

Delgan commented May 5, 2021

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

@deknowny
Copy link

deknowny commented May 5, 2021

I guess filter is the solution. Thanks!

@braveltd
Copy link

environ*

@erezsh
Copy link

erezsh commented Mar 30, 2023

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.

@Delgan
Copy link
Owner

Delgan commented Mar 31, 2023

@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")

@glass-ships
Copy link

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
Copy link
Owner

Delgan commented May 5, 2023

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.

@glass-ships
Copy link

awesome, thanks so much delgan!!

@nav9
Copy link

nav9 commented Aug 24, 2023

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

@polvoazul
Copy link

polvoazul commented Sep 2, 2023

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
Copy link
Owner

Delgan commented Sep 2, 2023

@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])

@polvoazul
Copy link

polvoazul commented Sep 28, 2023

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

@dhdaines
Copy link

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.

@ryaminal
Copy link

ryaminal commented Dec 11, 2023

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.

@2catycm
Copy link

2catycm commented Jul 29, 2024

LOGURU_LEVEL

Setting the log level is common enough that if there is no such function probably consider not use loguru. It is a pity that it seems there is still not a solution that can automatically gets the last handler and set its level. And what's worse, the environment variable solution needs to restart the program all the time, not compatible to the os.environ['LOGURU_LEVEL'] method.

@Stefanhg
Copy link

Hello,

I have been looking into a simple logging tool and this was recommended to me and it looks very promising.
But as others have mentioned it is not a very clean way having to do a remove and add a "new" handle but I understand that implementation is the reason why this feature is not implemented. But if people doesn't comment about the needs, nothing happens.

I have a few thoughts about the topic:

  1. If you have a thread running that uses the handle you need to change level of, could the data be lost when removing the handle and overriding the pointer to your logger.bind?
  2. It is much more clean to do logger.set_level("INFO") rather than the shown solutions
  3. logger.configure does not work well because that means you need the configuration saved somewhere. Could be fixed by being able to pass an already opened logger to the configure command and then saving the options in the logger so the tool can pull it's configurations from there.

My Usecase:
If you have used logger.level to add 5 different levels with a bit more aggressive logging then it is nice being able to in a clean way calm down the logging.

@Stefanhg
Copy link

Stefanhg commented Dec 1, 2024

On top of my comment yesterday, I am very unsure about how this is supposed to be done in practical. It is such a shame this is a HUGE roadblock in this utility for my case.

Can anyone help me with figuring out how I am supposed to do this?
I have two classes, each have it's own logging instance with separate logging level. Each class will have a method for setting log level so they independently can be changed.

# Class 1
self.id = logger.add(sys.stdout, level="INFO")
self.log = logger.bind(name="my_utility_class1")
# Class 2
self.id = logger.add(sys.stdout, level="INFO")
self.log= logger.bind(name="my_utility_class2")

Now we change each class's log level

class1 = MyUtilityClass1()
class2 = MyUtilityClass2()
logger.remove(class1.id)
class1.id = logger.add(sys.stdout, level="DEBUG")
class1.log = logger.bind(name="my_utility_class1")

logger.remove(class2.id)
class2.id = logger.add(sys.stdout, level="DEBUG")
class2.log = logger.bind(name="my_utility_class2")

I haven't tested this since I cannot wrap my head around how this is supposed to work. It does not seem very logical how this is supposed to be approached. Anyone can help me here?

@Delgan
Copy link
Owner

Delgan commented Dec 1, 2024

Hey @Stefanhg.

Thanks for the concrete use cases. That's definitely helpful to shape the proper API of Loguru.

The solution of simply using remove() before add() (or using configure()) is mainly useful for adjusting the level of the default handler. For more advanced use cases, such as updating the level in the middle of the application's execution, this approach is probably not the best one (in particular due to multi-threading concerns, as you mentioned).

In such cases, I would suggest using a custom filter to control the level of the handler:

min_level = logger.level("DEBUG").no

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


logger.remove()
logger.add(sys.stderr, filter=filter_by_level, level=0)

logger.debug("Logged")

min_level = logger.level("WARNING").no

logger.debug("Notogged")

Regarding your second post, I notice that you add the same sink twice (sys.stdout), which is generally an anti-pattern. In such a situation, the sink won't be thread-safe. Additionally, you risk seeing duplicate logs.

If you want to adapt the level of different modules, you can use a dict as a filter or a custom function:

filtering = {
    "module_1": "DEBUG",
    "module_2": "INFO",
}

logger.add(sys.stdout, filter=filtering)

This question appears off-topic, though. Please open a new issue and I'll try to help you in more detail.

@johann-petrak
Copy link

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.

Changing the level at runtime is an extremely common use case (as the contributions to this issues showed). I am with the original creator or this issue: people pick loguru exepcting it to be simple just to find out that something that has to be done everytime a CLI is run is a) surprising and be) much more difficult as it needs to be.
Having an API method to simply set the logging level of all handlers would be extremely useful and not prevent at all to do things differently if more complex handler situations require it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests