蒙珣的博客

活好当下,做好今天该做的事情。

0%

logging日志模块

Logging库日志级别

级别 级别数值 使用时机
DEBUG 10 详细信息,常用于调试
INFO 20 程序正常运行过程中产生的一些信息
WARNNING 30 警告用户,虽然程序还在正常工作,但有可能发生错误
ERROR 40 由于更严重的问题,程序已不能执行一些功能了
CRITICAL 50 严重错误,程序已不能继续运行

默认的日志级别是warning

我们来看一下代码

1
2
3
4
5
6
7
8
9
import logging

# 默认的日志输出级别为Warning

logging.debug("This is a debug message")
logging.info("This is an info")
logging.warning("This is an warning")
logging.error("This is an error")
logging.critical("This is an critical")

结果

1
2
3
WARNING:root:This is an warning
ERROR:root:This is an error
CRITICAL:root:This is an critical

正常不会打印出debug和info日志。现在我们更改下日志输出的级别,将日志的输出级别改成DEBUG

1
2
3
4
5
6
7
8
9
10
import logging

# 默认的日志输出级别为Warning
# 使用baseConfig()来指定日志输出级别
logging.basicConfig(level=logging.DEBUG)
logging.debug("This is a debug message")
logging.info("This is an info")
logging.warning("This is an warning")
logging.error("This is an error")
logging.critical("This is an critical")

结果

1
2
3
4
5
DEBUG:root:This is a debug message
INFO:root:This is an info
WARNING:root:This is an warning
ERROR:root:This is an error
CRITICAL:root:This is an critical

现在打印出了所有的日志了

Logging.basicConfig()

logging.basicConfig() 的参数除了日志等级的这个参数可以设置以外,还可以设置其他的参数,比如:

格式 描述
filename 使用指定的文件名创建一个 FileHandler,而不是 StreamHandler
filemode 如果指定了 filename,则用此 模式 打开该文件。 默认模式为 'a'
format 使用指定的格式字符串作为处理器。 默认为属性以冒号分隔的 levelname, namemessage
datefmt 使用指定的日期/时间格式,与 time.strftime() 所接受的格式相同。
style 如果指定了 format,将为格式字符串使用此风格。 '%', '{''$' 分别对应于 printf 风格, str.format()string.Template。 默认为 '%'
level 设置根记录器级别为指定的 level.
stream 使用指定的流初始化 StreamHandler。 请注意此参数与 filename 不兼容 —— 如果两者同时存在,则会引发 ValueError
handlers 如果指定,这应为一个包含要加入根日志记录器的已创建处理器的可迭代对象。 任何尚未设置格式描述符的处理器将被设置为在此函数中创建的默认格式描述符。 请注意此参数与 filenamestream 不兼容 —— 如果两者同时存在,则会引发 ValueError
force 如果将此关键字参数指定为 true,则在执行其他参数指定的配置之前,将移除并关闭附加到根记录器的所有现有处理器。
encoding 如果此关键字参数与 filename 一同被指定,则其值会在创建 FileHandler 时被使用,因而也会在打开输出文件时被使用。
errors 如果此关键字参数与 filename 一同被指定,则其值会在创建 FileHandler 时被使用,因而也会在打开输出文件时被使用。 如果未指定,则会使用值 ‘backslashreplace’。 请注意如果指定为 None,它将被原样传给 open(),这意味着它将会当作传入 ‘errors’ 一样处理。

上表提到的日志的输出格式参数format,其控制着日志输出的一些格式:

