python中的logging记录日志,python的logging模块详解

  python中的logging记录日志,python的logging模块详解

  本文主要介绍2022年最新Pythonlogging库的总结。Python日志库被设计得非常灵活。如果有特殊需求,可以在这个基础日志库上进行改进,创建新的Handler类来解决实际开发中的问题。有需要的朋友可以参考一下。

  00-1010 1、日志级别2、日志记录流程3、日志输出格式4、基本使用5、自定义日志器6、日志器配置7、实战中的问题。部署项目时,不可能直接将所有信息输出到控制台。我们可以将这些信息记录到日志文件中,不仅方便我们查看程序的运行情况,还可以根据项目运行过程中生成的日志快速定位问题的位置。

  

目录

  Python标准库日志记录用于日志记录,默认分为六个日志级别(括号内为级别对应的数值),NOTSET(0)、DEBUG(10)、INFO(20)、WARNING(30)、ERROR(40)和CRITICAL(50)。当我们自定义日志级别时,我们应该注意不要与默认的日志级别值相同。执行日志记录时,将输出大于或等于设置日志级别的日志信息。如果日志级别设置为info,将输出INFO、WARNING、ERROR和CRITICAL级别的日志。

  

1、日志级别

  官方测井模块流程图如下:

  我们可以从下图中看到这些Python类型:Logger,LogRecord,Filter,HandlerFormatter.

  类型描述:

  Logger:日志,向应用程序公开函数,并根据日志记录器和过滤器级别决定哪些日志是有效的。

  LogRecord:日志记录器,将日志传送到相应的处理器进行处理。

  Handler:将日志记录(由日志记录器生成)发送到适当目的地的处理器。

  Filter:过滤器提供了更好的粒度控制,可以决定输出哪些日志记录。

  Formatter:格式化程序,指示最终输出中日志记录的布局。

  判断Logger对象对于设定的级别是否可用,如果可用,继续执行,否则,流程结束。创建LogRecord对象。如果Logger对象中注册的Filter对象过滤后返回False,则不会记录日志,流程结束;否则,将向下执行。log对象将Handler对象传递给当前的LogRecord对象。(图中的子流程)如果Handler对象的日志级别高于设置的日志级别,则判断Handler对象中注册的Filter对象过滤后是否返回True,释放输出日志信息;否则,它将不会被释放,并且该过程结束。如果传入的处理程序大于Logger中设置的级别,即处理程序有效,则向下执行;否则,该过程结束。确定这个Logger对象是否还有一个父Logger对象,如果没有(意味着当前Logger对象是最顶层的Logger对象根Logger),过程结束。否则,将Logger对象设置为其父Logger对象,重复上述步骤3和4,并输出父Logger对象中的日志输出,直到它是根Logger。

  

2、logging 流程

  日志的输出格式可以认为是设置好的,默认格式如下图所示。

  

3、日志输出格式

  日志记录使用起来非常简单。basicConfig()方法可以满足基本需求。如果该方法没有传入参数,将根据默认配置创建Logger对象。默认日志级别设置为WARNING,默认日志输出格式如上图所示。下表显示了该功能的可选参数。

  参数名称参数描述

  th>filename日志输出到文件的文件名filemode文件模式,r[+]、w[+]、a[+]format日志输出的格式datefat日志附带日期时间的格式style格式占位符,默认为 "%" 和 {}level设置日志输出级别stream定义输出流,用来初始化 StreamHandler 对象,不能 filename 参数一起使用,否则会ValueError 异常handles定义处理器,用来创建 Handler 对象,不能和 filename 、stream 参数一起使用,否则也会抛出 ValueError 异常

  示例代码如下:

  

import logging

  logging.basicConfig()

  logging.debug(This is a debug message)

  logging.info(This is an info message)

  logging.warning(This is a warning message)

  logging.error(This is an error message)

  logging.critical(This is a critical message)

  输出结果如下:

  

WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message

  

  传入常用的参数,示例代码如下(这里日志格式占位符中的变量放到后面介绍):

  

import logging

  logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%m-%Y %H:%M:%S", level=logging.DEBUG)

  logging.debug(This is a debug message)

  logging.info(This is an info message)

  logging.warning(This is a warning message)

  logging.error(This is an error message)

  logging.critical(This is a critical message)

  生成的日志文件 test.log ,内容如下:

  

