Log Related Concepts

Logging is a way to keep track of events that occur while some software is running. Software developers can call logging related methods into their code to indicate that something has happened. An event can be described by a message that may contain optional variable data. In addition, events also have the concept of importance, which can also be called severity level.

Functions of Logs

Through log analysis, users can easily understand the running status of the system, software, and applications. If your app log is rich enough, you can also analyze past user behavior, type preferences, geographic distribution, and more. If an application has multiple log levels, it is easy to analyze the health status of the application, discover problems in time, and quickly locate, resolve problems, and recover losses.

To put it simply, by recording and analyzing logs, we can know whether a system or software program is running properly, and can quickly locate faults in application programs. For example, students who are engaged in operation and maintenance usually read various logs first when troubleshooting problems after receiving alarms or feedbacks of various problems. Most problems can be found in logs. For example, students who do development can debug programs through various logs output from the IDE console. Experienced operation and maintenance drivers or developers can quickly locate the root cause of a problem through logs. Obviously, the importance of the log can not be underestimated. The functions of logs can be summarized as follows:

  • Program debugging
  • Understand the running status of the software program, whether it is normal
  • Fault analysis and problem location of software program

If the log information is detailed and rich enough, it can be used to analyze user behavior, such as user operation behavior, type, geographical distribution, and other information, to improve services and business interests.

Log level

Let’s consider the following two questions:

  • What log information do you need as a developer to develop an application? What log information does the application need after it goes live?
  • What log information does an application o&M person need to deploy a development environment? What log information is required when deploying a production environment?

In the software development stage or the deployment of the development environment, in order to view the running status of the application as detailed as possible to ensure the stability after the launch, we may need to record all the running logs of the application for analysis, which is very costly to the machine performance. When an application is released or deployed in production, we usually only need to log exceptions, errors, etc. This can reduce the I/O burden on the server and save us from drowning in logs during troubleshooting. So how do you achieve different levels of detail logging in different environments without changing your application code? This is where the log level comes in. We specify the log level we need through the configuration file.

The log levels defined by different applications may vary, and may include the following levels:

  • DEBUG
  • INFO
  • NOTICE
  • WARNING
  • ERROR
  • CRITICAL
  • ALERT
  • EMERGENCY
level When to use
DEBUG Details are of interest when typically debugging problems. Detailed debugging information.
INFO Prove that things work as expected. Critical events.
WARNING Indicates that something unexpected has happened or that a problem will occur in the near future (such as’ Disk is full ‘). The software is still working.
ERROR Due to more serious problems, the software can no longer perform some functions. General error message.
CRITICAL Critical error, indicating that the software can no longer run.
NOTICE It’s not an error, but it might need to be handled. Ordinary but important events.
ALERT Immediate repair is required, for example, system database corruption.
EMERGENCY In an emergency, all users are notified of system unavailability (such as a system crash).

Log field information and log format

A log corresponds to the occurrence of an event, and an event usually contains the following contents:

  • Time of event
  • Location of event
  • Severity of event – Log level
  • The event content

These are all possible fields in a log, as well as other information such as process ID, process name, thread ID, thread name, and so on. Log formats are used to define which fields are included in a log record, and log formats are usually customizable.

Implementation of log function

Almost all development languages have logging functionality built in, or there are good third-party libraries that provide logging functionality, such as log4j, log4PHP, etc. They are powerful and simple to use. Python itself provides a standard library module for logging, called Logging.

Logging modules

The Logging module is a standard module built into Python. It is mainly used to output run logs. You can set the log output level, log saving path, and log file rollback. Compared with print, it has the following advantages:

  • You can set different log levels so that only important information can be printed in the Release version without having to display a lot of debugging information.
  • Print prints all the information to standard output, seriously affecting the developer’s ability to view other data from standard output; Logging allows developers to decide where and how to output information.

1. Log level of the logging module

The logging module defines the following logging levels by default. It allows developers to customize other logging levels, but this is not recommended, especially when developing libraries for others to use, because it can lead to confusion in logging levels.

Log Level (Level) describe
DEBUG The most detailed log information is used in problem diagnosis
INFO This information is second only to DEBUG and usually only records key node information to ensure that everything is working as expected
WARNING Information that is recorded when something unexpected happens (for example, disk free space is low), but the application is still running
ERROR Information recorded when some function is not working properly due to a more serious problem
CRITICAL Information that is recorded when a critical error occurs that causes the application to fail to continue running

