【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
print 不是一个好主意
虽然日志很重要,但并不是所有的开发者都知道如何正确的记录日志。之前在开发过程中往代码里插入print语句,然后在开发完成之后删掉他们……但是针对以简单的脚本时,这种方式很有效;但是对于复杂的系统,这么做并不是一个明智的选择。首先你不可能指望日志里输出重要的信息,你可能在日志里看到一大堆垃圾信息,却找不到任何有用的内融。print语句不能很好的做到控制,除非你修改源代码。如果忘记删除没有用的print,所有的消息都会给输出到stdout,终究不是记录日志的好习惯。
使用 Python longging标准库
使用Python内置的标准模块,是记录日志的正确姿势。logging是一个标准模块,设计优良,易于使用,同时易于扩展。
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info('Start reading database')
# read database here
records = {'john': 55, 'tom': 66}
logger.debug('Records: %s', records)
logger.info('Updating records ...')
# update records here
logger.info('Finish updating records')
输出结果如下
INFO:main:Start reading database INFO:main:Updating records ... INFO:main:Finish updating records [Finished in 0.3s]
与print的区别:
- 你可以控制消息级别,把没那么重要的内容过滤掉
- 你可以晚一点在决定要输出到哪里,以及输出的方式
日志级别分为: debug、info、warning、error、critical
日志级别说明
级别 | 数值 | 何时使用 |
---|---|---|
DEBUG | 详细信息,典型的调试问题时会感兴趣 | |
INFO | 证明事情按预期工作 | |
WARNING | 报名发生了一些意外,或者不就得将来会发生的问题,但是软件还能正常工作 | |
ERROR | 由于严重的问题,异常抛出,IO操作失败,软件已经不能执行一些功能了 | |
CRITICAL | 严重错误,表明软件已经不能正常运行了,内存不足,磁盘满了 |
建议使用__name__作为logger名称
__name__变量在Python里面是当前模块的名字。例如,你在模块 ** foo.bar.my_module ** 里调用 logging.getLogger(__name__) ,等价于 logging.getLogger('foo.bar.my_module') 。当你需要设置 logger 的时候,设置为 foo ,那么 foo 包里的所有模块都会共享同一台设置,可以志短的看到这是那个木块记录的日志信息。
捕获异常 记录跟踪信息
出错的时候记录日志是好的,但是没有 traceback 也没什么用。在你捕获异常时,在日志中附加 tracebask 信息,例如:
try:
open('/path/to/does/not/exist', 'rb')
except (SystemExit, KeyboardInterrupt):
raise
except Exception, e:
logger.error('Failed to open file', exc_info=True)
调用logger来记录日志是,指定 exc_info=True参数,traceback信息将会被保存到日志。你也可以调用 logger.exception(msg, *args),这跟 logger.error(msg, *args, exc_info=True) 是一样的
ERROR:main:Failed to open file Traceback (most recent call last): File "example.py", line 6, in <module> open('/path/to/does/not/exist', 'rb') IOError: [Errno 2] No such file or directory: '/path/to/does/not/exist'
##### 除非 disable_existing_logger == False,不然不要在模块级别获取 logger
在网络上可以找到很多例子(这篇文章中我也特意举了这么一个例子),它们在模块级别获取 logger。看起来无害,但实际上是有隐患的——Python 的 logging 模块,在从文件中读取设置之前,对所有 logger 都一视同仁。
使用 JSON 或 YAML 作为日志配置
你可以在 Python 代码中配置你的日志系统,但似乎不是那么灵活。更好的方式是用配置文件。在 Python 2.7 之后,你可以从一个字典中加载日志配置了。这意味着你可以从 JSON 或是 YAML 文件中读取配置。虽然旧的 .ini 格式配置也还是被支持,但那很难写,阅读也不方便。这里举了一个使用 JSON 或 YAML 配置的例子:
logging.json
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"info_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "info.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
},
"error_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "ERROR",
"formatter": "simple",
"filename": "errors.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
}
},
"loggers": {
"my_module": {
"level": "ERROR",
"handlers": ["console"],
"propagate": "no"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "info_file_handler", "error_file_handler"]
}
}```
logging.yaml
---
version: 1
disable_existing_loggers: False
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
info_file_handler:
class: logging.handlers.RotatingFileHandler
level: INFO
formatter: simple
filename: info.log
maxBytes: 10485760 # 10MB
backupCount: 20
encoding: utf8
error_file_handler:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: errors.log
maxBytes: 10485760 # 10MB
backupCount: 20
encoding: utf8
loggers:
my_module:
level: ERROR
handlers: [console]
propagate: no
root:
level: INFO
handlers: [console, info_file_handler, error_file_handler]
...
####下面的代码片段展示了如何从 JSON 文件中读取日志配置:
- - -
import os import json import logging.config
def setup_logging( default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG' ): """Setup logging configuration
"""
path = default_path
value = os.getenv(env_key, None)
if value:
path = value
if os.path.exists(path):
with open(path, 'rt') as f:
config = json.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level=default_level)
#####JSON 有个优势就是 json 是个标准库,你不需要安装就可以使用。但是个人来说我更喜欢 YAML,读写都方便。使用如下代码片段可以加载 YAML 配置:
- - -
import os import logging.config
import yaml
def setup_logging( default_path='logging.yaml', default_level=logging.INFO, env_key='LOG_CFG' ): """Setup logging configuration
"""
path = default_path
value = os.getenv(env_key, None)
if value:
path = value
if os.path.exists(path):
with open(path, 'rt') as f:
config = yaml.load(f.read())
logging.config.dictConfig(config)
else:
logging.basicConfig(level=default_level)
#####现在,要设置日志记录的话,在程序启动的时候调用 setup_logging 就行了。它默认读取 logging.json 或是 logging.yaml。你可以设置 LOG_CFG 环境变量来指定从某个路径中加载日志配置。例如:
>LOG_CFG=my_logging.json python my_server.py
或者,如果你偏好 YAML:
>LOG_CFG=my_logging.yaml python my_server.py
### 轮换日志处理器
如果你用 FileHandler 来写日志的话,日志文件大小会随时间不断增长。总有一天磁盘会被它占满的。为了避免这种情况,你应该在生产环境中使用 RotatingFileHandler 代替 FileHandler。
### 在有多台服务器的情况下,设置中心日志服务器
当你有多台服务器和多个日志文件的时候,你可以设置一台中心日志服务器来收集所有重要的信息(大部分情况下是警告、错误等)。这样你监视起来会比较简单,出错的时候也更容易注意到。
### 最后
Pythonde logging 弄快设计的很棒,而且是标准库,很容易扩展,你可以编写自己的处理器和过滤器。还有一些第三方的处理器,pyzmq提供的ZeroMQ处理器,可以让你通过一个zmq套接字来发送日志信息。
来源:oschina
链接:https://my.oschina.net/u/2526721/blog/690964