Pingfan's Blog

python日志记录模块

字数统计: 2k阅读时长: 9 min
2018/11/15 Share

为什么要记录日志

  • 它可以让你更好的理解程序的运行流程,甚至帮你发现你在开发时没有考虑到的细节。

  • 持续监控程序的运行,存储用户名、IP等信息。

  • 如果程序出错,在出错之前所有的信息都会被记录下来,效果堪比调试。

  • 可以通过日志数据分析应用的性能,如瓶颈在哪;也可以利用它做市场优化。

日志模块

python在标准库里提供了日志记录功能。大部分python的三方库都使用了这个模块

总共有五个级别,从低到高分别为:

  • DEBUG
  • INFO
  • WARNING
  • ERROR
  • CRITICAL

例如:

import logging

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

首先输出的是日志的严重级别,接着是一个root,这是默认日志的名字。后面就是错误的具体信息了。默认格式为:level:name:message,当然这个格式可以自定义,例如加上时间戮,行数等信息。

我们发现debug(), info()的信息没有被输出,这是由于默认的日志记录级别是WARNING及以上。可以配置这个级别,但不推荐这么做,因为这会与你使用的三方库起冲突。

基本配置

可以使用basicConfig(***kwargs*)方法来配置日志功能

可以看到这个方法违背了PEP8的命令规则(本来应该为basic_config),这是因为它借鉴了Java的Log4j库。在成为标准库之前,它就被许多开发者使用,如果将它重构成符合PEP8规范,可能会带来许多兼容性问题。

它主要有四个参数:

  • level:设置默认的错误等级。
  • filename:输出日志的名称
  • filemode:必须指定filename,默认是a(append)模式.
  • format:日志的输出格式

level设定好后,级别等于或高于该值会被记录:

import logging

logging.basicConfig(level=logging.DEBUG)
logging.debug('This will get logged')
DEBUG:root:This will get logged

所有等于或高于 DEBUG 的日志将会被记录。

默认是将信息打印在控制台,也可以使用filenamefilemode参数将日志保存到文件中;使用format参数来控制输出格式,下面是一个例子:

import logging

logging.basicConfig(filename='app.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
logging.warning('This will get logged to a file')
root - ERROR - This will get logged to a file

注意,basicConfig()只能被调用一次!!

格式化输出

如果你想记录进程的ID:

import logging

logging.basicConfig(format='%(process)d-%(levelname)s-%(message)s')
logging.warning('This is a Warning')
18472-WARNING-This is a Warning

如果你想记录时间

import logging

logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
logging.info('Admin logged in')
2018-07-11 20:12:06,288 - Admin logged in

%(asctime)s 指定了日志的记录时间,当然这个日期的格式可以通过datafmt来更改,它的格式和time.strftime()的格式一样:

import logging

logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
logging.warning('Admin logged out')
12-Jul-18 20:53:19 - Admin logged out

记录变量的数据

通过传入变量,可以记录下变量的信息:

import logging

name = 'John'

logging.error('%s raised an error', name)
ERROR:root:John raised an error

可以使用python3.6的 f-strings 来格式化字符串:

import logging

name = 'John'

logging.error(f'{name} raised an error')
ERROR:root:John raised an error

记录堆栈信息

日志模块也可以记录堆栈信息,异常信息可能通过指定exc_info=True来记录。例如:

import logging

a = 5
b = 0

try:
  c = a / b
except Exception as e:
  logging.error("Exception occurred", exc_info=True)
ERROR:root:Exception occurred
Traceback (most recent call last):
  File "exceptions.py", line 6, in <module>
    c = a / b
ZeroDivisionError: division by zero
[Finished in 0.2s]

如果exc_info没有设置成True,上面程序的异常就不会被记录,日志只有一行:

ERROR:root:Exception occurred

在异常处理模块可以调用logging.exception()方法,默认级别是ERROR,它等效于logging.error(exc_info=True),但它只应在异常处理模块中被调用:

import logging

a = 5
b = 0
try:
  c = a / b
except Exception as e:
  logging.exception("Exception occurred")
ERROR:root:Exception occurred
Traceback (most recent call last):
  File "exceptions.py", line 6, in <module>
    c = a / b
ZeroDivisionError: division by zero
[Finished in 0.2s]

类和函数

当日志模块被直接调用,如loggin.debug(),日志的默认nameroot。可以通过创建Logger类来自定义属于自己的logger,特别是当你的应用有很多模块时(不然全都使用root,都不知道是哪个模块输出的日志了,一锅搅在一起让你头大)。

可以使用logging.getLogger(name)方法来指定logger的名称,例如:

import logging

logger = logging.getLogger('example_logger')
logger.warning('This is a warning')
This is a warning

当我们在它的配置中传入name时,它会这样输出:

WARNING:example_logger:This is a warning

和root logger不一样,这种logger不能使用basicConfig()方法来配置,而是使用Handlers和Formatters。

使用Handlers

如果想配置logger并将日志发送到不同的位置,就可以使用handlers,它可以将日志信息输出到一个文件、http流或者SMTP邮件。一个logger可以有多个handler,例如同时保存到日志文件和以邮件形式发送。

可以指定handler的严重等级,当为一个logger设置多个handler时会非常有用,例如将WARNING以上级别的日志输出到控制台,将ERROR以上的日志输出到文件:

# logging_example.py

import logging

# Create a custom logger
logger = logging.getLogger(__name__)

# Create handlers
c_handler = logging.StreamHandler() # 这里的c指console
f_handler = logging.FileHandler('file.log') # f指file
c_handler.setLevel(logging.WARNING)
f_handler.setLevel(logging.ERROR)

# Create formatters and add it to handlers
c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)

