一、logging简介

        Python 的 logging 模块是一个强大的内置日志记录系统,它提供了灵活的日志记录功能,远超简单的 print 语句。这个模块为应用与库实现了灵活的事件日志系统的函数与类。使用标准库提供的 logging API 最主要的好处是,所有的 Python 模块都可能参与日志输出,包括你自己的日志消息和第三方模块的日志消息。

官方文档参见:https://docs.python.org/3.11/library/logging.html#logging.basicConfig

二、logging和print的区别

logging 模块提供了 print 无法比拟的优势:

  • 日志级别(Levels):支持 DEBUG, INFO, WARNING, ERROR, CRITICAL 等级别,可以方便地控制不同环境(开发、测试、生产)输出的信息量。

  • 灵活的输出目标(Handlers):日志可以同时输出到控制台、文件、网络、邮件、数据库等,而 print 只能输出到标准输出。

  • 丰富的格式化(Formatters):可以自定义日志的输出格式,包含时间、模块名、行号、日志级别等信息。

  • 性能:在生产环境中,可以将日志级别设为 WARNING,此时 debug() 调用几乎不消耗性能(因为消息不会被格式化),而 print 语句即使注释掉也需要手动操作。

  • 结构化与可维护性:logging 提供了清晰的架构,易于配置和管理,适合大型项目。

三、logging基础用法

3.1、日志级别

        logging的日志有6个级别,日志的数值越高,日志的级别也就越高。

级别 数值 描述 举例
1 NOTEST -
2 DEBUG 10 调试 函数调用、变量值
3 INFO 20 一般信息 程序入参、执行结果
4 WARNING(默认) 30 警告 磁盘空间不足、网络连接中断
5 ERROR 40 错误 函数调用失败、异常
6 CRITICAL 50 严重错误 程序崩溃

3.2、logging打印日志

import logging

logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
#也可以通过logging.log指定日志等级。
logging.log(level=logging.ERROR,msg="log_test")
logging.log(level=100,msg="log_test_a")

输出如下:
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
ERROR:root:log_test
Level 100:root:log_test_a

结果说明:

  • 默认日志级别 warning:在默认情况下,只打印 warning 级别及以上的日志,低于该级别以下的日志信息将会被忽略

  • 日志级别修改:可通过 logging.basiConfig 进行调整(下面有介绍)

  • root:日志记录器的名称,会随文件位置的改变而改变

3.3、logging.basicConfig配置

        logging.basicConfig用于一次性设置日志系统的基本参数(如日志级别、输出格式、输出位置等)。只需调用一次,后续所有日志输出都会遵循这个配置,即使再次定义logging.basicConfig也不会生效。常见参数如下:

参数 作用说明 示例值
level 设置日志级别(只显示大于等于该级别的日志)。默认是WARING logging.INFO, logging.DEBUG
filename 指定日志输出到的文件名(不指定则输出到控制台) ‘app.log’
filemode 文件写入模式,默认为 ‘a’(追加),可设为 ‘w’(覆盖) ‘w’
format 日志输出格式字符串 ‘%(asctime)s - %(levelname)s - %(message)s’
datefmt 时间格式字符串。不设置时默认时间格式为‘2025-11-14 15:24:51,668’ ‘%Y-%m-%d %H:%M:%S’
encoding 文件编码(仅当指定 filename 时有效) ‘utf-8’
stream 指定日志应写入哪个流对象(例如,一个文件对象或 sys.stderr)。如果指定了这个参数,则不会使用 filename 参数。
handlers 如果已经创建了处理程序(handler),则可以将它们作为此参数的列表传递。

如果不调用basicConfig(),日志系统有默认配置:

  • 日志级别为 WARNING,只显示 WARNING 及以上级别的日志。

  • 日志输出到控制台

  • 日志格式为:LEVEL:logger_name:message,如:WARNING:root:demo

import logging
​
logging.warning('Started')
logging.warning('Finished')
​
输出为:
WARNING:root:Started
WARNING:root:Finished

调用了basicConfig(),则按照配置的basicConfig显示日志

import logging
​
logging.basicConfig(level=logging.INFO, filename="log_file.log", filemode="a", format='%(asctime)s %(message)s',
                    datefmt='%m/%d/%Y %I:%M:%S %p', encoding="UTF-8")
logging.info('Started')
logging.info('Finished')
​
当前目录下的生成一个文件名为log_file.log的文件,内容如下:
10/15/2025 03:22:09 PM Started
10/15/2025 03:22:09 PM Finished

3.4、format的常见参数

字段 说明 示例输出
%(asctime)s 日志创建时间 2023-01-01 12:00:00,123
%(levelname)s 日志级别名称 INFO
%(message)s 日志消息内容 程序启动成功
%(name)s logger 的名字 my_logger
%(filename)s 生成日志的文件名 app.py
%(lineno)d 生成日志的行号 42
%(funcName)s 生成日志的函数名 main
%(pathname)s 生成源文件的完整路径
%(module)s 生成模块名(文件名去掉 .py)
%(process)d 生成进程ID
%(thread)d 生成线程 ID
import logging
​
​
class logging_sample():
    logging.basicConfig(level=logging.INFO, filemode="a", filename="log_file.log",
                        format='[%(asctime)s] [%(levelname)s] [%(pathname)s %(filename)s %(module)s %(funcName)s:%('
                               'lineno)d]  %(name)s  %(process)d %(thread)d %(message)s', 
                        datefmt='%m/%d/%Y %I:%M:%S %p', encoding="UTF-8")
​
    def logging_test(self):
        logging.info('Started')
        logging.info('Finished')
​
​
if __name__ == '__main__':
    logging_sample().logging_test()
​
执行后再同级目录下生成了一个文件名为log_file.log的文件,文件内容如下:
[10/15/2025 03:48:26 PM] [INFO] [C:\Users\python_logging\sample.py sample.py sample logging_test:19]  root  19032 23648 Started
[10/15/2025 03:48:26 PM] [INFO] [C:\Users\python_logging\sample.py sample.py sample logging_test:20]  root  19032 23648 Finished

如果设置了datefmt想要时间格式输出为毫秒,可以修改format的asctime格式:

logging.basicConfig(format=[%(asctime)s.%(msecs)03d] %(message)s',datefmt='%H:%M:%S')
时间格式输出结果为:15:37:21.640

四、logging的进阶用法

logging 四个组成部分

logging 模块基于一个经典的 生产者-过滤器-处理器 模型,主要由四个核心对象构成:

说明 示例
logging.Logger 记录器,用于发出日志消息 logger = logging.getLogger("my_logger")
logging.Handler 处理器,决定日志输出位置(如文件、控制台等) handler = logging.FileHandler("app.log")
logging.Formatter 格式化器,控制日志输出的格式 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
logging.Filter 过滤器,用于更精细地控制日志记录 filter = logging.Filter("module.name")

4.1、logging.Logger

        负责创建一个日志收集器,默认的名称为root。同一个名称的日志收集器的对应的实例也是同一个。即只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个,也即name与Logger实例一一对应。

        Logger是一个树形层级结构,输出信息之前都要获得一个Logger,如果沒有获得logger,则自动创建并且使用root的logger,比如3.2、logging打印日志的例子。logger = logging.getLogger()返回一个默认的Logger也即root Logger,并应用默认的日志级别、Handler和Formatter设置。指定了name的logger实际上其父logger为root,比如通过logger = logging.getLogger()显示的创建了root Logger,而logger1 = logging.getLogger('mylogger')创建了root Logger的子级(root.)mylogger,子集会将消息分发给他的handler进行处理也会传递给所有的祖先Logger处理。所以我们创建日志收集器的时候一定要指定name,否则会修改root的logger默认配置,并且某些第三方库也使用了根日志记录器,并且它们的日志级别被设为了 DEBUG,那么你就会看到很多 DEBUG 级别的输出。并且一个文件中最好不要存在父子级的多个日志收集器,不然输出的日志会重复。

import logging
​
# 创建一个logger
logger = logging.getLogger()
​
logger1 = logging.getLogger('mylogger')
​
#设置日志收集器的等级
logger1.setLevel(logging.INFO)
​
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log')
​
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
​
# 定义handler的输出格式formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
​
#日志收集器对接输出渠道
logger.addHandler(fh)
logger.addHandler(ch)
​
logger1.addHandler(fh)
logger1.addHandler(ch)
​
# 记录一条日志
logger.debug('logger debug message')
logger.info('logger info message')
logger.warning('logger warning message')
logger.error('logger error message')
logger.critical('logger critical message')
​
logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')
​
文件和控制台输出为:
2025-10-15 18:00:18,566 - root - WARNING - logger warning message
2025-10-15 18:00:18,566 - root - ERROR - logger error message
2025-10-15 18:00:18,567 - root - CRITICAL - logger critical message
2025-10-15 18:00:18,567 - mylogger - INFO - logger1 info message
2025-10-15 18:00:18,567 - mylogger - INFO - logger1 info message
2025-10-15 18:00:18,567 - mylogger - WARNING - logger1 warning message
2025-10-15 18:00:18,567 - mylogger - WARNING - logger1 warning message
2025-10-15 18:00:18,567 - mylogger - ERROR - logger1 error message
2025-10-15 18:00:18,567 - mylogger - ERROR - logger1 error message
2025-10-15 18:00:18,567 - mylogger - CRITICAL - logger1 critical message
2025-10-15 18:00:18,567 - mylogger - CRITICAL - logger1 critical message

        一般推荐的常见日志收集器的命令为:logging.getLogger(__name__) 创建,__name__ 是模块的全名。

关键属性:

  • 名称(Name):通常使用 logging.getLogger(name) 创建,name 是模块的全名(如 myapp.module),这有助于组织日志层次结构。

  • 级别(Level):Logger 有一个日志级别。只有级别大于或等于此级别的日志记录才会被处理。如果 Logger 本身没有设置级别,它会向上传递到父 Logger(最终到根 Logger)。通过obj.setLevel(logging.XXX)来设置

4.2、logging.Handler

        决定日志消息的输出,比如控制台、文件等。一个 Logger 可以有多个 Handler

常见的handler为:

  • StreamHandler:输出到流(如 sys.stdout, sys.stderr)。

  • FileHandler:输出到文件。

  • RotatingFileHandler:输出到文件,并在文件达到一定大小时轮转。

  • TimedRotatingFileHandler:按时间(如每天)轮转日志文件。

  • SMTPHandler:通过邮件发送日志。

  • SocketHandler :通过网络发送日志

关键属性:

  • 级别(Level):Handler 也有自己的级别。即使 Logger 接受了一条日志,Handler 也会根据自己的级别再次过滤。例如,Logger 级别是 DEBUG,Handler 级别是 INFO,那么 DEBUG 级别的日志不会被这个 Handler 处理。通过obj.setLevel(logging.XXX)来设置

  • Formatter:关联一个 Formatter 来格式化日志消息。通过obj.setFormatter(formatter)来设置

  • FileHandler 会无限增长文件。使用 RotatingFileHandler 或 TimedRotatingFileHandler 可以自动管理日志文件大小和数量。

4.3、logging.Formatter

        设置日志消息的最终格式,格式一般为使用类似 %(asctime)s - %(name)s - %(levelname)s - %(message)s 的字符串。可以给不同的handler设置不同的Formatter。可能用到的格式化串参见上文(logging.basicConfig()函数format参数)

4.4、logging.Filter

        提供更精细的日志过滤机制,可以基于 Logger 名称、日志级别或其他属性进行过滤。一般使用比较少。比如我们定义了filter = logging.Filter('a.b.c'),并将这个Filter添加到了一个Handler上,则使用该Handler的Logger中只有名字带a.b.c前缀的Logger才能输出其日志。当然也可以直接给Logger加Filter。若为Handler加Filter则所有使用了该Handler的Logger都会受到影响。而为Logger添加Filter只会影响到自身。

4.5、logging.conf

        除了直接在程序中设置Logger,Handler,Filter,Formatter外还可以将这些信息写进配置文件中。名称为logging.conf,格式如下:

[loggers]
keys=root,fileLogger,rotatingFileLogger
​
[handlers]
keys=consoleHandler,fileHandler,rotatingFileHandler
​
[formatters]
keys=simpleFormatter
​
[logger_root]
level=DEBUG
handlers=consoleHandler
​
[logger_fileLogger]
level=DEBUG
# 该logger中配置的handler
handlers=fileHandler
# logger 的名称
qualname=fileLogger
propagate=0
​
[logger_rotatingFileLogger]
level=DEBUG
# 这样配置,rotatingFileLogger中就同时配置了consoleHandler,rotatingFileHandler
# consoleHandler 负责将日志输出到控制台
# rotatingFileHandler 负责将日志输出保存到文件中
handlers=consoleHandler,rotatingFileHandler
qualname=rotatingFileLogger
propagate=0
​
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
​
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('logs/logging.log', 'a')
​
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=WARNING
formatter=simpleFormatter
args=("logs/rotating_logging.log", "a", 1*1024*1024, 5)
​
[formatter_simpleFormatter]
#format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
format=[%(asctime)s]-[%(levelname)s]-[%(filename)s]-[%(funcName)s:%(lineno)d] : %(message)s
datefmt=%Y-%m-%d %H:%M:%S

以上配置文件主要包含以下几部分:

        loggers : 配置logger信息。必须包含一个名字叫做root的logger,当使用无参函数logging.getLogger()时,默认返回root这个logger,其他自定义logger可以通过 logging.getLogger("fileLogger") 方式进行调用 handlers:定义声明handlers信息。常用的handlers包括 StreamHandler(仅将日志输出到kong控制台)、FileHandler(将日志信息输出保存到文件)、RotaRotatingFileHandler(将日志输出保存到文件中,并设置单个日志文件的大小和日志文件个数) formatter : 设置日志格式 logger_xxx : 对loggers中声明的logger进行逐个配置,且要一一对应。声明level,handlers,qualname(logger的名称)、propagate(是否将传递给所有父Logger处理) handler_xxx : 对handlers中声明的handler进行逐个配置,且要一一对应。声明class、level、formatter、args formatter_xxx : 对声明的formatter文件进行配置。声明format和datefmt

logging.conf的调用

import os
import logging.config
​
logging_conf = os.path.join(os.getcwd(), "logging.conf")
​
# 避免出现格式解析问题
with open(logging_conf, 'r', encoding='utf-8') as f:
    logging.config.fileConfig(f)
​
# rotatingFileLogger中额consoleHandler输出到控制台,rotatingHandler输出日志到文件
rotating_logger = logging.getLogger(name="rotatingFileLogger")
​
rotating_logger.critical('logger critical message')
输出结果为:
[2025-10-15 19:51:17]-[CRITICAL]-[logging_conf_demo.py]-[<module>:27] : logger critical message

五、logging的封装

        自动化测试时会封装一个logging,然后testcase运行时统一调用。目录结构为logger_utils.py在common目录下,生成的日志目录为同级的log。然后在testcase中调用logger_utils,然后在步骤中加上对应的日志就行。

import logging
import os
import datetime
​
​
class Loggers:
    def __init__(self):
        # 设置输出格式
        formater = logging.Formatter(
            '[%(asctime)s]-[%(levelname)s]-[%(filename)s]-[%(funcName)s:%(lineno)d] %(message)s')
        # 定义一个日志收集器
        self.logger = logging.getLogger('autotest_log')
        # 设定级别
        self.logger.setLevel(logging.DEBUG)
​
        # 输出方式1 - 文件形式
        sh = self.fileLogger = logging.FileHandler(log_path, encoding='utf-8')
        # 输出方式2 - 控制台
        ch = self.console = logging.StreamHandler()
​
        # 设置console和file输出的日志级别
        sh.setLevel(logging.DEBUG)
        ch.setLevel(logging.DEBUG)
​
        # 对输出方式设置日志格式
        sh.setFormatter(formater)
        ch.setFormatter(formater)
​
        # 日志收集器添加输出方式
        self.logger.addHandler(sh)
        self.logger.addHandler(ch)
​
    def debug(self, msg):
        self.logger.debug(msg=msg)
​
    def info(self, msg):
        self.logger.info(msg=msg)
​
    def warn(self, msg):
        self.logger.warning(msg=msg)
​
    def error(self, msg):
        self.logger.error(msg=msg)
​
    def critical(self, msg):
        self.logger.critical(msg=msg)
​
​
# 指定文件目录路径
log_dir = os.path.join(os.path.dirname(__file__), "../log")
​
# 判断文件是否存在,不存在就新建
if not os.path.exists(log_dir):
    os.makedirs(log_dir)
​
# 指定文件名称和文件路径
filename = 'autotest' + datetime.datetime.now().strftime('%Y%m%d') + '.log'
log_path = os.path.abspath(os.path.join(log_dir, filename))
​
# 实例化的时候需要带上logger,如果用log = Loggers()实例化,打印出来的日志line数为logger_utils.py的行数
log = Loggers().logger
​
if __name__ == '__main__':
    log.debug('debug')
    log.info('info')
log.info('-' * 5 + '返回状态码是' + str(re.status_code) + '-' * 5)
log.info('-' * 5 + '返回结果集是' + re.text + '-' * 5)

参考文档:

https://blog.csdn.net/cxx654/article/details/83216337

https://blog.csdn.net/zyz511919766/article/details/25136485

https://blog.csdn.net/weixin_51924657/article/details/150420755

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