python初识日志记录logging模块

本文介绍了Python内置的logging模块,详细讲解了如何使用logging进行日志记录,包括配置全局日志、Handler、Formatter、捕获Traceback和使用文件配置。通过实例展示了如何将日志输出到控制台、文件,以及如何设置日志格式和级别,帮助开发者更好地管理和维护日志信息。
摘要由CSDN通过智能技术生成

python 初识 logging 模块

需求

因为最近公司有一个监听微信公众号消息的项目需要24小时运行,测试了一段时间要部署到线上了,感觉不能再无脑使用Print,打印的消息也不能24小时盯着,还是用日志保存起来规范。Python 的 logging 模块早有耳闻,而且也再很多项目中看见过,但是因为自己的项目都比较小型,平时很少用到,一直对这个模块模模糊糊的感觉,这次也抽了一天来学习,接下来就进入正题吧。

日志的好处

  1. 持久的记录,因为print的输出信息打印在控制台的消息有上限,而且容易被清除掉。
  2. Logging模块的输出语句可以通过多个处理器输出到任意位置,如即打印再控制台,又输出到日志文件,还能写入远程服务器等等,而且配置很简单。
  3. logging能输出很多额外的信息,时间,文件名,函数名,路径,线程id等等

下面看一下基本用法,第一个实例

import logging

