Python logging 日志

介绍:在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在Python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?可以使用logging模块完成。

print虽然也可以输出信息,但是print不知道日志记录的位置,也没有可读的日志格式,还不能把日志输出到指定文件。所以最佳的做法是使用内置的logging模块,因为logging模块为开发者提供了丰富的功能。

目录

日志级别

日志的使用

输出到控制台

日志等级和输出格式设置

日志数值设置日志级别

保存到日志文件

记录器(logger)

记录器层级示例

处理器(Handler)

按日期轮换日志文件(TimedRotatingFileHandler)

按文件大小轮换日志文件(RotatingFileHandler)

格式器(formatter)

logger.basicConfig

Flask完整的大小轮换日志案例

日志配置文件


目的:

  1. 可以很方便的了解程序的运行情况
  2. 可以分析用户的操作行为、喜好等信息
  3. 方便开发人员检查BUG

日志级别

日志等级分为5个,从低到高分别是:

  1. DEBUG:程序调试bug时使用,用来记录详细的信息,方便定位问题,在生产环境下一般不开启DEBUG;
  2. INFO:程序正常运行时使用,用来记录关键代码点的信息,生产环境下常设置为INFO级别;
  3. WARNING:程序未能按照预期运行时使用,但并不报错,如:用户登录密码错误;
  4. ERROR:程序出现错误时使用,导致某些功能不能正常运行时记录的信息,如IO操作失败;
  5. CRITICAL:特别严重问题,导致程序不能继续运行时使用,如:磁盘空间为空,一般很少使用;

说明:默认是WARNING等级,当在WAENING或WAENING之上等级的才记录日志信息。

日志的使用

在logging模块中记录日志的方式有两种:“输出到控制台”和“保存到日志文件”。

注意:logging和print虽然类似,但是括号中只能输出一个字符串,不能用“,”隔开,可以使用“+”拼接成一个字符串。

输出到控制台

说明:日志信息默认只显示大于等于WAENING级别的日志,这说明默认的日志级别设置为WAENING。

import logging

logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
WARNING:root:这是一个warning级别的日志信息
ERROR:root:这是一个error级别的日志信息
CRITICAL:root:这是一个critical级别的日志信息

日志等级和输出格式设置

import logging
logging.basicConfig(
    level=日志等级, 
    format=输出格式, 
    datefmt=日期时间格式, 
    encoding=日志编码格式,
    filename=日志文件,
    filemode=日志文件写入模式,
)

参数

说明

level

日志等级,如:logger.DEBUG, logger.INFO, logger.WARN, logger.ERROR, logger.CRITICAL

format

日志的输出格式

%(levelname)s:打印日志级别名称,如DEBUG, INFO, WARNING, ERROR, CRITICAL

%(filename)s:打印当前执行程序名

%(pathname)s:打印当前执行程序路径

%(lineno)d:打印日志的当前行号

%(asctime)s:打印日志的时间,默认形式'2022-05-13 20:05:45,896'(逗号后的数字为毫秒)

%(message)s:打印日志信息

%(remote_add)s:打印日志的请求访问的主机IP

%(url)s:打印日志的要请求的路由

%(name)s: 用于记录日志的记录器名称

%(process)d: 进程ID(如果可用)

%(thread)d: 线程ID(如果可用)

datefmt

格式化日志的展示时间,如:'%Y-%m-%d %H:%M:%S'

encoding

日志编码格式(Python3.9才有)

filename

日志文件路径,如:'./django.log'

filemode

日志文件写入模式,与open方法相同,如:每次重启程序覆盖之前的日志'w',追加'a'

import logging

# 设置日志等级和输出日志格式
logging.basicConfig(
    level=logging.DEBUG,  # 设置输出日志级别
    # 时间 - 程序名[行号] - 日志级别:信息
    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'
)
    
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
2020-12-28 17:02:39,442 - 测试.py[line: 7] - DEBUG: 这是一个debug级别的日志信息
2020-12-28 17:02:39,442 - 测试.py[line: 8] - INFO: 这是一个info级别的日志信息
2020-12-28 17:02:39,442 - 测试.py[line: 9] - WARNING: 这是一个warning级别的日志信息
2020-12-28 17:02:39,443 - 测试.py[line: 10] - ERROR: 这是一个error级别的日志信息
2020-12-28 17:02:39,443 - 测试.py[line: 11] - CRITICAL: 这是一个critical级别的日志信息

