Updated On : Aug-14,2022 Time Investment : ~45 mins

logging: An In-Depth Guide to Logging in Python

> What is Logging in Computer Science?

In computer science, Logging is the process of recording events that have happened when the software or process was running.

The log can be an event of any type, from simple debug messages about function entries, informational messages about function calls/completions, warning messages about failures that can be avoided, error messages which caused the big failure, etc.

> What Are Benefits of Logging?

As the application gets big, it's recommended to log its events in case of failures or intermittent issues, these logs can help us detect the root cause. It can even help us with any performance improvement. It can help us better understand behavior of an application. It is useful for debugging purposes as well when solving an issue.

> How to Log Messages in Python? | How to Perform Logging in Python?

Python has a module named 'logging' which lets us log important messages and direct them to different destinations like standard output, files on disks, a socket on a network, mails, etc. It even lets us filter log messages based on importance at any point in time.

> What Can You Learn From This Article?

As a part of this tutorial, we have explained how to perform logging in Python using "logging" module. We have explained with simple and easy-to-understand examples how to log messages of different types (error, warning, info, debug, etc), how to format log messages, how to direct log messages to files, and how to filter log messages, etc.

From a high level, we have divided tutorial into two parts (sub-sections listed little below).

  1. Basic Logging
  2. Advanced Logging

> How to Utilize This Tutorial Effectively?

For small applications or scripts, the basic logging section provides enough information to log events. If you want to learn logging in Python faster then you can just go through that section and you'll be good. It explains a simple way of logging and formatting log messages. It should be enough in a majority of cases.

On the other hand, if your application is big with lots of sub-modules such that you have requirements like creating different types of loggers for different sub-modules, direct log messages to different destinations (file, network stream, mail, etc), filter log messages of different log levels, create custom log levels (other than default), create a hierarchy of loggers, etc. Then, we would recommend that you go through advanced logging section.

Below, we have listed important sections of Tutorial to give an overview of the material covered.

Important Sections Of Tutorial

  1. Basic Logging (Getting Started with Logging)
    • Important Methods of "logging" Module
    • What is Relation Between "Log Message" and "Log Level"?
    • 1 - How to Log Events?
    • 2 - Basic Example Explaining Default Logging Level (Warning)
    • 3 - How to Modify Default Log Level?
    • 4 - How to Log Messages to a File?
    • 5 - How to Format Log Messages?
    • 6 - How to Format Log Messages? (Cont.)
  2. Advanced Logging
    • What are Important Components of Logging?
    • How Log Messages are Handled by Loggers?
      • Simple Example Explaining Hierarchy Of Loggers
    • 1 - How to Create Logger to Log Messages to Standard Output?
    • 2 - How to Format Log Messages?
    • 3 - How Hierarchy of Loggers Work?
    • 4 - How to Create Custom New Log Level?
    • 5 - How to Direct Log Messages to Different Destinations (File, Stream, etc)?
    • 6 - How to Direct Different Types of Log Messages to Different Files?
    • 7 - How to Filter Log Messages?
    • 8 - How to Define More than One Filters to Filter Log Messages?
    • 9 - How to Perform Logging in Multiprocessing Environment?
    • 10 - How to Perform Logging in Multithreading Environment?

1. Basic Logging (Getting Started with Logging)

Important Methods of "logging" Module

As a part of this section, we'll explain how we can use methods of 'logging' module to log messages for small applications. We won't dive into creating loggers in this section. It'll be covered in the advanced logging section. If your application is small with few python scripts then this section should be enough to guide you to log events.

The logging module provides 5 important methods to log 5 different kinds of log messages.

  • debug(msg) - It helps us log messages which will only be used by the developers for debugging applications when failure happens.
  • info(msg) - It helps us log messages that will hold information about the normal running of the application like successful completion of functions, etc.
  • warning(msg) - It helps us log messages informing us that some kind of unexpected behavior or some problem has arisen but it’s not that high priority and can be handled later. It won't affect the normal run of applications.
  • error(msg) - It helps us log messages indicating some serious failure that might stop the application from running normally and needs urgent attention. It informs about the failure of some functionality of the application.
  • critical(msg) - It helps us log messages indicating that the application is faced with a fatal error and might not be able to continue normal operations. It'll need immediate attention.

The 'logging' module has internally assigned a numeric value to the criticality of log message type.

  • CRITICAL - 50
  • ERROR - 40
  • WARNING - 30
  • INFO - 20
  • DEBUG - 10
  • NOTSET - 0

These values are generally referred to as log levels.

What is Relation Between "Log Message" and "Log Level"?

The loggers generally display log messages whose log level matches or are above the set log level. The default log level set by 'logging' module is 'WARNING' hence it'll only print a warning, error, and critical messages by default.

We'll now explain with simple examples, how we can log messages with different log levels, how we can change the format of log messages getting displayed, etc.

1. How to Log Messages/Events?

As a part of our first example, we have explained how we can log messages to standard output.

We have designed a simple method named addition(a,b) which takes two parameter values, adds them and returns the result. We have kept the logic of converting arguments to float and performing summation in try-except block to catch exceptions and record them. We have added three different kinds of log messages in the function (DEBUG, INFO, and ERROR). We then call the addition function with different types of argument values to see log messages get printed.

We can notice from the output that it only printed the ERROR type of log message and did not print INFO and DEBUG log messages. The reason behind this is that logging module is set by default to print log messages with a log level of WARNING and above as explained earlier.

What is Default Format of Log Messages?

The default format of log message provided by logging is 'log_level:logger_name:log_message'. We can see that first, it has printed log level as ERROR, then the logger name as root, and at last logging message.

The logging module lets us create more than one logger and they follow a hierarchy like a directory system. The root logger of the hierarchy has the name of root generally. This is the reason all log messages have logger name as root by default. We'll explain in the later example how we can format log messages.

logging_example_1.py

import logging

