Python编程-使用logging管理程序日志
自动化运维时时常用到,故抽时间专门总结
Python的logging模块是Python内置的日志记录工具,用于在程序中实现灵活的日志记录功能。使用logging模块可以记录程序运行时的各种信息,如调试信息、警告、错误等,以便在需要时进行排查和分析。
Python的logging模块定义了5个日志级别,用于指示日志消息的重要程度,从低到高分别为:
- DEBUG:最详细的日志级别,通常用于调试目的,记录详细的系统状态信息。对于生产环境,应该将DEBUG日志级别关闭,以避免记录过多的信息。
- INFO:用于记录程序的正常操作信息,例如启动信息、关闭信息等。对于生产环境,INFO级别是默认的日志级别,可以提供足够的信息来了解系统的运行情况。
- WARNING:警告级别,表示可能出现的问题,但不会影响系统的正常运行。例如,使用了过期的函数或者接口,但是系统仍然能够正常工作。
- ERROR:错误级别,表示出现了一些问题,但不会导致系统崩溃。例如,文件无法读取、网络连接失败等。
- CRITICAL:最高级别的日志级别,表示严重问题,可能会导致系统崩溃或无法继续运行。例如,关键文件丢失、关键服务停止等。
需要注意的是,logging允许我们自定义日志级别,但是最好不要这样操作,有可能导致日志级别出现混乱,加大维护与管理的难度
日志处理模块的四大组件
- Logger(日志器):Logger对象用于创建和记录日志消息。可以通过
logging.getLogger(name)
方法获取Logger对象,其中name
参数为日志器的名称。每个Logger对象都可以设置自己的日志级别和处理器,用于控制日志消息的输出。 - Handler(处理器):Handler对象用于将日志消息发送到指定的位置,如控制台、文件、网络等。可以通过
logging.StreamHandler
、logging.FileHandler
等类创建不同类型的处理器。每个Handler对象都可以设置自己的日志级别和格式,用于控制输出的细节。 - Formatter(格式器):Formatter对象用于指定日志消息的输出格式。可以通过
logging.Formatter
类创建格式化器,并将其应用到Handler对象中。可以自定义输出的格式,包括时间、日志级别、日志名称、日志消息等信息。 - Filter(过滤器):Filter对象用于过滤日志消息,只有符合条件的日志消息才会被处理和输出。可以通过继承
logging.Filter
类创建自定义的过滤器,并将其应用到Logger或Handler对象中。过滤器可以根据日志消息的属性(如级别、名称、消息内容等)进行过滤。
日志器Logger使用案例
日志器的创建
日志器的创建一个是通过其本身的构造器创建,它接收两个参数,一个是日志器名称,一个是日志器最低等级。Logger的构造器中的level参数用于设置日志器的最低级别(或者说最低优先级),低于该级别的日志消息将被忽略。level参数的类型是整数,对应着不同的日志级别,如下所示:
logging.DEBUG
(整数值为10):调试信息级别,最低的日志级别。logging.INFO
(整数值为20):信息消息级别。logging.WARNING
(整数值为30):警告消息级别。logging.ERROR
(整数值为40):错误消息级别。logging.CRITICAL
(整数值为50):严重错误消息级别,最高的日志级别。
通过设置level参数,可以控制日志记录器记录哪些级别以上的日志消息。例如,如果将level参数设置为logging.WARNING
,则只有警告级别及以上的日志消息才会被记录,而调试和信息级别的消息将被忽略。
import logging
logging.Logger(name='my_logger', level=logging.DEBUG)
一个是通过getLogger([name])
来进行创建,它只接受日志器的名称
import logging
logging.getLogger(name='my_logger')
注意:在未指定日志级别时,默认为
Warning
级别日志
日志器的常用方法
方法 | 描述 |
---|---|
debug(msg, *args, **kwargs) | 记录调试级别的日志消息。 |
info(msg, *args, **kwargs) | 记录信息级别的日志消息。 |
warning(msg, *args, **kwargs) | 记录警告级别的日志消息。 |
error(msg, *args, **kwargs) | 记录错误级别的日志消息。 |
critical(msg, *args, **kwargs) | 记录严重级别的日志消息。 |
exception(msg, *args, **kwargs) | 类似于error()方法,但是会同时记录异常信息。 |
log(level, msg, *args, **kwargs) | 记录指定级别的日志消息。 |
setLevel(level) | 设置日志记录的最低级别。 |
addHandler(hdlr) | 添加日志处理器(handler)到日志器中。 |
removeHandler(hdlr) | 从日志器中移除指定的日志处理器。 |
setFormatter(fmt) | 设置日志消息的格式化器(formatter)。 |
hasHandlers() | 检查日志器是否有任何处理器。 |
创建并添加日志
import logging
# 创建Logger对象
my_logger = logging.getLogger('my_logger')
my_logger.setLevel(logging.DEBUG)
# 添加五种不同级别的日志消息
my_logger.debug('This is a DEBUG message')
my_logger.info('This is an INFO message')
my_logger.warning('This is a WARNING message')
my_logger.error('This is an ERROR message')
my_logger.critical('This is a CRITICAL message')
注意:日志器是一个单例模式,在未指定
name
时,多次处理均为同一个logger
对象,要解决这个问题可以运行结束后移除处理器,或者对每次的日志器进行不同的命名(网上看到有人在提,不给过个人觉得一般不会这样写程序)import logging def self_define_log() -> logging.Logger: logger = logging.getLogger() logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler() console_handler.setLevel(logging.DEBUG) logger.addHandler(console_handler) return logger if __name__ == "__main__": self_define_log().info('This is an INFO message') self_define_log().debug('This is a DEBUG message')
我们将会得到输出结果:
This is an INFO message This is a DEBUG message This is a DEBUG message
日志处理器Handler使用案例
日志处理器需要通过Logger的addHandler
方法来进行添加,我们给出常用方法表进行展示:
方法 | 描述 |
---|---|
setLevel(level) | 设置处理器将处理的日志消息的最低级别。 |
setFormatter(formatter) | 为处理器设置日志消息的格式化方式。 |
addFilter(filter) | 添加过滤器,过滤器可以根据特定的条件决定是否处理日志消息。 |
removeFilter(filter) | 移除指定的过滤器。 |
但是我们使用它的时候实质上使用的是它的子类对象:
子类对象 | 描述 |
---|---|
StreamHandler | 将日志消息输出到流(通常是sys.stdout或sys.stderr)。 |
FileHandler | 将日志消息写入到文件中。 |
RotatingFileHandler | 将日志消息写入到文件中,并可以在达到一定大小或时间间隔时自动切割文件。 |
TimedRotatingFileHandler | 类似于RotatingFileHandler,但是根据时间间隔来切割文件,比如每天、每周或每月。 |
SocketHandler | 将日志消息发送到网络套接字。 |
SMTPHandler | 将日志消息通过电子邮件发送。 |
MemoryHandler | 将日志消息存储在内存中,可以设置当达到一定数量或大小时将日志消息写入到其他处理器(如FileHandler)中。 |
QueueHandler | 将日志消息放入队列中,可以与多个处理器共享一个队列,用于实现多线程日志记录。 |
NullHandler | 空处理器,用于禁用日志记录。 |
HTTPHandler | 将日志消息发送到远程HTTP服务器。 |
将日志输出到控制台
import logging
import logging.handlers
logger = logging.getLogger('example_logger')
logger.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
logger.addHandler(stream_handler)
logger.debug('This is a DEBUG message')
logger.info('This is an INFO message')
logger.warning('This is a WARNING message')
logger.error('This is an ERROR message')
logger.critical('This is a CRITICAL message')
要输出DEBUG
和INFO
级别的日志消息,需要在添加处理程序时将其级别设置为相应的级别。默认情况下,StreamHandler
的级别是WARNING
,因此它将忽略DEBUG
和INFO
级别的消息。要输出这些级别的消息,需要在创建StreamHandler
时将其级别设置为DEBUG
或更低级别。
将日志发送到指定服务器
import logging
import logging.handlers
logger = logging.getLogger('example_logger')
logger.setLevel(logging.DEBUG)
# 创建HTTPHandler,指定远程HTTP服务器的URL和HTTP方法
http_handler = logging.handlers.HTTPHandler('localhost:8000', '/log', method='POST')
http_handler.setLevel(logging.DEBUG)
logger.addHandler(http_handler)
logger.debug('This is a DEBUG message sent via HTTP')
logger.info('This is an INFO message sent via HTTP')
logger.warning('This is a WARNING message sent via HTTP')
logger.error('This is an ERROR message sent via HTTP')
logger.critical('This is a CRITICAL message sent via HTTP')
日志过滤器使用案例
在使用日志过滤器时,我们需要自定义一个Filter类,在该类中重写filter方法,来自定义过滤条件:
- 基于Logger名称过滤:可以根据Logger的名称来过滤日志记录,只输出特定名称的Logger的日志记录。
class CustomFilter(logging.Filter):
def filter(self, record):
return record.name == 'example'
filter_instance = CustomFilter()
logger.addFilter(filter_instance)
- 基于日志消息内容过滤:可以根据日志消息的内容来过滤日志记录,只输出包含特定内容的日志记录。
class CustomFilter(logging.Filter):
def filter(self, record):
return 'error' in record.msg.lower()
filter_instance = CustomFilter()
logger.addFilter(filter_instance)
- 基于创建线程过滤:可以根据其他条件来过滤日志记录,比如根据日志记录的创建时间、线程名等。
class CustomFilter(logging.Filter):
def filter(self, record):
return record.threadName == 'MainThread'
filter_instance = CustomFilter()
logger.addFilter(filter_instance)
- 基于日志等级过滤:指定日志的等级条件来过滤日志
class CustomFilter(logging.Filter):
def filter(self, record):
return record.levelno == logging.INFO
filter_instance = CustomFilter()
logger.addFilter(filter_instance)
日志格式器使用案例
格式器的构造方式
格式器的构造器接受五个参数,依次是输出消息的格式,输出日期时的格式,日志风格字符,另外两个参数是后来新加的:
validate
:如果为 True,则不正确或不匹配的fmt
和style
将引发 ValueError。默认为 True。defaults
:一个字典,包含在自定义字段中使用的默认值。例如logging.Formatter('%(ip)s %(message)s', defaults={"ip": None})
。
def __init__(self, fmt=None, datefmt=None, style='%', validate=True, *,
defaults=None):
if style not in _STYLES:
raise ValueError('Style must be one of: %s' % ','.join(
_STYLES.keys()))
self._style = _STYLES[style][0](fmt, defaults=defaults)
if validate:
self._style.validate()
self._fmt = self._style._fmt
self.datefmt = datefmt
我们来看简单的使用示例:
import logging
logger = logging.getLogger('example')
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
logger.addHandler(console_handler)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
logger.debug('This is a DEBUG message')
logger.info('This is an INFO message')
logger.warning('This is a WARNING message')
logger.error('This is an ERROR message')
logger.critical('This is a CRITICAL message')
style
参数指定了格式字符串的风格,可以是 %
、{
或 $
之一,决定了格式字符串将如何与数据合并。以下是每种风格的用法:
%
:使用 printf 风格的字符串格式化。例如,logging.Formatter('%(asctime)s - %(message)s')
。{
:使用str.format()
。例如,logging.Formatter('{asctime} - {message}')
。$
:使用string.Template
。例如,logging.Formatter('$asctime - $message')
。
格式器的内容指定符
格式化字符串 | 描述 |
---|---|
%(name)s | 日志记录器的名称(日志通道) |
%(levelno)s | 消息的数字日志级别(DEBUG、INFO、WARNING、ERROR、CRITICAL) |
%(levelname)s | 消息的文本日志级别(“DEBUG”、“INFO”、“WARNING”、“ERROR”、“CRITICAL”) |
%(pathname)s | 发出日志调用的源文件的完整路径名(如果可用) |
%(filename)s | 路径名的文件名部分 |
%(module)s | 模块(文件名的名称部分) |
%(lineno)d | 发出日志调用的源行号(如果可用) |
%(funcName)s | 函数名 |
%(created)f | 创建 LogRecord 的时间(time.time() 的返回值) |
%(asctime)s | LogRecord 创建时的文本时间 |
%(msecs)d | 创建时间的毫秒部分 |
%(relativeCreated)d | 创建 LogRecord 时的时间(相对于加载日志模块的时间,通常为应用程序启动时间)的毫秒数 |
%(thread)d | 线程 ID(如果可用) |
%(threadName)s | 线程名称(如果可用) |
%(process)d | 进程 ID(如果可用) |
%(message)s | 记录.getMessage() 的结果,即记录被发出时计算的消息内容 |
日志器统一配置添加
基本参数配置
logging.basicConfig
方法用于配置默认的日志记录器的基本配置,例如日志级别、输出格式和处理程序。如果在应用程序中没有显式配置日志系统,则会使用默认配置。该方法的签名如下:
logging.basicConfig(**kwargs)
参数说明:
filename
:要将日志消息写入的文件名称。filemode
:写入文件时使用的模式,默认为'a'
(追加)。如果指定了filename
,则此参数有效。format
:用于设置日志消息的格式。datefmt
:用于设置日期/时间部分的格式。level
:设置日志记录器的级别。stream
:指定一个流(例如sys.stdout
或sys.stderr
),用于输出日志消息。如果指定了filename
,则stream
参数无效。style
:设置格式化字符串的风格,默认为'%'
。handlers
:设置默认的处理器
例如,要将日志消息记录到文件中,并设置日志级别为 DEBUG
,可以使用以下代码:
import logging
logging.basicConfig(filename='example_log.log', level=logging.DEBUG)
这将导致日志消息被写入到名为 example.log
的文件中,并且只有 DEBUG
级别或更高级别的消息会被记录。
需要注意的是filename
与stream
参数不能够同时设置,两者定义的都是输出流,将会引发ValueError
可迭代处理器
logging.basicConfig
方法还可以接受一个 handlers
参数,用于指定要添加到根记录器的处理程序列表。处理程序用于定义将日志消息发送到的位置,例如控制台、文件、网络等。
以下是一个示例,将日志同时输出到控制台和文件中,handlers
列表中的参数将依次运行:
import logging
# 创建一个日志记录器
logger = logging.getLogger(name='my_logger')
logger.setLevel(logging.DEBUG)
# 创建一个控制台处理程序,将日志输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 创建一个文件处理程序,将日志写入到文件中
file_handler = logging.FileHandler('example.log')
file_handler.setLevel(logging.DEBUG)
# 将处理程序添加到日志记录器中,并进行格式化
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[file_handler, console_handler])
# 记录日志消息
logger.info('This is an info message')
logger.debug('This is a debug message')
logger.log(level=logging.WARNING, msg='This is a warning message')
这里需要注意的是,写日志的方法也可以脱离Logger对象使用:
logger.info('This is an info message') logger.debug('This is a debug message') logger.log(level=logging.WARNING, msg='This is a warning message')
日志模块配置分离
logging.config
模块提供了用于配置 Python 日志系统的功能。它允许您从配置文件、字典或其他来源加载日志配置,并将其应用于日志记录器。
主要的函数和类包括:
dictConfig(config)
:从字典配置日志系统。字典可以包含处理程序、格式化器和日志记录器的配置信息。fileConfig(fname, defaults=None, disable_existing_loggers=True)
:从配置文件配置日志系统。配置文件的格式可以是 INI 格式或 JSON 格式。listen(port)
:在给定端口上启动一个网络日志监听器,用于接收远程日志记录请求,但是这个有一定安全风险。stopListening()
:停止网络日志监听器。threading.Thread(target=handleLogRecords)
:一个线程类,用于处理接收到的日志记录。
其实我们常用的还是fileConfig
,所以这里我也仅介绍此方式
配置日志器config
在编写config
文件时,我们默认使用的是ini
风格:
[loggers]
keys=root,exampleLogger
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_exampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=logger1
propagate=0
-
[loggers]
:这个部分定义了所有的logger
,并使用keys
关键字指定了所有logger
的名称。需要注意的是,我们必须定义root
日志器,以保证全局配置完整 -
logger_xxx
:用于单独配置每个logger
level=DEBUG
:设置日志级别DEBUG
handlers=consoleHandler
:指定了处理器为consoleHandler
qualname=logger1
:指定了logger
的名称为logger1
。这个名称将在代码中使用,用于获取对应的logger
对象。propagate=0
:禁止消息传播到父logger
。如果不禁用传播,将会在打印日志输出两份相同日志,一个传递到父日志器,一个传递给当前日志器
还有个与
propagate
相关的属性:parent
属性用于指定父日志器,在未声明情况下默认为root
日志器
配置处理器config
[handlers]
keys=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
-
[handlers]
:这个部分定义了所有的handlers
,并使用keys
关键字指定了所有handlers
的名称。 -
handlers_xxx
:用于单独配置每个handlers
class=StreamHandler
:设置处理器为StreamHandler
level=DEBUG
:设置处理器级别DEBUG
formatter=simpleFormatter
:指定了格式器为simpleFormatter
args=(sys.stdout,)
:指定了处理器的接受参数为(sys.stdout, )
元组
配置格式器config
[formatters]
keys=simpleFormatter
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
style=%
这里就不过多赘述了,只需要注意format
控制的是输出msg的格式,datefmt
控制的是日期格式,style
控制格式风格即可
使用配置文件
import logging
from logging.config import fileConfig
# 加载配置文件
fileConfig('config.ini')
# 获取名为 'logger1' 的日志记录器
logger1 = logging.getLogger('logger1')
print(logger1.getEffectiveLevel())
logger1.debug('This is a debug message from logger1')
logger1.info('This is an info message from logger1')
需要注意的是,尽管使用了配置文件,但是我们仍然可以获取配置文件以外的日志器,并且这些配置文件外的日志器默认继承配置文件中的root
日志器