Skip to main content

在您的日志中标记和跟踪事件,类似同位素标记

项目描述

最新的 PyPI 包 下载最新的 PyPI 包 支持的 Python 版本 执照

Unix 上 master 分支的构建状态 Windows 上 master 分支的构建状态 测试覆盖率

代码问题 代码气候 Codacy 代码审查 审查员代码质量 代码健康 需求状态

目录

<nav class="contents local" id="contents" role="doc-toc"> </nav>

概要

isotopic-logging是一个小的 Python 库,旨在帮助您在整个执行流程中跟踪单独的操作及其部分。这是通过在日志消息的开头注入操作前缀来完成的。

这个库诞生于具有 Web 应用程序和后台任务队列的真实项目的深处,每个项目都可以有多个工作人员。这个库解决了两个关键点:

  • 作为管理员,我希望在单个操作中使用相同前缀标记日志条目,以便即使日志是从多个线程或源写入的,我也可以区分和跟踪操作。

  • 作为开发人员,我想在某些上下文中存储前缀,这样我就不需要在每次调用记录器时对其进行格式化,这样我就可以在嵌套函数调用中访问它,而无需直接将前缀传递给函数并搞砸它的语义。

当您有一个由并行源(线程或进程)填充的日志流时, isotopic-logging非常有用,并且您需要在一堆交织的日志消息中检测单个操作的流并区分不同的实例相同的操作。

该库也可用于单进程和单线程应用程序。您可能仍然需要检测操作并跟踪它们的执行时间,并且您可以做得很好。

快速输出示例

例如,单个复杂操作的日志可能如下所示:

INFO     [2015-12-15 21:45:04,339] D6EF95 | Heavy task has started.
DEBUG    [2015-12-15 21:46:36,148] D6EF95 | Checking user permissions.
INFO     [2015-12-15 21:46:36,654] D6EF95 | Analysis | Analysis phase has started.
DEBUG    [2015-12-15 21:46:41,756] D6EF95 | Analysis | Analysing current state of devices.
DEBUG    [2015-12-15 21:46:42,959] D6EF95 | Analysis | Analysing new state of devices.
DEBUG    [2015-12-15 21:46:47,565] D6EF95 | Analysis | Analysing changes.
INFO     [2015-12-15 21:46:51,871] D6EF95 | Analysis | Analysis phase has finished.
INFO     [2015-12-15 21:46:54,073] D6EF95 | Pushing data to central storage.
INFO     [2015-12-15 21:46:55,278] D6EF95 | Communication | Communication phase has started.
DEBUG    [2015-12-15 21:46:58,884] D6EF95 | Communication | Spreading out parallel subtasks for every involved device.
DEBUG    [2015-12-15 21:47:02,089] D6EF95 | Communication | 478272 | Connecting to device #3.
DEBUG    [2015-12-15 21:47:03,493] D6EF95 | Communication | 28B208 | Connecting to device #1.
INFO     [2015-12-15 21:47:04,798] D6EF95 | Communication | 28B208 | Running job at device #1.
DEBUG    [2015-12-15 21:47:10,501] D6EF95 | Communication | AE2677 | Connecting to device #2.
INFO     [2015-12-15 21:47:12,501] D6EF95 | Communication | AE2677 | Running job at device #2.
INFO     [2015-12-15 21:47:17,707] D6EF95 | Communication | 478272 | Running job at device #3.
INFO     [2015-12-15 21:47:21,709] D6EF95 | Communication | Communication phase has finished.
DEBUG    [2015-12-15 21:47:24,412] D6EF95 | Commiting changes.
INFO     [2015-12-15 21:47:27,013] D6EF95 | Heavy task has finished, elapsed time: 00:23:11.004120.

需要注意的重要事项:上面示例日志的每一行都可能由在不同线程或进程中运行的不同函数产生。他们不需要记住并将日志前缀从一个传递到另一个,这让您专注于开发过程并防止您因日志消息格式而分心。

安装

要安装该库,只需在Cheese Shop (PyPI) 获取它:

pip install isotopic-logging

关键概念

