跳转至

添加 Logfire 手动追踪

跨度、日志和跟踪

以下是使用 Logfire 的简单示例:

import time

import logfire

logfire.configure()

with logfire.span('This is a span'):
    time.sleep(1)
    logfire.info('This is an info log')
    time.sleep(2)

如果你运行这个,它应该打印出类似的东西:

Logfire project URL: https://logfire.pydantic.dev/my_username/my_project_name
21:02:55.078 This is a span
21:02:56.084   This is an info log

打开项目 URL 应在 Live 视图中显示如下内容:

Simple example in Live view

带有 1+ 的蓝色框表示跨度包含 1 个直接子项。单击该框可扩展跨度以显示其子项:

Simple example in Live view with span opened

请注意:

  1. with logfire.span(...): 块中创建的任何跨度或日志都将是该跨度的子项。这使您可以在结构化树中很好地组织日志。您还可以在基于缩进的控制台日志中查看此父子关系。

  2. 跨度具有开始时间和结束时间,因此具有持续时间。此跨度需要 3 秒才能完成。

  3. 对于日志,开始和结束时间是相同的,因此它们没有持续时间。但是,您仍然可以在 UI 中看到日志是在跨度开始后 1 秒和跨度结束前 2 秒创建的。

如果单击顶部导航栏中的“探索”链接,则可以编写 SQL 以进一步探索,例如:

Query in Explore view: select extract('seconds' from end_timestamp - start_timestamp) as duration, kind, message, trace_id, span_id, parent_span_id from records order by start_timestamp

注意:

  1. 跨度和日志一起存储在同一个记录表中。

  2. 日志的parent_span_id是跨度的span_id

  3. 两者都具有相同的trace_id。您可以单击它以在“实时”视图中打开一个新选项卡,该选项卡已过滤到该_跟踪_。

_跟踪_是共享同一根的跨度/日志树。每当在没有活动跨度的情况下创建新的跨度/日志时,都会创建一个新的跟踪。如果它是一个跨度,则该跨度的任何后代都将是同一跟踪的一部分。为了将日志很好地组织到跟踪中,最好在顶层创建跨度,以表示高级操作,例如处理 Web 服务器请求。

属性

跨度和日志可以附加结构化数据,例如:

logfire.info('你好', name='世界')

如果在实时视图中单击“Hello”日志,则应在右侧的详细信息面板中看到以下内容:

name attribute in Live view

此数据以 JSON 格式存储在记录表的属性列中。例如,您可以在实时视图顶部的 SQL 过滤器中使用 attributes->>'name' = 'world' 来仅显示此日志。这用作记录表上 SQL 查询的 WHERE 子句。

span 和 logs 都可以具有包含任意值的属性,这些值将根据需要智能地序列化为 JSON。您可以传递任何关键字参数来设置属性,只要它们不以下划线 (_) 开头即可。该命名空间是为具有 logfire 特定含义的其他关键字参数保留的。

有时,在创建范围之后但在范围完成之前将属性附加到范围非常有用。您可以通过调用 span.set_attribute 方法来执行此操作:

with logfire.span('Calculating...') as span:
    result = 1 + 2
    span.set_attribute('result', result)

消息和跨度名称

如果运行此代码:

import logfire

logfire.configure()

for name in ['Alice', 'Bob', 'Carol']:
    logfire.info('Hello {name}', name=name)

Query in Explore view: select span_name, attributes->>'name' as name, message from records order by start_timestamp

在这里,您可以看到:

  1. 第一个参数“Hello {name}”成为span_name列的值。您可以使用它来查找来自相同代码的所有记录,即使消息不同,例如使用 SQL 过滤器 span_name = 'Hello {name}'

  2. span 名称也用作 str.format 样式的模板,该模板使用属性进行格式化以生成消息列。该消息是控制台日志和实时视图中显示的内容。

您也可以在跨度开始之后但在跨度完成之前设置 span.message,例如:

