返回顶部
首页 > 资讯 > 后端开发 > Python >一篇文章教你如何用 Python 记录日
  • 305
分享到

一篇文章教你如何用 Python 记录日

教你一篇文章如何用 2023-01-31 01:01:47 305人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

(点击上方公众号,可快速关注)编译: python开发者 - 李趴趴要化身女超人,英文:Mario CorcheroHttp://python.jobbole.com/89007/对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不熟

(点击上方公众号,可快速关注)


编译: python开发者 - 李趴趴要化身女超人,英文:Mario Corchero

Http://python.jobbole.com/89007/


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

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

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

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

这篇文章介绍了 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 上的任何东西,并且到控制台以上的级别。以下是编写基础设置的一些参数:

参数说明举例
filename指定创建的文件处理器,使用特定的文件名,而不是流处理器/var/logs/logs.txt
format为处理器使用特定格式的字符串“‘%(asctime)s %(message)s'”
datefmt使用特定的日期/时间格式“%H:%M:%S”
level为根记录器等级设置特定等级“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


更多信息

这篇关于日志记录库的灵活性和可配置性的介绍,目的在于证明它如何设计了分别的关注点的美学。它同样为任何对 logging documentation 和 how-to guide 感兴趣的人提供了一个坚实的基础。虽然这篇文章对于 Python 日志模块并不是一个综合性的知道,但是这里有一些针对于常见的问题的回答。

问:我的库发送了一个“ no logger configured” 的警告

答:从 The Hitchhiker’s Guide to Python 查阅 how to configure logging in a library

问:如果一个记录器没有层级设置会怎么样?

答:记录器的有效层级,会由它的父级递归定义。

问:我所有的日志都在本地时间,我如何记录在 UTC ?

答:格式化就是答案!你需要在你的格式化中设置 converter 属性为通用的 UTC 时间。使用 converter = time.gmtime 。


看完本文有收获?请转发分享给更多人

关注「Python开发者」,提升Python技能

--结束END--

本文标题: 一篇文章教你如何用 Python 记录日

