236

In a python script I am writing, I am trying to log events using the logging module. I have the following code to configure my logger:

ERROR_FORMAT = "%(levelname)s at %(asctime)s in %(funcName)s in %(filename) at line %(lineno)d: %(message)s"
DEBUG_FORMAT = "%(lineno)d in %(filename)s at %(asctime)s: %(message)s"
LOG_CONFIG = {'version':1,
              'formatters':{'error':{'format':ERROR_FORMAT},
                            'debug':{'format':DEBUG_FORMAT}},
              'handlers':{'console':{'class':'logging.StreamHandler',
                                     'formatter':'debug',
                                     'level':logging.DEBUG},
                          'file':{'class':'logging.FileHandler',
                                  'filename':'/usr/local/logs/DatabaseUpdate.log',
                                  'formatter':'error',
                                  'level':logging.ERROR}},
              'root':{'handlers':('console', 'file')}}
logging.config.dictConfig(LOG_CONFIG)

When I try to run logging.debug("Some string"), I get no output to the console, even though this page in the docs says that logging.debug should have the root logger output the message. Why is my program not outputting anything, and how can I fix it?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
murgatroid99
  • 19,007
  • 10
  • 60
  • 95

8 Answers8

311

Many years later there seems to still be a usability problem with the Python logger. Here's some explanations with examples:

import logging
# This sets the root logger to write to stdout (your console).
# Your script/app needs to call this somewhere at least once.
logging.basicConfig()

# By default the root logger is set to WARNING and all loggers you define
# inherit that value. Here we set the root logger to NOTSET. This logging
# level is automatically inherited by all existing and new sub-loggers
# that do not set a less verbose level.
logging.root.setLevel(logging.NOTSET)

# The following line sets the root logger level as well.
# It's equivalent to both previous statements combined:
logging.basicConfig(level=logging.NOTSET)


# You can either share the `logger` object between all your files or the
# name handle (here `my-app`) and call `logging.getLogger` with it.
# The result is the same.
handle = "my-app"
logger1 = logging.getLogger(handle)
logger2 = logging.getLogger(handle)
# logger1 and logger2 point to the same object:
# (logger1 is logger2) == True

logger = logging.getLogger("my-app")
# Convenient methods in order of verbosity from highest to lowest
logger.debug("this will get printed")
logger.info("this will get printed")
logger.warning("this will get printed")
logger.error("this will get printed")
logger.critical("this will get printed")


# In large applications where you would like more control over the logging,
# create sub-loggers from your main application logger.
component_logger = logger.getChild("component-a")
component_logger.info("this will get printed with the prefix `my-app.component-a`")

# If you wish to control the logging levels, you can set the level anywhere 
# in the hierarchy:
#
# - root
#   - my-app
#     - component-a
#

# Example for development:
logger.setLevel(logging.DEBUG)

# If that prints too much, enable debug printing only for your component:
component_logger.setLevel(logging.DEBUG)


# For production you rather want:
logger.setLevel(logging.WARNING)

A common source of confusion comes from a badly initialised root logger. Consider this:

import logging
log = logging.getLogger("myapp")
log.warning("woot")
logging.basicConfig()
log.warning("woot")

Output:

woot
WARNING:myapp:woot

Depending on your runtime environment and logging levels, the first log line (before basic config) might not show up anywhere.