13-10-18 21:10:32 root:DEBUG:This is a debug message
13-10-18 21:10:32 root:INFO:This is an info message
13-10-18 21:10:32 root:WARNING:This is a warning message
13-10-18 21:10:32 root:ERROR:This is an error message
13-10-18 21:10:32 root:CRITICAL:This is a critical message

  

  但是当发生异常时,直接使用无参数的 debug()、info()、warning()、error()、critical() 方法并不能记录异常信息,需要设置 exc_info 参数为 True 才可以,或者使用 exception() 方法,还可以使用 log() 方法,但还要设置日志级别和 exc_info 参数。

  

import logging

  logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)

  a = 5

  b = 0

  try:

   c = a / b

  except Exception as e:

   # 下面三种方式三选一,推荐使用第一种

   logging.exception("Exception occurred")

   logging.error("Exception occurred", exc_info=True)

   logging.log(level=logging.DEBUG, msg="Exception occurred", exc_info=True)

  

  

  

5、自定义 Logger

  上面的基本使用可以让我们快速上手 logging 模块,但一般并不能满足实际使用,我们还需要自定义 Logger。

  一个系统只有一个 Logger 对象,并且该对象不能被直接实例化,没错,这里用到了单例模式,获取 Logger 对象的方法为 getLogger

  注意:这里的单例模式并不是说只有一个 Logger 对象,而是指整个系统只有一个根 Logger 对象,Logger 对象在执行 info()、error() 等方法时实际上调用都是根 Logger 对象对应的 info()、error() 等方法。

  我们可以创造多个 Logger 对象,但是真正输出日志的是根 Logger 对象。每个 Logger 对象都可以设置一个名字,如果设置logger = logging.getLogger(__name__),__name__ 是 Python 中的一个特殊内置变量,他代表当前模块的名称(默认为 __main__)。则 Logger 对象的 name 为建议使用使用以点号作为分隔符的命名空间等级制度。

  Logger 对象可以设置多个 Handler 对象和 Filter 对象,Handler 对象又可以设置 Formatter 对象。Formatter 对象用来设置具体的输出格式,常用变量格式如下表所示,所有参数见 Python(3.7)官方文档:

  变量格式变量描述asctime%(asctime)s将日志的时间构造成可读的形式,默认情况下是精确到毫秒,如 2018-10-13 23:24:57,832,可以额外指定 datefmt 参数来指定该变量的格式name%(name)日志对象的名称filename%(filename)s不包含路径的文件名pathname%(pathname)s包含路径的文件名funcName%(funcName)s日志记录所在的函数名levelname%(levelname)s日志的级别名称message%(message)s具体的日志信息lineno%(lineno)d日志记录所在的行号pathname%(pathname)s完整路径process%(process)d当前进程IDprocessName%(processName)s当前进程名称thread%(thread)d当前线程IDthreadName%threadName)s当前线程名称

  Logger 对象和 Handler 对象都可以设置级别,而默认 Logger 对象级别为 30 ,也即 WARNING,默认 Handler 对象级别为 0,也即 NOTSET。logging 模块这样设计是为了更好的灵活性,比如有时候我们既想在控制台中输出DEBUG 级别的日志,又想在文件中输出WARNING级别的日志。可以只设置一个最低级别的 Logger 对象,两个不同级别的 Handler 对象,示例代码如下:

  

import logging

  import logging.handlers

  logger = logging.getLogger("logger")

  handler1 = logging.StreamHandler()

  handler2 = logging.FileHandler(filename="test.log")

  logger.setLevel(logging.DEBUG)

  handler1.setLevel(logging.WARNING)

  handler2.setLevel(logging.DEBUG)

  formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")

  handler1.setFormatter(formatter)

  handler2.setFormatter(formatter)

  logger.addHandler(handler1)

  logger.addHandler(handler2)

  # 分别为 10、30、30

  # print(handler1.level)

  # print(handler2.level)

  # print(logger.level)

  logger.debug(This is a customer debug message)

  logger.info(This is an customer info message)

  logger.warning(This is a customer warning message)

  logger.error(This is an customer error message)

  logger.critical(This is a customer critical message)

  

  控制台输出结果为:

  