When developing applications or deploying the development environment, you can use logs at the DEBUG or INFO level to obtain detailed log information for development or deployment debugging.

When an application goes online or a production environment is deployed, use WARNING, ERROR, or CRITICAL logs to reduce I/O pressure and improve the efficiency of obtaining ERROR logs. Log levels are usually specified in the application’s configuration file.

Description:

  • The log level in the preceding list increases from top to bottom, that is, DEBUG < INFO < WARNING < ERROR < CRITICAL, while the log information decreases in descending order.
  • When a logging level is specified for an application, the application logs all logs at or equal to the specified log level, rather than only logs at the specified log level. This is true for applications such as Nginx, PHP, and here in Python’s logging module. Similarly, the logging module can specify the logger log level. Only logs with a log level greater than or equal to the specified log level are output, and logs with a log level less than this level are discarded.

2. Introduction to the usage of logging module

The Logging module provides two ways to log:

  • The first is provided by loggingModule-level functions
  • The second way is to use LoggingFour major components

Logging provides module-level logging functions that encapsulate the logging logging system-related classes.

Common module-level functions defined by the logging module
function instructions
logging.debug(msg, *args, **kwargs) Example Create a log record whose severity level is DEBUG
logging.info(msg, *args, **kwargs) Example Create a log record whose severity level is INFO
logging.warning(msg, *args, **kwargs) Example Create a log record whose severity is WARNING
logging.error(msg, *args, **kwargs) Example Create a log record whose severity level is ERROR
logging.critical(msg, *args, **kwargs) Example Create a log record whose severity is CRITICAL
logging.log(level, *args, **kwargs) Example Create a log record whose severity level is Level
logging.basicConfig(**kwargs) Configure root Logger once

BasicConfig (**kwargs) is used to specify information such as “log level to be recorded”, “log format”, “log output location”, “log file opening mode”, etc. Other functions are used to record logs of different levels.

3. The first way of use: simple configuration

import logging
logging.debug("debug_msg")
logging.info("info_msg")
logging.warning("warning_msg")
logging.error("error_msg")
logging.critical("critical_msg")
Copy the code

The output

WARNING:root:warning_msg
ERROR:root:error_msg
CRITICAL:root:critical_msg
Copy the code

By default, Python’s logging module prints logs to standard output and only displays logs that are greater than or equal to WARNING. The default log level is set to WARNING (Log level CRITICAL > ERROR > WARNING > INFO > DEBUG)

The default output format is

The default log format is log level: Logger Name: user output messageCopy the code

You can use the logging.basicconfig () function to adjust the log level, output format, and so on

Simple example

import logging
logging.basicConfig(level=logging.DEBUG,
                    format="%(asctime)s %(name)s %(levelname)s %(message)s",
                    datefmt = '%Y-%m-%d %H:%M:%S %a'    The formatter is the same as in the time module
                    )
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")
Copy the code

The output

2018- 05-0923:37:49 Wed root DEBUG msg1
2018- 05-0923:37:49 Wed root INFO msg2
2018- 05-0923:37:49 Wed root WARNING msg3
2018- 05-0923:37:49 Wed root ERROR msg4
2018- 05-0923:37:49 Wed root CRITICAL msg5
Copy the code

The logging.basicConfig() function contains parameter descriptions

