Python 记录日志的灵活性和可配置性介绍

对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不熟悉的应用为何不工作。有时候,你甚至不知道系统运行,是否跟原始设计一致。

在线运行的应用就是黑盒子,需要被跟踪监控。最简单也最重要的方式就是记录日志。记录日志允许我们在开发软件的同时,让程序在系统运行时发出信息,这些信息对于我们和系统管理员来说都是有用的。

就像为将来的程序员写代码文档一样,我们应该让新软件产生足够的日志供系统的开发者和管理员使用。日志是关于应用运行状态的系统文件的关键部分。给软件加日志产生句时,要向给未来维护系统的开发者和管理员写文档一样。

一些纯粹主义者认为一个受过训练的开发者使用日志和测试的时候几乎不需要交互调试器。如果我们不能用详细的日志解释开发过程中的应用,那么当代码在线上运行的时候,解释它们会变得更困难。

这篇文章介绍了 Python 的 logging 模块,包括它的设计以及针对更多复杂案例的适用方法。这篇文章不是写给开发者的文档,它更像是一个指导手册,来说明 Python 的 logging 模板是如何搭建的,并且激发感兴趣的人深入研究。

为什么使用 logging 模块?

也许会有开发者会问,为什么不是简单的 print 语句呢? Logging 模块有很多优势,包括:

1.多线程支持

2.通过不同级别的日志分类

3.灵活性和可配置性

4.将如何记录日志与记录什么内容分离

最后一点,将我们记录内容从记录方式中真正分离,保证了软件不同部分的合作。举个例子,它允许一个框架或库的开发者增加日志并且让系统管理员或负责运行配置的人员决定稍后应该记录什么。

Logging 模块中有什么

Logging 模块完美地将它的每个部分的职责分离(遵循 Apache Log4j API 的方法)。让我们看看一个日志线是如何通过这个模块的代码,并且研究下它的不同部分。

记录器(Logger)

记录器是开发者经常交互的对象。那些主要的 API 说明了我们想要记录的内容。

举个记录器的例子,我们可以分类请求发出一条信息,而不用担心它们是如何从哪里被发出的。

比如,当我们写下 logger.info(“Stock was sold at %s”, price) 我们在头脑中就有如下模块:

我们需要一条线。假设有些代码在记录器中运行,让这条线出现在控制台或文件中。但是在内部实际发生了什么呢?

日志记录

日志记录是 logging 模块用来满足所有需求信息的包。它们包含了需要记录日志的地方、变化的字符串、参数、请求的信息队列等信息。

它们都是被记录的对象。每次我们调用记录器时,都会生成这些对象。但这些对象是如何序列化到流中的呢?通过处理器!

处理器

处理器将日志记录发送给其他输出终端,他们获取日志记录并用相关函数中处理它们。

比如,一个文件处理器将会获取一条日志记录,并且把它添加到文件中。

标准的 logging 模块已经具备了多种内置的处理器,例如:

多种文件处理器(TimeRotated, SizeRotated, Watched),可以写入文件中

1.StreamHandler 输出目标流比如 stdout 或 stderr

2.SMTPHandler 通过 email 发送日志记录

3.SocketHandler 将日志文件发送到流套接字

4.SyslogHandler、NTEventHandler、HTTPHandler及MemoryHandler等

目前我们有个类似于真实情况的模型:

大部分的处理器都在处理字符串(SMTPHandler和FileHandler等)。或许你想知道这些结构化的日志记录是如何转变为易于序列化的字节的。

格式器

格式器负责将丰富的元数据日志记录转换为字符串,如果什么都没有提供,将会有个默认的格式器。

一般的格式器类由 logging 库提供,采用模板和风格作为输入。然后占位符可以在一个 LogRecord 对象中声明所有属性。

比如:'%(asctime)s %(levelname)s %(name)s: %(message)s' 将会生成日志类似于 2017-07-19 15:31:13,942 INFO parent.child: Hello EuroPython.

请注意:属性信息是通过提供的参数对日志的原始模板进行插值的结果。(比如,对于 logger.info(“Hello %s”, “Laszlo”) 这条信息将会是 “Hello Laszlo”)