日志数值设置日志级别

日志级别数值

级别

critical

error

warning

info

debug

notset

数值

50

40

30

20

10

0

import logging

format = logging.Formatter(
    # 时间 - 日志器名 - 日志级别 - 文件路径 - 行号: 信息
    '%(asctime)s - %(name)s - %(levelname)s - %(pathname)s - %(lineno)s: %(message)s', 
    datefmt='%Y-%m-%d %H:%M:%S'
)
sh = logging.StreamHandler()  # 控制台日志记录器
sh.setFormatter(format)  # 对象的形式设置日志格式

logger = logging.getLogger("MyLog")  # 设置全局日志工具对象
logger.setLevel(10)  # 设置日志级别
logger.addHandler(sh)  # 添加日志记录器到全局工具对象

logger.info("这是普通级别的日志信息")
logger.warning("这是警告级别的日志信息")
logger.critical("这是极重要级别的日志信息")

try:
    a = 2 / 0
except ZeroDivisionError as e:
    logger.error("错误信息:" + str(e))
2020-12-28 17:15:07 - MyLog - INFO - c:\Users\hp\Desktop\测试.py - 13: 这是普通级别的日志信息
2020-12-28 17:15:07 - MyLog - WARNING - c:\Users\hp\Desktop\测试.py - 14: 这是警告级别的日志信息
2020-12-28 17:15:07 - MyLog - CRITICAL - c:\Users\hp\Desktop\测试.py - 15: 这是极重要级别的日志信息
2020-12-28 17:15:07 - MyLog - ERROR - c:\Users\hp\Desktop\测试.py - 20: 错误信息:division by zero

保存到日志文件

将异常信息输出到控制台的做法在生产环境中并不实用,使用logging将异常信息保存到外部文件,更利于运维人员发现错误。

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
    filename="log.txt",
    filemode="w"  # 每次重启程序,覆盖之前的日志
)

logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')

记录器(logger)

前面的日志记录,其实都是通过一个叫做日志记录器(Logger)的实例对象创建的,每个记录器都有一个名称,直接使用logging来记录日志时,系统会默认创建名为root的记录器。记录器支持层级结构,子记录器通常不需要单独设置日志级别以及Handler(处理器),如果自己记录器没有单独设置,则它的行为会委托给父级。

import logging
logger = logging.getLogger(__name__)

默认情况下,记录器是层级结构,会根据项目的层级分层,子记录器会集成父级记录器的配置。

记录器层级示例

 在foo.py中设置记录器的级别为INFO

import logging

logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

logger.info('this is foo')

在子文件bar.py中的记录器不设置日志级别,默认是WARGING

import logging

logger = logging.getLogger(__name__)
logger.info('this is bar')

其他子模块都是bar.py一样类似的代码,没有设置日志级别,最后输出的结果如下:

INFO:foo:this is foo
INFO:foo.bar:this is bar
INFO:foo.bam:this is bam
INFO:foo.bar.baz:this is baz

发现都可以输出日志级别为INFO的日志,这是因为子文件没有设置日志级别,就会向上找已经设置了日志级别的日志器,这里刚好找到了foo的级别为INFO,如果foo也没有设置的话,就会找到跟记录器root,root默认级别为WARGING。

处理器(Handler)

记录器负责日志的记录,但是日志最终记录到哪里并不关心,而是交给处理器(handler)去处理。

例如一个Flask项目,你可能会将INFO级别的日志记录到文件,将ERROR级别的日志记录到标准输出,将某些关键日志(例如订单或者严重错误)发送到某个邮件地址。这时候你的记录器添加多个不同的处理器来处理不同的消息日志,以此根据消息的重要性发送到特定的位置。

logging中内置了很多使用的处理器,常用的有:

  • StreamHandler标准流处理器,将消息发送到标准的输出流、错误流;
  • FileHandler文件处理器,将文件发送到文件;
  • RotatingFileHandler文件处理器,文件达到指定大小后,启用新文件春初日志;
  • TimedRotatingFileHandler文件处理器,日志以特定的时间间隔轮换日志文件;