The parameter name describe
filename Specifies the name of the log output target file(You can either write the file name or the complete absolute path of the file, put the log file name in the directory of the execution file, and write the complete path to generate the log file according to the complete path),Log confidence is not printed to the console after this setting is specified
filemode Specifies the opening mode for the log file. Default is’ A ‘. It’s important to note that,This option is valid only when filename is specified
format Specifies the log format string, that is, specifies the field information to be included in the log output and their order. The format fields defined by the Logging module are listed below.
datefmt Specifies the date/time format. It’s important to note that,This option is valid only if the format contains the time field %(asctime)s
level Specifies the log level of the logger
stream Specify log output target streams such as sys.stdout, sys.stderr, and network stream. Note that both stream and filename cannot be supplied at the same time, otherwise it will raiseValueErrorabnormal
style New configuration item in Python 3.2. Specifies the style of the format string. The value can be ‘%’, ‘{‘, or ‘$’. The default is ‘%’.
handlers New configuration item in Python 3.3. This option, if specified, should be an iterable that creates multiple handlers that will be added to the root Logger. Note that only one of the three configuration items, filename, Stream, and Handlers, exists, but not two or three at the same time. Otherwise, ValueError is raised.

The logging module defines the format string specification

Field/attribute name Use the format describe
asctime %(asctime)s Construct the log time in a readable form, which by default is’ 2016-02-08 12:00:00,123 ‘accurate to milliseconds
name %(name)s The logger name used, which defaults to ‘root’ because rootLogger is used by default
filename %(filename)s The file name of the module that called the logging function; The filename part of pathName, including the file suffix
funcName %(funcName)s The name of the function that calls the log output function
levelname %(levelname)s The final log level (modified by filter)
message %(message)s Log information: the text content of a log
lineno %(lineno)d The line number of the current log, the line of code where the statement calling the log output function is located
levelno %(levelno)s Log level (10, 20, 30, 40, 50) in digital form for this log recording
pathname %(pathname)s The full path, the full pathname of the module that called the logging function, may not exist
process %(process)s ID of the current process. There may be no
processName %(processName)s Process name, new in Python 3.1
thread %(thread)s Current thread, thread ID. There may be no
threadName %(thread)s Name of the thread
module %(module)s The name of the module that calls the log output function. The name part of filename does not contain the suffix, that is, the filename does not contain the file suffix
created %(created)f The current time, represented by the UNIX standard floating point time; The time at which the logging event occurred — the timestamp, which is the value returned by calling time.time() at that time
relativeCreated %(relativeCreated)d The number of milliseconds since Logger was created when the log message was output; The number of milliseconds in which the log event occurred relative to the logging module loading time
msecs %(msecs)d Log event The millisecond portion of the event that occurred. Logging.basicconfig () uses the datefmt argument, which will remove the milliseconds generated in asctime

Upgrade log example

import logging
LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "Configure the output log format
DATE_FORMAT = '%Y-%m-%d %H:%M:%S %a ' Configure the format of the output time. Note that month and day do not confuse
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filename=r"d:\test\test.log" The filename argument does not output directly to the console, but writes directly to the file
                    )
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")
Copy the code

The output

Log in the D :\test directory, log details

2018- 05 -10  02:13:46 Thu AM  root DEBUG D:/06python/exercise/test_package/test.py msg1
2018- 05 -10  02:13:46 Thu AM  root INFO D:/06python/exercise/test_package/test.py msg2
2018- 05 -10  02:13:46 Thu AM  root WARNING D:/06python/exercise/test_package/test.py msg3
2018- 05 -10  02:13:46 Thu AM  root ERROR D:/06python/exercise/test_package/test.py msg4
2018- 05 -10  02:13:46 Thu AM  root CRITICAL D:/06python/exercise/test_package/test.py msg5
Copy the code

instructions

  • The logging.basicConfig() function is a simple one-time configuration tool, meaning that it only works the first time it is called. Subsequent calls to the function do nothing at all. The Settings of multiple calls are not cumulative.

  • Logger is hierarchical. The Logger used by the logging module-level function called above is an instance of the RootLogger class named ‘root’. It is the Logger at the top of the Logger hierarchy and exists in singleton mode.

  • If the log to be logged contains variable data, use a format string as the event description message (logging.debug, logging.info, etc.), and pass the variable data as the value of the second parameter *args.

    Such as:

    Warning ('%s is %d years old.', 'Tom', 10),Copy the code

    The output is

    WARNING:root:Tom is 10 years old.
    Copy the code
  • Methods like logging.debug(), logging.info() are defined with a **kwargs parameter in addition to MSG and args parameters. They support three keyword arguments: exc_info, stack_info, extra, and **, which are described below. For descriptions of exc_info, stack_info, and extra keyword parameters, see Reference 1. (understand)

4. The second usage: log flow processing process

In the simple configuration example above, we saw logging.debug(), logging.info(), logging.warning(), logging.error(), and logging.critical(), which are used to record different levels of logging information, respectively. Logging.basicconfig () creates a default StreamHandler for the logging system using the Formatter. Module-level functions that set basic configuration (such as log level, etc.) and add to root Logger.