2018-10-13 23:24:57,832 logger WARNING This is a customer warning message
2018-10-13 23:24:57,832 logger ERROR This is an customer error message
2018-10-13 23:24:57,832 logger CRITICAL This is a customer critical message

  

  文件中输出内容为:

  

2018-10-13 23:44:59,817 logger DEBUG This is a customer debug message
2018-10-13 23:44:59,817 logger INFO This is an customer info message
2018-10-13 23:44:59,817 logger WARNING This is a customer warning message
2018-10-13 23:44:59,817 logger ERROR This is an customer error message
2018-10-13 23:44:59,817 logger CRITICAL This is a customer critical message

  

  创建了自定义的 Logger 对象,就不要在用 logging 中的日志输出方法了,这些方法使用的是默认配置的 Logger 对象,否则会输出的日志信息会重复。

  

import logging

  import logging.handlers

  logger = logging.getLogger("logger")

  handler = logging.StreamHandler()

  handler.setLevel(logging.DEBUG)

  formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")

  handler.setFormatter(formatter)

  logger.addHandler(handler)

  logger.debug(This is a customer debug message)

  logging.info(This is an customer info message)

  logger.warning(This is a customer warning message)

  logger.error(This is an customer error message)

  logger.critical(This is a customer critical message)

  输出结果如下(可以看到日志信息被输出了两遍):

  

2018-10-13 22:21:35,873 logger WARNING This is a customer warning message
WARNING:logger:This is a customer warning message
2018-10-13 22:21:35,873 logger ERROR This is an customer error message
ERROR:logger:This is an customer error message
2018-10-13 22:21:35,873 logger CRITICAL This is a customer critical message
CRITICAL:logger:This is a customer critical message

  

  说明:在引入有日志输出的 python 文件时,如 import test.py,在满足大于当前设置的日志级别后就会输出导入文件中的日志。

  

  

6、Logger 配置

  通过上面的例子,我们知道创建一个 Logger 对象所需的配置了,上面直接硬编码在程序中配置对象,配置还可以从字典类型的对象和配置文件获取。打开 logging.config Python 文件,可以看到其中的配置解析转换函数。

  从字典中获取配置信息:

  

import logging.config

  config = {

   version: 1,

   formatters: {

   simple: {

   format: %(asctime)s - %(name)s - %(levelname)s - %(message)s,

   },

   # 其他的 formatter

   },

   handlers: {

   console: {

   class: logging.StreamHandler,

   level: DEBUG,

   formatter: simple

   },

   file: {

   class: logging.FileHandler,

   filename: logging.log,

   level: DEBUG,

   formatter: simple

   },

   # 其他的 handler

   },

   loggers:{

   StreamLogger: {

   handlers: [console],

   level: DEBUG,

   },

   FileLogger: {

   # 既有 console Handler,还有 file Handler

   handlers: [console, file],

   level: DEBUG,

   },

   # 其他的 Logger

   }

  }

  logging.config.dictConfig(config)

  StreamLogger = logging.getLogger("StreamLogger")

  FileLogger = logging.getLogger("FileLogger")

  # 省略日志输出

  

  从配置文件中获取配置信息:

  常见的配置文件有 ini 格式、yaml 格式、JSON 格式,或者从网络中获取都是可以的,只要有相应的文件解析器解析配置即可,下面只展示了 ini 格式和 yaml 格式的配置。

  test.ini 文件

  

[loggers]

  keys=root,sampleLogger

  [handlers]

  keys=consoleHandler

  [formatters]

  keys=sampleFormatter

  [logger_root]

  level=DEBUG

  handlers=consoleHandler

  [logger_sampleLogger]

  level=DEBUG

  handlers=consoleHandler

  qualname=sampleLogger

  propagate=0

  [handler_consoleHandler]

  class=StreamHandler

  level=DEBUG

  formatter=sampleFormatter

  args=(sys.stdout,)

  [formatter_sampleFormatter]

  format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

  testinit.py 文件

  

import logging.config

  logging.config.fileConfig(fname=test.ini, disable_existing_loggers=False)

  logger = logging.getLogger("sampleLogger")

  # 省略日志输出

  test.yaml 文件

  