with logfire.span('Calculating...') as span:
    result = 1 + 2
    span.message = f'Calculated: {result}'

您可以使用消息来筛选相关记录,例如“Hello%”之类的消息,但对span_name列进行筛选效率更高,因为它已编制索引。同样,使用 span_name = 'Hello {name}' 和 attributes->>'name' = 'Alice' 比使用 message = 'Hello Alice' 更好。

为了能够有效地筛选相关记录,跨度名称的_基数_应该较低,这意味着它们不应变化太大。例如,这将是糟糕的:

name = get_username()
logfire.info('Hello ' + name, name=name)

因为现在span_name列将为每个用户名具有不同的值。但这很好:

因为现在 span_name 列只有两个值('Goodbye {name}''Hello {name}'),并且过滤 span_name = 'Hello {name}' 比过滤 span_name = '{word} {name}' 和 attributes->>'word' = 'Hello' 更容易、更有效。

当您希望 span 名称与消息模板不同时,可以使用 _span_name 参数,例如:

word = 'Goodbye' if leaving else 'Hello'
logfire.info(word + ' {name}', name=name)

这会将span_name设置为“Hello”,将消息设置为“Hello world”。请注意,_span_name 参数以下划线开头,以将其与属性区分开来。

logfire.info('Hello {name}', name='world', _span_name='Hello')

f-strings


取而代之的是:

logfire.info('Hello {name}', name=name)

使用 F-String 来避免重复 NAME 三次要方便得多:

logfire.info(f'Hello {name}')

与上一节相反,这在 Python 3.11+ 中_效果_很好,因为 Logfire 将使用特殊魔法将span_name设置为“Hello {name}”并将 name 属性设置为 name 变量的值,因此它等同于前面的代码片段。以下是您需要了解的内容:

  • 该功能在 Python 3.11+ 中默认启用。您可以使用 logfire.configure(inspect_arguments=False) 禁用它。您也可以在 Python 3.9 和 3.10 中启用它,但它更有可能无法正常工作。

  • 在正常情况下,检查参数应始终有效。需要注意的是,源代码必须可用,因此,例如,仅部署 .pyc 文件将导致其失败。

  • 如果检查参数失败,您将收到警告,并且 f-string 参数将用作格式模板。这意味着您将获得高基数跨度名称,例如“Hello Alice”,并且没有名称属性,但信息不会完全丢失。

  • 如果启用了检查参数,则无论是否使用 f 字符串,都将检查参数。因此,如果您编写 logfire.info('Hello {name}', name=name) 并且检查参数失败,那么您仍然会收到警告。

  • f-strings 中的值由 Logfire 再次计算和格式化。这意味着如果 get_username() (或它返回的任何内容的字符串转换)成本高昂或有副作用,您应该避免使用类似 logfire.info(f'Hello {get_username()}') 的代码。

  • 第一个参数必须是实际的 f 字符串。logfire.info(f'Hello {name}') 将起作用,但 message = f'Hello {name}'; logfire.info(message) 不会,logfire.info('Hello ' + name) 也不会。

  • 检查参数被缓存,以便重复检查同一 f 字符串的性能开销最小。但是,在第一次需要检查参数时,解析大型源文件会产生不可忽视的开销。无论哪种方式,避免这种开销都需要完全禁用检查参数,而不仅仅是避免 f 字符串。

异常

logfire.span 上下文管理器将自动记录导致其退出的任何异常,例如:

import logfire

logfire.configure()

with logfire.span('This is a span'):
    raise ValueError('This is an error')

如果在实时视图中单击跨度,右侧面板将有一个“异常回溯”选项卡:

Traceback in UI

被捕获但未重新引发的异常将不会被记录,例如:

with logfire.span('This is a span'):
    try:
        raise ValueError('This is an acceptable error not worth recording')
    except ValueError:
        pass

如果要记录已处理的异常,请使用 span.record_exception方法:

