Cannot set log level of `ignite.distributed` because of calls to setup_logger inside auto functions
See original GitHub issue🐛 Bug description
The ignite.distributed
auto functions such as ignite.distributed.auto_dataloader
use setup_logger
to get a logging.Logger
instance. However, this function always sets the logging level to logging.INFO
and furthermore removes ands sets up the logging handlers. Functions should not be setting up a logger object if it is already initialized such that user code can specify a different log level, handlers, or log message format string in their code (e.g., main function).
See for example:
- https://github.com/pytorch/ignite/blob/6d490c8f3083f21ba8f82eb646d9052a06fae3e2/ignite/distributed/auto.py#L67
- https://github.com/pytorch/ignite/blob/6d490c8f3083f21ba8f82eb646d9052a06fae3e2/ignite/distributed/launcher.py#L201
Note that in case of ignite.distributed.launcher.Parallel
one can modify the self.logger
instance, but this logger already prints messages right after setup_logger
in its __init__
function, hence, making it impossible to adjust the log level, format, or handlers for these first messages also.
Environment
- PyTorch Version (e.g., 1.4):
- Ignite Version (e.g., 0.3.0):
- OS (e.g., Linux):
- How you installed Ignite (
conda
,pip
, source): - Python version:
- Any other relevant information:
Issue Analytics
- State:
- Created 3 years ago
- Comments:14 (8 by maintainers)
Hey, this is great! Thanks for working on this and keeping me in the loop. Sorry I didn’t provide feedback as I was distracted.
The general recommendation for any Python lib should be to never use the root logger internally. Root is for applications. So I think this works well with how you’ve been adapting it now. I would probably have picked the default
name
to be simplyignite
, because technically that is the “root” logger for the ignite library. The.
separators in the logger name define a hierarchy. So if I want to modify all loggers used by ignite only, I can use thelogging.getLogger("ignite")
object in my application code. Was there another motivation for choosingignite.root.logger
instead?I think it’s good to have the
reset
flag default toFalse
, even though it changes the behavior ofsetup_logger()
compared to previous ignite versions. But as it’s a pre-stable release version, this should certainly be fine with users. Otherwise, I think thereset
flag may not actually have any meaningful use? I was mostly suggesting it in the first place to maintain existing behavior, but be able to alter it via opt-in. Having thereset=False
behavior should be the new norm, though, so not sure there is any meaningful use case forreset=True
. Application code should normally set up logging at the very start of the main function, after which there seems no need to reset. But fine if the option exists, of course.Instead of a
reset
flag, another option may have been to split the functionality ofsetup_logger()
into separate more reusable functions with each a clearer and more limited scope. To set the logging level, one should anyway just use the API provided by Python’s logging module. I guess this mainly leaves a utility function to (re-)set the handlers depending on distributed_rank. If one were to removelevel
setting fromsetup_logger()
, the function would indeed only be used to set the handlers as desired (as insetup_logger_handlers()
kind of)?Anyway, these were just some thoughts I had and missed to share earlier. It is looking fine as is for my use case.
I would say you already have global logging objects for these functions anyway by the use of
logging.getLogger()
insetup_logger()
. This is what makes it possible to adjust the logger without accessing a module level logger instance reference. Multiple calls toauto_*()
functions will give you the same logger instance. The issue is just that this instance will also always reset the level, format, and handlers.Would it work to just use a
setup_logger
variant (maybe a flag for this function) which instead of removing any previous handlers and setting up the logger retrieved bylogging.getLogger(name)
would simply checklogger.hasHandlers()
and just return the already set up logger object ifTrue
at this line? One could still check and remove all handlers fordistributed_rank > 0
. But given that these loggers have aNullHandler
set by any previous call to theauto_*
functions, I don’t think this would be necessary. Also, by only checkinghasHandlers()
and making any changes to the logger object only ifFalse
, you leave the user the option to setup any desired handlers even for loggers where thedistributed_rank
of the process is greater than zero.For example:
EDIT: It may be desirable to have the handlers always be removed and replaced by a
NullHandler
ifdistributed_rank > 0
regardless of thereset
flag (to be used inauto_*()
functions when callingsetup_logger()
), though. Because then a user can setup the handler for the named logger in the main process before any processes are spawned.