# Add handlers to the logger
logger.addHandler(c_handler)
logger.addHandler(f_handler)

logger.warning('This is a warning')
logger.error('This is an error')
__main__ - WARNING - This is a warning
__main__ - ERROR - This is an error

logger.warning()创建了一条日志记录,会同时发送到c_handlerf_handler这两个handler,c_handler是一个StreamHandler,它的级别是WARNING,所以它会将日志信息输出到控制台,而f_handler是一个FileHandler,它的级别是ERROR,所以它会忽略这条日志记录。当logger.error()产生日志记录时,两个handler都会处理,其中f_handler会写入日志文件:

2018-08-03 16:12:21,723 - __main__ - ERROR - This is an error

由于logger是当前脚本运行,所以__name____main__,如果文件被外部模块引入,那么__name__为文件名logging_example

# run.py

import logging_example
logging_example - WARNING - This is a warning
logging_example - ERROR - This is an error

其他配置方法

可以把配置写到文件或者字典中,然后通过fileConfig()dictConfgi()来加载配置。这非常适合于在运行时更改日志配置。下面是将配置写到文件的例子:

[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

上面的文件中,有两个logger,一个handler,一个fomatter。他们的名字定义好后,以下划线形式说明进行具体的配置,如logger_root、logger_sampleLogger、handler_consoleHandler、formatter_sampleFormatter

为了加载这个配置文件,需要使用fileConfig()函数:

import logging
import logging.config

logging.config.fileConfig(fname='file.conf', disable_existing_loggers=False)

# Get the logger specified in the file
logger = logging.getLogger(__name__)

logger.debug('This is a debug message')
2018-07-13 13:57:45,467 - __main__ - DEBUG - This is a debug message

disable_existing_loggers用开决定是否弃用掉当前已经开启的logger,如果未声明这个参数,默认情况下是True

认真分析你的日志

你可以根据项目需要自定义你的日志类。如果使用得当,日志会让你的开发过程规避掉不少坑,让你的开发能力更上一层楼。

原文

https://realpython.com/python-logging/

CATALOG
  1. 1. 为什么要记录日志
  2. 2. 日志模块
  3. 3. 基本配置
  4. 4. 格式化输出
  5. 5. 记录变量的数据
  6. 6. 记录堆栈信息
  7. 7. 类和函数
  8. 8. 使用Handlers
  9. 9. 其他配置方法
  10. 10. 认真分析你的日志