with logfire.span('This is a span') as span:
    try:
        raise ValueError('Catch this error, but record it')
    except ValueError as e:
        span.record_exception(e)

或者,如果您只想记录异常而不为正常情况创建跨度,则可以使用 logfire.exception

try:
    raise ValueError('This is an error')
except ValueError:
    logfire.exception('Something went wrong')

logfire.exception(...)等效于 logfire.error(..., _exc_info=True)。如果要在日志中记录非错误级别的回溯,也可以将 _exc_info 与其他日志记录方法一起使用。如果该异常对象不是正在处理的对象,则可以将_exc_info设置为该异常对象。别忘了前导下划线!

通过@logfire.instrument实现便捷的功能跨度

通常,您希望将整个函数包装在一个跨度中。而不是这样做:

def my_function(x, y):
    with logfire.span('my_function', x=x, y=y):
        ...

您可以使用 @logfire.instrument 装饰器:

默认情况下,这会将函数参数作为属性添加到 span 中。要禁用此功能(例如,如果参数是不值得收集的大对象),请使用 instrument(extract_args=False)。

默认的跨度名称类似于 Calling module_name.my_function。您可以将备用跨度名称作为第一个参数传递给 instrument,它甚至可以作为参数格式化的模板,例如:

@logfire.instrument('Applying my_function to {x=} and {y=}')
def my_function(x, y):
    ...

my_function(3, 4)
# Logs: Applying my_function to x=3 and y=4

注意

  • '@logfire.instrument' 装饰器必须首先应用,即在任何其他装饰器下。
  • 函数的源代码必须是可访问的。

日志级别

创建不同级别的日志的方法如下:

  • logfire.trace
  • logfire.debug
  • logfire.info
  • logfire.notice(日志火灾通知)
  • logfire.warn
  • logfire.error
  • logfire.fatal

默认情况下,跟踪调试日志是隐藏的。您可以通过单击实时视图中的“默认级别”下拉菜单来更改此设置:

Default levels dropdown

您还可以使用 logfire.configure 设置用于控制台日志记录的最低级别,例如:

import logfire

logfire.configure(console=logfire.ConsoleOptions(min_log_level='debug'))

要记录具有变量级别的消息,您可以使用logfire.log,例如 logfire.log('info', 'This is an info log') 等同于 logfire.info('This is an info log')。

默认情况下,跨度是级别信息。您可以使用 _level 参数更改此参数,例如使用 logfire.span('This is a debug span', _level='debug'):。您还可以在跨度开始后但在跨度完成之前使用 span.set_level 更改级别。LogfireSpan.set_level],例如:

with logfire.span('Doing a thing') as span:
    success = do_thing()
    if not success:
        span.set_level('error')

在“实时”视图中,跨度根据其最高级别及其后代进行着色。例如,以下代码:

import logfire

logfire.configure()

with logfire.span('Outer span'):
    with logfire.span('Inner span'):
        logfire.info('This is an info message')
        logfire.error('This is an error message')

将像这样显示:

Spans colored by level

在这里,跨度本身的级别仍设置为默认信息,但它们被涂成红色而不是蓝色,因为它们包含错误日志。

如果跨度以未处理的异常结束,则除了如上所述记录回溯外,跨度的日志级别也将设置为错误。使用 span.record_exception 时不会发生这种情况。LogfireSpan.record_exception] 方法。

在数据库中,日志级别以数字的形式存储在级别列中。这些值基于 OpenTelemetry,例如 info9。您可以使用 level_num SQL 函数将级别名称转换为数字,例如级别 > level_num('info') 将查找所有“异常”记录。您还可以使用 level_name SQL 函数将数字转换为名称,例如 SELECT level_name(level)、... 在“探索”视图中查看人类可读的级别。请注意,level 列已编制索引,因此对 level = level_num('error') 进行筛选是有效的,但对 level_name(level) = 'error' 进行筛选则无效。


本文总阅读量