所有默认的属性都可以在日志文档中找到。

好了,现在我们了解了格式器,我们的模型又发生了变化:

过滤器

我们日志工具的最后一个对象就是过滤器。

过滤器允许对应该发送的日志记录进行细粒度控制。多种过滤器能同时应用在记录器和处理器中。对于一条发送的日志来说,所有的过滤器都应该通过这条记录。

用户可以声明他们自己的过滤器作为对象,使用 filter 方法获取日志记录作为输入,反馈 True / False 作为输出。

出于这种考虑,以下是当前的日志工作流:

记录器层级

此时,你可能会对大量复杂的内容和巧妙隐藏的模块配置印象深刻,但是还有更需要考虑的:记录器分层。

我们可以通过 logging.getLogger() 创建一个记录器。这条字符向 getLogger 传递了一个参数,这个参数可以通过使用圆点分隔元素来定义一个层级。

举个例子,logging.getLogger(“parent.child”) 将会创建一个 “child” 的记录器,它的父级记录器叫做 “parent.” 记录器是被 logging 模块管理的全局对象,所以我们可以方便地在项目中的任何地方检索他们。

记录器的例子通常也被认为是渠道。层级允许开发者去定义渠道和他们的层级。

在日志记录被传递到所有记录器内的处理器时,父级处理器将会进行递归处理,直到我们到达顶级的记录器(被定义为一个空字符串),或者有一个记录器设置了 propagate = False。我们可通过更新的图中看出:

请注意父级记录器没有被调用,只有它的处理器被调用。这意味着过滤器和其他在记录器类中的代码不会在父级中被执行。当我们在记录器中增加过滤器时,这通常是个陷阱。

工作流小结

我们已经阐明过职责的划分以及我们是如何微调日志过滤。然而还是有两个其他的属性我们没有提及:

1.记录器可以是残缺的,从而不允许任何记录从这被发出。

2.一个有效的层级可以同时在记录器和处理器中被设置。

举个例子,当一个记录器被设置为 INFO 的等级,只有 INFO 等级及以上的才会被传递,同样的规则适用于处理器。

基于以上所有的考虑,最后的日志记录的流程图看起来像这样:

如何使用日志记录模块

现在我们已经了解了 logging 模块的部分及设计,是时候去了解一个开发者是如何与它交互的了。以下是一个代码例子:

import logging
def sample_function(secret_parameter):
logger = logging.getLogger(__name__) # __name__=projectA.moduleB
logger.debug("Going to perform magic with '%s'", secret_parameter)
...
try:
result = do_magic(secret_parameter)
except IndexError:
logger.exception("OMG it happened again, someone please tell Laszlo")
except:
logger.info("Unexpected exception", exc_info=True)
raise
else:
logger.info("Magic with '%s' resulted in '%s'", secret_parameter, result, stack_info=True)

它用模块 __name__ 创建了一个日志记录器。它会基于项目结构创建渠道和等级,正如 Pyhon 模块用圆点连接一样。

记录器变量引用记录器的 “module” ,用 “projectA” 作为父级, “root” 作为父级的父级。

在第五行,我们看到如何执行调用去发送日志。我们可以用 debug 、 info 、error 或 critical 这些方法之一在合适的等级上去记录日志。

当记录一条信息时,除了模板参数,我们可以通过特殊的含义传递密码参数,最有意思的是 exc_info 和 stack_info。它们将会分别增加关于当前异常和栈帧的信息。为了方便起见,在记录器对象中有一个方法异常,正如这个错误调用 exc_info=True 。

这些是如何使用记录器模块的基础,但是有些通常被认为是不良操作的做法同样值得说明。

过度格式化字符串

应该尽量避免使用 loggger.info(“string template {}”.format(argument)) ,可能的话尽量使用 logger.info(“string template %s”, argument)。 这是个更好的实践,因为只有当日志被发送时,字符串才会发生真正改变。当我们记录的层级在 INFO 之上时,不这么做会导致浪费周期,因为这个改变仍然会发生。

捕捉和格式化异常

