Python日志最佳实践


日志对于系统开发的开发、调试和运行整个过程中都起着很重要的作用,调试阶段需要查看日志来明确问题所在,运行阶段如果程序崩溃,日志可以记录程序崩溃的相关原因。 刚初学Python的时候,想调试代码基本靠print, 导致项目代码多了之后,print也不知到具体是那部分的。而且print打印日志也不符合PEP8的规范,到调试成功后还得一行行地把print删除,所以logging标准库很好地解决了这个问题,。

日志的作用

通过记录日志,可以了解系统、程序的运行情况;某些情况下,日志还可以记录用户的操作行为、类型爱好等可以用来分析。日志可以在程序发现问题后让开发人员或运维人员快速定位问题所在。总而言是,日志在开发调试、定位故障、了解程序运行情况等方面发挥重要作用。

简单示例

记录日志最简单正确的方式就是使用logging内置模块来实现。最简单一个例子如下:

import logging

# filename 指定日志存放文件,level 指定logging级别
logging.basicConfig(filename="out.log", level=logging.INFO)

logging.debug("hello, debug")
logging.info("hello, info")

logging.info("finsh, info")

运行结果如下:

INFO:root:hello, info
INFO:root:finsh, info

如上三行代码就可以把日志记录到文件中。有没有好奇为什么第一条logging没有存入日志文件中,这就和我们指定的level有关了,如下指定了日志的级别是INFO级别:

logging.basicConfig(filename="out.log", level=logging.INFO)

下面是logging模块的几种级别和对应的权重:

logging_level.png

从图中就可以解释为什么上面第一条logging没有记录到日志文件中了,因为INFO的级别比DEBUG大,而日志记录只会记录级别比INFO大或相等级别的日志。也就是说我们可以自己控制消息的级别,来保证重要的消息显示,或者说过滤掉一些不重要的日志,而且你还可以决定输出到哪里,输出的格式等等。不同等级一般适用于不同的用途,比如开发环境中,我们需要调试程序,通常需要看到具体详细的日志信息来定位问题;而生产环境中,我们就不需要太过于详细的日志,一般只需要异常、错误等级的日志信息,这样避免日志文件很大导致定位问题难,还可以减轻服务器的IO压力。

常见示例

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# 创建 handler 输出到文件
handler = logging.FileHandler("file.log", mode='w')
handler.setLevel(logging.INFO)

# handler 输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# 创建 logging format
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
ch.setFormatter(formatter)

# add the handlers to the logger
logger.addHandler(handler)
logger.addHandler(ch)

logger.info("hello, guoweikuang")
logger.debug("print to debug")
logger.error("error logging")
logger.warning("warning logging")
logger.critical("critical logging")

运行结果如下:

2018-04-11 09:30:25,354 - __main__ - INFO - hello, guoweikuang
2018-04-11 09:30:25,354 - __main__ - ERROR - error logging
2018-04-11 09:30:25,354 - __main__ - WARNING - warning logging
2018-04-11 09:30:25,354 - __main__ - CRITICAL - critical logging

logging模块有很多handler处理器,例如StreamHandler、RotatingFileHandler、TimedRotatingFileHandler等。

  • StreamHandler 可用来输出到控制台
  • RotatingFileHandler 基于文件大小切分日志, 防止一个日志文件过大
  • TimedRotatingFileHandler 基于时间间隔切分日志,防止一个日志文件过大

__name__ 作为logger对象名称

还有上面例子中getLogger(__name__)里面的__name__是什么作用呢?
这里贴一下官方文档的解释:

The name is potentially a period-separated hierarchical value, like foo.bar.baz 
(though it could also be just plain foo, for example). Loggers that are further 
down in the hierarchical list are children of loggers higher up in the list. 
For example, given a logger with a name of foo, loggers with names of foo.bar, 
foo.bar.baz, and foo.bam are all descendants of foo. The logger name hierarchy is 
analogous to the Python package hierarchy,and identical to it if you organise 
your loggers on a per-module basis using the recommended construction logging.getLogger(__name__). 
That’s because in a module, __name__ is the module’s name in the Python package namespace.

也就是说,logger对象名字的层级和python包的层级是相似的,并且如果你使用推荐的结构logging.getLogger(__name__)来管理你的loggers对象的话,那就与python包的层级结构是一模一样的。因为在一个模块中__name__是一个在python包中名字空间的模块的名称。
例如,你在模块 foo.bar.my_module 里调用 logging.getLogger(__name__),那么其实等价于 logging.getLogger('foo.bar.my_module')。这样做的目的是,你设置logger时候指定了foo包后,该包下的所有模块日志都是使用同一logger的配置,方便后期日志查看时清楚是那个模块的记录。

捕获异常,记录异常信息

# -*- coding: utf-8 -*-
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# handler 输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# 创建 logging format
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)

logger.addHandler(ch)


if __name__ == '__main__':
    try:
        open("/home/guoweikuang/fuck.log", 'rb')
    except (SystemExit, KeyboardInterrupt):
        raise

    except Exception as e:
        logger.error("failed to open file, ", exc_info=True)

运行结果如下:

2018-04-11 10:03:44,905 - __main__ - ERROR - failed to open file,
Traceback (most recent call last):
  File "logging_learn.py", line 20, in <module>
    open("/home/guoweikuang/fuck.log", 'rb')
IOError: [Errno 2] No such file or directory: '/home/guoweikuang/fuck.log'

指定了exc_info=True 参数后,traceback 信息将会被存入日志中,还有另外两个可设置参数,分别是stack_infoextra,其中stack_info 参数默认为False。如果为True,则将堆栈信息添加到日志记录消息中,包括实际的日志调用。第三个关键字参数是extra,可用于传递一个字典,用于填充为具有用户定义属性的日志记录事件创建的LogRecord的__dict__。具体了解请到官方文档查看。

未完待续。。。。。。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容