属性名称 格式 描述
args 此属性不需要用户进行格式化。 合并到 msg 以产生 message 的包含参数的元组,或是其中的值将被用于合并的字典(当只有一个参数且其类型为字典时)。
asctime %(asctime)s 表示人类易读的 LogRecord 生成时间。 默认形式为 ‘2003-07-08 16:49:45,896’ (逗号之后的数字为时间的毫秒部分)。
created %(created)f LogRecord 被创建的时间(即 time.time() 的返回值)。
exc_info 此属性不需要用户进行格式化。 异常元组(例如 sys.exc_info)或者如未发生异常则为 None
filename %(filename)s pathname 的文件名部分。
funcName %(funcName)s 函数名包括调用日志记录.
levelname %(levelname)s 消息文本记录级别('DEBUG''INFO''WARNING''ERROR''CRITICAL')。
levelno %(levelno)s 消息数字的记录级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL).
lineno %(lineno)d 发出日志记录调用所在的源行号(如果可用)。
message %(message)s 记入日志的消息,即 msg % args 的结果。 这是在发起调用 Formatter.format() 时设置的。
module %(module)s 模块 (filename 的名称部分)。
msecs %(msecs)d LogRecord 被创建的时间的毫秒部分。
msg 此属性不需要用户进行格式化。 在原始日志记录调用中传入的格式字符串。 与 args 合并以产生 message,或是一个任意对象 (参见 使用任意对象作为消息)。
name %(name)s 用于记录调用的日志记录器名称。
pathname %(pathname)s 发出日志记录调用的源文件的完整路径名(如果可用)。
process %(process)d 进程ID(如果可用)
processName %(processName)s 进程名(如果可用)
relativeCreated %(relativeCreated)d 以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。
stack_info 此属性不需要用户进行格式化。 当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。
thread %(thread)d 线程ID(如果可用)
threadName %(threadName)s 线程名(如果可用)

我们来看看怎么使用:

1
2
3
4
5
6
7
8
9
10
11
12
import logging

# 默认的日志输出级别为Warning

logging.basicConfig(filename='message.log',
format='%(asctime)s - %(name)s - %(filename)s:%(lineno)s - %(levelname)s - %(message)s',
level=logging.DEBUG)
logging.debug("This is a debug message")
logging.info("This is an info")
logging.warning("This is an warning")
logging.error("This is an error")
logging.critical("This is an critical")

filemode也是一个常用的参数,不设的话默认为'a',即追加模式;也可以设为'w',那么每次写日志会覆盖之前的日志。

上述产生的日志文件message.log结果如下:

1
2
3
4
5
2024-04-10 22:35:26,490 - root - test.py:8 - DEBUG - This is a debug message
2024-04-10 22:35:26,490 - root - test.py:9 - INFO - This is an info
2024-04-10 22:35:26,491 - root - test.py:10 - WARNING - This is an warning
2024-04-10 22:35:26,491 - root - test.py:11 - ERROR - This is an error
2024-04-10 22:35:26,491 - root - test.py:12 - CRITICAL - This is an critical

Logging组件

Logging中除了以上的一些设置之外,logging还提供了几个组件(类)供我们实现一些特殊的功能,它们主要是:

组件名称 对应类名 对应描述
日志器 Logger 提供了应用程序可以一直使用的接口
处理器 Handler 将创建的日志记录发送到合适的目的地输出
过滤器 Filter 提供了更细颗粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
格式器 Formatter 决定日志记录的最终输出格式

这些组件共同完成日志的配置和输出:

  • Logger需要通过handler将日志信息输出到目标位置,目标位置可以是sys.stdout和文件等。
  • 不同的Handler可以将日志输出到不同的位置(不同的日志文件)。
  • Logger可以设置多个handler将同一条日志记录输出到不同的位置。
  • 每个Handler都可以设置自己的filter从而实现日志过滤,保留实际项目中需要的日志。
  • formatter实现同一条日志以不同的格式输出到不同的地方。

简单的说就是:日志器作为入口,通过设置处理器的方式将日志输出,处理器再通过过滤器和格式器对日志进行相应的处理操作。

我们来简单的介绍一下这些组件中一些常用的方法:

Logger 日志器

  1. 提供应用程序的调用接口

    logger = logging.getLogger(__name__)

    logger是单例的

  2. 决定日志记录的级别

    logger.setLevel()

  3. 将日志内容传递到相关联的headlers

    logger.addHandler() 和 logger.removeHandler()