通常,我们想记录在抓取模块异常的日志信息,如果这样写会很直观:

try:
..
except Exception as error:
logger.info("Something bad happened: %s", error)

但是这样的代码会给我们显示类似于 Something bad happened: “secret_key.” 的日志行,这并不是很有用。如果我们使用 exc_info 作为事先说明,那么它将会如下显示:

try:
..
except Exception:
logger.info("Something bad happened", exc_info=True)
Something bad happened
Traceback (most recent call last):
File "sample_project.py", line 10, in code
inner_code()
File "sample_project.py", line 6, in inner_code
x = data["secret_key"]
KeyError: 'secret_key'

这不仅仅会包含异常的准确资源,同时也会包含它的类型。

设置记录器

装备我们的软件很简单,我们需要设置日志栈,并且制定这些记录是如何被发出的。

以下是设置日志栈的多种方法

基础设置

这是至今最简单的设置日志记录的方法。使用 logging.basicConfig(level=”INFO”) 搭建一个基础的 StreamHandler ,这样就会记录在 INFO 上的任何东西,并且到控制台以上的级别。以下是编写基础设置的一些参数:

请注意, basicConfig 仅仅在运行的一开始可以这么调用。如果你已经设置了你的根记录器,调用 basicConfig 将不会奏效。

字典设置

所有元素的设置以及如何连接它们可以作为字典来说明。这个字典应当由不同的部分组成,包括记录器、处理器、格式化以及一些基本的通用参数。

例子如下:

config = {
'disable_existing_loggers': False,
'version': 1,
'formatters': {
'short': {
'format': '%(asctime)s %(levelname)s %(name)s: %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'formatter': 'short',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'ERROR',
},
'plugins': {
'handlers': ['console'],
'level': 'INFO',
'propagate': False
}
},
}
import logging.config
logging.config.dictConfig(config)

当被引用时, dictConfig 将会禁用所有运行的记录器,除非 disable_existing_loggers 被设置为 false。这通常是需要的,因为很多模块声明了一个全球记录器,它在 dictConfig 被调用之前被导入的时候将会实例化。

你可以查看 schema that can be used for the dictConfig method(链接)。通常,这些设置将会存储在一个 YAML 文件中,并且从那里设置。很多开发者会倾向于使用这种方式而不是使用 fileConfig(链接),因为它为定制化提供了更好的支持。

拓展 logging

幸亏设计了这种方式,拓展 logging 模块很容易。让我们来看些例子:

logging JSON | 记录 JSON

只要我们想要记录,我们可以通过创建一种自定义格式化来记录 JSON ,它会将日志记录转化为 JSON 编码的字符串。

import logging
import logging.config
import json
ATTR_TO_JSON = ['created', 'filename', 'funcName', 'levelname', 'lineno', 'module', 'msecs', 'msg', 'name', 'pathname', 'process', 'processName', 'relativeCreated', 'thread', 'threadName']
class JsonFormatter:
def format(self, record):
obj = {attr: getattr(record, attr)
for attr in ATTR_TO_JSON}
return json.dumps(obj, indent=4)
handler = logging.StreamHandler()
handler.formatter = JsonFormatter()
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.error("Hello")

添加更多上下文

在格式化中,我们可以指定任何日志记录的属性。

我们可以通过多种方式增加属性,在这个例子中,我们用过滤器来丰富日志记录。

import logging
import logging.config
GLOBAL_STUFF = 1
class ContextFilter(logging.Filter):
def filter(self, record):
global GLOBAL_STUFF
GLOBAL_STUFF += 1
record.global_data = GLOBAL_STUFF
return True
handler = logging.StreamHandler()
handler.formatter = logging.Formatter("%(global_data)s %(message)s")
handler.addFilter(ContextFilter())
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.error("Hi1")
logger.error("Hi2")

这样有效地在所有日志记录中增加了一个属性,它可以通过记录器。格式化会在日志行中包含这个属性。

请注意这会在你的应用中影响所有的日志记录,包含你可能用到以及你发送日志的库和其他的框架。它可以用来记录类似于在所有日志行里的一个独立请求 ID ,去追踪请求或者去添加额外的上下文信息。

