返回介绍

建议47:使用 logging 记录日志信息

发布于 2024-01-30 22:19:09 字数 4894 浏览 0 评论 0 收藏 0

仅仅将栈信息输出到控制台是远远不够的,更为常见的是使用日志保存程序运行过程中的相关信息,如运行时间、描述信息以及错误或者异常发生时候的特定上下文信息。Python中自带的logging模块提供了日志功能,它将logger的level分为5个级别(如表4-3所示),可以通过Logger.setLevel(lvl)来设置,其中DEBUG为最低级别,CRITICAL为最高级别,默认的级别为WARNING。

表4-3 日志级别

logging lib包含以下4个主要对象:

1)logger。logger是程序信息输出的接口,它分散在不同的代码中,使得程序可以在运行的时候记录相应的信息,并根据设置的日志级别或filter来决定哪些信息需要输出,并将这些信息分发到其关联的handler。常用的方法有Logger.setLevel()、Logger.addHandler()、Logger.removeHandler()、Logger.addFilter()、Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、etLogger()等。

2)Handler。Handler用来处理信息的输出,可以将信息输出到控制台、文件或者网络。可以通过Logger.addHandler()来给logger对象添加handler,常用的handler有StreamHandler和FileHandler类。StreamHandler发送错误信息到流,而FileHandler类用于向文件输出日志信息,这两个handler定义在logging的核心模块中。其他的handler定义在logging.handles模块中,如HTTPHandler、SocketHandler。

3)Formatter。决定log信息的格式,格式使用类似于%(< dictionary key >)s的形式来定义,如'%(asctime)s - %(levelname)s - %(message)s',支持的key可以在Python自带的文档LogRecord attributes中查看。

4)Filter。用来决定哪些信息需要输出。可以被handler和logger使用,支持层次关系,比如,如果设置了filter名称为A.B的logger,则该logger和其子logger的信息会被输出,如A.B、A.B.C.

logging.basicConfig([**kwargs])提供对日志系统的基本配置,默认使用StreamHandler和Formatter并添加到root logger,该方法自Python2.4开始可以接受字典参数,支持的字典参数如表4-4所示。

表4-4 字典参数格式类型

我们通过修改上一节的例子来看如何结合traceback和logging,记录程序运行过程中的异常。

import traceback
import sys
import logging
gList = ['a','b','c','d','e','f','g']
logging.basicConfig( #
配置日志的输出方式及格式
  level=logging.DEBUG,
  filename='log.txt',
  filemode='w',
  format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
)
def f():
  gList[5]
  logging.info('[INFO]:calling method g() in f()')#
记录正常的信息
  return g()
def g():
  logging.info('[INFO]:calling method h() in g()')
  return h()
def h():
  logging.info('[INFO]:Delete element in gList in h()')
  del gList[2]
  logging.info('[INFO]:calling method i() in h()')
  return i()
def i():
  logging.info('[INFO]:Append element i to gList in i()')
  gList.append('i')
  print gList[7]
if __name__ == '__main__':
  logging.debug('Information during calling f():')
  try:
    f()
  except IndexError as ex:
      print "Sorry,Exception occured,you accessed an element out of range"
      #traceback.print_exc()
      ty,tv,tb = sys.exc_info()
      logging.error("[ERROR]:Sorry,Exception occured,you accessed an 
        element out of range")#
记录异常错误信息
      logging.critical('object info:%s' %ex)
      logging.critical('Error Type:{0},Error Information:{1}'.format(ty, tv))
        #
记录异常的类型和对应的值
      logging.critical(''.join(traceback.format_tb(tb)))#
记录具体的trace
信息
      sys.exit(1)

修改程序后在控制台上对用户仅显示错误提示信息“Sorry,Exception occured,you accessed an element out of range”,而开发人员如果需要debug可以在日志文件中找到具体运行过程中的信息。

#
为了节省篇幅仅显示部分日志
2013-06-26 12:05:18,923 traceexample.py[line:41] CRITICAL object info:list 
  index out of range
2013-06-26 12:05:18,923 traceexample.py[line:42] CRITICAL Error Type:<type 
  'exceptions.IndexError'>,Error Information:list index out of range
2013-06-26 12:05:18,924 traceexample.py[line:43] CRITICAL   File "traceexample.py", 
  line 35, in <module>
  f()
  File "traceexample.py", line 15, in f
  return g()
  File "traceexample.py", line 19, in g
  return h()
  File "traceexample.py", line 25, in h
  return i()
  File "traceexample.py", line 30, in i
  print gList[7]

上面的代码中控制运行输出到console上用的是print(),但这种方法比较原始,logging模块提供了能够同时控制输出到console和文件的方法。下面的例子中通过添加StreamHandler并设置日志级别为logging.ERROR,可以在控制台上输出错误信息。

    console = logging.StreamHandler()
    console.setLevel(logging.ERROR)
    formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
    console.setFormatter(formatter)
    logging.getLogger('').addHandler(console)

为了使Logging使用更为简单可控,logging支持loggin.config进行配置,支持dictConfig和fileConfig两种形式,其中fileConfig是基于configparser()函数进行解析,必须包含的内容为[loggers]、[handlers]和[formatters]。具体例子示意如下:

[loggers]
keys=root
[logger_root]
level=DEBUG
handlers=hand01
[handlers]
keys=hand01
[handler_hand01]
class=StreamHandler
level=INFO
formatter=form01
args=(sys.stderr,)
[formatters]
keys=form01
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%a, %d %b %Y %H:%M:%S

最后关于logging的使用,提以下几点建议:

1)尽量为logging取一个名字而不是采用默认,这样当在不同的模块中使用的时候,其他模块只需要使用以下代码就可以方便地使用同一个logger,因为它本质上符合单例模式。

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger( __name__ )

2)为了方便地找出问题所在,logging的名字建议以模块或者class来命名。Logging名称遵循按“.”划分的继承规则,根是root logger,logger a.b的父logger对象为a。

3)Logging只是线程安全的,不支持多进程写入同一个日子文件,因此对于多个进程,需要配置不同的日志文件。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文