In my program I define a logger at the beginning that looks similar to this:
def start_logger():
fh = logging.handlers.RotatingFileHandler('logger.log',
maxBytes=1000000,
backupCount=100)
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.DEBUG)
fh_fmt = '%(asctime)s %(levelname)8s %(message)s [%(filename)s:%(lineno)d]'
#fh_fmt = '%(asctime)s - %(funcName)s - %(levelname)s - %(message)s'
ch_fmt = '%(asctime)s %(levelname)8s %(message)s [%(filename)s:%(lineno)d]'
#ch_fmt = '%(funcName)s - %(levelname)s - %(message)s'
fh.setFormatter(logging.Formatter(fh_fmt))
ch.setFormatter(logging.Formatter(ch_fmt))
root = logging.getLogger()
root.addHandler(fh)
root.addHandler(ch)
I then have multiple files that are called from my main program. In order for them to work correctly I need to do the following:
import logging
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
log.debug("This debug message is ounly output when I set the level again to debug for this file. Otherwise the the log level for this file is WARNING.")
Why is the default level for all modules that I import set to warning. Why do I have to set the level again to DEBUG for each of them when they import my root logger with log = logging.getLogger(name)? Is this the best way to create a logging module accross a package with different modules or is there a better solution?
Let's start by looking at what Handler.setLevel does:
Sets the threshold for this handler to lvl. Logging messages which are less severe than lvl will be ignored. When a handler is created, the level is set to NOTSET (which causes all messages to be processed).
Any messages less severe than lvl are ignored. Effectively, setting it to DEBUG is meaningless (unless you define your own log levels), because there is no less severe message than debug. So, that means that the handler won't ignore any messages.
setLevel is the right idea, but you're calling it on the wrong object. Look at Logger.setLevel:
Sets the threshold for this logger to lvl. Logging messages which are less severe than lvl will be ignored. When a logger is created, the level is set to NOTSET (which causes all messages to be processed when the logger is the root logger, or delegation to the parent when the logger is a non-root logger). Note that the root logger is created with level WARNING.
The term ‘delegation to the parent’ means that if a logger has a level of NOTSET, its chain of ancestor loggers is traversed until either an ancestor with a level other than NOTSET is found, or the root is reached.
If an ancestor is found with a level other than NOTSET, then that ancestor’s level is treated as the effective level of the logger where the ancestor search began, and is used to determine how a logging event is handled.
You're creating the children right, but they're all children of the root logger. They're level is set to NOTSET, and it propagates up to the root, whose default value is WARNING. Consequently, you don't see any messages.
TL;DR:
The solution is simple: set the level on the logger, not the handler. The following code should do what you need:
def start_logger():
fh = logging.handlers.RotatingFileHandler('logger.log',
maxBytes=1000000,
backupCount=100)
ch = logging.StreamHandler(sys.stdout)
fh_fmt = '%(asctime)s %(levelname)8s %(message)s [%(filename)s:%(lineno)d]'
#fh_fmt = '%(asctime)s - %(funcName)s - %(levelname)s - %(message)s'
ch_fmt = '%(asctime)s %(levelname)8s %(message)s [%(filename)s:%(lineno)d]'
#ch_fmt = '%(funcName)s - %(levelname)s - %(message)s'
fh.setFormatter(logging.Formatter(fh_fmt))
ch.setFormatter(logging.Formatter(ch_fmt))
logging.basicConfig(level=logging.DEBUG)
root = logging.getLogger()
root.addHandler(fh)
root.addHandler(ch)
Once you do that, you shouldn't need the setLevel call when making the children.
Oh, and to answer your other questions: this is exactly the way you're supposed to use the logging library. (I actually just log everything to the root logger, because I don't need the kind of granularity you developed, but when you have a case for it, you're doing it just as you should.)
EDIT: Evidently, setLevel doesn't seem to work on the root logger. Instead, before accessing the root logger, you have to set basicConfig. Setting the level in logging.basicConfig will do what you need (at least, it worked in my tests). Note, doing this matches the example given in Logging from multiple modules, so should solve your problem.
Related
Hi I'm wanting to create a logging module that will allow me to set the logging level and pass a messaging to the function. Although this doesn't work, it's my idea and needs some guidance.
def log_message(loglevel, message):
logging.getLogger().setLevel(loglevel.upper())
logging.basicConfig(level=logging.getLevelName, filename="mylog.log",
filemode="w", format="%(asctime)s - %(levelname)s - %(message)s")
logging.getLogger(message)
log_message("info", 'this is a test')
This implementation may not be thread safe and should not be used for production level as it is just to fix what is not working in your function.
import logging
# map logging levels
LEVELS = {"NOTSET":logging.NOTSET,"DEBUG":logging.DEBUG, "INFO":logging.INFO,
"WARNING":logging.WARNING,"ERROR":logging.ERROR, "CRITICAL":logging.CRITICAL}
handler = logging.FileHandler("mylog.log","a")
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(LEVELS["DEBUG"])
# your function
def log_message(logLevel, message):
# log file name
# map logging level to int values
logger.log(LEVELS[logLevel.upper()],message)
log_message("info", "asd")
Well. it is said that setLevel just specifies minimum threshold to display the log, so it should be fine.
Logger.setLevel() specifies the lowest-severity log message a logger will handle, where debug is the lowest built-in severity level and critical is the highest built-in severity.
Yet, I will not recommend this for production, as you need better written logging function. Please refer to https://docs.python.org/3/howto/logging.html#basic-logging-tutorial and https://docs.python.org/3/howto/logging.html#advanced-logging-tutorial for better logging setup.
When I run the below piece of code, the logging is working fine. But when I comment #1) and #2) under setup_logger the log is not displayed.
What does #1) and #2) do here?
import logging
import sys
def get_logger(name):
print('get_logger -- ', name)
log = logging.getLogger("hello.{}".format(name))
return log
def setup_logger():
print('setup_logger')
root = logging.getLogger("")
root.setLevel(logging.ERROR)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
))
root.addHandler(handler)
logger = logging.getLogger("hello") #1
logger.setLevel(logging.DEBUG) #2
LOG = get_logger(__name__)
setup_logger()
print(LOG)
def main():
LOG.debug('hello')
if __name__ == '__main__':
main()
The first line you marked gets a logger with given name. If there currently is no logger with the name you provided, a new one will be created (Also known as Singleton). You can read more about logging.getLogger in the Python documentation :
Return a logger with the specified name or, if name is None, return a logger
which is the root logger of the hierarchy. If
specified, the name is typically a dot-separated hierarchical name
like ‘a’, ‘a.b’ or ‘a.b.c.d’. Choice of these names is entirely up to
the developer who is using logging.
All calls to this function with a given name return the same logger instance.
This means that logger instances never need to be
passed between different parts of an application.
The second line you marked sets the loglevel on the logger instance. If you set the loglevel to DEBUG, all messages you log will be printed to the console / file. If you set it to INFO, all messages except for
debug messages will be logged. And so on. Quote from the docs again:
Sets the threshold for this logger to level. Logging messages which
are less severe than level will be ignored; logging messages which
have severity level or higher will be emitted by whichever handler or
handlers service this logger, unless a handler’s level has been set to
a higher severity level than level.
When a logger is created, the level is set to NOTSET (which causes all
messages to be processed when the logger is the root logger, or
delegation to the parent when the logger is a non-root logger). Note
that the root logger is created with level WARNING.
If you still have questions, I can edit my post to answer them.
The below code is copied from the documentation. I am supposed to be able to see all the info logs. But I don't. I am only able to see the warn and above even though I've set setLevel to INFO.
Why is this happening? foo.py:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
Output:
workingDirectory$ python foo.py
warn message
error message
critical message
Where did the info and debug messages go??
Replace the line
logger.setLevel(logging.DEBUG)
with
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
and it should work as expected. If you don't configure logging with any handlers (as in your post - you only configure a level for your logger, but no handlers anywhere), you'll get an internal handler "of last resort" which is set to output just the message (with no other formatting) at the WARNING level.
Try running logging.basicConfig() in there. Of note, I see you mention INFO, but use DEBUG. As written, it should show all five messages. Swap out DEBUG with INFO, and you should see four messages.
import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
edit: Do you have logging set up elsewhere in your code already? Can't reproduce the exact behavior you note with the specific code provided.
As pointed by some users, using:
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
like written in the accepted answer is not a good option because it sets the log level for the root logger, so it may lead to unexpected behaviours (eg. third party libraries may start to log debug messages if you set loglevel=logging.DEBUG)
In my opinion the best solution is to set log level just for your logger, like this:
import logging
logger = logging.getLogger('MyLogger')
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
Not really intuitive solution, but is necessary if you want to set log level only for 'MyLogger' and leave the root logger untouched.
So, why is logging.basicConfig(level=logging.DEBUG, format='%(message)s') setting the log level globally?
Well, actually it doesn't. As said, it's just changing the configuration of the root logger and, as described in the python documentation:
Loggers should NEVER be instantiated directly, but always through the
module-level function logging.getLogger(name). Multiple calls to
getLogger() with the same name will always return a reference to the
same Logger object.
So, logging.basicConfig is creating a StreamHandler with a default Formatter and adding it to the root logger.
The point is that if any other library is using the "root logger", you're going to set that log level for that library too so it can happen that you start to see debug logs from third party libraries.
This is why I think it's better to create your own logger and set your own formatters and handlers, so you can leave the root logger untouched.
This is technically also an "answer", because it can "solve" the problem. BUT I definitely DO NOT like it. It is not intuitive, and I lost 2+ hours over it.
Before:
import logging
logger = logging.getLogger('foo')
logger.setLevel(logging.INFO)
logger.info('You can not see me')
# Or you can just use the following one-liner in command line.
# $ python -c "import logging; logger = logging.getLogger('foo'); logger.setLevel(logging.INFO); logger.info('You can not see me')"
After:
import logging
logging.debug('invisible magic') # <-- magic
logger = logging.getLogger('foo')
logger.setLevel(logging.INFO)
logger.info('But now you can see me')
# Or you can just use the following one-liner in command line.
$ python -c "import logging; logging.debug('invisible magic'); logger = logging.getLogger('foo'); logger.setLevel(logging.INFO); logger.info('But now you see me')"
PS: Comparing it to the current chosen answer, and #Vinay-Sajip's explanation, I can kind of understand why. But still, I wish it was not working that way.
If you want this to work WITHOUT basicConfig, you have to first set up the lowest possible level you'll log onto the logger. Since the logger sets a minimum threshold, handlers which have a lower threshold but belong to the same logger won't get those lower threshold messages since they're ignored by the logger in the first place. Intuitive, but not obvious.
We start by doing this:
lgr = logging.getLogger(name)
lgr.setLevel(logging.DEBUG)
Then, set up the handlers with the different levels you need, in my case I want DEBUG logging on stdout and INFO logging to a rotating file, so I do the following:
rot_hndlr = RotatingFileHandler('filename.log',
maxBytes=log_size,
backupCount=3)
rot_hndlr.setFormatter(formatter)
rot_hndlr.setLevel(logging.INFO)
lgr.addHandler(rot_hndlr)
stream_hndlr = logging.StreamHandler()
stream_hndlr.setFormatter(stream_formatter)
lgr.addHandler(stream_hndlr)
Then, to test, I do this:
lgr.debug("Hello")
lgr.info("There")
My stdout (console) will look like this:
Hello
There
and my filename.log file will look like this:
There
In short, change the level in logging.basicConfig will influence the global settings.
You should better set level for each logger and the specific handler in the logger.
The following is an example that displays all levels on the console and only records messages >= errors in log_file.log. Notice the level for each handler is different.
import logging
# Define logger
logger = logging.getLogger('test')
# Set level for logger
logger.setLevel(logging.DEBUG)
# Define the handler and formatter for console logging
consoleHandler = logging.StreamHandler() # Define StreamHandler
consoleHandler.setLevel(logging.DEBUG) # Set level
concolsFormatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') # Define formatter
consoleHandler.setFormatter(concolsFormatter) # Set formatter
logger.addHandler(consoleHandler) # Add handler to logger
# Define the handler and formatter for file logging
log_file = 'log_file'
fileHandler = logging.FileHandler(f'{log_file}.log') # Define FileHandler
fileHandler.setLevel(logging.ERROR) # Set level
fileFormatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # Define formatter
fileHandler.setFormatter(fileFormatter) # Set formatter
logger.addHandler(fileHandler) # Add handler to logger
# Test
logger.debug('This is a debug')
logger.info('This is an info')
logger.warning('This is a warning')
logger.error('This is an error')
logger.critical('This is a critical')
Console output:
# Test
test - DEBUG - This is a debug
test - INFO - This is an info
test - WARNING - This is a warning
test - ERROR - This is an error
test - CRITICAL - This is a critical
File log_file.log content:
2021-09-22 12:50:50,938 - test - ERROR - This is an error
2021-09-22 12:50:50,938 - test - CRITICAL - This is a critical
To review your logger's level:
logger.level
The result should be one of the following:
10 # DEBUG
20 # INFO
30 # WARNING
40 # ERROR
50 # CRITICAL
To review your handlers's levels:
logger.handlers
[<StreamHandler stderr (DEBUG)>,
<FileHandler ***/log_file.log (ERROR)>]
The accepted answer does not work for me on Win10, Python 3.7.2.
My solution:
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
It's order sensitive.
You have to set the basicConfig of the root logger to DEBUG, then you can set the level of your individual loggers to more restrictive levels.
This is not what I expected. Here is what I had to do:
#!/usr/bin/env python3
import logging
# by default this is WARNING. Leaving it as WARNING here overrides
# whatever setLevel-ing you do later so it seems they are ignored.
logging.basicConfig(level=logging.DEBUG)
l = logging.getLogger(__name__)
l.setLevel(level=logging.INFO)
# if I hadn't called basicConfig with DEBUG level earlier,
# info messages would STILL not be shown despite calling
# setLevel above. However now debug messages will not be shown
# for l because setLevel set it to INFO
l.warning('A warning message will be displayed')
l.info('A friendly info message will be displayed')
l.debug('A friendly debug message will not be displayed')
Most of the answers that I've found for this issue uses the basicConfig of the root logger.
It's not helpful for those who intend to use multiple independent loggers that were not initialised with basicConfig. The use of basicConfig implies that the loglevels of ALL loggers will be changed. It also had the unfortunate side effect of generating duplicate logs.
So I tried over several days experimenting with different ways to manipulate the loglevels and came up with one that finally worked.
The trick was to not only change the log levels of all the handlers but also the all the handlers of the parent of the logger.
def setLevel(self, infoLevel):
# To dynamically reset the loglevel, you need to also change the parent levels as well as all handlers!
self.logger.parent.setLevel(infoLevel)
for handler in self.logger.parent.handlers:
handler.setLevel(infoLevel)
self.logger.setLevel(infoLevel)
for handler in self.logger.handlers:
handler.setLevel(infoLevel)
The inspiration came from the fact that the basicConfig changes the root logger settings, so I was trying to do the same without using basicConfig.
For those that are interested, I did a little Python project on Github that illustrates the different issues with setting loglevel of the logger (it works partially), proves the SLogger (Sample Logger) implementation works, and also illustrates the duplicate log issue with basicConfig when using multiple loggers not initialised with it.
https://github.com/FrancisChung/python-logging-playground
TLDR: If you're only interested in a working sample code for the logger, the implentation is listed below
import logging
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
class SLogger():
"""
SLogger : Sample Logger class using the standard Python logging Library
Parameters:
name : Name of the Logger
infoLevel : logging level of the Logger (e.g. logging.DEBUG/INFO/WARNING/ERROR)
"""
def __init__(self, name: str, infoLevel=logging.INFO):
try:
if name is None:
raise ValueError("Name argument not specified")
logformat = '%(asctime)s %(levelname)s [%(name)s %(funcName)s] %(message)s'
self.logformat = logformat
self.name = name.upper()
self.logger = logging.getLogger(self.name)
self.logger.setLevel(infoLevel)
self.add_consolehandler(infoLevel, logformat)
except Exception as e:
if self.logger:
self.logger.error(str(e))
def error(self, message):
self.logger.error(message)
def info(self, message):
self.logger.info(message)
def warning(self, message):
self.logger.warning(message)
def debug(self, message):
self.logger.debug(message)
def critical(self, message):
self.logger.critical(message)
def setLevel(self, infoLevel):
# To dynamically reset the loglevel, you need to also change the parent levels as well as all handlers!
self.logger.parent.setLevel(infoLevel)
for handler in self.logger.parent.handlers:
handler.setLevel(infoLevel)
self.logger.setLevel(infoLevel)
for handler in self.logger.handlers:
handler.setLevel(infoLevel)
return self.logger.level
def add_consolehandler(self, infoLevel=logging.INFO,
logformat='%(asctime)s %(levelname)s [%(name)s %(funcName)s] %(message)s'):
sh = logging.StreamHandler()
sh.setLevel(infoLevel)
formatter = logging.Formatter(logformat)
sh.setFormatter(formatter)
self.logger.addHandler(sh)
Create object the right way, e.g. inspired by Google:
import logging
formatter = logging.Formatter('%(asctime)s %(threadName)s: %(message)s')
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
log.addHandler(handler)
log.debug('debug message')
log.info('info message')
log.warn('warn message')
log.error('error message')
log.critical('critical message')
2022-11-22 23:17:59,342 MainThread: debug message
2022-11-22 23:17:59,342 MainThread: info message
2022-11-22 23:17:59,342 MainThread: warn message
2022-11-22 23:17:59,342 MainThread: error message
2022-11-22 23:17:59,342 MainThread: critical message
As pointed out by #ManuelFedele, logging.basicConfig is not a good solution.
#VinaySajip explained that the setLevel is ignored because the logger is using the internal handler "of last resort", whose level is set to WARNING.
This explanation was also helpful:
The Handler.setLevel() method, just as in logger objects, specifies the lowest severity that will be dispatched to the appropriate destination. Why are there two setLevel() methods? The level set in the logger determines which severity of messages it will pass to its handlers. The level set in each handler determines which messages that handler will send on.
So a good solution is to add a handler to the logger, with the appropriate level:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG) # or whatever level should be displayed on the console
logger.addHandler(ch)
Output
>>> logger.debug('debug message')
debug message
>>> logger.info('info message')
info message
>>> logger.warn('warn message')
<stdin>:1: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
warn message
>>> logger.error('error message')
error message
>>> logger.critical('critical message')
critical message
I have create a global logger using the following:
def logini():
logfile='/var/log/cs_status.log'
import logging
import logging.handlers
global logger
logger = logging.getLogger()
logging.basicConfig(filename=logfile,filemode='a',format='%(asctime)s %(name)s %(levelname)s %(message)s',datefmt='%y%m%d-%H:%M:%S',level=logging.DEBUG,propagate=0)
handler = logging.handlers.RotatingFileHandler(logfile, maxBytes=2000000, backupCount=5)
logger.addHandler(handler)
__builtins__.logger = logger
It works, however I am getting 2 outputs for every log, one with the formatting and one without.
I realize that this is being caused by the file rotater as I can comment out the 2 lines of the handler code and then I get a single outputted correct log entry.
How can I prevent the log rotator from outputting a second entry ?
Currently you're configuring two file loggers that point to the same logfile. To only use the RotatingFileHandler, get rid of the basicConfig call:
logger = logging.getLogger()
handler = logging.handlers.RotatingFileHandler(logfile, maxBytes=2000000,
backupCount=5)
formatter = logging.Formatter(fmt='%(asctime)s %(name)s %(levelname)s %(message)s',
datefmt='%y%m%d-%H:%M:%S')
handler.setFormatter(formatter)
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
All basicConfig does for you is to provide an easy way to instantiate either a StreamHandler (default) or a FileHandler and set its loglevel and formats (see the docs for more information). If you need a handler other than these two, you should instantiate and configure it yourself.
I'm using Python logging, and for some reason, all of my messages are appearing twice.
I have a module to configure logging:
# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
self.logger = logging.getLogger("my_logger")
self.logger.setLevel(logging.DEBUG)
self.logger.propagate = 0
# Format for our loglines
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# Setup console logging
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
self.logger.addHandler(ch)
# Setup file logging as well
fh = logging.FileHandler(LOG_FILENAME)
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
self.logger.addHandler(fh)
Later on, I call this method to configure logging:
if __name__ == '__main__':
tom = Boy()
tom.configure_logging(LOG_FILENAME)
tom.buy_ham()
And then within say, the buy_ham module, I'd call:
self.logger.info('Successfully able to write to %s' % path)
And for some reason, all the messages are appearing twice. I commented out one of the stream handlers, still the same thing. Bit of a weird one, not sure why this is happening. I'm assuming I've missed something obvious.
You are calling configure_logging twice (maybe in the __init__ method of Boy) : getLogger will return the same object, but addHandler does not check if a similar handler has already been added to the logger.
Try tracing calls to that method and eliminating one of these. Or set up a flag logging_initialized initialized to False in the __init__ method of Boy and change configure_logging to do nothing if logging_initialized is True, and to set it to True after you've initialized the logger.
If your program creates several Boy instances, you'll have to change the way you do things with a global configure_logging function adding the handlers, and the Boy.configure_logging method only initializing the self.logger attribute.
Another way of solving this is by checking the handlers attribute of your logger:
logger = logging.getLogger('my_logger')
if not logger.handlers:
# create the handlers and call logger.addHandler(logging_handler)
If you are seeing this problem and you're not adding the handler twice then see abarnert's answer here
From the docs:
Note: If you attach a handler to a logger and one or more of its
ancestors, it may emit the same record multiple times. In general, you
should not need to attach a handler to more than one logger - if you
just attach it to the appropriate logger which is highest in the
logger hierarchy, then it will see all events logged by all descendant
loggers, provided that their propagate setting is left set to True. A
common scenario is to attach handlers only to the root logger, and to
let propagation take care of the rest.
So, if you want a custom handler on "test", and you don't want its messages also going to the root handler, the answer is simple: turn off its propagate flag:
logger.propagate = False
I'm a python newbie, but this seemed to work for me (Python 2.7)
while logger.handlers:
logger.handlers.pop()
The handler is added each time you call from outside. Try Removeing the Handler after you finish your job:
self.logger.removeHandler(ch)
In my case I'd to set logger.propagate = False to prevent double printing.
In below code if you remove logger.propagate = False then you will see double printing.
import logging
from typing import Optional
_logger: Optional[logging.Logger] = None
def get_logger() -> logging.Logger:
global _logger
if _logger is None:
raise RuntimeError('get_logger call made before logger was setup!')
return _logger
def set_logger(name:str, level=logging.DEBUG) -> None:
global _logger
if _logger is not None:
raise RuntimeError('_logger is already setup!')
_logger = logging.getLogger(name)
_logger.handlers.clear()
_logger.setLevel(level)
ch = logging.StreamHandler()
ch.setLevel(level)
# warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
ch.setFormatter(_get_formatter())
_logger.addHandler(ch)
_logger.propagate = False # otherwise root logger prints things again
def _get_formatter() -> logging.Formatter:
return logging.Formatter(
'[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
This can also happen if you are trying to create a logging object from the parent file. For e.g.
This is the main application file test.py
import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
def my_code():
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
And below is the parent file main.py
import test
test.my_code()
The output of this will print only once
2021-09-26 11:10:20,514 - simple_example - DEBUG - debug message
2021-09-26 11:10:20,514 - simple_example - INFO - info message
2021-09-26 11:10:20,514 - simple_example - WARNING - warn message
2021-09-26 11:10:20,514 - simple_example - ERROR - error message
2021-09-26 11:10:20,514 - simple_example - CRITICAL - critical message
But if we had a parent logging object, then it will be printed twice. For e.g. if this is the parent file
import test
import logging
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
test.my_code()
The the output will be
2021-09-26 11:16:28,679 - simple_example - DEBUG - debug message
2021-09-26 11:16:28,679 - simple_example - DEBUG - debug message
2021-09-26 11:16:28,679 - simple_example - INFO - info message
2021-09-26 11:16:28,679 - simple_example - INFO - info message
2021-09-26 11:16:28,679 - simple_example - WARNING - warn message
2021-09-26 11:16:28,679 - simple_example - WARNING - warn message
2021-09-26 11:16:28,679 - simple_example - ERROR - error message
2021-09-26 11:16:28,679 - simple_example - ERROR - error message
2021-09-26 11:16:28,679 - simple_example - CRITICAL - critical message
2021-09-26 11:16:28,679 - simple_example - CRITICAL - critical message
A call to logging.debug() calls logging.basicConfig() if there are no root handlers installed. That was happening for me in a test framework where I couldn't control the order that test cases fired. My initialization code was installing the second one. The default uses logging.BASIC_FORMAT that I didn't want.
It seems that if you output something to the logger (accidentally) then configure it, it is too late. For example, in my code I had
logging.warning("look out)"
...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)
root.info("hello")
I would get something like (ignoring the format options)
look out
hello
hello
and everything was written to stdout twice. I believe this is because the first call to logging.warning creates a new handler automatically, and then I explicitly added another handler. The problem went away when I removed the accidental first logging.warning call.
I was struggling with the same issue in the context of multiple processes. (For the code see the docs which I was following almost verbatim.) Namely, all log messages originating from any of the child processes got duplicated.
My mistake was to call worker_configurer(),
def worker_configurer(logging_queue):
queue_handler = logging.handlers.QueueHandler(logging_queue)
root = logging.getLogger()
root.addHandler(queue_handler)
root.setLevel(level)
both in the child processes and also in the main process (since I wanted the main process to log stuff, too). The reason this led to trouble (on my Linux machine) is that on Linux the child processes got started through forking and therefore inherited the existing log handlers from the main process. That is, on Linux the QueueHandler got registered twice.
Now, preventing the QueueHandler from getting registered twice in the worker_configurer() function is not as trivial as it seems:
Logger objects like the root logger root have a handlers property but it is undocumented.
In my experience, testing whether any([handler is queue_handler for handler in root.handlers]) (identity) or any([handler == queue_handler for handler in root.handlers]) (equality) fails after forking, even if root.handlers seemingly contains the same QueueHandler. (Obviously, the previous two expressions can be abbreviated by queue_handler in root.handlers, since the in operator checks for both identity and equality in the case of lists.)
The root logger gets modified by packages like pytest, so root.handlers and root.hasHandlers() are not very reliable to begin with. (They are global state, after all.)
The clean solution, therefore, is to replace forking with spawning to prevent these kinds of multiprocessing bugs right from the start (provided you can live with the additional memory footprint, of course). Or to use an alternative to the logging package that doesn't rely on global state and instead requires you to do proper dependency injection but I'm digressing… :)
With that being said, I ended up going for a rather trivial check:
def worker_configurer(logging_queue):
queue_handler = logging.handlers.QueueHandler(logging_queue)
root = logging.getLogger()
for handler in root.handlers:
if isinstance(handler, logging.handlers.QueueHandler):
return
root.addHandler(queue_handler)
root.setLevel(level)
Obviously, this will have nasty side effects the second I decide to register a second queue handler somewhere else.
From the docs:
"Loggers have the following attributes and methods. Note that Loggers should NEVER be instantiated directly, but always through the module-level function logging.getLogger(name).
Multiple calls to getLogger() with the same name will always return a reference to the same Logger object".
Make sure you don't initialise your loggers with the same name
I advise you to initialise the logger with __name__ as name param i.e:
import logging
logger = logging.getLogger(__name__)
NOTE:
even if you init a loggers from other modules with same name, you will still get the same logger, therefore calling i.e logger.info('somthing') will log as many times as you initiated the logger class.
I was getting a strange situation where console logs were doubled but my file logs were not. After a ton of digging I figured it out.
Please be aware that third party packages can register loggers. This is something to watch out for (and in some cases can't be prevented). In many cases third party code checks to see if there are any existing root logger handlers; and if there isn't--they register a new console handler.
My solution to this was to register my console logger at the root level:
rootLogger = logging.getLogger() # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
rootLogger.addHandler(ch)
If you are using any config for logging, For instance log.conf
In .conf file you can do it by adding this line in the [logger_myLogger] section: propagate=0
[logger_myLogger]
level=DEBUG
handlers=validate,consoleHandler
qualname=VALIDATOR
propagate=0
I had the same issue. In my case, it was not due to handlers or duplicate initial configuration but a stupid typo. In main.py I was using a logger object but in my_tool.py I was directly calling to the logging module by mistake, hence after invoking functions from my_tool module everything was messed up and the messages appeared duplicated.
This was the code:
main.py
import logging
import my_tool
logger_name = "cli"
logger = logging.getLogger(logger_name)
logger.info("potato")
logger.debug("potato)
my_tool.function()
logger.info("tomato")
my_tool.py
import logging
logger_name = "cli"
logger = logging.getLogger(logger_name)
# some code
logging.info("carrot")
and the result
terminal
>> potato
>> potato
>> carrot
>> tomato
>> tomato
Adding to others' useful responses...
In case you are NOT accidentally configuring more than one handler on your logger:
When your logger has ancestors(root logger is always one) and they have their own handlers, they will also output when your logger outputs (by default), which will create duplicates. You have two options:
Don't propagate your log event to your ancestors by setting:
my_logger.propagate = False
If you only have one ancestor(root logger), you could directly configure their handler instead. For example:
# directly change the formatting of root's handler
root_logger = logging.getLogger()
roots_handler = root_logger.handlers[0]
roots_handler.setFormatter(logging.Formatter(': %(message)s')) # change format
my_logger = logging.getLogger('my_logger') # my_logger will use new formatting
If you use the standard construction logger = logging.getLogger('mymodule') and then accidentally mistype loggger as logging i.e.
logger = logging.getLogger('mymodule')
# configure your handlers
logger.info("my info message") # won't make duplicate
logging.info("my info message") # will make duplicate logs
then this will cause duplicate messages to come up because the call to logging creates a new logger.