添加 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 视图中显示如下内容:
带有 1+
的蓝色框表示跨度包含 1 个直接子项。单击该框可扩展跨度以显示其子项:
请注意:
-
在
with logfire.span(...):
块中创建的任何跨度或日志都将是该跨度的子项。这使您可以在结构化树中很好地组织日志。您还可以在基于缩进的控制台日志中查看此父子关系。 -
跨度具有开始时间和结束时间,因此具有持续时间。此跨度需要 3 秒才能完成。
-
对于日志,开始和结束时间是相同的,因此它们没有持续时间。但是,您仍然可以在 UI 中看到日志是在跨度开始后 1 秒和跨度结束前 2 秒创建的。
如果单击顶部导航栏中的“探索”链接,则可以编写 SQL 以进一步探索,例如:
注意:
-
跨度和日志一起存储在同一个
记录
表中。 -
日志
的parent_span_id
是跨度的span_id
。 -
两者都具有相同的
trace_id
。您可以单击它以在“实时”视图中打开一个新选项卡,该选项卡已过滤到该_跟踪_。
_跟踪_是共享同一根的跨度/日志树。每当在没有活动跨度的情况下创建新的跨度/日志时,都会创建一个新的跟踪。如果它是一个跨度,则该跨度的任何后代都将是同一跟踪的一部分。为了将日志很好地组织到跟踪中,最好在顶层创建跨度,以表示高级操作,例如处理 Web 服务器请求。
属性¶
跨度和日志可以附加结构化数据,例如:
logfire.info('你好', name='世界')
如果在实时视图中单击“Hello”日志,则应在右侧的详细信息面板中看到以下内容:
此数据以 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)
在这里,您可以看到:
-
第一个参数
“Hello {name}”
成为span_name
列的值。您可以使用它来查找来自相同代码的所有记录,即使消息不同,例如使用 SQL 过滤器span_name = 'Hello {name}'
。 -
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')
如果在实时视图中单击跨度,右侧面板将有一个“异常回溯”选项卡:
被捕获但未重新引发的异常将不会被记录,例如:
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
默认情况下,跟踪
和调试
日志是隐藏的。您可以通过单击实时视图中的“默认级别”下拉菜单来更改此设置:
您还可以使用 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')
将像这样显示:
在这里,跨度本身的级别仍设置为默认
信息,但它们被涂成红色而不是蓝色,因为它们包含错误日志。
如果跨度以未处理的异常结束,则除了如上所述记录回溯外,跨度的日志级别也将设置为错误
。使用 span.record_exception
时不会发生这种情况。LogfireSpan.record_exception] 方法。
在数据库中,日志级别以数字的形式存储在级别
列中。这些值基于 OpenTelemetry,例如 info
为 9
。您可以使用 level_num
SQL 函数将级别名称转换为数字,例如级别 > level_num('info')
将查找所有“异常”记录。您还可以使用 level_name
SQL 函数将数字转换为名称,例如 SELECT level_name(level)、...
在“探索”视图中查看人类可读的级别。请注意,level
列已编制索引,因此对 level = level_num('error')
进行筛选是有效的,但对 level_name(level) = 'error'
进行筛选则无效。
本文总阅读量次