version: 1

  formatters:

   simple:

   format: %(asctime)s - %(name)s - %(levelname)s - %(message)s

  handlers:

   console:

   class: logging.StreamHandler

   level: DEBUG

   formatter: simple

  loggers:

   simpleExample:

   handlers: [console]

   propagate: no

  root:

   level: DEBUG

   handlers: [console]

  testyaml.py 文件

  

import logging.config

  # 需要安装 pyymal 库

  import yaml

  with open(test.yaml, r) as f:

   config = yaml.safe_load(f.read())

   logging.config.dictConfig(config)

  logger = logging.getLogger("sampleLogger")

  # 省略日志输出

  

  

  

7、实战中的问题

  1、中文乱码

  上面的例子中日志输出都是英文内容,发现不了将日志输出到文件中会有中文乱码的问题,如何解决到这个问题呢?FileHandler 创建对象时可以设置文件编码,如果将文件编码设置为 utf-8(utf-8 和 utf8 等价),就可以解决中文乱码问题啦。一种方法是自定义 Logger 对象,需要写很多配置,另一种方法是使用默认配置方法 basicConfig(),传入 handlers 处理器列表对象,在其中的 handler 设置文件的编码。网上很多都是无效的方法,关键参考代码如下:

  

# 自定义 Logger 配置

  handler = logging.FileHandler(filename="test.log", encoding="utf-8")

  

  

# 使用默认的 Logger 配置

  logging.basicConfig(handlers=[logging.FileHandler("test.log", encoding="utf-8")], level=logging.DEBUG)

  

  2、临时禁用日志输出

  有时候我们又不想让日志输出,但在这后又想输出日志。如果我们打印信息用的是 print() 方法,那么就需要把所有的 print() 方法都注释掉,而使用了 logging 后,我们就有了一键开关闭日志的 "魔法"。一种方法是在使用默认配置时,给 logging.disabled() 方法传入禁用的日志级别,就可以禁止设置级别以下的日志输出了,另一种方法时在自定义 Logger 时,Logger 对象的 disable 属性设为 True,默认值是 False,也即不禁用。

  

logging.disable(logging.INFO)

  

  

logger.disabled = True

  3、日志文件按照时间划分或者按照大小划分

  如果将日志保存在一个文件中,那么时间一长,或者日志一多,单个日志文件就会很大,既不利于备份,也不利于查看。我们会想到能不能按照时间或者大小对日志文件进行划分呢?答案肯定是可以的,并且还很简单,logging 考虑到了我们这个需求。logging.handlers 文件中提供了 TimedRotatingFileHandlerRotatingFileHandler 类分别可以实现按时间和大小划分。打开这个 handles 文件,可以看到还有其他功能的 Handler 类,它们都继承自基类 BaseRotatingHandler

  

# TimedRotatingFileHandler 类构造函数

  def __init__(self, filename, when=h, interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):

  # RotatingFileHandler 类的构造函数

  def __init__(self, filename, mode=a, maxBytes=0, backupCount=0, encoding=None, delay=False)

  

  示例代码如下:

  

# 每隔 1000 Byte 划分一个日志文件,备份文件为 3 个

  file_handler = logging.handlers.RotatingFileHandler("test.log", mode="w", maxBytes=1000, backupCount=3, encoding="utf-8")

  

# 每隔 1小时 划分一个日志文件,interval 是时间间隔,备份文件为 10 个

  handler2 = logging.handlers.TimedRotatingFileHandler("test.log", when="H", interval=1, backupCount=10)

  Python 官网虽然说 logging 库是线程安全的,但在多进程、多线程、多进程多线程环境中仍然还有值得考虑的问题,比如,如何将日志按照进程(或线程)划分为不同的日志文件,也即一个进程(或线程)对应一个文件。

  总结:Python logging 库设计的真的非常灵活,如果有特殊的需要还可以在这个基础的 logging 库上进行改进,创建新的 Handler 类解决实际开发中的问题。

  到此这篇关于2022最新Python日志库logging总结的文章就介绍到这了,更多相关Python日志库logging内容请搜索盛行IT软件开发工作室以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT软件开发工作室!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: