By Filip Zeman
Last Validated on May 7 2021 · Originally Published on May 7, 2021 · Viewed 2.1k times

Introduction

Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems.

The Python standard library and Flask already comes with an integrated logging module that provides basic as well as advanced logging features. Log messages can give much helpful information about various events happening behind the scenes.

In this tutorial, you will learn how to:

  • Use the default handler.
  • Configure Handlers, Loggers, and Formatters with Flask configuration
  • Log to a Console and a File.

Prerequisites

  • Python 3.x installed
  • IDE set up for Python
  • Flask installed and added to your project
  • Flask project created

Step 1 — Basic Logging

Python already provides us with a built-in module. You can import these functionalities in your Flask project with this line of code:

import logging

Even now, you can use the default Flask handler to create simple logging messages. Normally, you have five levels indicating the importance of an alert. They have their methods with the same name that can make them. The levels, in order of increasing severity, are:

  • Debug: used for detailed information, typically of interest only when diagnosing problems.
  • Info: used to get a confirmation that things are working as expected.
  • Warning: used as an indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.
  • Error: used in cases of more serious problems the software has not been able to perform some function.
  • Critical: used to indicate serious errors which might cause the program to stop running.

You can start creating them without needing to do any configuration. You can create the messages in the following fashion:

@app.route("/")
def main():
    app.logger.debug("debug")
    app.logger.info("info")
    app.logger.warning("warning")
    app.logger.error("error")
    app.logger.critical("critical")
    return ""

You will get this output in the console:

OUTPUT
[2021-04-27 17:08:34,778] DEBUG in app: debug
[2021-04-27 17:08:34,779] INFO in app: info
[2021-04-27 17:08:34,780] WARNING in app: warning
[2021-04-27 17:08:34,781] ERROR in app: error
[2021-04-27 17:08:34,781] CRITICAL in app: critical

You can also set the minimal level for the default handler so that it handles only the messages of specific importance:

@app.before_first_request
def before_first_request():
    app.logger.setLevel(logging.INFO)

Now only the messages of the level INFO and higher will be displayed:

OUTPUT
[2021-04-27 17:13:09,608] INFO in app: info
[2021-04-27 17:13:09,609] WARNING in app: warning
[2021-04-27 17:13:09,610] ERROR in app: error
[2021-04-27 17:13:09,610] CRITICAL in app: critical

Logging To A File

You can also configure the handler to log to the file instead of the console:

@app.before_first_request
def before_first_request():
    log_level = logging.INFO
 
    for handler in app.logger.handlers:
        app.logger.removeHandler(handler)
 
    root = os.path.dirname(os.path.abspath(__file__))
    logdir = os.path.join(root, 'logs')
    if not os.path.exists(logdir):
        os.mkdir(logdir)
    log_file = os.path.join(logdir, 'app.log')
    handler = logging.FileHandler(log_file)
    handler.setLevel(log_level)
    app.logger.addHandler(handler)
 
    app.logger.setLevel(log_level)

Let us dissect the code. First, you need to remove the default handler Flask provides, so you can later use your own.

Then choose a folder and a filename in which you will keep to logs. If the directory does not exist, the program will create it.

After that, create the handler, set your handler's log level, and add your handler to the logger. Finally, add the log level to the logger itself.

Do not forget to import the library:

import os

When you run the program, the file will be created. It will look like this:

OUTPUT
info
warning
error
critical

You can see that the debug message was ignored, as you set the minimum log level to INFO.


Formatting The Output

This output, however, does not look clear, and after some time, you can get lost in your logs. For that, we will use a formatter. Add these two lines of code at the end of your before_first_request() function:

defaultFormatter = logging.Formatter('[%(asctime)s] %(levelname)s in %(module)s: %(message)s')
handler.setFormatter(defaultFormatter)

In the first step, you will create a formatter. Formatter takes as the parameter the actual desirable format of the logged message. You can use the following:

