diff --git a/docs/guides/first_steps/index.md b/docs/guides/first_steps/index.md index bba8f9b24..2926a4b8a 100644 --- a/docs/guides/first_steps/index.md +++ b/docs/guides/first_steps/index.md @@ -2,23 +2,6 @@ This guide will walk you through getting started with **Logfire**. You'll learn how to install Logfire, authenticate your local environment, and use traces and spans to instrument your code for observability. -## OpenTelemetry Concepts - -Before diving in, let's briefly cover two fundamental OpenTelemetry concepts: - -- **Traces:** A trace represents the entire journey of a request or operation as it moves through a - (possibly distributed) system. It's composed of one or more spans. -- **Spans:** A span represents a unit of work within a trace, and are a way to track the execution of your code. - Unlike traditional logs, which contain a message at a single moment in time, spans can be nested to form a tree-like - structure, with a root span representing the overall operation, and child spans representing sub-operations. - Spans are used to measure timing and record metadata about the work being performed. - -In Logfire, we'll frequently visualize traces as a tree of its spans: - -![Example trace screenshot](../../images/logfire-screenshot-first-steps-example-trace.png) - -Using traces and spans, you can gain valuable insights into your system's behavior and performance. - ## Installation {#install} To install the latest version of **Logfire**, run: diff --git a/docs/guides/onboarding_checklist/add_manual_tracing.md b/docs/guides/onboarding_checklist/add_manual_tracing.md index 145135384..f5e502d21 100644 --- a/docs/guides/onboarding_checklist/add_manual_tracing.md +++ b/docs/guides/onboarding_checklist/add_manual_tracing.md @@ -1,291 +1,297 @@ -In the previous sections, we focused on how to integrate **Logfire** with your application and leverage automatic -instrumentation. In this section, we'll go into more detail about manual tracing, which allows you to add custom spans -and logs to your code for targeted data collection. +## Spans, logs, and traces -Because the specifics of where and how to add manual tracing will depend on your particular application, we'll also -spend time discussing the general principles and scenarios where manual tracing can be especially valuable. +Here's a simple example of using Logfire: -## How to Add Manual Tracing +```python +import time -### Using the `@logfire.instrument` Decorator +import logfire -The [`@logfire.instrument`][logfire.Logfire.instrument] decorator is a convenient way to create a span around an entire -function. To use it, simply add the decorator above the function definition: +logfire.configure() -```python -@logfire.instrument() -def my_function(arg1, arg2): - ... +with logfire.span('This is a span'): + time.sleep(1) + logfire.info('This is an info log') + time.sleep(2) ``` -By default, this will add the function arguments to the span as attributes. -To disable this (e.g. if the arguments are large objects not worth collecting), use `instrument(extract_args=False)`. +If you run this it should print something like: -The default span name will be something like `Calling module_name.my_function`. -You can pass an alternative span name as the first argument to `instrument`, and it can even be a template -into which arguments will be formatted, e.g: +``` +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 +``` -```python -@logfire.instrument('Applying my_function to {arg1=} and {arg2=}') -def my_function(arg1, arg2): - ... +Opening the project URL should show something like this in the Live view: -my_function(3, 4) -# Logs: Applying my_function to arg1=3 and arg2=4 -``` +![Simple example in Live view](../../images/guide/manual-tracing-basic-closed-span.png) -!!! note +The blue box with `1+` means that the span contains 1 direct child. Clicking on that box expands the span to reveal its children: - - The [`@logfire.instrument`][logfire.Logfire.instrument] decorator MUST be applied first, i.e., UNDER any other decorators. - - The source code of the function MUST be accessible. +![Simple example in Live view with span opened](../../images/guide/manual-tracing-basic.png) + +Note that: + +1. Any spans or logs created inside the `with logfire.span(...):` block will be children of that span. This lets you organize your logs nicely in a structured tree. You can also see this parent-child relationship in the console logs based on the indentation. +2. Spans have a start and an end time, and thus a duration. This span took 3 seconds to complete. +3. For logs, the start and end time are the same, so they don't have a duration. But you can still see in the UI that the log was created 1 second after the span started and 2 seconds before it ended. + +If you click on the 'Explore' link in the top navbar, you can write SQL to explore further, e.g: + +![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 ](../../images/guide/manual-tracing-explore-basic.png) -### Creating Manual Spans +Note: -To create a manual span, use the [`logfire.span`][logfire.Logfire.span] context manager: +1. Spans and logs are stored together in the same `records` table. +2. The `parent_span_id` of the log is the `span_id` of the span. +3. Both have the same `trace_id`. You can click on it to open a new tab in the Live view filtered to that _trace_. + +A _trace_ is a tree of spans/logs sharing the same root. Whenever you create a new span/log when there's no active span, a new trace is created. If it's a span, any descendants of that span will be part of the same trace. To keep your logs organized nicely into traces, it's best to create spans at the top level representing high level operations such as handling web server requests. + +## Attributes + +Spans and logs can have structured data attached to them, e.g: ```python -with logfire.span("Span Name", key1=value1, key2=value2): - # Code block - logfire.info("Log message", key3=value3) +logfire.info('Hello', name='world') ``` -The first argument is the name of the span, and you can optionally provide key-value pairs to include custom data in the -span. +If you click on the 'Hello' log in the Live view, you should see this in the details panel on the right: -### Nesting Spans +![name attribute in Live view](../../images/guide/manual-tracing-attribute-hello-world.png) -You can nest spans to create a hierarchical structure: +This data is stored in the `attributes` column in the `records` table as JSON. You can use e.g. `attributes->>'name' = 'world'` in the SQL filter at the top of the Live view to show only this log. This is used as the `WHERE` clause of a SQL query on the `records` table. + +Both spans and logs can have attributes containing arbitrary values which will be intelligently serialized to JSON as needed. You can pass any keyword arguments to set attributes as long as they don't start with an underscore (`_`). That namespace is reserved for other keyword arguments with logfire-specific meanings. + +Sometimes it's useful to attach an attribute to a span after it's been created but before it's finished. You can do this by calling the `span.set_attribute` method: ```python -with logfire.span("Outer Span"): - # Code block - with logfire.span("Inner Span"): - # Code block - logfire.info("Log message") +with logfire.span('Calculating...') as span: + result = 1 + 2 + span.set_attribute('result', result) ``` -When nesting spans, try to keep the hierarchy clean and meaningful, and use clear and concise names for your spans. - -### Recording Custom Data +## Messages and span names -To record custom data within a span, simply pass key-value pairs when creating the span or when logging messages: +If you run this code: ```python -with logfire.span("User Login", user_id=user_id): - logfire.info("User logged in", user_email=user_email) +import logfire + +logfire.configure() + +for name in ['Alice', 'Bob', 'Carol']: + logfire.info('Hello {name}', name=name) ``` -Consider recording data that will be useful for debugging, monitoring, or analytics purposes. +![Query in Explore view: select span_name, attributes->>'name' as name, message from records order by start_timestamp](../../images/guide/manual-tracing-span-names.png) -### Capturing Exceptions +Here you can see that: -Logfire automatically captures exceptions that bubble up through spans. To ensure that exceptions are properly captured -and associated with the relevant span, make sure to wrap the code that may raise exceptions in a span: +1. The first argument `'Hello {name}'` becomes the value of the `span_name` column. You can use this to find all records coming from the same code even if the messages are different, e.g. with the SQL filter `span_name = 'Hello {name}'`. +2. The span name is also used as a `str.format`-style template which is formatted with the attributes to produce the `message` column. The message is what's shown in the console logs and the Live view. + +You can also set `span.message` after a span is started but before it's finished, e.g: ```python -with logfire.span("Database Query"): - try: - result = db.query(query) - except DatabaseError as e: - logfire.error(f"Database query failed: {str(e)}") - raise +with logfire.span('Calculating...') as span: + result = 1 + 2 + span.message = f'Calculated: {result}' ``` -## When to Use Manual Tracing +You could use `message` to filter for related records, e.g. `message like 'Hello%'`, but filtering on the `span_name` column is more efficient because it's indexed. Similarly, it's better to use `span_name = 'Hello {name}' and attributes->>'name' = 'Alice'` than `message = 'Hello Alice'`. -Now that we've seen how to use manual tracing, let's discuss some scenarios where manual tracing can be particularly -useful in enhancing your application's observability: +To allow efficiently filtering for related records, span names should be _low cardinality_, meaning they shouldn't vary too much. For example, this would be bad: -### Scenario 1: Improving Log Organization and Readability +```python +name = get_username() +logfire.info('Hello ' + name, name=name) +``` -When working with complex functions or code blocks, manually nested spans can help organize your logs into a -hierarchical structure. This makes it easier to navigate and understand the flow of your application, especially during -debugging sessions. +because now the `span_name` column will have a different value for every username. But this would be fine: ```python -import logfire +word = 'Goodbye' if leaving else 'Hello' +logfire.info(word + ' {name}', name=name) +``` +because now the `span_name` column will only have two values (`'Goodbye {name}'` and `'Hello {name}'`) and it's both easier and more efficient to filter on `span_name = 'Hello {name}'` than `span_name = '{word} {name}' and attributes->>'word' = 'Hello'`. -@logfire.instrument("Complex Operation") -def complex_operation(data): - # Step 1 - with logfire.span("Data Preprocessing"): - preprocessed_data = preprocess(data) - logfire.info("Data preprocessed successfully") +You can use the `_span_name` argument when you want the span name to be different from the message template, e.g: - # Step 2 - with logfire.span("Data Analysis"): - analysis_result = analyze(preprocessed_data) - logfire.info("Analysis completed") +```python +logfire.info('Hello {name}', name='world', _span_name='Hello') +``` - # Step 3 - with logfire.span("Result Postprocessing"): - final_result = postprocess(analysis_result) - logfire.info("Result postprocessed") +This will set the `span_name` to `'Hello'` and the `message` to `'Hello world'`. Note that the `_span_name` argument starts with an underscore to distinguish it from attributes. - return final_result +## f-strings + +Instead of this: + +```python +logfire.info('Hello {name}', name=name) ``` -In this example, the `complex_operation` function is decorated with [`@logfire.instrument`][logfire.Logfire.instrument], -which automatically creates a span for the entire function. Additionally, the function is broken down into three main steps, -each wrapped in its own span, and you can imagine that the functions called in each of these sections might each produce -various spans as well. This creates a clear hierarchy in the logs, making it easier to identify and focus on relevant -sections during debugging. +it's much more convenient to use an f-string to avoid repeating `name` three times: -[TODO: Include a screenshot of the web UI showing the hierarchical structure of spans] +```python +logfire.info(f'Hello {name}') +``` -### Scenario 2: Measuring Execution Duration +Contrary to the previous section, this _will_ work well in Python 3.11+ because Logfire will use special magic to both set the `span_name` to `'Hello {name}'` and set the `name` attribute to the value of the `name` variable, so it's equivalent to the previous snippet. Here's what you need to know about this: -Manual spans can be used to measure the duration of specific code sections, helping you identify performance bottlenecks -and detect regressions. +- The feature is enabled by default in Python 3.11+. You can disable it with [`logfire.configure(inspect_arguments=False)`][logfire.configure(inspect_arguments)]. You can also enable it in Python 3.9 and 3.10, but it's more likely to not work correctly. +- Inspecting arguments is expected to always work under normal circumstances. The main caveat is that the source code must be available, so e.g. deploying only `.pyc` files will cause it to fail. +- If inspecting arguments fails, you will get a warning, and the f-string argument will be treated as a normal string. This means you will get high-cardinality span names such as `'Hello Alice'` and no `name` attribute, but the information won't be completely lost. +- If inspecting arguments is enabled, then arguments will be inspected regardless of whether f-strings are being used. So if you write `logfire.info('Hello {name}', name=name)` and inspecting arguments fails, then you will still get a warning and `'Hello {name}'` will be used as the message rather than formatting it. +- The values inside f-strings are evaluated and formatted by Logfire a second time. This means you should avoid code like `logfire.info(f'Hello {get_username()}')` if `get_username()` (or the string conversion of whatever it returns) is expensive or has side effects. +- The first argument must be an actual f-string. `logfire.info(f'Hello {name}')` will work, but `message = f'Hello {name}'; logfire.info(message)` will not, nor will `logfire.info('Hello ' + name)`. +- Inspecting arguments is cached so that the performance overhead of repeatedly inspecting the same f-string is minimal. However, there is a non-negligible overhead of parsing a large source file the first time arguments need to be inspected inside it. Either way, avoiding this overhead requires disabling inspecting arguments entirely, not merely avoiding f-strings. + +## Exceptions + +The `logfire.span` context manager will automatically record any exceptions that cause it to exit, e.g: ```python import logfire +logfire.configure() -@logfire.instrument("Process Data Batch", extract_args=True) -def process_data_batch(batch): - # Process the data batch - processed_data = [] - for item in batch: - with logfire.span("Process Item {item}"): - item = step_1(item) - item = step_2(item) - item = step_3(item) - processed_data.append(item) - - return processed_data +with logfire.span('This is a span'): + raise ValueError('This is an error') ``` -In this example, the process_data_batch function is decorated with `@logfire.instrument`, which automatically creates a -span for the entire function and logs the batch argument as a span attribute. +If you click on the span in the Live view, the panel on the right will have an 'Exception Traceback' tab: -Additionally, each item in the batch is processed within a separate span created using the [`logfire.span`][logfire.Logfire.span] context -manager. The span name includes the item being processed, providing more granular visibility into the processing of -individual items. +![Traceback in UI](../../images/guide/manual-tracing-traceback.png) -By using manual spans in this way, you can measure the duration of the overall data batch processing, as well as the -duration of processing each individual item. This information can be valuable for identifying performance bottlenecks -and optimizing your code. +Exceptions which are caught and not re-raised will not be recorded, e.g: -[Include a screenshot of the web UI showing the duration of the Process Data Batch span and the individual Process Item spans] - -### Scenario 3: Capturing Exception Information +```python +with logfire.span('This is a span'): + try: + raise ValueError('This is an acceptable error not worth recording') + except ValueError: + pass +``` -Logfire automatically captures full stack traces when exceptions bubble up through spans. By strategically placing spans -around code that may raise exceptions, you can ensure that you have the necessary context and information for debugging -and error monitoring. +If you want to record a handled exception, use the [`span.record_exception`][logfire.LogfireSpan.record_exception] method: ```python -import logfire +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) +``` +Alternatively, if you only want to log exceptions without creating a span for the normal case, you can use [`logfire.exception`][logfire.Logfire.exception]: -@logfire.instrument("Fetch Data from API", extract_args=True) -def fetch_data_from_api(api_url): - response = requests.get(api_url) - response.raise_for_status() - data = response.json() - logfire.info("Data fetched successfully") - return data +```python +try: + raise ValueError('This is an error') +except ValueError: + logfire.exception('Something went wrong') ``` -If an exception occurs while fetching data from the API, Logfire will capture the stack trace and associate it with the -span created by the `@logfire.instrument` decorator. The `api_url` argument will also be logged as a span attribute, -providing additional context for debugging. +`logfire.exception(...)` is equivalent to `logfire.error(..., _exc_info=True)`. You can also use `_exc_info` with the other logging methods if you want to record a traceback in a log with a non-error level. You can set `_exc_info` to a specific exception object if it's not the one being handled. Don't forget the leading underscore! -[TODO: Include a screenshot of the web UI showing the exception details, stack trace, and `api_url` attribute] +## Convenient function spans with `@logfire.instrument` -### Scenario 4: Recording User Actions and Custom Data +Often you want to wrap a whole function in a span. Instead of doing this: -Manual spans can be used to record user actions, input parameters, or other custom data that may be valuable for -analytics and business intelligence purposes. +```python +def my_function(x, y): + with logfire.span('my_function', x=x, y=y): + ... +``` + +you can use the [`@logfire.instrument`][logfire.Logfire.instrument] decorator: ```python -import logfire +@logfire.instrument() +def my_function(x, y): + ... +``` + +By default, this will add the function arguments to the span as attributes. +To disable this (e.g. if the arguments are large objects not worth collecting), use `instrument(extract_args=False)`. + +The default span name will be something like `Calling module_name.my_function`. +You can pass an alternative span name as the first argument to `instrument`, and it can even be a template +into which arguments will be formatted, e.g: +```python +@logfire.instrument('Applying my_function to {x=} and {y=}') +def my_function(x, y): + ... -def search_products(user_id, search_query, filters): - with logfire.span(f"Performing search: {search_query}", search_query=search_query, filters=filters): - results = perform_search(search_query, filters) - - if not results: - logfire.info("No results found for search query", search_query=search_query) - with logfire.span("Suggesting Related Products"): - related_products = suggest_related_products(search_query) - return { - "status": "no_results", - "related_products": related_products - } - else: - logfire.info(f"Found {len(results)} results for search query", search_query=search_query) - return { - "status": "success", - "results": results - } +my_function(3, 4) +# Logs: Applying my_function to x=3 and y=4 ``` -In this example, the `search_products` function is instrumented with manual spans and logs to capture user actions and -custom data related to product searches. +!!! note + + - The [`@logfire.instrument`][logfire.Logfire.instrument] decorator MUST be applied first, i.e., UNDER any other decorators. + - The source code of the function MUST be accessible. + +## Log levels -The function starts by creating a span named `"Performing search: {search_query}"` that measures the duration and -captures the details of the actual search operation. The `search_query` and `filters` are included as span attributes, -allowing for fine-grained analysis of search performance and effectiveness. +The following methods exist for creating logs with different levels: -After performing the search, the function checks the results: +- `logfire.trace` +- `logfire.debug` +- `logfire.info` +- `logfire.notice` +- `logfire.warn` +- `logfire.error` +- `logfire.fatal` -1. If no results are found, an info-level log message is recorded, indicating that no results were found for the given - search query. Then, a `"Suggesting Related Products"` span is created, and the `suggest_related_products` function is - called to generate a list of related products. The function returns a response with a `status` of `"no_results"` and - the list of `related_products`. This data can be used to identify common search queries that yield no results and - help improve the product catalog or search functionality. +By default, `trace` and `debug` logs are hidden. You can change this by clicking the 'Default levels' dropdown in the Live view: -2. If results are found, an info-level log message is recorded, indicating the number of results found for the search - query. The function then returns a response with a `status` of `"success"` and the `results` list. +![Default levels dropdown](../../images/guide/manual-tracing-default-levels.png) -By structuring the spans and logs in this way, you can gain insights into various aspects of the product search -functionality: +You can also set the minimum level used for console logging with [`logfire.configure`][logfire.configure], e.g: -- The `"Performing search: {search_query}"` span measures the duration of each search operation and includes the - specific search query and filters, enabling performance analysis and optimization. -- The info-level log messages indicate whether results were found or not, helping to identify successful and - unsuccessful searches. -- The `"Suggesting Related Products"` span captures the process of generating related product suggestions when no - results are found, providing data for analyzing and improving the suggestion algorithm. +```python +import logfire + +logfire.configure(console=logfire.ConsoleOptions(min_log_level='debug')) +``` -[TODO: Include a screenshot of the web UI showing the spans and custom data logged during a product search] +To log a message with a variable level you can use `logfire.log`, e.g. `logfire.log('info', 'This is an info log')` is equivalent to `logfire.info('This is an info log')`. -This example demonstrates how manual spans and logs can be strategically placed to capture valuable data for analytics -and business intelligence purposes. +Spans are level `info` by default. You can change this with the `_level` argument, e.g. `with logfire.span('This is a debug span', _level='debug'):`. You can also change the level after the span has started but before it's finished with [`span.set_level`][logfire.LogfireSpan.set_level], e.g: -Some specific insights you could gain from this instrumentation include: +```python +with logfire.span('Doing a thing') as span: + success = do_thing() + if not success: + span.set_level('error') +``` -- Identifying the most common search queries and filters used by users, helping to optimize the search functionality and - product catalog. -- Measuring the performance of search operations and identifying potential bottlenecks or inefficiencies. -- Understanding which search queries frequently result in no results, indicating areas where the product catalog may - need to be expanded or the search algorithm improved. -- Analyzing the effectiveness of the related product suggestion feature in helping users find relevant products when - their initial search yields no results. +In the Live view, **spans are colored based on the highest level of them and their descendants**. So e.g. this code: -By capturing this data through manual spans and logs, you can create a rich dataset for analytics and business -intelligence purposes, empowering you to make informed decisions and continuously improve your application's search -functionality and user experience. +```python +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') +``` -## Best Practices and Tips +will be displayed like this: -- Use manual tracing judiciously. While it can provide valuable insights, overusing manual spans can lead to cluttered - logs and source code, and increased overhead in hot loops. -- Focus on critical or complex parts of your application where additional context and visibility will be most - beneficial. -- Choose clear and concise names for your spans to make it easier to understand the flow and purpose of each span. -- Record custom data that will be useful for debugging, monitoring, or analytics purposes, but avoid including sensitive - or unnecessary information. +![Spans colored by level](../../images/guide/manual-tracing-level-colors.png) -## Conclusion +Here the spans themselves still have their level set to `info` as is the default, but they're colored red instead of blue because they contain an error log. -Manual tracing is a powerful tool for adding custom spans and logs to your code, providing targeted visibility into your -application's behavior. By understanding the principles and best practices of manual tracing, you can adapt this -technique to your specific use case and enhance your application's observability. +If a span finishes with an unhandled exception, then in addition to recording a traceback as described above, the span's log level will be set to `error`. This will not happen when using the [`span.record_exception`][logfire.LogfireSpan.record_exception] method. -Remember to balance the benefits of detailed tracing with the overhead of adding manual spans, and focus on the areas -where additional context and visibility will be most valuable. +In the database, the log level is stored as a number in the `level` column. The values are based on OpenTelemetry, e.g. `info` is `9`. You can convert level names to numbers using the `level_num` SQL function, e.g. `level > level_num('info')` will find all 'unusual' records. You can also use the `level_name` SQL function to convert numbers to names, e.g. `SELECT level_name(level), ...` to see a human-readable level in the Explore view. Note that the `level` column is indexed so that filtering on `level = level_num('error')` is efficient, but filtering on `level_name(level) = 'error'` is not. diff --git a/docs/images/guide/manual-tracing-attribute-hello-world.png b/docs/images/guide/manual-tracing-attribute-hello-world.png new file mode 100644 index 000000000..81a2fd0ed Binary files /dev/null and b/docs/images/guide/manual-tracing-attribute-hello-world.png differ diff --git a/docs/images/guide/manual-tracing-basic-closed-span.png b/docs/images/guide/manual-tracing-basic-closed-span.png new file mode 100644 index 000000000..cf7163091 Binary files /dev/null and b/docs/images/guide/manual-tracing-basic-closed-span.png differ diff --git a/docs/images/guide/manual-tracing-basic.png b/docs/images/guide/manual-tracing-basic.png new file mode 100644 index 000000000..72943acc1 Binary files /dev/null and b/docs/images/guide/manual-tracing-basic.png differ diff --git a/docs/images/guide/manual-tracing-default-levels.png b/docs/images/guide/manual-tracing-default-levels.png new file mode 100644 index 000000000..730c40983 Binary files /dev/null and b/docs/images/guide/manual-tracing-default-levels.png differ diff --git a/docs/images/guide/manual-tracing-explore-basic.png b/docs/images/guide/manual-tracing-explore-basic.png new file mode 100644 index 000000000..956d73304 Binary files /dev/null and b/docs/images/guide/manual-tracing-explore-basic.png differ diff --git a/docs/images/guide/manual-tracing-level-colors.png b/docs/images/guide/manual-tracing-level-colors.png new file mode 100644 index 000000000..c607debac Binary files /dev/null and b/docs/images/guide/manual-tracing-level-colors.png differ diff --git a/docs/images/guide/manual-tracing-span-names.png b/docs/images/guide/manual-tracing-span-names.png new file mode 100644 index 000000000..5cdd11a4f Binary files /dev/null and b/docs/images/guide/manual-tracing-span-names.png differ diff --git a/docs/images/guide/manual-tracing-traceback.png b/docs/images/guide/manual-tracing-traceback.png new file mode 100644 index 000000000..0f12707f1 Binary files /dev/null and b/docs/images/guide/manual-tracing-traceback.png differ diff --git a/logfire/_internal/config.py b/logfire/_internal/config.py index 762d93ce3..b0cd60a1f 100644 --- a/logfire/_internal/config.py +++ b/logfire/_internal/config.py @@ -212,7 +212,8 @@ def configure( If it returns `None`, the value is redacted. Otherwise, the returned value replaces the matched value. The function accepts a single argument of type [`logfire.ScrubMatch`][logfire.ScrubMatch]. - inspect_arguments: Whether to enable f-string magic. + inspect_arguments: Whether to enable + [f-string magic](https://docs.pydantic.dev/logfire/guides/onboarding_checklist/add_manual_tracing/#f-strings). If `None` uses the `LOGFIRE_INSPECT_ARGUMENTS` environment variable. Defaults to `True` if and only if the Python version is at least 3.11. """