Word of advice
Firstly, simple and complex (or basic and advanced) are relative terms. You could have just the root logger with a very complex logging configuration, would you call that simple logging, because you're using the root logger ? No. You shouldn't tie the semantics (meaning) of relative terms like basic and advanced to Python objects. The semantics of language constructs is denoted either by the computation they induce or by the effect they produce, which is always the same for everybody.
Lexicon
Secondly, let's clear up a few terms.
logging
is a Python module
.
basicConfig
& getLogger
are module level functions.
debug()
, info()
, warning()
, etc. are both module level functions and class methods, depending on how you call them. If you do logging.debug(msg)
you're calling a module level function, if you do some_logger.debug(msg)
you're calling a method. The module level function itself also calls the root method under the hood.
Flow of execution & Hierarchies
The root
logger is automatically created when you import the logging machinery, i.e when you do import logging
- the root
logger is automatically created which, in turn, enables you to do straightforward calls such as logging.debug()
, which use that root logger.
Basically, a module level function looks like this:
def debug(msg, *args, **kwargs):
"""
Log a message with severity 'DEBUG' on the root logger. If the logger has
no handlers, call basicConfig() to add a console handler with a pre-defined
format.
"""
if len(root.handlers) == 0:
basicConfig()
root.debug(msg, *args, **kwargs)
Loggers are organized in hierarchies, and all loggers are descendants of the root
logger.
When you do a call to getLogger(name)
if the name
exists it will return that logger
, if it doesn't, it will create that logger
. The getLogger(name)
function is idempotent, meaning, for subsequent calls with the same name it will just return that existing logger no matter how many times you call it.
The name is potentially a period-separated hierarchical value, like foo.bar.baz
. Loggers that are further down in the hierarchical list are children of loggers higher up in the list. For example, given a logger with a name of foo
, loggers with names of foo.bar
, foo.bar.baz
, and foo.bam
are all descendants of foo
.
When a logger is created, the level is set to NOTSET (which causes all messages to be delegated to the parent when the logger is a non-root logger). This 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.
Without going very deep in the details, here are the relevant links: logger objects, module level functions, flow of execution.
Your questions
In simple logging, we can configure the log path and msg format using
logging. basicConfig whereas in case of advanced logging we have the
concept of a formatter, handler which is assigned to the logger
obtained by using logging.getlogger(some_name).addhandlers..
No.
basicConfig, as we now know, is a module level function. This function sets up the basic configuration for your logging system and should be called before anything else, because if you do any kind of logging before calling that yourself, functions like debug()
, info()
, etc. will call basicConfig()
automatically if no handlers are defined for the root logger. This function is also idempotent, meaning once you call it once, you can call it a billion times after with no effect. But this call will determine how your logging will work for all loggers not just the root (because all loggers are connected through hierarchies) and pass messages from one to another, unless you specify explicit configuration for descendant loggers.
The path is where you want your log messages to be recorded, and this is set up via handlers and it can be the console, a file, an email, whatever... see a complete list here.
The format is how you want your messages to show, what kind of information you want them to contain, and that is done via formatters, where you provide the log record attributes you want. Those attributes determine which information a logrecord knows about.
But this all works together. Handlers
are attached to loggers
and formatters
are attached to handlers
. You can set these up one time per your entire application via basicConfig or dictConfig or fileConfig or you can set these up individually, per logger
.
So the only benefit of advanced logging is the possibility for us to
add the logger name either to a hardcoded value or to name which
is respective module value.
No.
More complex logging means that you can split your application into modules and have separate loggers
for each module, and have a very refined message system, where each part of the application logs different things (you'd want sensitive parts to log very specific information and maybe send them rapidly via email or log them to a file) whereas you'd want trivial parts to log lightly and just print them via console.
Can basicConfig only be used on root logger and handlers/formatter
only be used on namedloggers?
basicConfig
will set the configuration for the root
logger which in turn all loggers will use, unless otherwise specified.
Example
import logging
root = logging.getLogger()
print(root.handlers) # no handlers at this point
logging.warning('hello') # calls basicConfig
print(root.handlers) # has handler now
# create file handler
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
# add the handlers to the logger
root.addHandler(fh)
print(root.handlers) # now has 2 handlers
root.warning('whats good') # will only show to console
root.error('whats good') # will show to console and file
random_logger = logging.getLogger('bogus') # another logger, descendant from root
random_logger.warning('im random') # will use root handlers, meaning it will show to console
random_logger.error('im random error') # same as above, both console and file
# and you can ofc add handlers and what not differently to this non root logger