Hubert Grzeskowiak
  • 15,137
  • 5
  • 57
  • 74
  • My logging isn't working, in that it produces no output file. Do you see anything I'm doing that is clearly wrong?```logging.basicConfig( filename='logging.txt', level=logging.DEBUG) logger = logging.getLogger() logger.info('Test B') logging.info('Test A')``` – Rylan Schaeffer Aug 11 '20 at 15:39
  • The logging file isn't even created – Rylan Schaeffer Aug 11 '20 at 15:40
  • I noticed when I drop a break point after `logger = logging.getLogger()`, the level is set to WARNING even though I specified the level as `DEBUG`. Do you know what I'm doing wrong? – Rylan Schaeffer Aug 11 '20 at 15:44
  • Hi @RylanSchaeffer, you might want to create a new question and provide some more details. This will also give others a chance to help you. – Hubert Grzeskowiak Aug 12 '20 at 02:44
  • Finally! Something that works in here (with some correction)! Specifically, e.g. for **DUBUG** level, `logger.setLevel(logging.DEBUG)` and then `logger.debug("Debug message")`. Only this way the message is printed. But of course, one has to set first **'logger'**, which is missing in the answer! All the rest is unncessesary! And yet, even if incomplete, I upvoted this answer because it shows a way out of the impasse. – Apostolos Jul 08 '21 at 07:37
  • P.S. After setting 1) `logger = logging.getLogger()` and 2) `logger.setLevel(logging.DEBUG)`, `logging.debug()` also works, that is you get the same output with it as with `logger.debug()`. – Apostolos Jul 08 '21 at 07:58
  • Thanks for the feedback @Apostolos. Much appreciated. I added the missing line to the example code. – Hubert Grzeskowiak Jul 11 '21 at 07:30
  • You are welcome, @Hubert. But it would be better if you have added the missing codes at the end, as a note or something, because my comment shows that I totally missed the code in the first place! When I make changes or add things in an answer of mine, I always do that at the end, as an appendum. – Apostolos Jul 11 '21 at 07:55
  • The [docs](https://docs.python.org/3/howto/logging.html) currently say `The call to basicConfig() should come before any calls to debug(), info(), etc. Otherwise, those functions will call basicConfig()`. That reads to me like the call to basicConfig should not be necessary anymore – lucidbrot Jul 27 '22 at 13:26
  • 1
    Note that if you are using Matplotlib and run `import matplotlib.pyplot as plt` after the logging import but before the rest of the code (which would follow pep8), the Matplotlib import borks the logging configuration and none of the logging messages are shown. – Steven C. Howell Jul 27 '22 at 15:38
  • 1
    every few weeks I'll forget this and land up back at this answer – axolotl Jul 07 '23 at 15:22
185

The default logging level is warning. Since you haven't changed the level, the root logger's level is still warning. That means that it will ignore any logging with a level that is lower than warning, including debug loggings.

This is explained in the tutorial:

import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything

The 'info' line doesn't print anything, because the level is higher than info.

To change the level, just set it in the root logger:

'root':{'handlers':('console', 'file'), 'level':'DEBUG'}

In other words, it's not enough to define a handler with level=DEBUG, the actual logging level must also be DEBUG in order to get it to output anything.

murgatroid99
  • 19,007
  • 10
  • 60
  • 95
Omri Barel
  • 9,182
  • 3
  • 29
  • 22
  • 27
    The documentation says thats its default level is NOTSET which is a level of 0 which should output everything... Why is this not true? – Ben Sep 28 '17 at 23:03
  • @Ben where does it say that? All I can see is "The default level is WARNING, which means that only events of this level and above will be tracked, unless the logging package is configured to do otherwise." – Omri Barel Oct 02 '17 at 10:45
  • 1
    https://docs.python.org/3.6/library/logging.html#logging.Logger.setLevel – Ben Oct 03 '17 at 15:10
  • 3
    @Ben according to the docs the loggers are traversed to find the first parent with `level != NOTSET` or the root (if none is found). The root has `WARNING` level by default. This is written in the section you've linked to (`Logger.setLevel`). – Omri Barel Oct 03 '17 at 16:32
  • 17
    Keep in mind that after importing `logging` you need to call `logging.basicConfig()` at least once. Otherwise you might be badly surprised that child loggers will not print anything. Logging functions on the root logger call it lazily. – Hubert Grzeskowiak Aug 28 '19 at 04:25
  • 17
    What do you mean by set it in root logger `'root':{'handlers':('console', 'file'), 'level':'DEBUG'}` ? – Ari Dec 02 '19 at 21:56
  • `AttributeError: module 'logging' has no attribute 'config'` – Cerin May 11 '20 at 01:06
  • 1
    @Cerin are you trying to use `logging.config`? I think that things have changed since 2011. – Omri Barel May 17 '20 at 18:37
  • 3
    This is not what the documentation (**not tutorial!**) says! The documentation says to just use e.g. `logging.basicConfig(level=logging.DEBUG)`. Then `logging.debug(...)` is supposed to be printed (They also show what is printed). Well, not in my case either! As for `'root':{'handlers':('console', 'file'), 'level':'DEBUG'}` ... How and where is this used??? You can't throw in a thing like this without an example of its application! I really wonder how such a bad and useless answer got so many upvotes! It should be downvoted instead! (I didn't, because I don't like that.) – Apostolos Jul 08 '21 at 07:07
  • @Apostolos this is a systemic problem with both StackOverflow and Python: old answers are no longer correct, but are still found many years later, and Python links are not permanent but instead always lead you to the latest version, making old links useless. As the author of this obsolete answer I suggest you do your own research. I'm fairly sure that I wrote it for Python 2.x and in any case it's 10 years old. Even _I_ changed in the last decade. – Omri Barel Aug 28 '21 at 18:06
  • You are absolutely right, @Omri. Indeed, I normally don't leave comments to old posts, esp. when they contain dead links! Sometimes though I do it, hoping that it will be read by the poster and I will get a reply ... It's free. I don't pay anything. :)) – Apostolos Aug 30 '21 at 09:59
121

For anyone here that wants a super-simple answer: just set the level you want displayed. At the top of all my scripts I just put:

import logging
logging.basicConfig(level = logging.INFO)

Then to display anything at or above that level:

logging.info("Hi you just set your fleeb to level plumbus")

It is a hierarchical set of five levels so that logs will display at the level you set, or higher. So if you want to display an error you could use logging.error("The plumbus is broken").

The levels, in increasing order of severity, are DEBUG, INFO, WARNING, ERROR, and CRITICAL. The default setting is WARNING.

This is a good article containing this information expressed better than my answer:
https://www.digitalocean.com/community/tutorials/how-to-use-logging-in-python-3

eric
  • 7,142
  • 12
  • 72
  • 138
  • 1
    Add execution time using `logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s: %(message)s')` – Jason Jun 16 '22 at 17:44
  • I defined in a separate script a simple function that does both actions above (setting the logging level to info and the formatting of the message). The function is then used in the main script, just after the declaration `id __name__ == "__main__"`. But that does not work, basic configurations are not properly set. Where can be the problem? – mattiatantardini Sep 12 '22 at 14:48
  • 1
    @mattiatantardini configuring across modules can be tricky: https://stackoverflow.com/questions/15727420/using-logging-in-multiple-modules – eric Aug 15 '23 at 19:14
32

This problem wasted me so much time, so I'll just invest some more to write an answer and save yours

Problem

Cannot set logging level for custom loggers. (e.g: to DEBUG level)

What DOESN'T work

Setting logging level to the handler.

import logging

# Get logger
logger = logging.getLogger("my logger")

# Create a handler and set logging level for the handler
c_handler = logging.StreamHandler()
c_handler.setLevel(logging.DEBUG) # <- Here things went wrong

# link handler to logger
logger.addHandler(c_handler)

# test
logger.debug('This is a debug message') # WILL NOT WORK

SOLUTION

Set the logging level via the logger object (instead of the handler) Customlogger.setLevel(logging.DEBUG)

import logging

# Get logger
logger = logging.getLogger("my logger")

# Create a handler
c_handler = logging.StreamHandler()

# link handler to logger
logger.addHandler(c_handler)

# Set logging level to the logger
logger.setLevel(logging.DEBUG) # <-- THIS!

# test
logger.debug('This is a debug message') # WILL WORK
Mossab
  • 818
  • 8
  • 13
24

Maybe try this? It seems the problem is solved after remove all the handlers in my case.

for handler in logging.root.handlers[:]:
    logging.root.removeHandler(handler)

logging.basicConfig(filename='output.log', level=logging.INFO)
andho
  • 1,166
  • 1
  • 15
  • 27
yue dong
  • 597
  • 5
  • 6
  • `SyntaxError: invalid syntax` – Eric Aug 28 '18 at 22:23
  • 6
    Why is this necessary? What handlers come stock with the python logger and why are they there to begin with? Or maybe the question is, why doesn't basicConfig override them / replace them? – jrh May 17 '19 at 20:01
  • 1
    @yue dong, this has worked for me as well but can you please explain more about this ? I mean why this is required? – Parashuram Jan 03 '21 at 07:12
  • @jrh Perhaps the behavior was different for earlier versions of python but for 3.8, an unconfigured root logger will have 0 handlers by default (check by printing `logging.root.handlers` -- it will be an empty list). Calling `logging.basicConfig()` adds a `StreamHandler`. So, the clearing of handlers is redundant. – Rimov Jan 24 '22 at 22:16
  • @Rimov can confirm it's empty in Linux with Python 3.8 (inside or outside of Spyder), I do remember it not being empty when I tried it a few years ago. Unfortunately I don't remember what version of Python that was, or what platform, probably Windows, maybe Python 2.7, maybe Spyder. – jrh Jan 24 '22 at 22:36
7

import logging
log = logging.getLogger()
log.setLevel(logging.DEBUG)

this code will set the default logging level to DEBUG.

Cjo
  • 1,265
  • 13
  • 19
  • 5
    No, it will not. – dc914337 Oct 27 '21 at 11:17
  • 2
    I'm not sure why this answer has been marked down, as it works. In fact, it's the way that AWS document the usage of `logging` within a Lambda - https://docs.aws.amazon.com/lambda/latest/dg/python-logging.html#python-logging-lib – David Gard Feb 23 '22 at 11:00
  • This won't work as last resort (https://docs.python.org/3/library/logging.html#logging.lastResort) handler will be invoked, effectively using WARNING level. – somenickname Mar 12 '22 at 09:04
  • this solution is the correct and simple one for my problem. 2022-07-26 – MIAWANG Jul 26 '22 at 06:32
3

That simply works fine for me ...

import logging

LOGGER = logging.getLogger("my-fetcher")
logging.basicConfig(level=logging.INFO)

LOGGER.info("Established Connection Successfully!")
# > INFO:my-fetcher:Established Connection Successfully!
Marwan Mostafa
  • 403
  • 4
  • 12
0

Calling removeHandler() function leaves stdout/stderr output even though all handlers have been removed.

one way to cleanup a logger is to empty the list of handlers, i.e. logger.handlers = [] or logger.root.handlers = []

This worked for me.