这篇文章主要参考: http://victorlin.me/posts/2012/08/26/good-logging-practice-in-python
==========================
应用程序的logging的做法
==========================
1. 对于主模块, 直接对root logger做logger输出设置. 这样所有模块都会按照root logger的设置进行log 输出.
2. 所有模块, 包括主模块, 都应使用logging.getLogger(__name__)来获得logger实例, 使用getLogger(__name__)获得的logger, 输出的日志能体现出模块的命名空间. 主模块getLogger()调用应放在logging初始化之后.
3. 对于非主模块,不应做logger的初始化配置, 也不应该在模块级上调用getLogger(), 而应该在function中或者class中来调用getLogger().
理由是: 最主要的原因是, log配置应该是主模块的特权. 还有一个理由是, 我们的主module往往是先import非主module, 然后再用logging.config.fileConfig()做初始化, 而fileConfig()缺省会disable已存在的所有logger, 也就是说非主模块级别的那些logger都被禁掉了, 也就无法再用来记日志.
4. 在主模块和非主模块推荐, 都应该使用logger.info()和logger.debug()来记日志, 不推荐使用logging.info()和logging.debug()这样的方法, 后者是用的是root logger, 所有模块都用root, 就无法区分日志是由哪个模块产生的了.
5. 推荐使用 cloghandler.py 的 ConcurrentRotatingFileHandler.
而不是FileHandler 和RotatingFileHandler 和TimedRotatingFileHandler .
理由是 FileHandler, 单文件日志缺点比较多.
而 RotatingFileHandler的问题是, 在windows下当文件大小到达max时, logging操作会报错, 新log有时候就记不进去了. 另外如果多个程序往同一个log文件写, RotatingFileHandler也会报错的, cloghandler.py没这个问题.
而TimedRotatingFileHandler的问题是, 在windows下, 如果log文件在23:59正在被写入, logging操作也会报错, 无法进行log文件重命名, 新log有时候就记不进去了. 当然这个几率要比RotatingFileHandler失败几率小.
参考 http://pydoc.net/Python/cloud/2.3.9/cloud.util.cloghandler.cloghandler/
6. 长log的format, 我使用的是,
说明: 前面加上####, 这样log parser容易分开不同的log记录. 分隔符使用|而不是逗号, 因为时间串中会包含个逗号的.
formatter = logging.Formatter(fmt='####%(asctime)s|pid=%(process)d|tid=%(thread)d|%(name)s|%(levelname)s|%(message)s')
短log的format, 我使用的是,
shortFormatter = logging.Formatter(fmt='####%(asctime)s|%(name)s|%(levelname)s|%(message)s', datefmt="%H:%M:%S")
7. 关于应用程序如何合并第3方类库的log输出, 比如合并SqlAlchemy的日志.
基本不用特别在意, 只要我们的程序设置了root logger, 应用程序和SqlAlchemy的日志自然会合并输出在root logger的handler中.
==========================
类库的logging的做法
==========================
1.类库logging的使用, 其实和application的非主模块类似.
2.在类库的top level库中, 最好为logger加上NullHandler, 这样即使应用程序根本没做logging配置, 也不会出现未配置logging的警告信息.
=========================
几个有关的函数
==========================
rootLogger = logging.getLogger() #getLogger()不带参数, 得到的即是rootLogger
logging.basicConfig(level=logging.DEBUG) #是一个很顺手的logging配置方案, 输出为stderr, 非常适合在demo和调试程序
logging.config.dictConfig(dict_LOG_CONFIG) #使用dict来初始化logger
logging.config.fileConfig(ini_file) #使用ini文件来初始化logger
==========================
选择合适的logging level
==========================
这里误区也比较多, 很多程序员喜欢滥用error级别. 可最佳日志实践中的介绍,
http://www.bitstech.net/2014/01/07/log-best-practice/DEBUG : 开发人员debug用, 比如记录详尽的业务动作, 比如SQL
INFO : 记录一些关键的动作和数据.
WARN : 用于那些对业务功能已有影响, 但不太严重的情形
ERROR : 仅用于记录那些已影响业务功能的Error或Exception
FATAL : (同CRITICAL)仅用于影响到程序完全不能工作的情况, 比如非法退出
如果还不容易区分, 换另一个角度, WARNING和ERROR级别的问题, 都是需要及时处理.
=========================
dict 配置的写法
==========================
1. 具体 ini 配置文件如何写, 参考<python日志不完全指南> http://cjs.linuxapp.org/?p=116
2. dict 配置, 后面有个sample.
#版本总是1
'version': 1,
#
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
},
'handlers': {
'default': {
'level':'INFO',
'class':'logging.StreamHandler',
},
},
'loggers': {
#加一个root logger
'': {
'handlers': ['default'],
'level': 'INFO',
'propagate': True
},
#加其他logger
'django.request': {
'handlers': ['default'],
'level': 'WARN',
'propagate': False
},
}
}
==========================
ini 配置的写法
==========================
参考<python日志不完全指南> http://cjs.linuxapp.org/?p=116
# 定义logger模块,root是必需的,其它的是自定义。
keys=root, web.debug, web.info, web.error
# 定义格式化输出
[formatters]
keys=simpleFormatter, webSimpleFormatter
# 定义handler
[handlers]
keys=consoleHandler,rotateFileHandler, webDebugRotateFileHandler, webInfoRotateFileHandler, webErrorRotateFileHandler, webSMTPHandler
#--------------------------------------------------
# 实现上面定义的logger模块,必需是[logger_xxxx]这样的形式
#--------------------------------------------------
# [logger_xxxx] logger_模块名称
# level 级别,级别有DEBUG、INFO、WARNING、ERROR、CRITICAL
# handlers 处理类,可以有多个,用逗号分开
# qualname logger名称,应用程序通过 logging.getLogger获取。对于不能获取的名称,则记录到root模块。
# propagate 是否继承父类的log信息,0:否 1:是
[logger_root]
level=DEBUG
handlers=consoleHandler,rotateFileHandler
[logger_web.debug]
level=DEBUG
handlers=consoleHandler,webDebugRotateFileHandler
qualname=web.debug
propagate=0
[logger_web.info]
level=INFO
handlers=consoleHandler,webInfoRotateFileHandler
qualname=web.info
propagate=0
[logger_web.error]
level=ERROR
handlers=consoleHandler,webErrorRotateFileHandler,webSMTPHandler
qualname=web.error
propagate=0
#--------------------------------------------------
# 日志格式
#--------------------------------------------------
# %(asctime)s 年-月-日 时-分-秒,毫秒 2013-04-26 20:10:43,745
# %(filename)s 文件名,不含目录
# %(pathname)s 目录名,完整路径
# %(funcName)s 函数名
# %(levelname)s 级别名
# %(lineno)d 行号
# %(module)s 模块名
# %(message)s 消息体
# %(name)s 日志模块名
# %(process)d 进程id
# %(processName)s 进程名
# %(thread)d 线程id
# %(threadName)s 线程名
[formatter_simpleFormatter]
format=%(asctime)s|%(name)s|%(threadName)s|%(levelname)s > %(message)s
[formatter_webSimpleFormatter]
format=%(asctime)s %(clientip)s %(levelname)s > %(message)s
#--------------------------------------------------
# handler
#--------------------------------------------------
# [handler_xxxx]
# class handler类名
# level 日志级别
# formatter,上面定义的formatter
# args handler初始化函数参数
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_rotateFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=('access.log','a',2000000,9)
[handler_webDebugRotateFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=('debug.log','a',2000000,9)
[handler_webInfoRotateFileHandler]
class=handlers.RotatingFileHandler
level=INFO
formatter=webSimpleFormatter
args=('info.log','a',2000000,9)
[handler_webErrorRotateFileHandler]
class=handlers.RotatingFileHandler
level=ERROR
formatter=webSimpleFormatter
args=('error.log','a',2000000,9)
[handler_webSMTPHandler]
class=handlers.SMTPHandler
level=ERROR
formatter=webSimpleFormatter
args=('mailhost', 'fromaddr', ('toaddrs1','toaddrs2'), 'subject', ('username','password'))