方法 方法描述
Logger.setLevel() 设置日志器将会处理的日志消息级别
Logger.addHander() 添加一个handler对象
Logger.addFilter() 添加一个filter对象
Logger.removeHander() 移除一个handler对象
Logger.removeFilter() 移除一个filter对象
Logger.debug() 设置DEBUG级别的日志记录
Logger.info() 设置INFO级别的日志记录
Logger.exception() 输出堆栈追踪信息
Logger.log() 设置一个自定义的lever参数来创建一个日志记录

Handler 处理器

Handler将日志分发到不同的目的地。可以是文件、标准输出、邮件、或者通过sockehttp等协议发送到任何地方

StreamHeadler

标准输出stdout(如显示器)分发器

创建方法:sh = logging.StreamHandler(stream=None)

FileHandler

将日志保存到磁盘文件的处理器

创建方法:fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)

setFormatter(): 设置当前handler对象使用的消息格式

方法 方法描述
logging.StreamHandler() 它可将日志记录输出发送到数据流例如 sys.stdout, sys.stderr 或任何文件型对象(或者更精确地说,任何支持 write()flush() 方法的对象)。
logging.FileHandler() 它可将日志记录输出到磁盘文件中
RotatingFileHandler() 将日志消息发送到磁盘文件,支持日志文件按大小切割
BaseRotatingHandler() 它是轮换文件处理程序类
TimedRotatingFileHandler() 将日志消息发送到磁盘文件,并支持日志文件按时间切割

Fliter 过滤器

看名字大家就知道这是一个过滤类,那么过滤什么呢?

Filter可以被Handler和Logger用来做比之前设置的日志等级level更为细粒度的、更复杂的相关过滤功能。

简单的说Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤,保存下来。该类定义如下:

1
2
class logging.Filter(name='')
filter(record)

过滤举例:

例如用 ‘A.B’ 初始化的 Filter,那么其允许Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’ 等日志记录的事件,logger‘A.BB’, ‘B.A.B’ 等就不满足过滤的条件。

如果用空字符串来对Filter初始化,所有日志记录的事件都将不会被过滤。

Formatter 格式器

Formater主要负责日志的格式化输出的

构造方法:ft = logging.Formatter.__init__(fmt=None, datefmt=None, style=' %')

datefmt默认是%Y-%m-%d %H:%M:%S样式的

style参数默认为百分符%,这表示%(<dictionary key>)s格式的字符串

如果使用logging的组件呢,来看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import logging

# logger 日志器
logger = logging.getLogger('applog')
logger.setLevel(logging.DEBUG) # console 和 file 想要打印不同的日志级别,需要logger设置成DEBUG

# 处理器handler
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG) # console 打印的日志级别

# 没有给handler指定日志级别,将使用logger的级别
fileHandler = logging.FileHandler(filename='message.log')
fileHandler.setLevel(logging.INFO) # file 打印日志级别

# formatter 格式
formatter = logging.Formatter('%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)s] [%(name)s] %(message)s')

# 给处理器设置格式
consoleHandler.setFormatter(formatter)
fileHandler.setFormatter(formatter)

# 记录器设置处理器
logger.addHandler(consoleHandler)
logger.addHandler(fileHandler)

# 日志打印
logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is an warning message')
logger.error('This is an error message')
logger.critical('This is an critical message')

结果如下

1
2
3
4
5
2024-04-10 23:46:28,312 [DEBUG] [test1.py:26] [applog] This is a debug message
2024-04-10 23:46:28,313 [INFO] [test1.py:27] [applog] This is an info message
2024-04-10 23:46:28,313 [WARNING] [test1.py:28] [applog] This is an warning message
2024-04-10 23:46:28,313 [ERROR] [test1.py:29] [applog] This is an error message
2024-04-10 23:46:28,313 [CRITICAL] [test1.py:30] [applog] This is an critical message

