Skip to content

CLI: Use logfire[extras] for instrumentation suggestions and improve environment-aware tips#1833

Open
jalpatel11 wants to merge 4 commits intopydantic:mainfrom
jalpatel11:improved-cli-suggestions
Open

CLI: Use logfire[extras] for instrumentation suggestions and improve environment-aware tips#1833
jalpatel11 wants to merge 4 commits intopydantic:mainfrom
jalpatel11:improved-cli-suggestions

Conversation

@jalpatel11
Copy link
Copy Markdown

This PR improves the recommendations provided by logfire run when missing instrumentations are detected. Instead of suggesting individual OpenTelemetry instrumentation packages, the CLI now recommends installing the corresponding Logfire extras (e.g., logfire[requests] instead of opentelemetry-instrumentation-requests). This ensures users are using the dependencies Pydantic Logfire is tested against and simplifies dependency management.

I have also added logic to detect the execution context (such as uvx or uv run --with) and suggest the appropriate re-run commands. This makes it easier for users in ephemeral environments to get their scripts running with the necessary dependencies.

Technical Implementation

  1. OTel to Logfire Extra Mapping: Added a mapping OTEL_PACKAGE_TO_LOGFIRE_EXTRA in logfire/_internal/cli/run.py to translate detected OTel packages into Logfire extras.
  2. Environment Detection:
    • Used os.environ.get('UV') == '1' and sys.executable path analysis to detect if logfire is running as a uv tool (uvx) or via an ephemeral uv run.
    • Updated _full_install_command to generate context-specific suggestions using --from and multiple --with flags for these environments.
  3. Command Formatting: Integrated shlex.quote for all generated commands to ensure that brackets in logfire[...] syntax are correctly escaped for shells like zsh.
  4. urllib Support: Added the urllib extra to pyproject.toml and updated the instrumentation maps in run.py to ensure it is recommended correctly as logfire[urllib].
  5. Multi-method Suggestions: For non-ephemeral environments where the installation method is unknown, the CLI now suggests both uv add and pip install.

Verification

All 91 CLI tests in tests/test_cli.py pass, including 5 new tests covering:

  • uvx and uv run heuristic detection.
  • Standalone OTel package handling with multiple --with flags.
  • Quoted extras syntax in generated output.
  • logfire[urllib] and logfire[sqlite3] mapping verification.

devin-ai-integration[bot]

This comment was marked as resolved.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@jalpatel11
Copy link
Copy Markdown
Author

Closes #1297

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +374 to +376
return f'uvx --from {shlex.quote(logfire_target)} {" ".join(with_args)} logfire {shlex.join(sys.argv[1:])}'.replace(
' ', ' '
).strip()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 str.replace(' ', ' ') corrupts shell-quoted arguments containing double spaces

The _full_install_command function uses .replace(' ', ' ') to clean up double spaces caused by empty list joins in the f-string. However, str.replace doesn't understand shell quoting, so it will also corrupt legitimate double spaces inside shell-quoted sys.argv values. For example, if sys.argv contains 'arg with spaces', shlex.join correctly preserves these as 'arg with spaces' (within shell quotes), but the subsequent .replace(' ', ' ') blindly reduces all double spaces, producing the corrupted 'arg with spaces'.

Demonstration of the issue

Input: sys.argv = ['logfire', 'run', 'arg with spaces']

Before replace: uvx --from 'logfire[req]' logfire run 'arg with spaces'
After replace: uvx --from 'logfire[req]' logfire run 'arg with spaces'

A safer approach would be to build the command as a list of parts and join once, avoiding the need for .replace().

Prompt for agents
The _full_install_command function at logfire/_internal/cli/run.py:374-376 and :382 uses .replace('  ', ' ') on the fully assembled shell command string. This is a blunt approach that does not respect shell quoting -- if any element of sys.argv contains a literal double space, shlex.join will correctly quote it, but the .replace will then corrupt the quoted content.

A safer approach is to build the command as a list of string parts (each being a logical token in the command) and then join them with a single space. For example:

  cmd_parts = ['uvx', '--from', shlex.quote(logfire_target)]
  cmd_parts.extend(with_args)   # each with_args element is already '--with quoted_pkg'
  cmd_parts.append('logfire')
  cmd_parts.extend(shlex.quote(a) for a in sys.argv[1:])
  return ' '.join(cmd_parts)

This eliminates the structural double-space problem without needing the fragile .replace() hack. Apply the same approach to the uv run path at line 382.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread tests/test_cli.py
Comment on lines 1645 to +1650
def test_get_recommendation_texts():
recs = {('opentelemetry-instrumentation-foo', 'foo'), ('opentelemetry-instrumentation-bar', 'bar')}
recommended, install = get_recommendation_texts(recs)
assert 'uv add opentelemetry-instrumentation-bar opentelemetry-instrumentation-foo' in install
assert 'need to install opentelemetry-instrumentation-bar' in recommended
assert 'need to install opentelemetry-instrumentation-foo' in recommended
assert 'pip install opentelemetry-instrumentation-bar opentelemetry-instrumentation-foo' in str(install)
assert 'need to install opentelemetry-instrumentation-bar' in str(recommended)
assert 'need to install opentelemetry-instrumentation-foo' in str(recommended)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Tests fail in CI because UV=1 environment variable is not cleared