logging.basicConfig(level=logging.INFO, 
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

logger.info('info')
logger.debug('debug')
logger.warning('warning')
logger.error('error')

​ 先看一下输出结果:

2019-05-14 20:09:54,906 - __main__ - INFO - info
2019-05-14 20:09:54,906 - __main__ - WARNING - warning
2019-05-14 20:09:54,906 - __main__ - ERROR - error

​ 我们逐句分析一下代码,从第一行开始 logging 是 python 内置的模块,直接导入即可,然后第二句代码是配置,通过 logging.basicConfig 配置全局的日志 level 和 format 信息。

​ level 顾名思义是等级的意思,下面是官方的等级信息表,每个等级都对应的一个数值,

也就是 level=logging.INFO 也可以写成 level = 20。这里我们设置成INFO,也就是只输出INFO 级别以上的信息。

等级数值
CRITICAL50
FATAL50
ERROR40
WARNING30
WARN30
INFO20
DEBUG10
NOTSET0

​ 另外我们还指定了 format 格式的字符串,包括 asctime、name、levelname、message 四个内容,分别代表运行时间、模块名称、日志级别、日志内容,输出语句,即日志的输出语句就是这四者的组合。还有很多 LogRecord attributes 也就是日志记录的属性,这里我从官方的表中选了一些比较有用的日志记录属性贴出来。

属性名称格式描述
asctime%(asctime)sLogRecord创建时的人类可读时间 。默认情况下,格式为’2003-07-08 16:49:45,896’(逗号后面的数字是毫秒部分时间)。
created%(created)fLogRecord创建时间(返回时间time.time())。
exc_infoYou shouldn’t need to format this yourself.异常元组(sys.exc_info)如果没有发生异常,则为None
filename%(filename)s调用日记的文件名pathname
funcName%(funcName)s包含日志记录调用的函数的名称。
levelname%(levelname)s文本日志记录级别消息('DEBUG''INFO''WARNING''ERROR''CRITICAL')。
LINENO%(lineno)d发出日志记录调用的源行号(如果代码没出错的话)。
message%(message)s记录的消息,可以使用 msg %args的形式传入消息
module%(module)s模块(名称部分filename)。
msg%(msecs)dLogRecord创建时的毫秒部分时间 。
name%(name)s记录器的名称。
pathname%(pathname)s发出日志记录调用的源文件的完整路径名(如果可用)。
process%(process)d进程ID(如果可用)。
processName%(processName)s进程名称(如果可用)。
thread%(thread)d线程ID(如果可用)。
threadName%(threadName)s线程名称(如果可用)。

​ 第三行代码声明了一个 logger 对象,它是日志输出的主类。官方声明:

Logger永远不会直接实例化,而是始终通过模块级函数实例化 logging.getLogger(name)getLogger()具有相同名称的多次调用将始终返回对同一Logger对象的引用。

​ 在初始化的时候我们传入了模块的名称,这里直接使用__name__ 来代替了,就是模块的名称,如果直接运行这个脚本的话就是__main__,如果是 import 的模块的话就是被引入模块的名称, 这个变量在不同的模块中的名字是不同的,所以一般使用__name__来表示就好了。例如我们把上面的脚本直接在别的文件导入,然后运行,结果如下:

import log_test1
2019-05-14 20:56:23,089 - log_test1 - INFO - info
2019-05-14 20:56:23,089 - log_test1 - WARNING - warning
2019-05-14 20:56:23,089 - log_test1 - ERROR - error

​ 我们再回顾一下刚才再主文件的输出

2019-05-14 20:09:54,906 - __main__ - INFO - info
2019-05-14 20:09:54,906 - __main__ - WARNING - warning
2019-05-14 20:09:54,906 - __main__ - ERROR - error

​ 第 4-7 行代码我们分别调用 logger 对象的 debug(), info(),warning(), error()等方法输出日志信息。可以看到只输出了3条日志信息,每条日志对应了指定的格式化内容,另外 DEBUG 的信息没有输出,因为我们在 logging.basicConfig 中的 level 设置为了 INFO ,所以只有大于 INFO 级别的日志会输出, 从日记表中可以看出 DEBUG 级别低于 INFO ,所以就过滤掉了。

​ 我们刚才只使用到了 logging.basicConfig 的2个参数,现在全面得了解一些全局配置有那些参数,这里引用一下官方的文档:

FormatDescription
filename即日志输出的文件名,如果指定了这个信息之后,实际上会启用 FileHandler,而不再是 StreamHandler,这样日志信息便会输出到文件中了。
filemode这个是指定日志文件的写入方式,有两种形式,一种是 w,一种是 a,分别代表清除后写入和追加写入,默认为 a 。
format使用指定的格式字符串作为处理程序。
datefmt使用指定的日期/时间格式,如time.strftime()所示
style如果 format 参数指定了,这个参数就可以指定格式化时的占位符风格,如 %、{、$ 等。
level指定日志输出的类别,程序会输出大于等于此级别的信息。
stream在没有指定 filename 的时候会默认使用 StreamHandler,这时 stream 可以指定初始化的文件流。
handlers可以指定日志处理时所使用的 Handlers,必须是可迭代的。

​ 我们再用一个实例来展示这些参数

import logging
logging.basicConfig(level=10,
                    filename='Filehandler_test.log',
                    format='%(asctime)s - %(filename)s - '
                           '%(name)s - %(levelname)s - '
                           '%(lineno)d - %(module)s - '
                           '%(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',
                    filemode='a')
logger = logging.getLogger(__name__)

logger.info('info message')
logger.debug('debug message')
logger.warning('Warning message')
logger.error('error message')

​ 这里 basicConfig 指定了输出文件的名称为 Filehandler_test.log,所有参数分别是日志的记录等级 level ,输出 format 格式化输出字段 format , 时间的输出格式 datefmt ,变成了 %Y-%m-%d,还有写入日志的方法 filemode 是 ‘a’ 即追加。

​ 另外输出的 format 格式增加了里面分别是:字符串格式的时间 %(asctime)s,文件名 %(filename)s,记录器的名称 %(name)s, 等级 %(levelname)s, 代码中写入日志的行数 %(lineno)d, 模块名(即去掉.py的文件名) %(module)s,输出消息 %(message)s。

​ 运行一下代码,可以看到代码目录下创建了一个 Filehandler_test.log 文件,其中内容如下:

2019-05-16 15:20:43 - log_test1.py - __main__ - INFO - 12 - log_test1 - info message
2019-05-16 15:20:43 - log_test1.py - __main__ - DEBUG - 13 - log_test1 - debug message
2019-05-16 15:20:43 - log_test1.py - __main__ - WARNING - 14 - log_test1 - Warning message
2019-05-16 15:20:43 - log_test1.py - __main__ - ERROR - 15 - log_test1 - error message

​ 从左到右对应 format 中的参数。

Handler

​ 接下来介绍一个我们上面提到一个名称 Handler ,百度翻译是“处理程序”的意思,以下是官方文档给出的 handler 子类

  1. StreamHandler 实例将消息发送到流(类文件对象), 一般用来输出到控制台。

  2. FileHandler 实例将消息发送到磁盘文件。

  3. BaseRotatingHandler是在某个点旋转日志文件的处理程序的基类,不建议直接实例化。建议使用RotatingFileHandlerTimedRotatingFileHandler

  4. RotatingFileHandler 实例将消息发送到磁盘文件,支持最大日志文件大小和日志文件轮换。

  5. TimedRotatingFileHandler 实例将消息发送到磁盘文件,以特定的时间间隔旋转日志文件。

  6. SocketHandler实例将消息发送到TCP / IP套接字。从3.4开始,也支持Unix域套接字。

  7. DatagramHandler实例将消息发送到UDP套接字。从3.4开始,也支持Unix域套接字。

  8. SMTPHandler 实例将消息发送到指定的电子邮件地址。

  9. SysLogHandler 实例将消息发送到Unix syslog守护程序,可能在远程计算机上。

  10. NTEventLogHandler 实例将消息发送到Windows NT / 2000 / XP事件日志。

  11. MemoryHandler 实例将消息发送到内存中的缓冲区,只要满足特定条件,就会刷新。

  12. HTTPHandler实例使用任一语义GETPOST语义将消息发送到HTTP服务器。

  13. WatchedFileHandler实例会监视他们要登录的文件。如果文件发生更改,则会关闭该文件并使用文件名重新打开。此处理程序仅在类Unix系统上有用; Windows不支持使用的基础机制。

  14. QueueHandler实例将消息发送到队列,例如在queuemultiprocessing模块中实现的那些队列。

  15. NullHandler实例对错误消息不做任何事情。它们由想要使用日志记录的库开发人员使用,但是想要避免“如果库用户没有配置日志记录,则可以显示”无法找到记录器XXX的消息处理器“消息。有关更多信息,请参阅配置库的日志记录

    下面我们使用二个 Handler 来实现日志同时输出到控制台、文件:

import logging
import sys

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

# StreamHandler
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=logging.DEBUG)
logger.addHandler(stream_handler)

# FileHandler
file_handler = logging.FileHandler('Filehandler_test2.log')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# Log
logger.info('info message')
logger.debug('debug message')
logger.warning('Warning message')
logger.error('error message')

控制台输出如下:

info message
debug message
Warning message
error message

文件输出如下:

2019-05-16 15:55:42,216 - __main__ - INFO - info message
2019-05-16 15:55:42,216 - __main__ - WARNING - Warning message
2019-05-16 15:55:42,216 - __main__ - ERROR - error message

控制台输出简单的原因是,我们没对控制台的 StreamHandler 配置 Formatter ,所以控制台只输出了日记的内容,没有包含其他格式化信息。而 FileHandler 设着了 Formatter 后,就又了输出格式后的信息。

​ 另外每个 Handler 还可以设置 level 信息,最终输出结果的 level 信息会取 Logger 对象的 level 和 Handler 对象的 level 的交集。

Formatter

​ 在进行日志格式化输出的时候,我们可以不借助于 basicConfig 来全局配置格式化输出内容,使用 Formatter 来对每个 handler 作不同的处理,下面我们再来单独看下 Formatter 的用法:

import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.WARN)
formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.debug('Debugging')
logger.critical('Critical Something')
logger.error('Error Occurred')
logger.warning('Warning exists')
logger.info('Finished')

