Python 本身有logging日志记录模块,之前发现了logbook这个包,介绍说是替代logging,索性整理一下,方便之后使用
>>> from logbook import Logger, StreamHandler
>>> import sys
>>> StreamHandler(sys.stdout).push_application()
>>> log = Logger('Logbook')
>>> log.info('Hello, World!')
[2015-10-05 18:55:56.937141] INFO: Logbook: Hello, World!
上边这是文档中给出的例子,它定义了许多的Handler,可以把日志记录到标准输出,文件,E-MAIL,甚至Twitter
StreamHandler
使用StreamHandler
记录的日志会以流输出,这里指定sys.stdout
也就是记录到标准输出,与print
一样
(一)可以使用with
来在一定作用域内记录日志
# -*- coding: utf-8 -*-
from logbook import Logger, StreamHandler
import logbook
import sys
handler = StreamHandler(sys.stdout)
log = Logger('test')
def main():
log.info('something logging')
if __name__ == '__main__':
with handler.applicationbound():
main()
(二)也可以指定作用于整个应用
# -*- coding: utf-8 -*-
from logbook import Logger, StreamHandler
import logbook
import sys
handler = StreamHandler(sys.stdout)
handler.push_application()
log = Logger('test')
def main():
log.info('something logging')
if __name__ == '__main__':
main()
FileHandler
使用FileHandler
可以把日志记录到文件,这也是最常见的方式
# -*- coding: utf-8 -*-
from logbook import Logger, FileHandler
import logbook
import sys
handler = FileHandler('app.log')
handler.push_application()
log = Logger('test')
def main():
log.info('something logging')
if __name__ == '__main__':
main()
日志就写到了app.log
文件
同时输出到文件与STDOUT
同时把记录输出到多个地方可以方便查阅和记录,初始化Handler
的时候设置bubble
参数就可以使得其它Handler
也可以接收到记录
from logbook import Logger, StreamHandler, FileHandler
import logbook
import sys
'''
记录日志到文件和STDOUT
'''
StreamHandler(sys.stdout, level='DEBUG').push_application()
FileHandler('app.log', bubble=True, level='INFO').push_application()
log = Logger('test')
def main():
log.info('hello world')
if __name__ == '__main__':
main()
另外,通过level
可以设置日志级别,级别如下,从下到上级别越来越高,如level
设置为INFO
, 则除了DEBUG
外都会记录,设置不同的级别,搭配各种Handler
可以让日志的记录更加灵活,上边使用的log.info
可以使用不同的记录级别
级别 | 说明 |
---|---|
critical | 严重错误,需要退出程序 |
error | 错误,但在可控范围内 |
warning | 警告 |
notice | 大多数情况下希望看到的记录 |
info | 大多数情况不希望看到的记录 |
debug | 调试程序的时候详细输出的记录 |
MailHandler
和日志文件同样重要的就是MailHandler
了,当出现了比较严重错误的时候就要发送邮寄进行通知
详细的文档见:http://logbook.readthedocs.io/en/stable/api/handlers.html#logbook.MailHandler
分别使用了163
和qq
邮箱发送邮件测试,使用的邮箱需要开启SMTP权限,代码如下(163和qq发送参数稍有不同)
163 Mail
# -*- coding: utf-8 -*-
from logbook import Logger, MailHandler
import logbook
import sys
sender = 'Logger<dongdong@163.com>'
recipients = ['dongdong@qq.com']
email_user = 'dongdong@163.com'
email_pass = 'password'
mail_handler = MailHandler(sender, recipients,
server_addr='smtp.163.com',
starttls=True,
secure = False,
credentials=(email_user, email_pass),
format_string=u'''\
Subject: {record.level_name} on My Application
Message type: {record.level_name}
Location: {record.filename}:{record.lineno}
Module: {record.module}
Function: {record.func_name}
Time: {record.time:%Y-%m-%d %H:%M:%S}
Remote IP: {record.extra[ip]}
Request: {record.extra[url]} [{record.extra[method]}]
Message: {record.message}
''',
bubble=True)
log = Logger('test')
def main():
log.info('something logging')
if __name__ == '__main__':
with mail_handler.threadbound():
main()
QQ Mail
# -*- coding: utf-8 -*-
from logbook import Logger, MailHandler
import logbook
import sys
sender = 'Logger<dongdong@qq.com>'
recipients = ['dongdong@163.com']
email_user = 'dongdong@qq.com'
email_pass = 'password'
mail_handler = MailHandler(sender, recipients,
server_addr='smtp.qq.com',
starttls=False,
secure = True,
credentials=(email_user, email_pass),
format_string=u'''\
Subject: {record.level_name} on My Application
Message type: {record.level_name}
Location: {record.filename}:{record.lineno}
Module: {record.module}
Function: {record.func_name}
Time: {record.time:%Y-%m-%d %H:%M:%S}
Remote IP: {record.extra[ip]}
Request: {record.extra[url]} [{record.extra[method]}]
Message: {record.message}
''',
bubble=True)
log = Logger('test')
def main():
log.info('something logging')
if __name__ == '__main__':
with mail_handler.threadbound():
main()
内容format_string
中的用大括号的会进行数值替换,Subject
字段上边的\
和下边需要空一行,这样解析参数收到的邮件才会正确的显示标题
Record Processors
上边发送邮件的例子,参数里面有一个record.extra[url]
,这个参数是可以自己指定的,比如编写WSGI的程序,处理URL,每一条记录都希望记录到访问者的IP,可以这样做:
# -*- coding: utf-8 -*-
from logbook import Logger, StreamHandler, Processor
import logbook
import sys
handler = StreamHandler(sys.stdout)
handler.format_string = '[{{record.time:%Y-%m-%d %H:%M:%S}}] IP:{record.extra[ip]} {record.level_name}: {record.channel}: {record.message}'
handler.formatter
log = Logger('test')
def inject_ip(record):
record.extra['ip'] = '127.0.0.1'
with handler.applicationbound():
with Processor(inject_ip).applicationbound():
log.error('something error')
使用自定义参数,需要重新设置format_string
,才能进行记录,record类可以在这里找到,详细参数见logbook.LogRecord
https://logbook.readthedocs.io/en/stable/api/base.html#logbook.LogRecord.args
Output:
[2016-12-13 12:20:46] IP:127.0.0.1 ERROR: test: something error
日期格式
上边在介绍的自定义日志格式的时候使用的时间是虽然指定了格式但是是UTC
格式,跟北京时间是差了8个小时的。所以需要设置让它记录本地的时间
在刚才的例子前面加上如下代码即可
logbook.set_datetime_format('local') # <= 新加入行
handler = StreamHandler(sys.stdout)
handler.format_string = '[{record.time:%Y-%m-%d %H:%M:%S}] IP:{record.extra[ip]} {record.level_name}: {record.channel}: {record.message}'
handler.formatter
更过日期格式化的设置参看:api/utilities - logbook.set_datetime_format(datetime_format)
threadbound与applicationbound
从最开始的例子来看,可以使用两种方式来记录日志,一种是在最开始使用push_*
来激活,另一种是使用的时候用with
构造上下文,现在进行一些补充
push | with | pop |
---|---|---|
push_application() | applicationbound() | pop_application() |
push_thread() | threadbound() | pop_threadbound() |
push_greenlet() | greenletbound() | pop_greenlet() |
- 使用
pop_*
可以取消记录的上下文 - application作用于整个应用,thread只针对当前线程
handler = MyHandler()
handler.push_application()
# all here goes to that handler
handler.pop_application()
消除嵌套
使用多个Handler的时候,使用push_
方式启动的上下文不会形成嵌套,但是使用with
启动的上下文会形成嵌套,可以使用nested handler
import os
from logbook import NestedSetup, NullHandler, FileHandler, \
MailHandler, Processor
def inject_information(record):
record.extra['cwd'] = os.getcwd()
setup = NestedSetup([
# NullHandler避免stderr接受消息
NullHandler(),
FileHandler('application.log', level='WARNING'),
MailHandler('servererrors@example.com', ['admin@example.com'],
level='ERROR', bubble=True),
Processor(inject_information)
])
使用的时候就只需要一个with
来启动
with setup.threadbound():
log.info('something logging')
logbook
是个不错的包,记录日志灵活方便,比自己包装发送邮件方便了不少,整理了一些基本用法,还有不少值得学习的功能,暂时能用到的基本上就这么多,之后用到高级的功能再继续研究补充