该库的工作基于几个关键概念:

  • 前缀注入器:它们存储或/和生成前缀并将它们注入到字符串中。

  • 注入上下文:它们管理注入器(获取或创建它们)并跟踪作用域执行时间。

  • 注入范围:它们驱动注入器的创建和绑定操作执行时间。

  • 记录器包装器:其他概念的高潮。包装记录器并提供创建注入上下文的方法。

这些概念可以单独使用,也可以以记录器包装器的形式作为一个整体组合使用。这种方法对于灵活的定制很有用。

前缀注入器

前缀注入器是存储或/和生成前缀属性访问的 前缀并使用mark()方法注入目标字符串的对象 。

默认注入器在isotopic_logging.injectors模块中定义,如下所述。

直接前缀喷射器

DirectPrefixInjector将精确地注入给定前缀的字符串:

from isotopic_logging.injectors import DirectPrefixInjector

inj = DirectPrefixInjector("foo > ")
inj.mark("message")
# "foo > message"

所有其他注入器都是DirectPrefixInjector的子类,通常您不需要直接使用它。只有当您需要在进程或线程之间传输前缀时才会出现异常。

静态前缀注入器

StaticPrefixInjector自动在前缀和目标字符串之间插入分隔符:

from isotopic_logging.injectors import StaticPrefixInjector

inj = StaticPrefixInjector("foo")
inj.mark("message")
# "foo | message"

默认分隔符定义为isotopic_logging.defaults.DELIMITER,因为它的值是“|”(空格-管道-空格)。

您可以设置自定义分隔符:

inj = StaticPrefixInjector("foo", delimiter=":")
inj.mark("message")
# "foo:message"

自动前缀注入器

AutoprefixInjectorStaticPrefixInjector一样工作,但它自己生成前缀。

一般用于区分相同操作的不同实例或对相同方法的不同调用等。

from isotopic_logging.injectors import AutoprefixInjector

inj1 = AutoprefixInjector()
inj1.mark("message")
# "C220A0 | message"

inj2 = AutoprefixInjector()
inj2.mark("message")
# "4118BB | message"

在这里你可以看到 2 个不同的注入器有 2 个不同的前缀。

默认前缀由线程安全生成器 isotopic_logging.generators.default_oid_generator生成,它使用uuid.uuid4 生成结果。

给定 6 个符号的默认前缀长度,默认生成器保证 99% 的生成前缀在来自 100 个并行线程的 500 个串行调用的情况下是唯一的。认为区分时间上彼此接近的操作就足够了。

您可以使用自定义生成器:

from itertools import cycle
from isotopic_logging.injectors import AutoprefixInjector

generator = cycle(["foo", "bar", ])

inj1 = AutoprefixInjector(generator)
inj1.mark("message")
# "foo | message"

inj2 = AutoprefixInjector(generator)
inj2.mark("message")
# "bar | message"

如果您确定需要自定义生成器,则必须确保它是线程安全的。您可以为此使用isotopic_logging.concurrency.threadsafe_iter :

from isotopic_logging.concurrency import threadsafe_iter

def generate():
    i = 1
    while True:
        yield "gen-%d" % i
        i += 1

generator = threadsafe_iter(generate())

在纯 Python 中实现的生成器需要threadsafe_iter 。例如,在 CPython 中itertools.cycle具有本地实现,并且它是开箱即用的线程安全的。此外,看起来 Python 3 也使您的生成器线程安全,因此您很可能 只需要 Python 2 的threadsafe_iter 。

AutoprefixInjector还支持自定义分隔符:

inj = AutoprefixInjector(delimiter=":")
inj.mark("message")
# "74D3B2:message"

混合前缀注入器

HybridPrefixInjector结合了AutoprefixInjectorStaticPrefixInjector的两个特性:它创建的前缀由生成的部分和静态部分组成,这些部分由默认或自定义分隔符分隔。

from isotopic_logging.injectors import HybridPrefixInjector

inj1 = HybridPrefixInjector("static")
inj1.mark("message")
# "78E519 | static | message"

inj2 = HybridPrefixInjector("static")
inj2.mark("message")
# "EF8A74 | static | message"

此前缀注入器还支持自定义分隔符和生成器:

from itertools import cycle
from isotopic_logging.injectors import HybridPrefixInjector

generator = cycle(["foo", "bar", ])

inj1 = HybridPrefixInjector("static", generator, delimiter=":")
inj1.mark("message")
# "foo:static:message"