从 Python 3.2 开始,你可以使用 setLogRecordFactory 去获得所有日志的创建记录和增加额外的信息。这个 extra attribute 和 LoggerAdapter class 或许同样是有趣的。

缓冲日志

有时候当错误发生时,我们想要排除日志故障。创建一个缓冲的处理器,来记录当错误发生时的最新故障信息是一种可行的办法。下面的代码是个非人为策划的例子:

import logging
import logging.handlers
class SmartBufferHandler(logging.handlers.MemoryHandler):
def __init__(self, num_buffered, *args, **kwargs):
kwargs["capacity"] = num_buffered + 2 # +2 one for current, one for prepop
super().__init__(*args, **kwargs)
def emit(self, record):
if len(self.buffer) == self.capacity - 1:
self.buffer.pop(0)
super().emit(record)
handler = SmartBufferHandler(num_buffered=2, target=logging.StreamHandler(), flushLevel=logging.ERROR)
logger = logging.getLogger(__name__)
logger.setLevel("DEBUG")
logger.addHandler(handler)
logger.error("Hello1")
logger.debug("Hello2") # This line won't be logged
logger.debug("Hello3")
logger.debug("Hello4")
logger.error("Hello5") # As error will flush the buffered logs, the two last debugs will be logged

总结

以上所述是小编给大家介绍的Python 记录日志的灵活性和可配置性,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

您可能感兴趣的文章:

  • python日志记录模块实例及改进
  • Python记录详细调用堆栈日志的方法
(0)