CI runs tests via uv run --no-sync coverage run -m pytest (.github/workflows/main.yml:129), which sets UV=1 in the environment. The _full_install_command function at logfire/_internal/cli/run.py:368 checks os.environ.get('UV') == '1' and takes a completely different code path (producing uv run --with ... commands) before ever reaching the is_uv_installed() check. Four tests don't isolate this variable:

  1. test_inspect (line 285 mocks is_uv_installed but snapshot on line 308 expects uv add ...)
  2. test_get_recommendation_texts (line 1648 asserts pip install in output)
  3. test_get_recommendation_texts_with_extras (line 1659 asserts pip install in output)
  4. test_get_recommendation_texts_uv (line 1665 mocks is_uv_installed but line 1668 asserts uv add in output)

With UV=1, all four assertions fail because the code returns uv run --with ... instead of the expected uv add ... or pip install ... output.

Prompt for agents
Four tests in tests/test_cli.py are missing UV environment variable isolation and will fail in CI (where UV=1 is set by uv run).

The root cause is that _full_install_command in logfire/_internal/cli/run.py:368 checks os.environ.get('UV') == '1' before checking is_uv_installed(). When UV=1 is set (as it is in CI via uv run), a completely different code path is taken, producing uv run --with output instead of uv add or pip install.

Affected tests:
1. test_get_recommendation_texts (line 1645) - needs monkeypatch parameter added, then monkeypatch.delenv('UV', raising=False)
2. test_get_recommendation_texts_with_extras (line 1653) - same fix needed
3. test_get_recommendation_texts_uv (line 1664) - already has monkeypatch, just needs monkeypatch.delenv('UV', raising=False)
4. test_inspect (line 269) - already has monkeypatch, needs monkeypatch.delenv('UV', raising=False)

For tests 1 and 2, the function signature needs to be updated to accept monkeypatch: pytest.MonkeyPatch as a parameter. All four tests need monkeypatch.delenv('UV', raising=False) added before calling get_recommendation_texts or main.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread tests/test_cli.py
Comment on lines 1645 to +1650
def test_get_recommendation_texts():
recs = {('opentelemetry-instrumentation-foo', 'foo'), ('opentelemetry-instrumentation-bar', 'bar')}
recommended, install = get_recommendation_texts(recs)
assert 'uv add opentelemetry-instrumentation-bar opentelemetry-instrumentation-foo' in install
assert 'need to install opentelemetry-instrumentation-bar' in recommended
assert 'need to install opentelemetry-instrumentation-foo' in recommended
assert 'pip install opentelemetry-instrumentation-bar opentelemetry-instrumentation-foo' in str(install)
assert 'need to install opentelemetry-instrumentation-bar' in str(recommended)
assert 'need to install opentelemetry-instrumentation-foo' in str(recommended)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 UV=1 environment variable in tests not isolated

Several new tests (test_get_recommendation_texts, test_get_recommendation_texts_with_extras) do not monkeypatch the UV environment variable. If these tests happen to run in a CI environment where UV=1 is set (e.g., if the test runner itself is invoked via uv run), the _full_install_command at logfire/_internal/cli/run.py:368 would take the UV-specific path instead of the is_uv_installed() path, causing unexpected command output and test failures. The tests test_get_recommendation_texts_uv, test_get_recommendation_texts_uv_run, and test_get_recommendation_texts_uvx correctly set/control the UV env var, but the base tests don't unset it.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +367 to +382
# If run via `uvx logfire run` or `uv run --with logfire logfire run`
if os.environ.get('UV') == '1':
logfire_target = f'logfire{extras_str}'

# Heuristic to detect `uvx` (uv tool run)
if '/uv/tools/' in sys.executable:
with_args = [f'--with {shlex.quote(p)}' for p in standalone_packages]
return f'uvx --from {shlex.quote(logfire_target)} {" ".join(with_args)} logfire {shlex.join(sys.argv[1:])}'.replace(
' ', ' '
).strip()

# Heuristic for `uv run --with logfire`
# We assume if it's run via uv and not as a tool, it's either `uv run` or `uv run --with`.
# Providing the `--with` version is safer for ephemeral environments.
all_with = [f'--with {shlex.quote(p)}' for p in [logfire_target] + standalone_packages]
return f'uv run {" ".join(all_with)} logfire {shlex.join(sys.argv[1:])}'.replace(' ', ' ').strip()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 UV path generates re-run commands, not install commands

When UV=1 is set, _full_install_command at logfire/_internal/cli/run.py:368-382 generates commands like uvx --from 'logfire[extras]' logfire ... or uv run --with 'logfire[extras]' logfire ... that re-invoke logfire with the extra dependencies, rather than uv add ... / pip install ... commands that permanently install packages. This is intentionally different behavior for ephemeral UV environments, but it means the logfire inspect command (which also calls this via print_otel_summary at logfire/_internal/cli/__init__.py:123) would suggest re-running logfire inspect rather than installing packages. This is semantically different from the non-UV path and could be confusing for users running logfire inspect in a uv-managed project.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant