Problem using logging and multiple logs

Have been encountering issues with Python’s logging module being seemingly unreliable. However, now tracked this down to the issue being with Python’s logging module which doesn’t seem to work if two different logs are active in the same application, see example below:

logging.basicConfig(filename=__name__ + '_process.log', format='%(asctime)s %(message)s', encoding="utf-8", level=logging.INFO)
logger1 = logging.getLogger(__name__ +"_process")
logger1.info("test info message")

logging.basicConfig(filename=__name__ + '_error.log', format='%(asctime)s %(message)s', encoding="utf-8", level=logging.ERROR)
logger2 = logging.getLogger(__name__ + "_error")
logger2.error("test error message")

I’d recommend looking at the python docs, they’re very helpful. Here’s the entry for logging.basicConfig - I’ve bolded the relevant part.

logging.basicConfig(**kwargs)

Does basic configuration for the logging system by creating a StreamHandler with a default Formatterand adding it to the root logger. The functions debug(), info(), warning(), error() and critical()will call basicConfig() automatically if no handlers are defined for the root logger.

This function does nothing if the root logger already has handlers configured, unless the keyword argument force is set to True.

Note that this is not suggesting you should add force=True to configure multiple loggers, because in your example this would just overwrite the earlier config you set, not create a separate config for a different logger. If you want multiple loggers with different configuration, don’t use logging.basicConfig at all, configure them independently like so:

import logging

logger1 = logging.getLogger(f"{__name__}_process")
logger1.setLevel(logging.INFO)
handler1 = logging.FileHandler(f"{__name__}_process.log")
handler1.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
logger1.addHandler(handler1)
logger1.info("test info message")

logger2 = logging.getLogger(f"{__name__}_error")
logger2.setLevel(logging.INFO)
handler2 = logging.FileHandler(f"{__name__}_error.log")
handler2.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
logger2.addHandler(handler2)
logger2.error("test error message")

One caveat - if you’re setting up two loggers just to put your info msgs and error msgs in different files, then two loggers isn’t the right solution in the first place, and may not behave how you expect (logger1 set to INFO will still log logger1.error messages if you call it). I won’t get into more detail though, unless you want to share more about what you’re trying to achieve. But I’d definitely recommend spending more time with the docs either way, I reference them regularly.

Many thanks dchevell, that works fine.

However I do think the docs should be updated to warn that logging.basicConfig can only be used once in an application. There is nothing that I can see in the docs: logging — Logging facility for Python — Python 3.14.5 documentation to suggest that it does not work with multiple logs. Certainly feels like a Python bug to me.

Although this works it is becoming a bit unwieldy, makes me think it might just be more efficient to write my own logging class.

Having added output to screen as well as to file, this is what I currently have:

import logging

logger1 = logging.getLogger(f"{__name__}_process")
logger1.setLevel(logging.INFO)
handler1a = logging.FileHandler(__name__ + '_process.log')
handler1b = logging.StreamHandler()
handler1a.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
handler1b.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
logger1.addHandler(handler1a)
logger1.addHandler(handler1b)

logger2 = logging.getLogger(f"{__name__}_error")
logger2.setLevel(logging.INFO)
handler2a = logging.FileHandler(__name__ + '_error.log')
handler2b = logging.StreamHandler()
handler2a.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
handler2b.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
logger2.addHandler(handler2a)
logger2.addHandler(handler2b)

logger1.info("test info message 1")
logger2.error("test error message 1")
logger1.info("test info message 2")
logger2.error("test error message 2")
logger1.info("test info message 3")
logger2.error("test error message 3")

After further reading, code now shortened to a much more manageable level:

logging.basicConfig(format='%(asctime)s %(message)s',encoding="utf-8", handlers = [logging.StreamHandler()])

logger1 = logging.getLogger(f"{__name__}_process")
logger1.setLevel(logging.INFO)
logger1.addHandler(logging.FileHandler(__name__ + '_process.log'))

logger2 = logging.getLogger(f"{__name__}_error")
logger2.setLevel(logging.INFO)
logger2.addHandler(logging.FileHandler(__name__ + '_error.log'))

Note that the logger names form their own hierarchy for inheriting configuration from a parent. I would suggest

# This goes in the application, not any library intended to be imported by a script.
logging.basicConfig(format='%(asctime)s %(message)s',encoding="utf-8", handlers = [logging.StreamHandler()])



# These both go in the relevant module. If it's foo.py, the loggers are named
# "foo.process" and "foo.error", and any configuration on a logger named "foo"
# will apply to both child loggers. Note the example of setting the level on foo
# once.

logging.getLogger(__name__).setLevel(logging.INFO)

logger1 = logging.getLogger(f"{__name__}.process")
logger1.addHandler(logging.FileHandler(__name__ + '_process.log'))

logger2 = logging.getLogger(f"{__name__}.error")
logger2.addHandler(logging.FileHandler(__name__ + '_error.log'))

Unfortunately in your example the format string is not propagated down to the child file handlers.

Also found that trying:

logger1 = logging.getLogger(f"{__name__}.process").setLevel(logging.INFO)

errors with:

logger1.addHandler(logging.FileHandler(__name__ + '_process.log'))
AttributeError: ‘NoneType’ object has no attribute ‘addHandler’

Yes, because setLevel doesn’t return the logger. Note that I just used logger.getLogger(…).setLevel (no assignment), because I wasn’t going to use the parent logger directly after settting its level.