inj2 = HybridPrefixInjector("static", generator, delimiter=":")
inj2.mark("message")
# "bar:static:message"

注入上下文

注入上下文用于范围管理。范围在下一节中描述。

上下文负责为您提供适当的注入器。注射器是按需创建的。一般来说,这可以描述为:

  • “给我当前的喷油器或创建新的特定喷油器,如果没有当前的喷油器

  • 或“尽管有任何事情,但仍创建从当前注入器继承的新注入器”。

上下文将注入器组织成堆栈。堆栈是线程本地的,不会相互干扰。堆栈大小没有限制。这应该不是问题,因为注入器是惰性创建的。仅当堆栈为空或您明确想要继承当前前缀(通常是为了区分子操作)时才会发生这种情况。

当前注入器是当前线程中堆栈顶部的注入器。

注入上下文管理器在isotopic_logging.context模块中定义。每种类型的前缀注入器都有一个适当的上下文管理器。上下文管理器接受与它们将要生成的注入器相同的参数。

例子:

from isotopic_logging.context import direct_injector, static_injector
from isotopic_logging.context import auto_injector, hybrid_injector

with direct_injector("foo > ") as inj:
    inj.mark("message")
    # "foo > message"

with static_injector("foo") as inj:
    inj.mark("message")
    # "foo | message"

with auto_injector() as inj:
    inj.mark("message")
    # "25EBB8 | message"

with hybrid_injector("static") as inj:
    inj.mark("message")
    # "0F9A8F | static | message"

注入范围

范围由上下文创建,它们用于驱动注入器的创建。有两种作用域:顶级作用域和嵌套作用域。嵌套范围允许继承前缀。

让我们看一些例子来抓住这个想法。

嵌套范围

from isotopic_logging.context import auto_injector, hybrid_injector

def helper():
    with auto_injector() as inj:
        print(inj.mark("call from helper"))

def operation():
    with hybrid_injector("operation") as inj:
        print(inj.mark("start"))
        helper()
        print(inj.mark("end"))

在这里,我们将帮助函数和操作函数分开。它们都通过上下文管理器定义自己的范围。

如果直接调用helper ,它的作用域将是顶​​级的,并且将为每个调用创建新的注入器:

helper()
# ED5ED5 | call from helper
helper()
# 14F7CE | call from helper

如果从operation调用helper,它的作用域将变成 嵌套的,它将重用在顶级作用域内创建的注入器:

operation()
# A15324 | operation | start
# A15324 | operation | call from helper
# A15324 | operation | end

在这种情况下,操作中的injhelper中的inj将是完全相同的对象。

继承范围

如果在可重用的帮助程序、实用程序等中使用嵌套范围是很好的,尤其是在它们很小的情况下。如果嵌套调用存在一些复杂的操作,您可能希望使用自己的前缀将它们分开,但保留父前缀。

您可以继承当前前缀来这样做:

from isotopic_logging.context import (
    auto_injector, static_injector, hybrid_injector,
)

def helper():
    with auto_injector() as inj:
        print(inj.mark("call from helper"))

def suboperation():
    with static_injector("suboperation", inherit=True) as inj:
        print(inj.mark("start"))
        helper()
        print(inj.mark("end"))

def operation():
    with hybrid_injector("operation") as inj:
        print(inj.mark("start"))
        suboperation()
        print(inj.mark("end"))

operation()
# 9F3A34 | operation | start
# 9F3A34 | operation | suboperation | start
# 9F3A34 | operation | suboperation | call from helper
# 9F3A34 | operation | suboperation | end
# 9F3A34 | operation | end

在这里,子操作使用带有标志的static_injector inherit=True。这将创建新的注入器,它是父前缀和给定静态前缀的组合。子操作还调用创建嵌套注入范围的助手,如前面的示例所示。

因此,如您所见,该库的主要优点之一是分离函数之间的前缀传输。与前缀管理相结合,这可以使您的函数及其主体的 API 保持清洁,节省您的时间和精力。

记录器包装器

isotopic_logging允许您包装您的记录器,以防止您在 每次将一些消息记录到日志时输入inj.mark() 。这节省了代码空间并使其更具可读性。

