Python logging 模块

Python logging 模块

给大家剖析下Python的logging模块。

logging模块是Python内置的一个强大易用的日志模块。简单到你只需要两行代码就能输出一些东西来:

import logging
logging.warning('Hi, I print something.')

输出:

WARNING:root:Hi, I print something.

当然,我们可不能满足于此,你也很容易发现把上面 warning 改成 info 就输不出来了。别急,往下看。

1. 先大体概括下logging的结构

  • 整体上来说,日志管理最大的结构是Manager,Manager管理所有的Logger,但你不用去管这个东西,它是藏在背后默默干活的。
  • Logger里最根部是一个RootLogger,name是root,其他所有Logger都是在这个Logger下,通过名字,形成父子、子孙的树杈结构。
  • Logger里面有一些东西,Filter、Handler、LogRecord、Formatter,可以看作是一些配置,各自有各自的作用,接下来会介绍。
  • 还能用LoggerAdapter对Logger进行一定的包装,一般我们用的很少。

logger是一个树结构,默认有个根root,其他logger都是其上的枝桠,比如创建一个name=’A.B’的logger,其实际结构就是 root-A-B

root-A-B结构

再创建一个name=’A.C.D’的logger,结构变为:

root-A-B-C-D结构

2. level —— 输出哪个级别的日志

在输出log的时候,不能所有日志都输出出来,而是有所选择,比如有时候我们只希望看到warning以上严重程度的,如果有太多info、debug会让log可读性变得很差。

我们可以通过设置logger的level来实现这一点。在logging中,将level设置如下:

LevelNameLevelValue
CRITICAL/ FATAL50
ERROR40
WARNING/ WARN30
INFO20
DEBUG10
NOTSET0

示例:

import logging
logger = logging.getLogger('test')
logging.basicConfig()  # basicConfig是logging提供的简单的配置方法,不用basicConfig则需要手动添加handler
logger.setLevel(logging.INFO)  # 输出所有大于等于INFO级别的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不输出

结果正如所料,info输出,debug未输出。

INFO:test:I am <info> message.

要注意的是: root 的默认级别是 WARNING!, 而且logger实际输出时的level是取决于EffectiveLevel,即从该级往上走,遇到的第一个level不为0的logger的level,也就是说如果你创建了logger,而没有为其设置level,那它默认是NOTSET,程序会往上层找,直到root,而root级别是WARNING,所以可能会导致没有输出日志。

3. handler —— 输出到哪里

我们写日志一个很重要的问题就是把日志输出到什么地方去,我们可能希望某些日志在console打印出来,可能希望有更详细的日志输出到log文件里去。怎么控制这些输出就需要用handler了。

import logging
logger = logging.getLogger('test')
logger.addHandler(logging.StreamHandler())  # 添加StreamHandler
logger.setLevel(logging.INFO)  # 输出所有大于INFO级别的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不输出

我们把上面的例子稍微改动了一下,可以看到输出如下,输出到了console里

I am <info> message.

这就是logging提供的最基本的一个handler,其他各种handler都是从这个handler继承发展来的。理论上可以把日志输出到各种流中,stderr、文件、socket等都可以。当然logging已经将各种流handler封装好了。

如:

import logging
logger = logging.getLogger('test')
logger.addHandler(logging.StreamHandler())
logger.addHandler(logging.FileHandler('test.log'))  # 再添一个FileHandler
logger.setLevel(logging.INFO)  # 输出所有大于INFO级别的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不输出

可以看到,info不仅仅输出到了console中,还在当前文件夹下创建了一个test.log文件并输出到了该文件中。在logging.handlers中还封装了一堆更高级的handlers,可以了解下,尤其是RotatingFileHandler和TimedRotatingFileHandler,可以把你的日志按一定规则分割成多份。你也可以自己封装handler哦,网上有人这么干的。

上面我们看到了logger的级别,可以控制这个logger要输出什么级别的log。但这里我们发现可以在logger里添加handler,控制输出log到哪里,明显发现,其实我们想要在不同的handler里输出不同级别的日志。

比如我们想要在console里输出warning以上的日志,在log文件里输出debug以上的日志,该怎么办呢?

handler也是有级别的。

如下:

import logging
logger = logging.getLogger('test')
logger.setLevel(logging.INFO)  # 输出所有大于INFO级别的log