Attribute name
Description
%(name)s Name of the logger (logging channel)
%(levelno)s Numeric logging level for the message
%(levelno)s Text logging level for the message
%(pathname)s Full pathname of the source file where the logging
call was issued (if available)
%(filename)s Filename portion of pathname
%(module)s Module (name portion of filename)
%(lineno)d Source line number where the logging call was issued (if available)
%(funcName)s Function name
%(created)f Time when the LogRecord was created
%(asctime)s Textual time when the LogRecord was created
%(msecs)d Millisecond portion of the creation time
%(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded (typically at application startup time)
%(thread)d Thread ID (if available)
%(threadName)s Thread name (if available)
%(process)d Process ID (if available)
%(message)s The result of record.getMessage()

In the second line of code, you will assign the formatter you just created to the handler. Now reload your Flask application and have a look at the log file. The output will be formatted:

[2021-04-27 17:25:38,737] INFO in app: info
[2021-04-27 17:25:38,737] WARNING in app: warning
[2021-04-27 17:25:38,738] ERROR in app: error
[2021-04-27 17:25:38,738] CRITICAL in app: critical

Step 2 — Using Handlers

When you continue, remove the before_first_request() function created in the previous step and keep only the main() function:

@app.route("/")
def main():
    app.logger.debug("debug")
    app.logger.info("info")
    app.logger.warning("warning")
    app.logger.error("error")
    app.logger.critical("critical")
    return ""

You can also create your loggers, handlers, and formatters. Start with importing dictConfig:

from logging.config import dictConfig

Now right at the start of the file after the imports, create the dictConfig. You need to act two parameters. Version and stop program from disabling already existing loggers set up by Python or Flask:

**dictConfig(
{
	'version': 1,
   'disable_existing_loggers': False,
})**

Now you need to create a handler:

dictConfig(
{
	'version': 1,
   'disable_existing_loggers': False,

	**'handlers': 
    {
        'custom_handler': {
            'class' : 'logging.FileHandler',
            'filename' : 'warnings.log',
						'level': 'WARN',
        }
    },**
})

You will start by the handler's name, which is, in this case, custom_handler.

There are two of the basic classes you can set in the attribute class: StreamHandler and FileHandler. First one for logging in to the console, and the second one for logging into a file.

In the last attributes, you will specify the file name and the log level.


Step 3 — Using Loggers

Then you need to create a logger. It is similar to creating a handler:

dictConfig({
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': 
    {
        'custom_handler': {
            'class' : 'logging.FileHandler',
            'filename' : 'warnings.log',
						'level': 'WARN',
        }
    },
    **'root': {
        'level': 'WARN',
        'handlers': ['custom_handler'],
    },**
})

In this case, you will set the minimal log level of the message that the logger will pick. As we specified the file name to be warnings.log, our choice is clear.

Then you will assign the handler to the newly created logger.

Now, you can look at the output in the warnings.log file:

OUTPUT
warning
error
critical

The program correctly does not log any message of importance lower than WARNING.

But as you can see, the output is again not formatted. Let us create a formatter to deal with this problem.


Step 4 — Using Formatters

In your Flask application, can similarly create formatters as handlers or loggers. The only attribute you will need is format:

dictConfig(
            {
    'version': 1,
    'disable_existing_loggers': False,
    **'formatters': {
            'default': {
                        'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
                       },
            'simpleformatter' : {
                        'format' : '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            }
    },**
    'handlers': 
    {
        'custom_handler': {
            'class' : 'logging.FileHandler',
            **'formatter': 'default',**
            'filename' : 'warnings.log',
            'level': 'WARN',
        }
    },
    'root': {
        'level': 'WARN',
        'handlers': ['custom_handler']
    },
})

You can find all attributes you can use a format in the first step.

In your Flask project, created two formatters, one a little larger and the second one more straightforward. Now assign one of your formatters to the handler and have a look at the output:

OUTPUT
[2021-04-27 18:45:16,160] WARNING in app: warning
[2021-04-27 18:45:16,161] ERROR in app: error
[2021-04-27 18:45:16,162] CRITICAL in app: critical

Conclusion

You have just learned the basics of logging in Python using Flask. The logging module included with Python and Flask from the start is considered to be good and sufficient. You can use it in a small project or go into an advanced category by creating your classes and levels.

If you have not been using logging into your project, now you have good basics to start. Logging can speed up your development in a big way when done right.