本文链接: https://lsjlt.com/news/185254.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 一篇文章教你如何用 Python 记录日
    (点击上方公众号,可快速关注)编译: Python开发者 - 李趴趴要化身女超人,英文:Mario Corcherohttp://python.jobbole.com/89007/对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不熟...
    99+
    2023-01-31
    教你 一篇文章 如何用
  • 一篇文章教你如何理解SQL
    这篇文章主要介绍了一篇文章教你如何理解SQL,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。SQL 是一种为数不多的声明性语言,它的运行方式完...
    99+
    2024-04-02
  • 一篇文章教你用Python绘画一个太阳系
    目录日地月三体日地火太阳系你们要的3D太阳系 图片上传之后不知为何帧率降低了许多。。。 日地月三体 所谓三体,就是三个物体在重力作用下的运动。由于三点共面,所以三个质点仅在重力作用下...
    99+
    2024-04-02
  • 一篇文章教会你PYcharm的用法
    目录一、界面介绍二、设置中文(无需汉化包)三、常用快捷键一些常用设置:四、Python 标识符和关键字1、标识符2、 关键字五、行和缩进六、Python 引号七、Python注释1、...
    99+
    2024-04-02
  • 一篇文章教你用Python实现一键文件重命名
    目录应用背景准备工作上脚本view.py功能展示打包方式windows打包方式:pycharm打包为exe执行文件方法总结应用背景 背景:“由于工作需要可能需要对一些文件进行重命名的...
    99+
    2024-04-02
  • 一篇文章教你如何在SpringCloud项目中使用OpenFeign
    目录OpenFeign的介绍OpenFeign是一种声明式 、模板化的HTTP客户端。OpenFeign与Feign的之间的关系OpenFegin中的两个常用注解在项目中使用Open...
    99+
    2024-04-02
  • 一篇文章教你JS函数继承
    目录一. 前言:二.原型链继承:三.借用构造函数继承(对象伪装):四.组合继承:五.寄生组合继承:六.class继承:七.总结:一. 前言: Hello,大家最近过得好吗,€...
    99+
    2024-04-02
  • 一篇文章教你如何排查.NET内存泄漏
    目录前言检查托管内存使用 生成dump文件 分析 core dump 总结前言 内存泄漏通常表示:一个应用程序的某些对象在完成它的的生命周期后,由于它被其他对象意外引用,导致后续gc...
    99+
    2024-04-02
  • 一篇文章教你学会使用Python绘制甘特图
    目录优点局限一日一书 用来制作甘特图的专业工具也不少,常见的有:Microsoft Office Project、GanttProject、WARCHART XGantt、jQuer...
    99+
    2024-04-02
  • 一篇文章教你如何用C语言实现strcpy和strlen
    目录 自己实现strcpy、strlenstrcpy的实现strlen的实现总结 自己实现strcpy、strlen strcpy的实现 思路: 通过指针访问地址...
    99+
    2024-04-02
  • 一篇文章带你了解一些Java反射的学习记录
    目录一、反射概述二、入门案例三、反射原理图四、反射性能测试五、Class类六、类加载总结一、反射概述 反射允许程序在运行中获取类的内部信息,例如构造器、成员变量、成员方法等...
    99+
    2024-04-02
  • 一篇文章带你自学python Django
    目录1. Django简介Django是什么?Django前景Django框架核心2. 设计模式MVT模式3. 开发环境简介4.创建虚拟环境4.1.首先安装管理环境的包以及虚拟环境包...
    99+
    2024-04-02
  • 一篇文章教你用Python实现一个学生管理系统
    目录片头源码:总结片头 Python看了差不多三四天吧,基本上给基础看差不多了。写个管理系统吧,后续不出意外SQL、文件存储版本都会更。 学习Python感想: ...
    99+
    2024-04-02
  • 一篇文章带你了解C++Primer学习日记--处理数据
    目录1.简单变量1.1变量名1.2整型short,int,long和long long1.3无符号类型1.4选择整型类型1.5char类型2.const限定符3.浮点类型4.类型转换...
    99+
    2024-04-02
  • 一篇文章教会你使用gs_restore导入数据
    目录背景信息命令格式示例总结背景信息 gs_restore是openGauss提供的针对gs_dump导出数据的导入工具。通过此工具可将由gs_dump生成的导出文件进行导入。 gs_restore工具由操作系统用户om...
    99+
    2024-04-02
  • 一篇文章教你用React实现菜谱系统
    目录一、菜谱大全1.1、项目背景1.2、技术栈1.3、开发环境1.4、项目效果展示1.5、项目初始化二、首页开发2.1、antd-mobile 组件库2.2、底部导航实现2.3、菜谱...
    99+
    2024-04-02
  • 一篇文章教你如何用Java自定义一个参数校验器
    目录注解 校验器异常处理测试总结 自定义一个唯一字段校验器 注解 @Target({ElementType.FIELD}) @Retention(RetentionPoli...
    99+
    2024-04-02
  • 一篇文章带你了解Python中的类
    目录1、类的定义2、创建对象3、继承总结1、类的定义 创建一个rectangle.py文件,并在该文件中定义一个Rectangle类。在该类中,__init__表示构造方法。其中,s...
    99+
    2024-04-02
  • 一篇文章带你搞定Python多进程
    目录1.Python多进程模块2.Python多进程实现方法一3.Python多进程实现方法二4.Python多线程的通信5.进程池1.Python多进程模块 Python中的多进程...
    99+
    2024-04-02
  • 一篇文章教你写出干净的JavaScript代码
    目录1. 变量使用有意义的名称避免添加不必要的上下文避免硬编码值2. 函数使用有意义的名称使用默认参数限制参数的数量避免在一个函数中做太多事情避免使用布尔标志作为参数避免写重复的代码...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作