相关推荐

  • Python记录详细调用堆栈日志的方法

    本文实例讲述了Python记录详细调用堆栈日志的方法.分享给大家供大家参考.具体实现方法如下: import sys import os def detailtrace(info): retStr = "" curindex=0 f = sys._getframe() f = f.f_back # first frame is detailtrace, ignore it while hasattr(f, "f_code"): co = f.f_code retSt

  • python日志记录模块实例及改进

    python 打印对象的所有属性值: def prn_obj(obj): print '\n'.join(['%s:%s' % item for item in obj.__dict__.items()]) Python logger对象属性(由上述函数获取的) name:get_data parent:<logging.RootLogger instance at 0x1d8bd88> handlers:[<logging.FileHandler instance at 0x21bcc

  • Python 记录日志的灵活性和可配置性介绍

    对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不熟悉的应用为何不工作.有时候,你甚至不知道系统运行,是否跟原始设计一致. 在线运行的应用就是黑盒子,需要被跟踪监控.最简单也最重要的方式就是记录日志.记录日志允许我们在开发软件的同时,让程序在系统运行时发出信息,这些信息对于我们和系统管理员来说都是有用的. 就像为将来的程序员写代码文档一样,我们应该让新软件产生足够的日志供系统的开发者和管理员使用.日志是关于应用运行状态的系统文件的关键部分.给软件加日志产生句时,要向给未来维护系统的开发者和管理员

  • Python 中的pygame安装与配置教程详解

    安装软件环境及版本说明 OS: Win10 x 64 专业版 Python: 2.7 IDE: PyCharm Community 2018 1. 安装python 1)下载并安装python python官网下载需要的版本,并安装(安装过程很简单,步骤略) https://www.python.org/downloads/windows/ 这里下载的是python2.7 2)配置环境变量 如果安装python时,没有勾选添加python到环境变量PATH,则需要手动添加 3)验证是否安装+配置

  • Pycharm及python安装详细步骤及PyCharm配置整理(推荐)

    首先我们来安装python 1.首先进入网站下载:点击打开链接(或自己输入网址: https://www.python.org/downloads/),进入之后如下图,选择图中红色圈中区域进行下载. 2.下载完成后如下图所示 3.双击exe文件进行安装,如下图,并按照圈中区域进行设置,切记要勾选打钩的框,然后再点击Customize installation进入到下一步: 4.对于上图中,可以通过Browse进行自定义安装路径,也可以直接点击Install进行安装,点击install后便可以完成

  • Python中PySide2的安装及配置

    以前学java的时候,接触过用Swing编写GUI程序,在入职第一份工作的时候,公司的入职培训还教了我用WPF编写GUI客户端,今天,学习到了一个新的方式:那就是使用Python中的相关库进行编写GUI程序. 在python中,支持图形化界面开发的库有很多,今天主要介绍一下PySide2这个库.这个的话,要结合PyQt一起对比理解,两者协议不同,也决定他们的使用场景不同. PyQt是GPLv3协议,如果你的程序中用了它,你的程序就要开源,如果闭源商用就会违反协议(后果自负,脸皮够厚无所谓). P

  • python数字图像处理环境安装与配置过程示例

    目录 引言 一.需要的安装包 二.下载并安装 anaconda 三.简单测试 四.skimage包的子模块 引言 一提到数字图像处理编程,可能大多数人就会想到matlab,但matlab也有自身的缺点: 1.不开源,价格贵 2.软件容量大.一般3G以上,高版本甚至达5G以上. 3.只能做研究,不易转化成软件. 因此,我们这里使用python这个脚本语言来进行数字图像处理. 要使用python,必须先安装python,一般是2.7版本以上,不管是在windows系统,还是linux系统,安装都是非

  • Python 任务自动化工具nox 的配置与 API详情

    前言: NoxfileNox 默认在一个名为noxfile.py的文件中查找配置.在运行 nox 时,你可以使用 --noxfile参数指定其它的文件. 定义会话格式:session(func=None, python=None, py=None, reuse_venv=None, name=None, venv_backend=None),将被装饰的函数指定为一个会话. Nox 会话是通过被@nox.session装饰的标准 Python 函数来配置的. 例如: import nox @nox

  • Python pyecharts 数据可视化模块的配置方法

    目录 1. pyecharts 模块介绍 2. pyecharts 模块安装 3. pyecharts 配置选项 3.1 全局配置选项 3.2 系列配置选项 4. 基础折线图的构建 4.1 基本使用流程 5. 基础地图构建 5.1 基本使用流程 5.2 实现国内疫情地图 5.3 实现省级疫情地图 6. 基础柱状图构建 6.1 基本使用流程 6.2 基础时间线柱状图 6.3 实现动态 GDP 柱状图 1. pyecharts 模块介绍 Echarts 是一个由百度开源的数据可视化,凭借着良好的交互

  • Python selenium模块的安装和配置教程

    目录 一.selenium的安装以及简单应用 二.selenium的简单使用 三.selenium提取数据 1.driver对象常用的属性和方法 2.driver对象定位标签元素获取标签对象的方法 3.标签对象提取文本内容和属性值 四.selenium无头模式 一.selenium的安装以及简单应用 我们以谷歌浏览器的chromedriver为例 1.在Python虚拟环境中安装selenium模块 pip/pip3 install selenium 2.下载版本符合的webdriver 以ch

  • Python写代码的七条重要技巧介绍

    目录 前言 0x00 规范命名 0x01 面向对象 0x02 使用 with 0x03 使用 get 0x04 提前返回 0x05 生成器 0x06 装饰器 前言 写出能完成功能的程序每个程序员都可以搞定,但能写出优雅的程序的程序员却寥寥无几,因此程序写的优雅与否则是区分顶级程序员与一般程序员的终极指标所在. 那身为一名 Pythoner,有哪些技巧能让我们写出优雅的 Python 代码呢,今天就给大家介绍七个能快速提升代码逼格的重要技巧. 0x00 规范命名 没有哪个程序员会抗拒一段命名规范的

  • 深入理解Python中的 __new__ 和 __init__及区别介绍

    本文的目的是讨论Python中 __new__ 和 __ini___ 的用法. __new__ 和 __init__ 的区别主要表现在:1. 它自身的区别:2. 及在Python中新式类和老式类的定义. 理解 __new__ 和 __init__ 的区别 这两个方法的主要区别在于:__new__ 负责对象的创建而 __init__ 负责对象的初始化.在对象的实例化过程中,这两个方法会有些细微的差别,表现于:如何工作,何时定义. Python中两种类的定义方式 Python 2.x 中类的定义分为

随机推荐