The second module-level function is logging.getLogger([name]) (returns a Logger object, or root Logger if no name is specified).

Logging The logging module consists of four components

Before introducing the logging module’s log flow processing process, let’s first introduce the four components of the Logging module:

Component name Corresponding to the name of the class Functional description
logger Logger Provides an interface that the application can use all the time
The processor Handler Send log records created by logger to the appropriate destination output
The filter Filter More fine-grained control tools are provided to determine which log records to output and which to discard
formatter Formatter Determines the final output format for logging

The logging module performs logging processing through these components, and the logging module-level functions used above are implemented through the corresponding classes of these components.

The relationship between these components is described:

  • The logger needs to output the log information to the target location through the handler, such as file, sys.stdout, and network.
  • Different handlers can output logs to different locations;
  • A logger can set up multiple handlers to output the same log record to different locations.
  • Each handler can set its own filter to filter logs, so that only interested logs can be retained.
  • Each handler can set its own formatter to output the same log to different places in different formats.

The logger is the entry point, and the handler is the real work. The handler can also filter and format the output log content through the filter and formatter.

Logging Logging module related classes and common methods

Classes related to the logging components: Logger, Handler, Filter, and Formatter.

The Logger class

A Logger object has three tasks to do:

  • 1) Expose application code to several methods that allow applications to log messages at run time;
  • 2) Determine which logs should be processed based on log severity level (the default filtering facility) or filter object;
  • 3) Log messages are sent to all interested log handlers.

The most common methods for Logger objects fall into two categories: configuration methods and message sending methods

The most common configuration methods are as follows:

methods describe
Logger.setLevel() Sets the minimum severity level of log messages that the logger will process
Logger. AddHandler () and Logger. RemoveHandler () Add and remove a handler object for this Logger object
Logger. AddFilter () and Logger. RemoveFilter () Add and remove a Filter object for this Logger object

Once the Logger object is configured, you can use the following methods to create logging:

methods describe
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() Create a log record that corresponds to the level of their method name
Logger.exception() Create a log message similar to logger.error ()
Logger.log() You need to get an explicit log level parameter to create a log record

What about a Logger object? One way is to create an instance of the Logger class through its instantiation method, but we usually use the second method –logging.getLogger().

The logging.getLogger() method has an optional parameter name that represents the name identifier of the logger to be returned, or ‘root’ if this parameter is not provided. Calling the getLogger() method multiple times with the same name parameter value returns a reference to the same Logger object.

Note: Do not create multiple loggers. Otherwise, repeated log output will occur.

Logger hierarchy and effective levels

  • Logger names are a hierarchy divided by ‘.’, each logger after ‘.’ is the children of the logger before ‘.’ for example, there is a logger named Foo, The other names are foo.bar, foo.bar.baz, and foo.bam are all descendants of foo.

  • Logger has a concept of an effective level. If no level is explicitly set on a logger, then the logger is the level that uses its parent. If its parent does not have a level set, it continues to look up the valid level of the parent’s parent, and so on, until it finds an ancestor that has a level set. Note that root Logger always has an explicit level setting (WARNING by default). When deciding whether to handle an event that has already occurred, logger’s effective level is used to determine whether to pass the event to the Handlers of that logger for processing.

  • After child Loggers have finished processing log messages, they default to the handlers associated with their loGGERS ancestors. Therefore, rather than defining and configuring Handlers for all loggers used in an application, it is sufficient to configure handlers for a top-level Logger and then create child Loggers as needed. We can also turn off this propagation mechanism by setting the propagate attribute for a Logger to False.

Handler class

The Handler object is used (based on the level of the log message) to distribute the message to the location (file, network, mail, etc.) specified by the Handler. Logger objects can add zero or more handler objects to themselves using the addHandler() method. For example, an application might want to implement the following logging requirements:

  • 1) Send all logs to a log file;
  • 2) Send all logs with severity greater than or equal to Error to STDOUT (standard output);
  • 3) Send all critical logs to a single email address. This scenario requires three different Handlers, each responsible for sending a particular severity level log to a particular location.