Handler提供了4个方法,setLevel, setFormatter, addFilter, removeFilter

下方案例通过setLevel可以将记录器的不同级别的消息发送到不同的地方去。

import logging
from logging import StreamHandler
from logging import FileHandler

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # 设置为DEBUG级别

# 标准流处理器,设置级别为WARNING
stream_handler = StreamHandler()
stream_handler.setLevel(logger.WARNING)
logger.addHandler(stream_handler)

# 文件处理器,设置的级别为INFO
file_handler = FileHandler(filename='test.log')
file_handler.setLevel(logging.INFO)
logger.debug('this is debug')
logger.info('this is info')
looger.warning('this is warning')
logger.error('this is error')
# 命令行窗口输出的日志为
this is warning
this is error

# 日志文件中输出的日志为
this is info
this is warning
this is error

尽管我们将logger的级别设置成了DEBUG,但是debug记录的消息并没有输出,因为我们给两个Handler设置的级别都比DEBUG高,所以消息被过滤掉了。同时也说明了同一条日志可以被同时记录在不同的地方。

按日期轮换日志文件(TimedRotatingFileHandler)

import logging
from logging import handlers

class Logger(object):
    def __init__(self, log_name, level):
        self.logger = logging.getLogger(log_name)  # 获取记录器,并设置记录器的名字
        # 设置格式
        format = logging.Formatter(
            """%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s""")
        # 设置显示级别
        self.logger.setLevel(level)
        # 实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件,具体看官方文档
        tfh = handlers.TimedRotatingFileHandler(filename=log_name, when="D", backupCount=2, encoding="utf-8")
        tfh.setFormatter(format)
        self.logger.addHandler(tfh)

if __name__ == "__main__":
    log = Logger("python_log.log", level=logging.DEBUG)
    try:
        a = 2/0
    except ZeroDivisionError as e:
        log.logger.error("错误信息是:{0}".format(e))

按文件大小轮换日志文件(RotatingFileHandler)

# 设置日志记录等级
logging.basicConfig(level=logging.DEBUG)  # 调试debug级
# 创建日志处理器,指明日志保存路径、每个日志文件的最大大小、保存的日志文件个数上限
file_log_handler = RotatingFileHandler("logs/log", maxBytes=1024 * 1024 * 100, backupCount=10)
# 创建日志记录的格式
formatter = logging.Formatter("%(levelname)s %(filename)s:%(lineno)sd %(message)s")
# 为刚创建的日志处理器设置日志记录格式
file_log_handler.setFormatter(formatter)
# 为全局的日志工具对象添加日志处理器
logging.getLogger().addHandler(file_log_handler)

格式器(formatter)

在logging.basicConfig中已经配置过日志格式,其实格式器还可以以对象的形式在Handler上设置。格式器可以指定日志的输出格式,如是否展示时间、设置时间格式、要不要展示日志级别、是否展示记录器名称等。

import logging
from logging import StreamHandler

logger = logging.getLogger(__name__)

# 标准流处理器
stream_handler = StreamHandler()
stream_handler.setLevel(logging.WARNING)

# 创建一个格式器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 作用在handler上
stream_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(stream_handler)

logger.info('this is info')
logger.warning('this is warning')
logger.error('this is error')

注意

  • 格式器只能作用在处理器上,通过处理器的setFormatter方法设置格式器。而且一个Handler只能设置一个格式器;
  • logger与格式器是一对多的关系,一个logger可以添加多个格式器(handler);
  • handler和logger都可以设置日志等级。

logger.basicConfig

最开始将如何配置logging时,使用的是logger.basicConfig。现在上面已经说了记录器、处理器和格式器,现在我们在来看看logger.basicConfig()方法为我们干了什么。

  1. 创建一个root记录器
  2. 设置root记录器的日志级别为warning
  3. 为root记录器添加StreamHandler处理器
  4. 为处理器设置一个格式器
import logging

logging.basicConfig()
logging.warning('this is warning')

这两行代码其实就等价于:

import logging

logger = logging.getLogger('root')  # 设置名为root的记录器
logger.setLevel(logging.WARNING)  # 设置日志级别为WARNING
handler = logging.StreamHandler(sys.stderr)
logger.addHandler(handler)  # 添加一个控制台处理器
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
handler.setFormatter(formatter)  # 设置格式器