​ 在第四行我们指定了一个 Formatter,并传入了 fmt 和 datefmt 参数,这样就指定了日志结果的输出格式和时间格式,然后 handler 通过 setFormatter() 方法设置此 Formatter 对象即可,输出结果如下:

2019-05-16 16:11:18 - __main__ - WARNING - Warning message
2019-05-16 16:11:18 - __main__ - ERROR - error message

​ 这样我们就可以每个 Handler 单独配置输出的格式,非常灵活。

捕获 Traceback

​ logging 有2种方法可以输出详细的 Traceback 错误信息,下面看一个实例:

import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

# Formatter
formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

# StreamHandler
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)

# log
logger.debug('debug message')
logger.info('info message')
try:
    result = 1 / 0
except ZeroDivisionError:
    logger.error('Faild result', exc_info=True)
    logger.exception('Faild result')
logger.info('finish')

​ 第一种方法:我们在 error() 方法中添加了一个参数,将 exc_info 设置为了 True,这样我们就可以输出执行过程中的信息了,即完整的 Traceback 信息。

​ 第二种方法:使用 logger.exception() 其实它的源码就是调用了 logger.error() 方法。

​ 上面的代码运行结果如下:

2019-05-16 16:23:55 - __main__ - INFO - info message
2019-05-16 16:23:55 - __main__ - ERROR - Faild result
Traceback (most recent call last):
  File "D:/cjx/python-work/spider_test/logging_test/log_test2.py", line 42, in <module>
    result = 1 / 0
ZeroDivisionError: division by zero
2019-05-16 16:23:55 - __main__ - ERROR - Faild result
Traceback (most recent call last):
  File "D:/cjx/python-work/spider_test/logging_test/log_test2.py", line 42, in <module>
    result = 1 / 0
ZeroDivisionError: division by zero
2019-05-16 16:23:55 - __main__ - INFO - finish