Handler.setlevel (lel): specifies the level of information to be processed. Messages below the LEL level are ignored handler.setformatter () : Select a format for this handler handler. AddFilter (filt), handler. RemoveFilter (filt) : Add or remove onefilterobjectCopy the code

It should be noted that application code should not directly instantiate and use Handler instances. Because Handler is a base class, it defines only the interfaces that should be given to the handlers and provides some default behavior that subclasses can use or override. Here are some common handlers:

Handler describe
logging.StreamHandler Send log messages to output to Stream, such as std.out, std.err, or any file-like object.
logging.FileHandler Log messages are sent to disk files, which by default grow indefinitely
logging.handlers.RotatingFileHandler Sends log messages to disk files and supports cutting log files by size
logging.hanlders.TimedRotatingFileHandler Sends log messages to disk files and supports log file fragmentation by time
logging.handlers.HTTPHandler Log messages are sent to an HTTP server as GET or POST
logging.handlers.SMTPHandler Sends log messages to a specified email address
logging.NullHandler This Handler instance ignores error messages and is commonly used by library developers who want to use logging to avoid the message ‘No Handlers could be found for Logger XXX’.

Formater class

The Formater object is used to configure the final order, structure, and content of log information. Unlike the logging.handler base class, application code can directly instantiate the Formatter class. Alternatively, if your application needs some special processing behavior, you can implement a subclass of Formatter to do it.

The Formatter class constructor is defined as follows:

logging.Formatter.__init__(fmt=None, datefmt=None, style=The '%')
Copy the code

As you can see, this constructor accepts three optional arguments:

  • FMT: Specifies the message formatting string. If this parameter is not specified, the original value of message is used by default
  • Datefmt: specifies the date format character string. If this parameter is not specified, “%Y-%m-%d %H:% m :%S” is used by default.
  • Style: Python 3.2 new argument, can be ‘%’, ‘{‘ and ‘$’, if not specified default ‘%’

Logging.Formatter (FMT, datefmt)

Filter class (for now)

The Filter can be used by handlers and Loggers to perform more fine-grained and complex filtering than level. Filter is a Filter base class that only allows log events at a logger level to pass the Filter. The class is defined as follows:

  class logging.Filter(name='')
      filter(record)
Copy the code

For example, if a filter instance passes the name parameter ‘A.B’, the filter instance will only allow logs generated by loGGERS with names like the following: ‘a.’, ‘a., C’, ‘A.B.C.D’, ‘A.B.D’, and the name is’ a. B ‘, ‘B.A.B loggers produce logs will be filtered out. If the value of name is an empty string, all log events are allowed to pass the filtering.

The filter method is used to control whether the passed record can pass filtering. If the return value of this method is 0, it indicates that the record cannot pass filtering. If the return value is non-0, it indicates that the record can pass filtering.

Description:

  • If necessary, you can also change the record within the Filter (Record) method, such as adding, deleting, or modifying properties.
  • We can also use the Filter to do some statistics, such as counting the number of records processed by a particular Logger or handler.

Brief log flow processing process

1. Create a Logger

2. Set the level of logger logs

Create an appropriate Handler(FileHandler must have a path)

4. Set the log level of each Handler

5. Create a log format

6. Add the format created above to the Handler

7. Add the Handler created above to logger

8. Print logger.debug logger.info logger.warning logger.error logger.critica

example

import logging

Create logger, return root logger if the argument is null
logger = logging.getLogger("nick")
logger.setLevel(logging.DEBUG)  Set logger log level

# to create a handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()

Set the output log format
formatter = logging.Formatter(
    fmt="%(asctime)s %(name)s %(filename)s %(message)s",
    datefmt="%Y/%m/%d %X"
    )

Note the case of logging.formatter
 
Specifies the output format for handler, case sensitive
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# Log handlers added for Logger
logger.addHandler(fh)
logger.addHandler(ch)

Output different levels of log
logger.warning("Muay Thai Warning.")
logger.info("Tip")
logger.error("Error")
Copy the code

Python Logging is a problem with repeated logging

When logging with Python’s Logging module, you may encounter the problem of logging twice for the first record, twice for the second record, and three times for the third record

Cause: No handler is removed. Solution: removeHandler is removed after logging

example

def log(msg) :
    Create logger, return root logger if the argument is null
    logger = logging.getLogger("nick")
    logger.setLevel(logging.DEBUG)  Set logger log level

    # to create a handler
    fh = logging.FileHandler("test.log",encoding="utf-8")
    ch = logging.StreamHandler()

    Set the output log format
    formatter = logging.Formatter(
        fmt="%(asctime)s %(name)s %(filename)s %(message)s",
        datefmt="%Y/%m/%d %X"
        )

    Specifies the output format for this handler
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)

    # Log handlers added for Logger
    logger.addHandler(fh)
    logger.addHandler(ch)

    Output different levels of log
    logger.info(msg)