包装是通过isotopic_logging.IsotopicLogger记录器包装器完成的。它包装了记录器,这些记录器是logging.Logger及其子类的实例。

Wrapper 提供了使用预定义的前缀注入器创建记录器代理的方法:

  • DirectPrefixInjectordirect()

  • 静态前缀注入器的静态()

  • 用于AutoprefixInjector的auto()

  • Hybrid()用于HybridPrefixInjector

这些方法接受与适当的注入上下文管理器相同的参数。他们返回上下文管理器以获取记录器代理。代理充当通常的记录器,它们使用特定前缀包装记录调用。

例子:

import logging

from isotopic_logging import IsotopicLogger

LOG = IsotopicLogger(logging.getLogger(__name__))

with LOG.auto() as log:
    log.debug("debug message")
    log.info("info message")
    log.warning("warning message")
    log.error("error message")
    log.critical("critical message")

# DEBUG    [2015-12-31 13:38:55,554] 4B9FB5 | debug message
# INFO     [2015-12-31 13:38:55,554] 4B9FB5 | info message
# WARNING  [2015-12-31 13:38:55,554] 4B9FB5 | warning message
# ERROR    [2015-12-31 13:38:55,554] 4B9FB5 | error message
# CRITICAL [2015-12-31 13:38:55,554] 4B9FB5 | critical message

在这里,LOG.auto()生成上下文,该上下文使用注入的自动前缀创建记录器代理。

时间跟踪

前缀注入器允许您跟踪范围内的执行时间。他们提供:

  • elapsed_time属性,以秒为单位计算 elapsed_time;

  • format_elapsed_time()方法,它可以接受自定义格式以将经过的时间输出为字符串。

例子:

import time
from isotopic_logging import auto_injector

with auto_injector() as inj:
    time.sleep(0.1)
    print(inj.elapsed_time)

# 0.105129003525

嵌套和继承范围有自己的内部时间跟踪:

with auto_injector() as inj1:
    time.sleep(0.1)

    with auto_injector() as inj2:
        time.sleep(0.1)
        print("inj2", inj2.elapsed_time)

    print("inj1", inj1.elapsed_time)

# ('inj2', 0.10514497756958008)
# ('inj1', 0.2101149559020996)

默认格式输出小时、分钟、秒和微秒:

with auto_injector() as inj:
    time.sleep(0.1)
    print(inj.format_elapsed_time())

# 00:00:00.105154

您可以使用与datetime.datetime.strftime()格式兼容的自定义格式 :

format = "%H/%M/%S"

with auto_injector() as inj:
    time.sleep(5)
    print(inj.format_elapsed_time(format))

# 00/00/05

线程间前缀传输

有时您可能需要在线程或进程之间传递操作前缀。例如,您通过处理 HTTP 请求开始操作并在后台工作程序中继续它。

这可以通过使用注入器的前缀属性和 DirectPrefixInjector轻松实现:

def suboperation_in_another_thread_or_process(parent_prefix):
    with direct_injector(parent_prefix) as inj:
        print(inj.mark("foo"))

def operation():
    with auto_injector() as inj:
        print(inj.mark("foo"))
        suboperation_in_another_thread_or_process(inj.prefix)

operation()

# 3539DB | foo
# 3539DB | foo

变更日志

  • 2.0.0(2015 年 12 月 31 日)

    • 功能:支持继承的前缀(问题 #1)。

    • 功能:将前缀注入到现有记录器的调用中的简单而干净的方法(问题 #4)。

    • 功能:获取上下文执行时间的能力(问题 #3)。

    • 优化:注入器的实例只有在定义了新的范围时才会被创建(问题#5)。

    • 改进:确保前缀和目标消息在连接期间转换为字符串。

    • 重命名:

      • prefix_injectorstatic_injector

      • autoprefix_injectorauto_injector

      旧名称被保留并且仍然可以使用

    • 减少:从任何地方删除可选的容器参数。

  • 1.0.1(2015 年 7 月 30 日)

    • 修复:对autoprefix_injectorhybrid_injector默认使用的default_oid_generator的线程支持(问题 #2)。

  • 1.0.0(2015 年 5 月 3 日)

    初始版本

项目详情


下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

isotopic-logging-2.0.0.tar.gz (21.4 kB 查看哈希

已上传 source