logger.warning('this is warning')

logger.basicConfig()方法做的事情是相当于给日志系统做一个最基本的配置,方便开发者快速上手使用,他必须在开始记录日之前调用。

注意:不过如果root记录器已经指定有其他处理器,这是用再调用basicConfig,该方法失效。

Flask完整的大小轮换日志案例

在框架中使用日志说明:logging日志配置信息在程序入口模块设置一次,整个程序都可以生效。

import os
import logging
import logging.handlers
from flask import request

class RequestFormatter(logging.Formatter):
    # 针对请求信息的日志格式
    def format(self, record):
        record.url = request.url
        record.remote_addr = request.remote_addr
        return super().format(record)


def create_logger(app):
    logging_level = app.config['LOGGING_LEVEL']  # 等级
    logging_file_dir = app.config['LOGGING_FILE_DIR']  # 路径
    logging_file_backup = app.config['LOGGING_FILE_BACKUP']  # 备份
    logging_file_max_bytes = app.config['LOGGING_FILE_MAX_BYTES']  # 最大占用空间

    # 日志信息会输出到控制中,如果stream为空则默认输出到sys.stderr。
    flask_console_handler = logging.StreamHandler()
    # 为处理器设置格式
    flask_console_handler.setFormatter(
        logging.Formatter('%(levelname)s %(pathname)s %(lineno)d %(message)s'))  # 级别 + 模块 + 行号 + 信息

    # 针对请求信息的日志格式:时间+   +请求地址+级别+模块+行号+信息
    request_formatter = RequestFormatter(
        '[%(asctime)s] %(remote_addr)s requested %(url)s\n%(levelname)s in %(pathname)s %(lineno)d: %(message)s')

    # flask.log循环日志文件 使用文件大小处理器
    flask_file_handler = logging.handlers.RotatingFileHandler(
        filename=os.path.join(logging_file_dir, 'flask.log'),  # 日志文件路径
        maxBytes=logging_file_max_bytes,  # 日志最大字节数
        backupCount=logging_file_backup,  # 做多保留几个日志
        encoding="UTF8",  # 日志编码
        delay=True  # 默认False,如果为True,则文件打开会被推迟至第一次调用(注意,在Flask中如果不设置为True,当文件满了,切换时会一直切不到空文件中)
    )
    flask_file_handler.setFormatter(request_formatter)  # 设置文件日志的记录格式

    # 获取flask.app日志
    log_flask_app = logging.getLogger('apps')  # 获取全局日志工具对象
    log_flask_app.addHandler(flask_file_handler)  # 为全局日志工具对象添加日志记录器
    log_flask_app.setLevel(logging_level)  # 设置级别

    # 获取输入到控制台的日志
    if app.debug:
        log_flask_app.addHandler(flask_console_handler)  # 如果是DEBUG模式,为全局日志对象增加控制台日志记录器

在Flask项目中,可以使用以下两种方式使用日志对象:

import logging
from flask import current_app

logging.info("使用方式1")
current_app.logger.debug("使用方式2")

日志配置文件

日志的配置除了前面介绍的将配置直接写在代码中,还可以将配置信息单独放在配置文件中,实现配置与代码分离。

日志配置文件logging.conf:

[loggers]
keys=root

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

 加载配置文件

import logging
import logging.config

# 加载配置文件
logging.config.fileConfig('logging.conf')

# 创建logger
logger = logging.getLogger()

# 输出日志
logger.debug('这是一个debug级别的日志信息')
logger.info('这是一个info级别的日志信息')
logger.warning('这是一个warning级别的日志信息')
logger.error('这是一个error级别的日志信息')
logger.critical('这是一个critical级别的日志信息')
2022-05-13 21:50:07,123 - root - DEBUG - 这是一个debug级别的日志信息
2022-05-13 21:50:07,123 - root - INFO - 这是一个info级别的日志信息
2022-05-13 21:50:07,123 - root - WARNING - 这是一个warning级别的日志信息
2022-05-13 21:50:07,123 - root - ERROR - 这是一个error级别的日志信息
2022-05-13 21:50:07,123 - root - CRITICAL - 这是一个critical级别的日志信息

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼义II虎神

打赏5C币,作者可获得4C币

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值