log("Muay Thai Warning.")
log("Tip")
log("Error")
Copy the code

The output

201805 /10 2006:18Nick test.py Muay Thai warning201805 /10 2006:18Nick test. Py hints201805 /10 2006:18Nick test. Py hints201805 /10 2006:18Nick test. Py error201805 /10 2006:18Nick test. Py error201805 /10 2006:18Nick test. Py errorCopy the code

Analysis: You can see that the output results have repeated printing

The reason: The second time you call log, you get the same logger from the name in getLogger(name) that already has the handler you added the first time, and the second time you add a handler, so, This logger has two of the same handlers in it, and so on, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth, and so forth.

  1. Solution 1

Add the removeHandler statement

import logging
def log(msg) :
    Create logger, return root logger if the argument is null
    logger = logging.getLogger("nick")
    logger.setLevel(logging.DEBUG)  Set logger log level

    # to create a handler
    fh = logging.FileHandler("test.log",encoding="utf-8")
    ch = logging.StreamHandler()

    Set the output log format
    formatter = logging.Formatter(
        fmt="%(asctime)s %(name)s %(filename)s %(message)s",
        datefmt="%Y/%m/%d %X"
        )

    Specifies the output format for this handler
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)

    # Log handlers added for Logger
    logger.addHandler(fh)
    logger.addHandler(ch)

    Output different levels of log
    logger.info(msg)

    # Solution 1: Add a removeHandler statement and remove the Handler after each use
    logger.removeHandler(fh)
    logger.removeHandler(ch)


log("Muay Thai Warning.")
log("Tip")
log("Error")
Copy the code

 

  1. Solution 2

Check in the log method that if the logger already has a handler, no handler will be added.

import logging
def log(msg) :
    Create logger, return root logger if the argument is null
    logger = logging.getLogger("nick")
    logger.setLevel(logging.DEBUG)  Set logger log level

    Handlers: If the logger.handlers list is empty, add it; otherwise, write to the log
    if not logger.handlers:
        # to create a handler
        fh = logging.FileHandler("test.log",encoding="utf-8")
        ch = logging.StreamHandler()

        Set the output log format
        formatter = logging.Formatter(
            fmt="%(asctime)s %(name)s %(filename)s %(message)s",
            datefmt="%Y/%m/%d %X"
            )

        Specifies the output format for this handler
        fh.setFormatter(formatter)
        ch.setFormatter(formatter)

        # Log handlers added for Logger
        logger.addHandler(fh)
        logger.addHandler(ch)

    Output different levels of log
    logger.info(msg)


log("Muay Thai Warning.")
log("Tip")
log("Error")
Copy the code

Examples of logger calls

import logging
def log() :
    Create logger, return root logger if the argument is null
    logger = logging.getLogger("nick")
    logger.setLevel(logging.DEBUG)  Set logger log level

    Handlers: If the logger.handlers list is empty, add it, otherwise write to the log
    if not logger.handlers:
        # to create a handler
        fh = logging.FileHandler("test.log",encoding="utf-8")
        ch = logging.StreamHandler()

        Set the output log format
        formatter = logging.Formatter(
            fmt="%(asctime)s %(name)s %(filename)s %(message)s",
            datefmt="%Y/%m/%d %X"
            )

        Specifies the output format for this handler
        fh.setFormatter(formatter)
        ch.setFormatter(formatter)

        # Log handlers added for Logger
        logger.addHandler(fh)
        logger.addHandler(ch)

    return logger Return directly to Logger

logger = log()
logger.warning("Muay Thai Warning.")
logger.info("Tip")
logger.error("Error")
logger.debug("Error")
Copy the code