配置文件

conf形式配置文件

我们知道在**logging.basicConfig()**中,是进行一些日志的配置的,如果每次都去改动代码,那将变得十分的麻烦,特别是在程序发布或者上线的时候。所以我们可以使用一个什么好的方法来规避这个问题呢?有的,那就是配置文件。

我们在文件logging.conf中写入相关的信息,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[loggers]
keys=root,appname

[handlers]
keys=fileHandler,consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
; logger.addHandler(consoleHandler)
handlers=consoleHandler

[logger_appname]
level=DEBUG
handlers=fileHandler,consoleHandler
; 别名 logging.getLogger('appname')
qualname=appname
; 继承关系 0 不继承
propagate=0

[handler_consoleHandler]
; consoleHandler = logging.StreamHandler()
class=StreamHandler
; sys.stdout 标准输出,即 console 输出
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter

[handler_fileHandler]
class=handlers.TimedRotatingFileHandler
; 日志滚动更新,每天24点分隔日志,1 代表 delay多少s,0 代表文件保留的时间,0即不删除
args=('appname.log', 'midnight', 1,0)
level=DEBUG
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)s] [%(name)s] %(message)s
;datefmt=%Y-%m-%d %H:%M:%S

使用配置文件的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import logging.config

# 载入配置文件信息
logging.config.fileConfig('logging.conf')

rootLogger = logging.getLogger()
logger = logging.getLogger('appname')

logger.debug('debug message')
rootLogger.info('info message')

a = 'abc'

try:
int(a)
except Exception as e:
logger.exception(e) # 使用logger.exception() 捕获异常,而非 logger.error()

结果

1
2
3
4
5
6
7
2024-04-11 00:28:56,077 [DEBUG] [test2.py:9] [appname] debug message
2024-04-11 00:28:56,077 [INFO] [test2.py:10] [root] info message
2024-04-11 00:28:56,077 [ERROR] [test2.py:17] [appname] invalid literal for int() with base 10: 'abc'
Traceback (most recent call last):
File "/Users/william/Desktop/tmp/logger_manager/test2.py", line 15, in <module>
int(a)
ValueError: invalid literal for int() with base 10: 'abc'

自定义logger日志输出文件

以上介绍的知识点,大家在平时或在实际的项目中使用基本上就足够了,下面我们简单的介绍一下怎么进行自定义logger进行日志的输出。

我们写一个sel_def_logger.py文件,文件中的定义一个类,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import os
import sys
import logging
from time import strftime
# 输出日志路径
PATH = os.path.abspath('.') + '/logs/'
# 设置日志格式#和时间格式
FMT = '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s: %(message)s'
DATEFMT = '%Y-%m-%d %H:%M:%S'

class MyLog(object):
def __init__(self):
self.logger = logging.getLogger()
self.formatter = logging.Formatter(fmt=FMT, datefmt=DATEFMT)
self.log_filename = '{0}{1}.log'.format(PATH, strftime("%Y-%m-%d"))

self.logger.addHandler(self.get_file_handler(self.log_filename))
self.logger.addHandler(self.get_console_handler())
# 设置日志的默认级别
self.logger.setLevel(logging.DEBUG)

# 输出到文件handler的函数定义
def get_file_handler(self, filename):
filehandler = logging.FileHandler(filename, encoding="utf-8")
filehandler.setFormatter(self.formatter)
return filehandler

# 输出到控制台handler的函数定义
def get_console_handler(self):
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(self.formatter)
return console_handler

那么使用这个类的方法可以这样写:

1
2
3
4
5
6
import datetime
from sef_def_logger import MyLog
my_logg = MyLog().logger
my_logg.info("代码开始运行的时间{}".format(datetime.datetime.now()))
my_logg.debug('看看debug')
my_logg.error('This is a error')

Reference

logging — Python的日志记录工具

Python实用教程系列——Logging日志模块