# 添加StreamHandler,并设置级别为WARNING
stream_hdl = logging.StreamHandler()
stream_hdl.setLevel(logging.WARNING)
logger.addHandler(stream_hdl)
# 添加FileHandler,并设置级别为DEBUG
file_hdl = logging.FileHandler('test.log')
file_hdl.setLevel(logging.DEBUG)
logger.addHandler(file_hdl)

logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不输出

设置了WARNING级别的StreamHandler以及DEBUG级别的FileHandler。在logger.info()执行时,程序判断log级别(info) >= logger级别(INFO),可以输出。

然后程序判断log级别(info) < StreamHandler级别(WARNING),不在stderr输出;同时log级别(info) > FileHandler级别(DEBUG),在test.log中输出。

而logger.debug()的级别 < logger级别(INFO),所以不会在stderr和test.log输出。

其实上面的程序有个问题,就是设置了logger是INFO,那么FileHandler是DEBUG没有什么意义的,DEBUG级别的日志过不了logger那关。

你在写代码的时候就需要注意了,要给logger添加handler,并且注意logger和handler的level,否则可能会输不出你想要的log哦。

4. formatter —— 输出日志的格式

细心的话可以发现,我们后来自己添加的handler输出的log是没有格式的,就仅仅是输出而已。但basicConfig()输出的log是有格式的(虽然很丑)。

不同在于basicConfig()中的handler是带有formatter的。我们要添加formatter就需要用到logging中的另一个类Formatter

import logging
logger = logging.getLogger('test')
logger.setLevel(logging.INFO)  # 输出所有大于INFO级别的log
fmt = logging.Formatter('%(name)s - %(levelname)s - %(asctime)s - %(message)s')
# 添加StreamHandler,并设置级别为WARNING
stream_hdl = logging.StreamHandler()
stream_hdl.setLevel(logging.WARNING)
stream_hdl.setFormatter(fmt)
logger.addHandler(stream_hdl)
# 添加FileHandler,并设置级别为DEBUG
file_hdl = logging.FileHandler('test.log')
file_hdl.setLevel(logging.DEBUG)
file_hdl.setFormatter(fmt)
logger.addHandler(file_hdl)

logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不输出

如上例中,我们添加了formatter,格式为 (logger name) - (level name) - (当前时间) - (日志信息),输出的结果为:

test - INFO - 2017-09-06 16:45:36,977 - I am <info> message.

是不是漂亮多了,logging的formatter可以输出的不止这几个信息,还有很多:

  • %(name)s ——Name of the logger (logging channel)
  • %(levelno)s ——Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  • %(levelname)s ——Text logging level for the message (“DEBUG”, “INFO”, “WARNING”, “ERROR”, “CRITICAL”)
  • %(pathname)s ——Full pathname of the source file where the logging call was issued (if available)
  • %(filename)s ——Filename portion of pathname
  • %(module)s ——Module (name portion of filename)
  • %(lineno)d ——Source line number where the logging call was issued(if available)
  • %(funcName)s ——Function name
  • %(created)f ——Time when the LogRecord was created (time.time() return value)
  • %(asctime)s ——Textual time when the LogRecord was created
  • %(msecs)d ——Millisecond portion of the creation time
  • %(relativeCreated)d ——Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded (typically at application startup time)
  • %(thread)d ——Thread ID (if available)
  • %(threadName)s ——Thread name (if available)
  • %(process)d ——Process ID (if available)
  • %(message)s ——The result of record.getMessage(), computed just as the record is emitted

我们也可以给Formatter传入第二个参数,修改日期格式,比如 %Y-%m-%d ,则输出的log就没有时分秒了。

这样就可以定制log的格式了,还可以为不同的handler制定不同的formatter,以适应不同情况下的阅读或传输需求。

5. Filter和LoggerAdapter

最后这里有两个我们不常用到的东西,懒得多写了。

6. 重复写日志的问题

这里有一个坑需要特别说明下,就是add handler的问题。一个logger创建之后不要重复添加handler,有时候我们多个文件都调用我们封装好的logger的类,就有可能会发生重复添加handler的情况,具体的看博主这篇文章——python logging 重复写日志问题

7. 差点忘了

差点忘了配置的问题,我们可以通过多种途径配置logger,像上面用过的basicConfig()(没有用参数,可以自己阅读源码,很简单的几个参数),或者像上面的例子手动添加handler、设置formatter,logging还给了我们其他高级的配置方法:

logging.config里有多种配置方法,像fileConfig,可以直接读一个文件;像dictConfig,可以传一个dict进行配置(django就是这么配置的);似乎还能起个socket,来实时修改config,听起来很吊的样子。

不过我们用其中的一种就可以了,随便配喽。

比如,像博主这样 怎样从0开始搭建一个测试框架_2

就说这些,跟其他人的讲法好像不太一样,结合着来看吧。

有什么好的建议或者问题,可以留言或者加我的QQ群:455478219讨论。

<think> 我们正在回答关于Python logging模块的使用和介绍的问题。根据用户的要求,我们需要参考提供的引用内容,给出结构清晰、真实可靠的回答。 引用[1]提到:logging模块Python的标准日志记录模块,用于记录应用程序的状态、错误和调试信息。 引用[2]和[3]都强调了logging模块相比print的优点:可以设置不同日志等级,在release版本中只输出重要信息;可以决定信息输出的位置和方式。 引用[4]给出了一个基本的使用示例,展示如何配置日志级别并记录不同级别的日志。 引用[5]则提供了更详细的内容,包括日志等级、格式设置、日志保存为文件以及模块化组件的使用(日志记录器和处理器的配置)。 回答结构: 1. 介绍logging模块及其优点 2. 日志等级说明 3. 基本使用:配置和记录日志 4. 进阶使用:自定义格式、输出到文件、使用多个处理器等 5. 总结 注意:由于引用[5]内容较多,我们重点参考其结构,但根据引用内容简洁地组织。 我们按照用户要求,在回答中引用的段落末尾添加引用标识。 </think>### Python logging模块使用指南 Python的`logging`模块是标准库中的日志记录工具,相比`print()`具有以下优势: 1. **分级日志**:可设置不同日志等级(DEBUG/INFO/WARNING/ERROR/CRITICAL),在release版本中只输出关键信息[^3] 2. **灵活输出**:可指定输出到控制台、文件、网络等不同目标[^3] 3. **格式定制**:支持自定义日志格式(时间、模块名、行号等)[^5] #### 基础使用 ```python import logging # 配置基础日志设置(控制台输出) logging.basicConfig( level=logging.DEBUG, # 设置日志级别 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logging.debug('调试信息') # 开发时使用 logging.info('状态信息') # 正常运行信息 logging.warning('潜在问题') # 不影响运行的异常 logging.error('功能错误') # 影响部分功能 logging.critical('严重故障') # 导致程序崩溃 ``` 输出示例: ``` 2023-05-10 14:30:00 - root - DEBUG - 调试信息 2023-05-10 14:30:00 - root - WARNING - 潜在问题 ``` #### 进阶功能 1. **文件日志记录** ```python logging.basicConfig( filename='app.log', filemode='a', # 追加模式 level=logging.INFO ) ``` 2. **模块化组件配置** ```python # 创建记录器 logger = logging.getLogger('my_logger') logger.setLevel(logging.DEBUG) # 创建文件处理器 file_handler = logging.FileHandler('debug.log') file_handler.setLevel(logging.DEBUG) # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.ERROR) # 设置日志格式 formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s') file_handler.setFormatter(formatter) # 添加处理器 logger.addHandler(file_handler) logger.addHandler(console_handler) logger.info('业务处理完成') # 仅写入文件 logger.error('数据库异常') # 同时写入文件和控制台 ``` 3. **日志格式标记符** | 标记符 | 说明 | |--------|------| | `%(asctime)s` | 日志时间 | | `%(levelname)s` | 日志级别 | | `%(filename)s` | 文件名 | | `%(lineno)d` | 行号 | | `%(funcName)s` | 函数名 | | `%(message)s` | 日志内容 | #### 最佳实践 1. 生产环境建议使用`WARNING`级别及以上 2. 通过`RotatingFileHandler`实现日志文件轮转 3. 不同模块使用独立记录器:`logger = logging.getLogger(__name__)` 4. 使用`try-except`配合异常日志: ```python try: # 业务代码 except Exception as e: logging.exception(f"操作失败: {str(e)}") ``` 日志级别金字塔: ``` CRITICAL(最高) ↑ ERROR ↑ WARNING ↑ INFO ↓ DEBUG(最低) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值