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.
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.
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.
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).
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.
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.
The 'logging' module has internally assigned a numeric value to the criticality of log message type.
These values are generally referred to as log levels.
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.
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.
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.
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
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.
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
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.
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
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.
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'
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.
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
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.
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
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
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.
Below is a list of components that coordinate with each other for performing logging in complex applications.
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.
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.
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.
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.
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.
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
As a part of our eighth example, we are building on our previous example and adding log message formatting to it using Formatter() instance.
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.
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
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.
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.
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
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.
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
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.
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
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.
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
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.
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.
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
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.
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.
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
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.
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
!cat logging_advanced_guide/errors.log
!cat logging_advanced_guide/warnings.log
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.
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
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.
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.
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
!cat logging_advanced_guide/errors_only.log
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.
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
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.
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.
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
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.
If you want to