​ 中间有2个 ERROR 级别的日志完整德记录下了错误,方便我们排查。

使用文件配置

官方文档中提到:在Python 3.2中,引入了一种新的配置日志记录的方法,使用字典来保存配置信息,是新应用程序和部署的推荐配置方法。因为Python字典用于保存配置信息,并且由于您可以使用不同的方式填充该字典,因此您有更多的配置选项。例如,您可以使用 JSON 格式的配置文件,或者,如果您有权访问 YAML 处理功能,则可以使用 YAML 格式的文件来填充配置字典。或者,当然,您可以在Python代码中构造字典,通过套接字以pickle形式接收它,或者使用对您的应用程序有意义的任何方法。

​ 将配置写在配置文件里面,我们可以将配置写入到配置文件,然后运行时读取配置文件里面的配置,这样是更方便管理和维护的,下面我们以一个实例来说明一下,首先我们定义一个 yaml 配置文件:

version: 1.0
formatters:
    brief:
        format: "%(asctime)s - %(levelname)s - %(message)s"
        datefmt: "%Y-%m-%d %H:%M:%S"
    simple:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        datefmt: "%Y-%m-%d %H:%M:%S"
handlers:
    console:
        class: logging.StreamHandler
        formatter: brief
        level: DEBUG
        stream: ext://sys.stdout
    file:
        class: logging.FileHandler
        formatter: simple
        level: INFO
        filename: INFO.log
        encoding: utf8
    error:
        class: logging.handlers.RotatingFileHandler
        level: ERROR
        formatter: simple
        filename: ERROR.log
        maxBytes: 10485760
        backupCount: 20
        encoding: utf8
loggers:
    main.core:
        level: DEBUG
        handlers: [console, file, error]
        propagate: False
root:
    level: DEBUG
    handlers: [console]

​ 这里我们定义了 formatters、handlers、loggers、root 等模块,实际上对应的就是各个 Formatter、Handler、Logger 的配置,参数和它们的构造方法都是相同的。

​ 注意其中 loggers 里面的 propagate 一定要设置为 False,不然会和 root 的输出重复打印。

​ 接下来我们定义一个主入口文件,main.py,内容如下

import logging
import core
import yaml
import logging.config
import os


def setup_logging(default_path='config.yaml', default_level=logging.INFO):
    path = default_path
    if os.path.exists(path):
        with open(path, 'r', encoding='utf-8') as f:
            config = yaml.full_load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)


def log():
    logging.debug('Start')
    logging.info('Exec')
    logging.info('Finished')


if __name__ == '__main__':
    yaml_path = 'config.yaml'
    setup_logging(yaml_path)
    log()
    core.run()

​ 其中读取 yaml 文件需要使用对应的库 pyyaml,我们先安装一下

pip install pyyaml

​ 另外这个模块还引入了另外一个模块 core,所以我们定义 core.py 如下:

import logging

logger = logging.getLogger('main.core')

def run():
    logger.info('Core Info')
    logger.debug('Core Debug')
    logger.error('Core Error')

​ 观察配置文件,主入口文件 main.py 实际上对应的是 root 一项配置,它指定了 handlers 是 console,即只输出到控制台。另外在 loggers 一项配置里面,我们定义了 main.core 模块,handlers 是 console、file、error 三项,即输出到控制台、输出到普通文件和回滚文件。

​ 这样运行之后,我们便可以看到所有的运行结果输出到了控制台:

2019-05-16 17:09:26 - DEBUG - Start
2019-05-16 17:09:26 - INFO - Exec
2019-05-16 17:09:26 - INFO - Finished
2019-05-16 17:09:26 - INFO - Core Info
2019-05-16 17:09:26 - DEBUG - Core Debug
2019-05-16 17:09:26 - ERROR - Core Error

​ 在 INFO.log 文件中则包含了 core.py 的运行结果:

2019-05-16 17:12:37 - main.core - INFO - Core Info
2019-05-16 17:12:37 - main.core - ERROR - Core Error

​ 可以看到,通过配置文件,我们可以非常灵活地定义 Handler、Formatter、Logger 等配置,同时也显得非常直观,也非常容易维护,在实际项目中,推荐使用此种方式进行配置。

参考资料

本文章引用 静觅 » Python中logging模块的基本用法 , 并在其之上修改补充了一些内容。

https://cuiqingcai.com/6080.html

https://docs.python.org/3/library/logging.html

http://www.cnblogs.com/dahu-daqing/p/7040764.html

https://docs.python.org/zh-cn/3/howto/logging-cookbook.html#logging-cookbook

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值