def addition(a, b):
    logging.debug("Inside Addition Function")
    try:
        result = float(a) + float(b)
        logging.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logging.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    result = addition(10,20)
    print("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    print("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    print("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Addition of 10 & 20 is : 30.0

Addition of 20 & 20 is : 40.0

ERROR:root:Error Type : ValueError, Error Message : could not convert string to float: 'A'
Addition of A & 20 is : None

2. Basic Example Explaining Default Logging Level (Warning)

Our second example builds on our first example.

We have introduced two if statements in addition(a,b) which checks for type and contents of arguments.

If the arguments are string and have digits only then it'll log warning messages informing that this should not happen in the future because even though currently it works fine, in the future this won't work.

All the remaining code is exactly the same as our previous example.

When we run the script, we can see that it adds the warning message to the log. Our second call to the addition function passed 20 as a string which results in the logging warning message. We can notice in this example as well that log messages with a log level of WARNING and above got printed.

logging_example_2.py

import logging

def addition(a, b):
    logging.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logging.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logging.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logging.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logging.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    result = addition(10,20)
    print("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    print("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    print("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Addition of 10 & 20 is : 30.0

WARNING:root:Warning : Parameter A is passed as String. Future versions won't support it.
Addition of '20' & 20 is : 40.0

ERROR:root:Error Type : ValueError, Error Message : could not convert string to float: 'A'
Addition of A & 20 is : None

3. How to Modify Default Log Level of "logging" Module?

As a part of our third example, we have demonstrated how we can override the default log level (WARNING) set by logging.

The code of this example is exactly the same as our previous example with only the addition of two lines which are related to overriding the default log level set by logging and printing log level.

The logging module has a method named basicConfig() let us override default log settings like log level, log format, date format, log location, etc.

We have set the log level to DEBUG as a part of this example which will result in printing all log messages with level DEBUG and above.

When we run the script, we can notice from the output that it has not printed log messages of all levels.

Please make a note that by default log levels are internally represented by an integer value. We can give level value as an integer as well and all messages with the level that and above will be printed.

logging_example_3.py

import logging

logging.basicConfig(level=logging.DEBUG)

def addition(a, b):
    logging.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logging.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logging.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logging.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logging.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    print("Current Log Level : {}\n".format(logging.DEBUG))

    result = addition(10,20)
    print("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    print("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    print("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Current Log Level : 10

DEBUG:root:Inside Addition Function
INFO:root:Addition Function Completed Successfully
Addition of 10 & 20 is : 30.0

DEBUG:root:Inside Addition Function
WARNING:root:Warning : Parameter A is passed as String. Future versions won't support it.
INFO:root:Addition Function Completed Successfully
Addition of '20' & 20 is : 40.0

DEBUG:root:Inside Addition Function
ERROR:root:Error Type : ValueError, Error Message : could not convert string to float: 'A'
Addition of A & 20 is : None

4. How to Log Messages to a File?

As a part of our fourth example, we have demonstrated how we can log messages to a separate file.

Our code for this example is almost the same as the last example with the addition of few parameters in a call to basicConfig() method.

We have retrieved file name using file attribute of python removing .py extension. We have then given the filename ending with .log extension to filename parameter of basicConfig() method.

The filemode parameter sets the file mode that will be used with the logging file. The default value of parameter filemode is 'a' which will result in appending new log messages to existing log messages every time we run the application. We have set this parameter to 'w' so that it overwrites old log messages and keeps only newly generated log messages in a log file. The choice of this parameter is up to developers.

When we ran the script, we can notice from the output that all log messages are now removed from the standard output. We have displayed the contents of file logging_example_4.log where all log messages have moved.

logging_example_4.py

import logging

file_name = __file__.split(".")[0]

logging.basicConfig(filename="{}.log".format(file_name), filemode="w", level=logging.INFO)

def addition(a, b):
    logging.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logging.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logging.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logging.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logging.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    print("Current Log Level : {}\n".format(logging.INFO))

    result = addition(10,20)
    print("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    print("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    print("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Current Log Level : 10

Addition of 10 & 20 is : 30.0

Addition of '20' & 20 is : 40.0

Addition of A & 20 is : None

logging_example_4.log

INFO:root:Addition Function Completed Successfully
WARNING:root:Warning : Parameter A is passed as String. Future versions won't support it.
INFO:root:Addition Function Completed Successfully
ERROR:root:Error Type : ValueError, Error Message : could not convert string to float: 'A'

5. How to Format Log Messages?

As a part of our fifth example, we have demonstrated how we can format log messages and override default 'log_level:logger_name:log_message' log message format set by logging.

We have introduced two new parameters named format and datefmt in basicConfig() function which let us format log message and date in the log message. The format parameter accepts a python-format string in a format %(variable_name)s.

The logging module stores various logging related information like time, level name, module named, function name, line number in file, process & thread details, etc in a variable. We can format python string with these variable names to generate a log message and this format will be followed for each log message.

As a part of this example, we have divided the log message into two lines.

The first line of log message consists of log time, log level name, module name, function name, line number, process details, and thread details separated by a colon.

The second line of the log message has an actual log message. We have also provided a date format which we want to use to format date using datefmt parameter.

We can see a change in the default log message format when we run the script. Please notice time, module name, function name, line number, process, and thread details included in log messages.

The list of variables that we can include in the format string and their format is available at the below link.

logging_example_5.py

import logging

logging.basicConfig(format="%(asctime)s : %(levelname)s : %(module)s : %(funcName)s : %(lineno)d : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",
                    level=logging.INFO)

def addition(a, b):
    logging.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logging.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logging.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logging.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logging.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    print("Current Log Level : {}\n".format(logging.INFO))

    result = addition(10,20)
    print("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    print("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    print("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Current Log Level : 20

29-01-2021 05:21:59 : INFO : logging_example_5 : addition : 19 : (Process Details : (5418, MainProcess), Thread Details : (140096320595776, MainThread))
Log Message : Addition Function Completed Successfully
Addition of 10 & 20 is : 30.0

29-01-2021 05:21:59 : WARNING : logging_example_5 : addition : 12 : (Process Details : (5418, MainProcess), Thread Details : (140096320595776, MainThread))
Log Message : Warning : Parameter A is passed as String. Future versions won't support it.
29-01-2021 05:21:59 : INFO : logging_example_5 : addition : 19 : (Process Details : (5418, MainProcess), Thread Details : (140096320595776, MainThread))
Log Message : Addition Function Completed Successfully
Addition of '20' & 20 is : 40.0

29-01-2021 05:21:59 : ERROR : logging_example_5 : addition : 22 : (Process Details : (5418, MainProcess), Thread Details : (140096320595776, MainThread))
Log Message : Error Type : ValueError, Error Message : could not convert string to float: 'A'
Addition of A & 20 is : None

6. How to Format Log Messages? (Cont.)

Our sixth example is exactly the same as our previous example with two changes.

The first change is that we have moved the addition function to addition_operations.py file.

The second change is that we have changed the date format in our log message. All the remaining code is exactly the same.

The main reason to create this example was to explain how log messages work on applications with multiple files.

arithmetic_operations.py

import logging

def addition(a, b):
    logging.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logging.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logging.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logging.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logging.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None

logging_example_6.py

import logging
from arithmetic_operations import addition

logging.basicConfig(format="%(asctime)s : %(levelname)s : %(module)s : %(funcName)s : %(lineno)d : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s",
                    datefmt="%d-%B,%Y %I:%M:%S %p",
                    level=logging.INFO)


if __name__ == "__main__":
    print("Current Log Level : {}\n".format(logging.INFO))

    logging.info("Main Func Started.")

    result = addition(10,20)
    print("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    print("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    print("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Current Log Level : 20

29-January,2021 06:13:36 PM : INFO : logging_example_6 : <module> : 12 : (Process Details : (5978, MainProcess), Thread Details : (140219984185152, MainThread))
Log Message : Main Func Started.
29-January,2021 06:13:36 PM : INFO : arithmetic_operations : addition : 13 : (Process Details : (5978, MainProcess), Thread Details : (140219984185152, MainThread))
Log Message : Addition Function Completed Successfully
Addition of 10 & 20 is : 30.0

29-January,2021 06:13:36 PM : WARNING : arithmetic_operations : addition : 6 : (Process Details : (5978, MainProcess), Thread Details : (140219984185152, MainThread))
Log Message : Warning : Parameter A is passed as String. Future versions won't support it.
29-January,2021 06:13:36 PM : INFO : arithmetic_operations : addition : 13 : (Process Details : (5978, MainProcess), Thread Details : (140219984185152, MainThread))
Log Message : Addition Function Completed Successfully
Addition of '20' & 20 is : 40.0

29-January,2021 06:13:36 PM : ERROR : arithmetic_operations : addition : 16 : (Process Details : (5978, MainProcess), Thread Details : (140219984185152, MainThread))
Log Message : Error Type : ValueError, Error Message : could not convert string to float: 'A'
Addition of A & 20 is : None

2. Advanced Logging

In this section, we have introduced how we can create Logger objects which can be used to perform logging in complex applications with lots of modules. The Logger objects can be used to direct log messages to different destinations for different modules as well as messages can be formatted in different ways for different modules. It also lets us filter log messages which need to be ignored or modified.

What are Important Components of Logging?

Below is a list of components that coordinate with each other for performing logging in complex applications.

  1. loggers - These objects are created in different parts of the application for logging events.
  2. handlers - The handlers are objects which are responsible for directing log messages to different destinations. The handlers are attached to the logger based on which messages of that logger are directed to the destination specified by the handler. The logging.handlers module has a list of handler classes that can help us direct log messages to destinations like standard output, email, socket, file, etc.
  3. formatters - The formatter are objects which help us define the format of log messages. We need to attach a formatter instance to the handler to format messages handled by that handler. We can create an instance of logging.Formatter with formatting details and attach it to the handler.
  4. filters - The filters are objects which can be used to filter log messages and ignore them or modify them. We can create a filter by extending logging.Filter class and implementing filter() method.

How Log Messages are Handled by Loggers?

Generally, The logger is created with all specifications (handlers, formatters, and filters) at the top level of the module which is referred to as the root logger.

All the submodules just define logger instances of theirs with their name. The loggers which are created at the sub-module level are sub-loggers of our root logger.

The logger objects follow a hierarchy just like the python module which also follows a hierarchy separated by a dot. All messages created by the individual logger are propagated to all parent loggers of it up to the root logger.

As we have defined handlers, formatters, and filters at the root logger, it'll then handle log messages propagated to it by sub-loggers. We can define handlers, formatters, and filters at any sub-logger level as well if we want special handling of messages generated by that logger.

Simple Example Explaining Hierarchy Of Loggers

To explain it with a simple example, let's say that our application has modules laid out according to the below diagram.

A
                    |
            -----------------
            |               |
            B               C
            |               |
      ------------     -----------
      |          |     |         |
      D          E     F         G
                 |               |
           ----------        ----------
           |        |        |        |
           H        I        J        K

How to Define Hierarchy Of Loggers?

We'll define a single root logger in module A which will have handlers, formatters, and filters.

We'll then define sub loggers at all submodules (A.B, A.C, A.B.D, A.B.E, A.C.F, A.C.G, A.B.E.H, A.B.E.I, A.C.G.J, A.C.G.K). We won't define handlers, formatters, and filters for loggers created in these submodules. We'll create all sub-module loggers with a name hierarchy which is followed by module names.

We can give loggers the same name as their module name using dunder name attribute of the script which will retrieve the whole module name (module I will have value A.B.E.I for dunder name attribute). The dot tells logging module that a particular logger is a child logger of another logger.

If we want special handling of log messages at any sub-module level then we can define handlers, formatters, and filters for the logger attached to that module.

How Log Messages Are Handled in Case Of Hierarchical Loggers?

All log messages generated by all sub-module loggers will be directed all the way up to the root logger defined at module A which will handle them.

NOTE

Please make a NOTE if we define handlers, formatters, and filters at sub-module logger then the log messages will get handled by them and then will be directed to all parent loggers up to root logger. This can result in the same log message getting printed twice or more times based on the number of handlers who handled it. If we don't want to propagate log messages generated by any logger then we can set the propagate parameter to False when creating logger.

We'll now explain with simple examples how we can create components described above and make them coordinate with each other for logging events.

1. How to Create Logger to Log Messages to Standard Output?

As a part of our seventh example, we'll explain how we can create an instance of Logger to log events and an instance of StreamHandler to handle/direct log messages generated by Logger instance.


  • StreamHandler(stream=None) - This constructor creates an instance of StreamHandler which can be used to direct log messages to a particular stream. The stream can be io stream, standard output, a file-like object, etc. By default, it'll direct log messages to sys.stderr stream. We can set stream to sys.stdout if we want to direct to standard output.

Important Methods of StreamHandler

  • setLevel(level) - This method can help set level for handling messages. It can accept integers or strings which are predefined like INFO, DEBUG, etc.

Important Methods of Logging Instance

  • getLogger(name=None) - This method accepts name of the logger and returns an instance of Logger with that name.
  • setLevel(level) - This method can help set level for handling messages.
  • addHandler(handler) - This will accept handler instance as input and add that handler to the list of handlers for that logger. One logger can have more than one handler to direct output to different destinations.
  • getEffectiveLevel() - It returns an effective level of logging events for the logger.
  • debug(message) - This method works exactly like logging module method of the same name.
  • info(message) - This method works exactly like logging module method of the same name.
  • warning(message) - This method works exactly like logging module method of the same name.
  • error(message) - This method works exactly like logging module method of the same name.
  • log(log_level, message) - This method works exactly like logging module method of the same name.

Our code for this example starts by creating an instance of logger and handler.

We have first created a Logger instance using getLogger() method giving it module name as input. We have then set the logging level at DEBUG for the logger using setLevel() method so that all messages at DEBUG level and above will be logged.

Then we created an instance of StreamHandler and attached it to the Logger instance. We have also set the level at the handler.

The addition() method and the main part of the code have the same coding as our previous examples with the only change that we are calling logging methods like info(), debug(), warn() and error() on an instance of Logger instead. In all our previous examples, we had called these methods from logging module.

When we run the below script, we can notice that the output is just simply printing log messages. The output for this script is different from previous scripts in a way log messages are formatted. There is no formatting of log messages in this example. We'll introduce it in the next example.

logging_advanced_guide_1.py

import logging

################ Logger #################
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.DEBUG)
logger.addHandler(std_out)


def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

Current Log Level : 10

Inside Addition Function
Addition Function Completed Successfully
Addition of 10 & 20 is : 30.0

Inside Addition Function
Warning : Parameter A is passed as String. Future versions won't support it.
Addition Function Completed Successfully
Addition of '20' & 20 is : 40.0

Inside Addition Function
Error Type : ValueError, Error Message : could not convert string to float: 'A'
Addition of A & 20 is : None

2. How to Format Log Messages?

As a part of our eighth example, we are building on our previous example and adding log message formatting to it using Formatter() instance.


  • Foramtter(fmt=None,datefmt=None) - This constructor accepts message formatting string and date formatting string as input and creates an instance of Formatter which can then be added to the handler to format log messages formatted by that handler.

Important Methods of StreamHandler

  • setFormatter(formatter) - This method accepts Formatter instance as input and sets it as formatter for that handler.

Our code for this example is exactly the same as our code from the previous example with the addition of lines to create and set formatter. We have created an instance of Formatter with formatting strings which we had used in our examples of the basic guide section above.

When we run the below script, the output will have all information specified in formatting strings like time, level name, module name, function name, line number, process details, thread details and log message itself. The output is the same as our previous example but with these extra details which can be useful in debugging in case of failures.

logging_advanced_guide_2.py

import logging

################ Logger #################
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.DEBUG)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(module)s : %(funcName)s : %(lineno)d : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)


std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.

def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:41:06 : INFO : logging_advanced_guide_2 : <module> : 37 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Current Log Level : 10

15-03-2021 04:41:06 : DEBUG : logging_advanced_guide_2 : addition : 20 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Inside Addition Function
15-03-2021 04:41:06 : INFO : logging_advanced_guide_2 : addition : 29 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Addition Function Completed Successfully
15-03-2021 04:41:06 : INFO : logging_advanced_guide_2 : <module> : 40 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Addition of 10 & 20 is : 30.0

15-03-2021 04:41:06 : DEBUG : logging_advanced_guide_2 : addition : 20 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Inside Addition Function
15-03-2021 04:41:06 : WARNING : logging_advanced_guide_2 : addition : 22 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:41:06 : INFO : logging_advanced_guide_2 : addition : 29 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Addition Function Completed Successfully
15-03-2021 04:41:06 : INFO : logging_advanced_guide_2 : <module> : 43 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Addition of '20' & 20 is : 40.0

15-03-2021 04:41:06 : DEBUG : logging_advanced_guide_2 : addition : 20 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Inside Addition Function
15-03-2021 04:41:06 : ERROR : logging_advanced_guide_2 : addition : 32 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:41:06 : INFO : logging_advanced_guide_2 : <module> : 46 : (Process Details : (18777, MainProcess), Thread Details : (140132340528960, MainThread))
Log : Addition of A & 20 is : None

3. How Hierarchy of Loggers Work?

As a part of our ninth example, we are demonstrating how the hierarchy works in loggers. We have created a single root logger and then one sub-logger under it to explain the usage of hierarchy.

3.1

Our code for this example starts by creating Logger instance, creates a stream handler for it, attaches formatter to the handler, and attaches a handler to the logger. This logger instance will be our root logger. We have given it name arithmetic_ops. The root logger is set at INFO level.

We have then created a class named Addition. We have created a new logger inside of it named arithmetic_ops.Addition. This logger will be sub logger of our root logger (arithmetic_ops). We have created a handler and formatter for this sub-logger as well. This sub-logger will pass all messages logged by it to its parent loggers as we had highlighted in our theories earlier. The sub-logger is set at DEBUG level.

The logic which we had been using inside of addition() method in our previous examples has now been moved to add() method of Addition class. We are using a sub logger created inside of Addition instance to log messages inside of add() method.

The main part of the code is the same as our previous examples with the only change is that instead of calling addition() method, we are calling add() method on an instance of Addition. We are using a root logger in this main part to log messages.

When we run the below script, we can notice that messages which are INFO and above log level are getting printed twice (Once by sub logger and one by root logger). This is happening because we have attached a handler to a sub logger as well which is handling log messages and then passing them to its parent logger which also has a handler to handle them. We have explained in our next example, how we can avoid repeat log messages.

logging_advanced_guide_3_1.py

import logging

################ Module Logger #################
logger = logging.getLogger("arithmetic_ops")
logger.setLevel(logging.INFO)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.INFO)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S")
std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.    

class Addition:
    def __init__(self):
        ################ Class Logger #################
        self.logger = logging.getLogger("arithmetic_ops.Addition")
        #self.logger.propagate=False
        self.logger.setLevel(logging.DEBUG)

        ############## Handler ####################
        std_out = logging.StreamHandler()
        std_out.setLevel(logging.DEBUG)

        ############## Formatter ####################
        formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S",)


        std_out.setFormatter(formatter) ### Register Formatter with Handler.
        self.logger.addHandler(std_out)      ### Register Handler with Logger.    

    def add(self, a,b):
        self.logger.debug("Inside Addition Function")
        if isinstance(a, str) and a.isdigit():
            self.logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

        if isinstance(b, str) and b.isdigit():
            self.logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

        try:
            result = float(a) + float(b)
            self.logger.info("Addition Function Completed Successfully")
            return result
        except Exception as e:
            self.logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
            return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    addition = Addition()
    result = addition.add(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition.add("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition.add("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:42:08 : INFO : arithmetic_ops : <module> : Current Log Level : 20

15-03-2021 04:42:08 : DEBUG : arithmetic_ops.Addition : add : Inside Addition Function
15-03-2021 04:42:08 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:42:08 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:42:08 : INFO : arithmetic_ops : <module> : Addition of 10 & 20 is : 30.0

15-03-2021 04:42:08 : DEBUG : arithmetic_ops.Addition : add : Inside Addition Function
15-03-2021 04:42:08 : WARNING : arithmetic_ops.Addition : add : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:42:08 : WARNING : arithmetic_ops.Addition : add : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:42:08 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:42:08 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:42:08 : INFO : arithmetic_ops : <module> : Addition of '20' & 20 is : 40.0

15-03-2021 04:42:08 : DEBUG : arithmetic_ops.Addition : add : Inside Addition Function
15-03-2021 04:42:08 : ERROR : arithmetic_ops.Addition : add : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:42:08 : ERROR : arithmetic_ops.Addition : add : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:42:08 : INFO : arithmetic_ops : <module> : Addition of A & 20 is : None

3.2

Our code for this example is exactly the same as our code from the previous example with the addition of one line inside of Addition class. We have set propagate attribute of sub logger inside of Addition class to False. This will prevent the sub-logger from propagating messages to its parent logger.

When we run the below script, we can notice that repeat log messages are gone now.

logging_advanced_guide_3_2.py

import logging

################ Module Logger #################
logger = logging.getLogger("arithmetic_ops")
logger.setLevel(logging.INFO)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.INFO)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S")
std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.    

class Addition:
    def __init__(self):
        ################ Class Logger #################
        self.logger = logging.getLogger("arithmetic_ops.Addition")
        self.logger.propagate=False
        self.logger.setLevel(logging.DEBUG)

        ############## Handler ####################
        std_out = logging.StreamHandler()
        std_out.setLevel(logging.DEBUG)

        ############## Formatter ####################
        formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S",)


        std_out.setFormatter(formatter) ### Register Formatter with Handler.
        self.logger.addHandler(std_out)      ### Register Handler with Logger.    

    def add(self, a,b):
        self.logger.debug("Inside Addition Function")
        if isinstance(a, str) and a.isdigit():
            self.logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

        if isinstance(b, str) and b.isdigit():
            self.logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

        try:
            result = float(a) + float(b)
            self.logger.info("Addition Function Completed Successfully")
            return result
        except Exception as e:
            self.logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
            return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    addition = Addition()
    result = addition.add(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition.add("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition.add("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:43:34 : INFO : arithmetic_ops : <module> : Current Log Level : 20

15-03-2021 04:43:34 : DEBUG : arithmetic_ops.Addition : add : Inside Addition Function
15-03-2021 04:43:34 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:43:34 : INFO : arithmetic_ops : <module> : Addition of 10 & 20 is : 30.0

15-03-2021 04:43:34 : DEBUG : arithmetic_ops.Addition : add : Inside Addition Function
15-03-2021 04:43:34 : WARNING : arithmetic_ops.Addition : add : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:43:34 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:43:34 : INFO : arithmetic_ops : <module> : Addition of '20' & 20 is : 40.0

15-03-2021 04:43:34 : DEBUG : arithmetic_ops.Addition : add : Inside Addition Function
15-03-2021 04:43:34 : ERROR : arithmetic_ops.Addition : add : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:43:34 : INFO : arithmetic_ops : <module> : Addition of A & 20 is : None

3.3

Our code for this example is exactly the same as our code from the previous example with two lines commented inside the definition of Addition class. We have commented lines that set the log level for the sub-logger and its handler. This will force the sub-logger to be set at the same logging level as that of its parent logger. The parent logger is set at INFO level hence sub logger will also log messages which are INFO and above.

When we run the below script, we can notice from the output that DEBUG log messages are no longer present.

logging_advanced_guide_3_3.py

import logging

################ Module Logger #################
logger = logging.getLogger("arithmetic_ops")
logger.setLevel(logging.INFO)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.INFO)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S")
std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.    

class Addition:
    def __init__(self):
        ################ Class Logger #################
        self.logger = logging.getLogger("arithmetic_ops.Addition")
        self.logger.propagate=False
        #self.logger.setLevel(logging.DEBUG)   ### Will Take Level from Parent. 

        ############## Handler ####################
        std_out = logging.StreamHandler()
        #std_out.setLevel(logging.DEBUG)      ### Will Take Level from Parent. 

        ############## Formatter ####################
        formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S",)


        std_out.setFormatter(formatter) ### Register Formatter with Handler.
        self.logger.addHandler(std_out)      ### Register Handler with Logger.    

    def add(self, a,b):
        self.logger.debug("Inside Addition Function")
        if isinstance(a, str) and a.isdigit():
            self.logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

        if isinstance(b, str) and b.isdigit():
            self.logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

        try:
            result = float(a) + float(b)
            self.logger.info("Addition Function Completed Successfully")
            return result
        except Exception as e:
            self.logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
            return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    addition = Addition()
    result = addition.add(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition.add("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition.add("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:45:34 : INFO : arithmetic_ops : <module> : Current Log Level : 20

15-03-2021 04:45:34 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:45:34 : INFO : arithmetic_ops : <module> : Addition of 10 & 20 is : 30.0

15-03-2021 04:45:34 : WARNING : arithmetic_ops.Addition : add : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:45:34 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:45:34 : INFO : arithmetic_ops : <module> : Addition of '20' & 20 is : 40.0

15-03-2021 04:45:34 : ERROR : arithmetic_ops.Addition : add : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:45:34 : INFO : arithmetic_ops : <module> : Addition of A & 20 is : None

3.4

Our code for this example is exactly the same as our code for previous examples with few lines removed from the definition of Addition class. This time we have just defined sub logger inside of Addition class. We have removed all handler and formatter information from it. This will force the sub-logger to route all the messages to its parent logger which has a handler and formatter for handling log messages. This is how generally logging is implemented in big projects. We define handlers, formatters, and filters only at the root logger once in the module. All sub loggers will be relaying messages to their parent loggers all the way up to this root logger which will then handle it.

When we run the below script, we can notice that the output is exactly the same as our previous example.

logging_advanced_guide_3_4.py

import logging

################ Module Logger #################
logger = logging.getLogger("arithmetic_ops")
logger.setLevel(logging.INFO)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.INFO)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(name)s : %(funcName)s : %(message)s",
                            datefmt="%d-%m-%Y %I:%M:%S")
std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.    

class Addition:
    def __init__(self):
        ################ Class Logger #################
        self.logger = logging.getLogger("arithmetic_ops.Addition")

    def add(self, a,b):
        self.logger.debug("Inside Addition Function")
        if isinstance(a, str) and a.isdigit():
            self.logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

        if isinstance(b, str) and b.isdigit():
            self.logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

        try:
            result = float(a) + float(b)
            self.logger.info("Addition Function Completed Successfully")
            return result
        except Exception as e:
            self.logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
            return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    addition = Addition()
    result = addition.add(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition.add("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition.add("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:46:17 : INFO : arithmetic_ops : <module> : Current Log Level : 20

15-03-2021 04:46:17 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:46:17 : INFO : arithmetic_ops : <module> : Addition of 10 & 20 is : 30.0

15-03-2021 04:46:17 : WARNING : arithmetic_ops.Addition : add : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:46:17 : INFO : arithmetic_ops.Addition : add : Addition Function Completed Successfully
15-03-2021 04:46:17 : INFO : arithmetic_ops : <module> : Addition of '20' & 20 is : 40.0

15-03-2021 04:46:17 : ERROR : arithmetic_ops.Addition : add : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:46:17 : INFO : arithmetic_ops : <module> : Addition of A & 20 is : None

4. How to Create Custom New Log Level?

As a part of our tenth example, we are demonstrating how we can attach a name to log level and how we can log messages using log() method of Logger instance.


  • addLevelName(log_level, name) - This method attaches name given as input to the log level given as input. All log messages then logged at that level will print the name defined through this method for the level name.

Our code for this example is exactly the same as our code for example 8 with few minor modifications. We have defined a new log level named INFORMATION for messages logged at level 15. Apart from this, we have logged messages using log() method of Logger instance this time instead.

logging_advanced_guide_4.py

import logging

################ Logger #################
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.DEBUG)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(module)s : %(funcName)s : %(lineno)d : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)

std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.

######## Define Logging Level Name ######
logging.addLevelName(15, "INFORMATION")

def addition(a, b):
    logger.log(10, "Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.log(30, "Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.log(30, "Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.log(15, "Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.log(40, "Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:47:51 : INFO : logging_advanced_guide_4 : <module> : 39 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Current Log Level : 10

15-03-2021 04:47:51 : DEBUG : logging_advanced_guide_4 : addition : 22 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Inside Addition Function
15-03-2021 04:47:51 : INFORMATION : logging_advanced_guide_4 : addition : 31 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Addition Function Completed Successfully
15-03-2021 04:47:51 : INFO : logging_advanced_guide_4 : <module> : 42 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Addition of 10 & 20 is : 30.0

15-03-2021 04:47:51 : DEBUG : logging_advanced_guide_4 : addition : 22 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Inside Addition Function
15-03-2021 04:47:51 : WARNING : logging_advanced_guide_4 : addition : 24 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:47:51 : INFORMATION : logging_advanced_guide_4 : addition : 31 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Addition Function Completed Successfully
15-03-2021 04:47:51 : INFO : logging_advanced_guide_4 : <module> : 45 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Addition of '20' & 20 is : 40.0

15-03-2021 04:47:51 : DEBUG : logging_advanced_guide_4 : addition : 22 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Inside Addition Function
15-03-2021 04:47:51 : ERROR : logging_advanced_guide_4 : addition : 34 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:47:51 : INFO : logging_advanced_guide_4 : <module> : 48 : (Process Details : (7093, MainProcess), Thread Details : (140435863553856, MainThread))
Log : Addition of A & 20 is : None

5. How to Direct Log Messages to Different Destinations (File, Stream, etc)?

As a part of our eleventh example, we are demonstrating how we can direct log messages to different destinations using different handlers attached to the same logger instance.


  • FileHandler(filename, mode='a') - This constructor takes as input file name and creates an instance of FileHandler which will then be used to direct log messages to the file by the logger to which it's attached.

The majority of our code for this example is the same as our code for example 8 with the addition of lines related to the file handler. We have defined a file handler with a file named Main.log. We have set the log level of the file handler to WARNING so only log messages with a level warning and above will be handled by it. We have attached this handler as well to our logger. This will make a warning and the above messages are stored in Main.log file.

When we run the below code the output is exactly the same as our output of example 8. We have also displayed the content of Main.log file which has log messages with level WARNING and above.

logging_advanced_guide_5.py

import logging

################ Logger #################
logger = logging.getLogger("Main")
logger.setLevel(logging.DEBUG)

################ Handler 1 #################
std_out = logging.StreamHandler()
std_out.setLevel(logging.DEBUG)
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(module)s : %(funcName)s : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)
std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.

################ Handler 2 #################
file_handler = logging.FileHandler("{}.log".format("Main"))
file_handler.setLevel(logging.WARNING)
formatter = logging.Formatter(fmt="%(asctime)s : %(name)s : %(levelname)s : %(funcName)s : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)
file_handler.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(file_handler)      ### Register Handler with Logger.

def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.setLevel(1)
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

15-03-2021 04:49:01 : INFO : logging_advanced_guide_5 : <module> : Current Log Level : 1

15-03-2021 04:49:01 : DEBUG : logging_advanced_guide_5 : addition : Inside Addition Function
15-03-2021 04:49:01 : INFO : logging_advanced_guide_5 : addition : Addition Function Completed Successfully
15-03-2021 04:49:01 : INFO : logging_advanced_guide_5 : <module> : Addition of 10 & 20 is : 30.0

15-03-2021 04:49:01 : DEBUG : logging_advanced_guide_5 : addition : Inside Addition Function
15-03-2021 04:49:01 : WARNING : logging_advanced_guide_5 : addition : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:49:01 : INFO : logging_advanced_guide_5 : addition : Addition Function Completed Successfully
15-03-2021 04:49:01 : INFO : logging_advanced_guide_5 : <module> : Addition of '20' & 20 is : 40.0

15-03-2021 04:49:01 : DEBUG : logging_advanced_guide_5 : addition : Inside Addition Function
15-03-2021 04:49:01 : ERROR : logging_advanced_guide_5 : addition : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:49:01 : INFO : logging_advanced_guide_5 : <module> : Addition of A & 20 is : None
!cat logging_advanced_guide/Main.log
15-03-2021 04:52:04 : Main : WARNING : addition : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:52:04 : Main : ERROR : addition : Error Type : ValueError, Error Message : could not convert string to float: 'A'

6. How to Direct Different Types of Log Messages to Different Files?

As a part of our twelfth example, we are again demonstrating how we can use more than one file handler to direct output to different destinations.

Our code for this example has created three file handlers.

The first file handler logs all messages at level DEBUG and above to a file named all.log.

The second file handler logs all messages at level WARNING and above to a file named warnings.log.

The third file handler logs all messages at level ERROR and above to a file named errors.log.

The rest of our code is exactly the same as our previous example. We are not directing log messages to standard output this time hence when we run this script, there won't be any output because all log messages are directed to different files.

When we run the below script, three different log files will be created, and log messages will be directed to them based on the log level of the handlers. We have displayed the contents of each file below.

logging_advanced_guide_6.py

import logging

################ Logger #################
logger = logging.getLogger("Main")
logger.setLevel(logging.DEBUG)

#### Handler 1 : All Messages #############
all_messages = logging.FileHandler("{}.log".format("all"))
all_messages.setLevel(logging.DEBUG)
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(module)s : %(funcName)s : %(lineno)d : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)
all_messages.setFormatter(formatter)
logger.addHandler(all_messages)

#### Handler 2 : Warnings and Above Messages #############
warn_n_above = logging.FileHandler("{}.log".format("warnings"))
warn_n_above.setLevel(logging.WARNING)
formatter = logging.Formatter(fmt="%(asctime)s : %(name)s : %(levelname)s : %(funcName)s : %(lineno)d : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)
warn_n_above.setFormatter(formatter)
logger.addHandler(warn_n_above)

#### Handler 3 : Errors and Above Messages #############
error_n_above = logging.FileHandler("{}.log".format("errors"))
error_n_above.setLevel(logging.ERROR)
formatter = logging.Formatter(fmt="%(asctime)s : %(name)s : %(levelname)s : %(funcName)s : %(lineno)d : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S",)
error_n_above.setFormatter(formatter)
logger.addHandler(error_n_above)

def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.setLevel(1)
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

!cat logging_advanced_guide/all.log
15-03-2021 04:50:45 : INFO : logging_advanced_guide_6 : <module> : 50 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Current Log Level : 1

15-03-2021 04:50:45 : DEBUG : logging_advanced_guide_6 : addition : 32 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Inside Addition Function
15-03-2021 04:50:45 : INFO : logging_advanced_guide_6 : addition : 41 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Addition Function Completed Successfully
15-03-2021 04:50:45 : INFO : logging_advanced_guide_6 : <module> : 53 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Addition of 10 & 20 is : 30.0

15-03-2021 04:50:45 : DEBUG : logging_advanced_guide_6 : addition : 32 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Inside Addition Function
15-03-2021 04:50:45 : WARNING : logging_advanced_guide_6 : addition : 34 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:50:45 : INFO : logging_advanced_guide_6 : addition : 41 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Addition Function Completed Successfully
15-03-2021 04:50:45 : INFO : logging_advanced_guide_6 : <module> : 56 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Addition of '20' & 20 is : 40.0

15-03-2021 04:50:45 : DEBUG : logging_advanced_guide_6 : addition : 32 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Inside Addition Function
15-03-2021 04:50:45 : ERROR : logging_advanced_guide_6 : addition : 44 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:50:45 : INFO : logging_advanced_guide_6 : <module> : 59 : (Process Details : (15590, MainProcess), Thread Details : (139688581674816, MainThread))
Log : Addition of A & 20 is : None
!cat logging_advanced_guide/errors.log
15-03-2021 04:50:45 : Main : ERROR : addition : 44 : Error Type : ValueError, Error Message : could not convert string to float: 'A'
!cat logging_advanced_guide/warnings.log
15-03-2021 04:50:45 : Main : WARNING : addition : 34 : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:50:45 : Main : ERROR : addition : 44 : Error Type : ValueError, Error Message : could not convert string to float: 'A'

7. How to Filter Log Messages?

As a part of our thirteenth example, we'll demonstrate how we can create filters to filter/modify log messages.

We have created a filter by defining a class named InfoAnWarningOnly by extending logging.Filter class. We have implemented filter() method of logging.Filter class takes as input instance of LogRecord and returns True if we want to keep that log messages else False. Inside of filter() method, we have a condition which returns True for messages with logging level between 10-31 (INFO and WARNING) and False for all other messages. This will filter all messages except INFO and WARNING.

We have then another condition that checks for a log level of 30 (WARNING) and changes the log message for that level by adding the string Error : in front of it. We have done this to indicate that warnings messages are now error messages. We have attached this filter to our logger using addFilter() method.

Our rest of the code is almost same as our previous examples.

When we run the below script, we can notice that only log messages with level INFO and WARNING are logged. All other messages are filtered. We can also notice from warning messages that Error : string is attached to them.

logging_advanced_guide_7.py

import logging

################ Logger #################
logger = logging.getLogger("Main")
logger.setLevel(logging.DEBUG)

####### Handler 1 : Info & Debug Messages ############
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(fmt="%(levelname)s : %(module)s : %(funcName)s : %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

class InfoAnWarningOnly(logging.Filter):
    def filter(self, record):
        if record.levelno >10 and record.levelno < 31:
            if record.levelno == 30: ## Updating Warning messages to Error.
                record.levelno = 40
                record.msg = "Error : " + record.msg
            return True
        else:
            return False

logger.addFilter(InfoAnWarningOnly()) ## Filter at Logger Level

def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.setLevel(1)
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

INFO : logging_advanced_guide_7 : <module> : Current Log Level : 1

INFO : logging_advanced_guide_7 : addition : Addition Function Completed Successfully
INFO : logging_advanced_guide_7 : <module> : Addition of 10 & 20 is : 30.0

WARNING : logging_advanced_guide_7 : addition : Error : Warning : Parameter A is passed as String. Future versions won't support it.
INFO : logging_advanced_guide_7 : addition : Addition Function Completed Successfully
INFO : logging_advanced_guide_7 : <module> : Addition of '20' & 20 is : 40.0

INFO : logging_advanced_guide_7 : <module> : Addition of A & 20 is : None

8. How to Define More than One Filters to Filter Log Messages?

As a part of our fourteenth example, we are demonstrating how we can define more than one filter and attach different filters to different handlers.

Our code for this example has created 3 different handlers.

  • debug_info_messages - This handler directs log messages to standard output. It has a filter named DebugAndInfoOnly attached to it which filters all messages except INFO and WARNING.
  • warnings_only - This handler directs log messages to a file named warnings_only.log. It has a filter named WarningsOnly attached to it which filters all log messages except WARNING.
  • errors_only - This handler directs log messages to a file named errors_only.log. It has a filter named ErrorsOnly attached to it which filters all log messages except ERROR.

The rest of our code is the same as our previous examples which we have been reusing.

When we run the below script, we can notice that only INFO and WARNING messages are printed to standard output. The WARNING log messages are logged in warnings_only.log file. The ERROR messages are logged in errors_only.log file. We have displayed the contents of warnings_only.log and errors_only.log file as well below.

logging_advanced_guide_8.py

import logging

################ Logger #################
logger = logging.getLogger("Main")
logger.setLevel(logging.DEBUG)

####### Handler 1 : Info & Debug Messages ############
debug_info_messages = logging.StreamHandler()
debug_info_messages.setLevel(logging.DEBUG)
formatter = logging.Formatter(fmt="%(levelname)s : %(module)s : %(funcName)s : %(lineno)d : %(message)s")
debug_info_messages.setFormatter(formatter)
logger.addHandler(debug_info_messages)

class DebugAndInfoOnly(logging.Filter): ### Filters all messages except Info and Debug
    def filter(self, record):
        if record.levelno < 21:
            return True
        else:
            return False

debug_info_messages.addFilter(DebugAndInfoOnly()) ## Filter at Handler Level

#### Handler 2 : Warnings Messages #############
warnings_only = logging.FileHandler("warnings_only.log")
warnings_only.setLevel(logging.DEBUG)
formatter = logging.Formatter(fmt="%(levelname)s : %(module)s : %(funcName)s : %(lineno)d : %(message)s")
warnings_only.setFormatter(formatter)
logger.addHandler(warnings_only)

class WarningsOnly(logging.Filter): ### Filters all messages except Warning
    def filter(self, record):
        if record.levelno > 20 and record.levelno < 40:
            return True
        else:
            return False

warnings_only.addFilter(WarningsOnly())      ## Filter at Handler Level

###### Handler 3 : Errors Only ###############
errors_only = logging.FileHandler("errors_only.log")
errors_only.setLevel(logging.DEBUG)
formatter = logging.Formatter(fmt="%(levelname)s : %(module)s : %(funcName)s : %(lineno)d : %(message)s")
errors_only.setFormatter(formatter)
logger.addHandler(errors_only)

class ErrorsOnly(logging.Filter): ### Filters all messages except Error
    def filter(self, record):
        if record.levelno > 30 and record.levelno < 50:
            return True
        else:
            return False

errors_only.addFilter(ErrorsOnly())     ## Filter at Handler Level


def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        return result
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        return None


if __name__ == "__main__":
    logger.setLevel(1)
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    result = addition(10,20)
    logger.info("Addition of {} & {} is : {}\n".format(10,20, result))

    result = addition("20",20)
    logger.info("Addition of {} & {} is : {}\n".format("'20'",20, result))

    result = addition("A",20)
    logger.info("Addition of {} & {} is : {}".format("A",20, result))

OUTPUT

INFO : logging_advanced_guide_8 : <module> : 75 : Current Log Level : 1

DEBUG : logging_advanced_guide_8 : addition : 57 : Inside Addition Function
INFO : logging_advanced_guide_8 : addition : 66 : Addition Function Completed Successfully
INFO : logging_advanced_guide_8 : <module> : 78 : Addition of 10 & 20 is : 30.0

DEBUG : logging_advanced_guide_8 : addition : 57 : Inside Addition Function
INFO : logging_advanced_guide_8 : addition : 66 : Addition Function Completed Successfully
INFO : logging_advanced_guide_8 : <module> : 81 : Addition of '20' & 20 is : 40.0

DEBUG : logging_advanced_guide_8 : addition : 57 : Inside Addition Function
INFO : logging_advanced_guide_8 : <module> : 84 : Addition of A & 20 is : None
!cat logging_advanced_guide/warnings_only.log
WARNING : logging_advanced_guide_8 : addition : 59 : Warning : Parameter A is passed as String. Future versions won't support it.
!cat logging_advanced_guide/errors_only.log
ERROR : logging_advanced_guide_8 : addition : 69 : Error Type : ValueError, Error Message : could not convert string to float: 'A'

9. How to Perform Logging in "Multiprocessing" Environment?

As a part of our fifteenth example, we are demonstrating how logging can be used in multiprocessing environments to log messages from different processes.

Our code for this example starts by creating a logger instance and attaching a stream handler to it. We have also modified the formatter to include information about process id and process name so that we can know which process has logged the message. Our definition of addition method is the same as the one from our previous examples.

The main part of our code now creates three processes each of which executes addition() method with different parameter settings. We have also made the main process wait for all three sub-processes to complete.

We have used multiprocessing module to create processes. If you are interested in learning about it then please feel free to check our tutorial on the same.

When we run the below script, we can notice from the output log messages logged by different processes based on their id and name.

logging_advanced_guide_9.py

import logging
import multiprocessing

################ Logger #################
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.DEBUG)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : (Process Details : (%(process)d, %(processName)s)) : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S")

std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.

def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        logger.info("Addition of {} & {} is : {}\n".format(a,b, result))
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        logger.info("Addition of {} & {} is : {}\n".format(a,b, None))


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    p1 = multiprocessing.Process(target=addition, args=(10,20), name="Addition1")
    p1.start()

    p2 = multiprocessing.Process(target=addition, args=("20",20), name="Addition2")
    p2.start()

    p3 = multiprocessing.Process(target=addition, args=("A",20), name="Addition3")
    p3.start()

    p1.join()
    p2.join()
    p3.join()

OUTPUT

15-03-2021 04:55:48 : INFO : (Process Details : (29932, MainProcess)) : Current Log Level : 10

15-03-2021 04:55:48 : DEBUG : (Process Details : (29937, Addition1)) : Inside Addition Function
15-03-2021 04:55:48 : INFO : (Process Details : (29937, Addition1)) : Addition Function Completed Successfully
15-03-2021 04:55:48 : INFO : (Process Details : (29937, Addition1)) : Addition of 10 & 20 is : 30.0

15-03-2021 04:55:48 : DEBUG : (Process Details : (29938, Addition2)) : Inside Addition Function
15-03-2021 04:55:48 : WARNING : (Process Details : (29938, Addition2)) : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:55:48 : INFO : (Process Details : (29938, Addition2)) : Addition Function Completed Successfully
15-03-2021 04:55:48 : INFO : (Process Details : (29938, Addition2)) : Addition of 20 & 20 is : 40.0

15-03-2021 04:55:48 : DEBUG : (Process Details : (29939, Addition3)) : Inside Addition Function
15-03-2021 04:55:48 : ERROR : (Process Details : (29939, Addition3)) : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:55:48 : INFO : (Process Details : (29939, Addition3)) : Addition of A & 20 is : None

10. How to Perform Logging in "Multithreading" Environment?

As a part of our sixteenth and last example, we'll demonstrate how we can log messages in a multithreading environment.

Our code for this example is almost the same as our code for the previous example with only changes that we are using threads instead of processes this time. We have modified the log message format to include thread details (id and name).

We have used threading module to create threads. If you are interested in learning about threading then please feel free to check our tutorial on the same.

When we run the below script, we can notice from the output which thread has logged which log message based on thread details included in the log message.

logging_advanced_guide_10.py

import logging
import threading

################ Logger #################
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

############## Handler ####################
std_out = logging.StreamHandler()
std_out.setLevel(logging.DEBUG)

############## Formatter ####################
formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : ((Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s)) : %(message)s",
                    datefmt="%d-%m-%Y %I:%M:%S")

std_out.setFormatter(formatter) ### Register Formatter with Handler.
logger.addHandler(std_out)      ### Register Handler with Logger.

def addition(a, b):
    logger.debug("Inside Addition Function")
    if isinstance(a, str) and a.isdigit():
        logger.warning("Warning : Parameter A is passed as String. Future versions won't support it.")

    if isinstance(b, str) and b.isdigit():
        logger.warning("Warning : Parameter B is passed as String. Future versions won't support it.")

    try:
        result = float(a) + float(b)
        logger.info("Addition Function Completed Successfully")
        logger.info("Addition of {} & {} is : {}\n".format(a,b, result))
    except Exception as e:
        logger.error("Error Type : {}, Error Message : {}".format(type(e).__name__, e))
        logger.info("Addition of {} & {} is : {}\n".format(a,b, None))


if __name__ == "__main__":
    logger.info("Current Log Level : {}\n".format(logger.getEffectiveLevel()))

    t1 = threading.Thread(target=addition, args=(10,20), name="Addition1")
    t1.start()

    t2 = threading.Thread(target=addition, args=("20",20), name="Addition2")
    t2.start()

    t3 = threading.Thread(target=addition, args=("A",20), name="Addition3")
    t3.start()

    t1.join()
    t2.join()
    t3.join()

OUTPUT

15-03-2021 04:56:25 : INFO : ((Process Details : (31798, MainProcess), Thread Details : (140243122845504, MainThread)) : Current Log Level : 10

15-03-2021 04:56:25 : DEBUG : ((Process Details : (31798, MainProcess), Thread Details : (140243099891456, Addition1)) : Inside Addition Function
15-03-2021 04:56:25 : INFO : ((Process Details : (31798, MainProcess), Thread Details : (140243099891456, Addition1)) : Addition Function Completed Successfully
15-03-2021 04:56:25 : INFO : ((Process Details : (31798, MainProcess), Thread Details : (140243099891456, Addition1)) : Addition of 10 & 20 is : 30.0

15-03-2021 04:56:25 : DEBUG : ((Process Details : (31798, MainProcess), Thread Details : (140243091498752, Addition2)) : Inside Addition Function
15-03-2021 04:56:25 : WARNING : ((Process Details : (31798, MainProcess), Thread Details : (140243091498752, Addition2)) : Warning : Parameter A is passed as String. Future versions won't support it.
15-03-2021 04:56:25 : INFO : ((Process Details : (31798, MainProcess), Thread Details : (140243091498752, Addition2)) : Addition Function Completed Successfully
15-03-2021 04:56:25 : INFO : ((Process Details : (31798, MainProcess), Thread Details : (140243091498752, Addition2)) : Addition of 20 & 20 is : 40.0

15-03-2021 04:56:25 : DEBUG : ((Process Details : (31798, MainProcess), Thread Details : (140243083106048, Addition3)) : Inside Addition Function
15-03-2021 04:56:25 : ERROR : ((Process Details : (31798, MainProcess), Thread Details : (140243083106048, Addition3)) : Error Type : ValueError, Error Message : could not convert string to float: 'A'
15-03-2021 04:56:25 : INFO : ((Process Details : (31798, MainProcess), Thread Details : (140243083106048, Addition3)) : Addition of A & 20 is : None

This ends our small tutorial explaining a simple way and an advanced way to log messages in Python using logging API. The main aim of this tutorial is to get beginners started with logging messages and take people who have already knowledge of logging to the next level in python.

We can also modify default logging configuration of "logging" module using a dictionary or config file. Please check below link if you are looking for a guide on it.

References

Sunny Solanki  Sunny Solanki

YouTube Subscribe Comfortable Learning through Video Tutorials?

If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.

Need Help Stuck Somewhere? Need Help with Coding? Have Doubts About the Topic/Code?

When going through coding examples, it's quite common to have doubts and errors.

If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.

You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.

Share Views Want to Share Your Views? Have Any Suggestions?

If you want to

  • provide some suggestions on topic
  • share your views
  • include some details in tutorial
  • suggest some new topics on which we should create tutorials/blogs
Please feel free to contact us at coderzcolumn07@gmail.com. We appreciate and value your feedbacks. You can also support us with a small contribution by clicking DONATE.


Subscribe to Our YouTube Channel

YouTube SubScribe

Newsletter Subscription