
Instrumentation
- 3 installs
- 3.6k repo stars
- Updated August 5, 2026
- basicmachines-co/basic-memory
instrumentation is a Claude skill that adds Pydantic Logfire observability (traces, logs, metrics) to Python, JavaScript/TypeScript, and Rust applications.
About
This skill adds Pydantic Logfire observability to an application, capturing traces, logs, and metrics via OpenTelemetry. It detects the language and frameworks, installs the right extras, and enforces the correct configure/instrument ordering so traces are not silently dropped. Developers use it when adding tracing, structured logging, or LLM monitoring to Python, JS/TS, or Rust code.
- Adds Pydantic Logfire observability (traces, logs, metrics) to Python, JavaScript/TypeScript, and Rust apps
- Gets the tricky ordering right: configure() before instrument_*() so traces are not silently dropped
- Auto-instruments AI/LLM libraries (PydanticAI, OpenAI, Anthropic) for token usage and tool calls
Instrumentation by the numbers
- 3 all-time installs (skills.sh)
- Ranked #1,119 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
instrumentation capabilities & compatibility
- Capabilities
- observability setup · tracing · structured logging · llm monitoring
- Works with
- openai · anthropic
- Use cases
- devops · debugging
What instrumentation says it does
Logfire is an observability platform built on OpenTelemetry. It captures traces, logs, and metrics from applications.
If you call `instrument_*()` before `configure()`, the hooks register but traces go nowhere.
npx skills add https://github.com/basicmachines-co/basic-memory --skill instrumentationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 3.6k |
| Last updated | August 5, 2026 |
| Repository | basicmachines-co/basic-memory ↗ |
What it does
Add Pydantic Logfire observability, tracing, and structured logging to a Python, JS/TS, or Rust app.
Who is it for?
Wiring Logfire tracing, structured logging, and metrics into an app with the correct configure/instrument ordering.
Skip if: Non-Logfire observability stacks, or apps where OpenTelemetry-style tracing is not wanted.
When should I use this skill?
Asked to add logfire, observability, tracing, monitoring, or logging to an app.
What you get
An app instrumented with Logfire so traces, logs, and metrics reach the observability platform.
- configured Logfire setup
- instrument_*() calls
- structured logging with spans
By the numbers
- 20+ Python instrumentation extras listed
- 6 AI extras (pydantic-ai, openai, anthropic, litellm, dspy, google-genai)
Files
Instrument with Logfire
When to Use This Skill
Invoke this skill when:
- User asks to "add logfire", "add observability", "add tracing", or "add monitoring"
- User wants to instrument an app with structured logging or tracing (Python, JS/TS, or Rust)
- User mentions Logfire in any context
- User asks to "add logging" or "see what my app is doing"
- User wants to monitor AI/LLM calls (PydanticAI, OpenAI, Anthropic)
- User asks to add observability to an AI agent or LLM pipeline
How Logfire Works
Logfire is an observability platform built on OpenTelemetry. It captures traces, logs, and metrics from applications. Logfire has native SDKs for Python, JavaScript/TypeScript, and Rust, plus support for any language via OpenTelemetry.
The reason this skill exists is that Claude tends to get a few things subtly wrong with Logfire - especially the ordering of configure() vs instrument_*() calls, the structured logging syntax, and which extras to install. These matter because a misconfigured setup silently drops traces.
Step 1: Detect Language and Frameworks
Identify the project language and instrumentable libraries:
- Python: Read
pyproject.tomlorrequirements.txt. Common instrumentable libraries: FastAPI, httpx, asyncpg, SQLAlchemy, psycopg, Redis, Celery, Django, Flask, requests, PydanticAI. - JavaScript/TypeScript: Read
package.json. Common frameworks: Express, Next.js, Fastify. Also check for Cloudflare Workers or Deno. - Rust: Read
Cargo.toml.
Then follow the language-specific steps below.
---
Python
Install with Extras
Install logfire with extras matching the detected frameworks. Each instrumented library needs its corresponding extra - without it, the instrument_*() call will fail at runtime with a missing dependency error.
uv add 'logfire[fastapi,httpx,asyncpg]'The full list of available extras: fastapi, starlette, django, flask, httpx, requests, asyncpg, psycopg, psycopg2, sqlalchemy, redis, pymongo, mysql, sqlite3, celery, aiohttp, aws-lambda, system-metrics, litellm, dspy, google-genai.
Configure and Instrument
This is where ordering matters. logfire.configure() initializes the SDK and must come before everything else. The instrument_*() calls register hooks into each library. If you call instrument_*() before configure(), the hooks register but traces go nowhere.
import logfire
# 1. Configure first - always
logfire.configure()
# 2. Instrument libraries - after configure, before app starts
logfire.instrument_fastapi(app)
logfire.instrument_httpx()
logfire.instrument_asyncpg()Placement rules:
logfire.configure()goes in the application entry point (main.py, or the module that creates the app)- Call it once per process - not inside request handlers, not in library code
instrument_*()calls go right afterconfigure()- Web framework instrumentors (
instrument_fastapi,instrument_flask,instrument_django) need the app instance as an argument. HTTP client and database instrumentors (instrument_httpx,instrument_asyncpg) are global and take no arguments. - In Gunicorn deployments, call
logfire.configure()inside thepost_forkhook, not at module level - each worker is a separate process
Structured Logging
Replace print() and logging.*() calls with Logfire's structured logging. The key pattern: use {key} placeholders with keyword arguments, never f-strings.
# Correct - each {key} becomes a searchable attribute in the Logfire UI
logfire.info("Created user {user_id}", user_id=uid)
logfire.error("Payment failed {amount} {currency}", amount=100, currency="USD")
# Wrong - creates a flat string, nothing is searchable
logfire.info(f"Created user {uid}")For grouping related operations and measuring duration, use spans:
with logfire.span("Processing order {order_id}", order_id=order_id):
items = await fetch_items(order_id)
total = calculate_total(items)
logfire.info("Calculated total {total}", total=total)For exceptions, use logfire.exception() which automatically captures the traceback:
try:
await process_order(order_id)
except Exception:
logfire.exception("Failed to process order {order_id}", order_id=order_id)
raiseAI/LLM Instrumentation (Python)
Logfire auto-instruments AI libraries to capture LLM calls, token usage, tool invocations, and agent runs.
uv add 'logfire[pydantic-ai]'
# or: uv add 'logfire[openai]' / uv add 'logfire[anthropic]'Available AI extras: pydantic-ai, openai, anthropic, litellm, dspy, google-genai.
logfire.configure()
logfire.instrument_pydantic_ai() # captures agent runs, tool calls, LLM request/response
# or:
logfire.instrument_openai() # captures chat completions, embeddings, token counts
logfire.instrument_anthropic() # captures messages, token usageFor PydanticAI, each agent run becomes a parent span containing child spans for every tool call and LLM request.
---
JavaScript / TypeScript
Install
# Node.js
npm install @pydantic/logfire-node
# Cloudflare Workers
npm install @pydantic/logfire-cf-workers logfire
# Next.js / generic
npm install logfireConfigure
Node.js (Express, Fastify, etc.) - create an instrumentation.ts loaded before your app:
import * as logfire from '@pydantic/logfire-node'
logfire.configure()Launch with: node --require ./instrumentation.js app.js
The SDK auto-instruments common libraries when loaded before the app. Set LOGFIRE_TOKEN in your environment or pass token to configure().
Cloudflare Workers - wrap your handler with instrument():
import { instrument } from '@pydantic/logfire-cf-workers'
export default instrument(handler, {
service: { name: 'my-worker', version: '1.0.0' }
})Next.js - set environment variables for OpenTelemetry export:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://logfire-api.pydantic.dev/v1/traces
OTEL_EXPORTER_OTLP_HEADERS=Authorization=<your-write-token>Structured Logging (JS/TS)
// Structured attributes as second argument
logfire.info('Created user', { user_id: uid })
logfire.error('Payment failed', { amount: 100, currency: 'USD' })
// Spans
logfire.span('Processing order', { order_id }, {}, async () => {
logfire.info('Processing step completed')
})
// Error reporting
logfire.reportError('order processing', error)Log levels: trace, debug, info, notice, warn, error, fatal.
---
Rust
Install
[dependencies]
logfire = "0.6"Configure
let shutdown_handler = logfire::configure()
.install_panic_handler()
.finish()?;Set LOGFIRE_TOKEN in your environment or use the Logfire CLI to select a project.
Structured Logging (Rust)
The Rust SDK is built on tracing and opentelemetry - existing tracing macros work automatically.
// Spans
logfire::span!("processing order", order_id = order_id).in_scope(|| {
// traced code
});
// Events
logfire::info!("Created user {user_id}", user_id = uid);Always call shutdown_handler.shutdown() before program exit to flush data.
---
Verify
After instrumentation, verify the setup works:
1. Run logfire auth to check authentication (or set LOGFIRE_TOKEN) 2. Start the app and trigger a request 3. Check https://logfire.pydantic.dev/ for traces
If traces aren't appearing: check that configure() is called before instrument_*() (Python), check that LOGFIRE_TOKEN is set, and check that the correct packages/extras are installed.
References
Detailed patterns and integration tables, organized by language:
- Python:
${CLAUDE_PLUGIN_ROOT}/skills/instrumentation/references/python/logging-patterns.md(log levels, spans, stdlib integration, metrics, capfire testing) and${CLAUDE_PLUGIN_ROOT}/skills/instrumentation/references/python/integrations.md(full instrumentor table with extras) - JavaScript/TypeScript:
${CLAUDE_PLUGIN_ROOT}/skills/instrumentation/references/javascript/patterns.md(log levels, spans, error handling, config) and${CLAUDE_PLUGIN_ROOT}/skills/instrumentation/references/javascript/frameworks.md(Node.js, Cloudflare Workers, Next.js, Deno setup) - Rust:
${CLAUDE_PLUGIN_ROOT}/skills/instrumentation/references/rust/patterns.md(macros, spans, tracing/log crate integration, async, shutdown)
JavaScript Framework Setup
Node.js (Express, Fastify, etc.)
Create instrumentation.ts and load it before your app:
// instrumentation.ts
import * as logfire from '@pydantic/logfire-node'
import 'dotenv/config'
logfire.configure()Launch:
node --require ./instrumentation.js app.js
# or with ts-node:
npx ts-node --require ./instrumentation.ts app.tsThe SDK auto-instruments common libraries (http, fetch, express, etc.) when loaded before the app via --require.
Cloudflare Workers
import { instrument } from '@pydantic/logfire-cf-workers'
const handler = {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return new Response('Hello')
},
}
export default instrument(handler, {
service: { name: 'my-worker', version: '1.0.0' },
})Add LOGFIRE_TOKEN to .dev.vars and enable nodejs_compat in wrangler.toml:
compatibility_flags = ["nodejs_compat"]Next.js / Vercel
Set environment variables in .env.local or Vercel dashboard:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://logfire-api.pydantic.dev/v1/traces
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://logfire-api.pydantic.dev/v1/metrics
OTEL_EXPORTER_OTLP_HEADERS=Authorization=<your-write-token>Optionally use the logfire package for manual spans in server components and API routes:
import * as logfire from 'logfire'
logfire.info('Server action executed', { action: 'createUser' })Deno
Deno has built-in OpenTelemetry support. Set environment variables:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://logfire-api.pydantic.dev/v1/traces
OTEL_EXPORTER_OTLP_HEADERS=Authorization=<your-write-token>Run with telemetry enabled:
deno run --allow-env --unstable-otel app.tsJavaScript / TypeScript Patterns
Log Levels
From lowest to highest severity:
logfire.trace('Detailed trace', { detail: x })
logfire.debug('Debug info', { state: s })
logfire.info('Normal operation', { event: e })
logfire.notice('Notable event', { event: e })
logfire.warn('Warning', { issue: i })
logfire.error('Error occurred', { error: err })
logfire.fatal('Fatal error', { error: err })All methods accept (message, attributes?, options?). Options can include { tags: ['tag1'] }.
Spans
Callback-based (auto-closes)
await logfire.span('Processing order', { order_id }, {}, async () => {
const items = await fetchItems(order_id)
logfire.info('Fetched items', { count: items.length })
return processItems(items)
})Manual control
const span = logfire.startSpan('Long operation', { job_id })
try {
await doWork()
} finally {
span.end()
}Child spans reference their parent via the parentSpan option.
Error Handling
try {
await processOrder(orderId)
} catch (error) {
logfire.reportError('order processing', error)
throw error
}reportError automatically extracts stack traces and error details into structured span attributes.
Configuration
Environment variables
LOGFIRE_TOKEN=your-write-token
LOGFIRE_SERVICE_NAME=my-service
LOGFIRE_SERVICE_VERSION=1.0.0Programmatic
logfire.configure({
token: process.env.LOGFIRE_TOKEN,
serviceName: 'my-service',
serviceVersion: '1.0.0',
})Python Integration Reference
Web Frameworks
| Framework | Instrumentor | Needs app instance | Extra |
|---|---|---|---|
| FastAPI | logfire.instrument_fastapi(app) | Yes | fastapi |
| Django | logfire.instrument_django(app) | Yes | django |
| Flask | logfire.instrument_flask(app) | Yes | flask |
| Starlette | logfire.instrument_starlette(app) | Yes | starlette |
| AIOHTTP | logfire.instrument_aiohttp_client() | No | aiohttp |
HTTP Clients
| Library | Instrumentor | Extra |
|---|---|---|
| httpx | logfire.instrument_httpx() | httpx |
| requests | logfire.instrument_requests() | requests |
Databases
| Library | Instrumentor | Extra |
|---|---|---|
| asyncpg | logfire.instrument_asyncpg() | asyncpg |
| psycopg | logfire.instrument_psycopg() | psycopg |
| psycopg2 | logfire.instrument_psycopg2() | psycopg2 |
| SQLAlchemy | logfire.instrument_sqlalchemy() | sqlalchemy |
| PyMongo | logfire.instrument_pymongo() | pymongo |
| MySQL | logfire.instrument_mysql() | mysql |
| SQLite3 | logfire.instrument_sqlite3() | sqlite3 |
| Redis | logfire.instrument_redis() | redis |
AI/LLM Frameworks
| Framework | Instrumentor | Extra |
|---|---|---|
| PydanticAI | logfire.instrument_pydantic_ai() | pydantic-ai |
| OpenAI | logfire.instrument_openai() | openai |
| Anthropic | logfire.instrument_anthropic() | anthropic |
| LiteLLM | logfire.instrument_litellm() | litellm |
| DSPy | logfire.instrument_dspy() | dspy |
| Google GenAI | logfire.instrument_google_genai() | google-genai |
Task Queues
| Framework | Instrumentor | Extra |
|---|---|---|
| Celery | logfire.instrument_celery() | celery |
Other
| Feature | Instrumentor | Extra |
|---|---|---|
| System Metrics | logfire.instrument_system_metrics() | system-metrics |
| Pydantic Models | logfire.instrument_pydantic() | - (built-in) |
| AWS Lambda | handler wrapper | aws-lambda |
Gunicorn Configuration
# gunicorn.conf.py
import logfire
def post_fork(server, worker):
logfire.configure()
logfire.instrument_fastapi(app)Python Logging Patterns
Log Levels
From lowest to highest severity:
logfire.trace("Detailed trace {detail}", detail=x)
logfire.debug("Debug info {state}", state=s)
logfire.info("Normal operation {event}", event=e)
logfire.notice("Notable event {event}", event=e)
logfire.warn("Warning {issue}", issue=i)
logfire.error("Error occurred {error}", error=err)
logfire.fatal("Fatal error {error}", error=err)Nested Spans
Spans nest to create a tree visible in the Logfire UI. Use them to show the structure of an operation, not just that it happened:
with logfire.span("HTTP request {method} {url}", method="POST", url=url):
with logfire.span("Serialize payload"):
payload = model.model_dump_json()
with logfire.span("Send request"):
response = await client.post(url, content=payload)
logfire.info("Response {status}", status=response.status_code)Standard Library Logging Integration
For projects that already use Python's logging module, route existing log calls through Logfire rather than rewriting them all:
from logging import basicConfig
import logfire
logfire.configure()
basicConfig(handlers=[logfire.LogfireLoggingHandler()])Or with dictConfig:
from logging.config import dictConfig
import logfire
logfire.configure()
dictConfig({
'version': 1,
'handlers': {
'logfire': {'class': 'logfire.LogfireLoggingHandler'},
},
'root': {'handlers': ['logfire']},
})Suppressing Noisy Libraries
Some libraries emit excessive debug logs. Silence them at the logging level:
import logging
logging.getLogger('httpcore').setLevel(logging.WARNING)
logging.getLogger('httpx').setLevel(logging.WARNING)Custom Metrics
For dashboards and alerting, create metrics:
counter = logfire.metric_counter("orders_processed", unit="1")
counter.add(1, {"status": "success"})
histogram = logfire.metric_histogram("request_duration", unit="s")
histogram.record(0.123, {"endpoint": "/api/users"})
gauge = logfire.metric_gauge("active_connections")
gauge.set(42)Testing with capfire
Use the capfire pytest fixture to assert on emitted spans without sending data to production:
from logfire.testing import CaptureLogfire
def test_order_processing(capfire: CaptureLogfire) -> None:
process_order(order_id=123)
spans = capfire.exporter.exported_spans_as_dict()
assert any(
span['attributes'].get('order_id') == 123
for span in spans
)Configure logfire with send_to_logfire=False in test fixtures to prevent production data leakage.
Rust Patterns
Core Macros
The Rust SDK is built on tracing and opentelemetry. All tracing macros work automatically with Logfire.
Events (log points)
logfire::trace!("Detailed trace {detail}", detail = x);
logfire::debug!("Debug info {state}", state = s);
logfire::info!("Normal operation {event}", event = e);
logfire::warn!("Warning {issue}", issue = i);
logfire::error!("Error occurred {err}", err = e);Spans
// Scoped - span closes when closure completes
logfire::span!("Processing order {order_id}", order_id = id).in_scope(|| {
let items = fetch_items(id);
logfire::info!("Fetched {count} items", count = items.len());
process_items(items)
});
// Guard-based - span closes when guard is dropped
let _guard = logfire::span!("Long operation {job_id}", job_id = id).entered();
do_work();
// span ends when _guard goes out of scopeConfiguration
use logfire;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let shutdown_handler = logfire::configure()
.install_panic_handler() // captures panics as error spans
.finish()?;
// application code...
shutdown_handler.shutdown()?; // flush all pending spans
Ok(())
}Set LOGFIRE_TOKEN in your environment or use the Logfire CLI (logfire auth).
Tracing Crate Compatibility
Any library using tracing macros automatically sends data through Logfire:
use tracing;
tracing::info!("This also appears in Logfire");
#[tracing::instrument]
fn my_function(param: &str) {
// automatically creates a span with param as an attribute
}Log Crate Integration
The log crate is automatically captured and forwarded to Logfire. Libraries using log::info!(), log::error!(), etc. will appear in your Logfire dashboard without any additional configuration.
Async Spans
use tracing::Instrument;
async fn process_order(order_id: u64) {
let span = logfire::span!("process order {order_id}", order_id = order_id);
async {
fetch_items(order_id).await;
logfire::info!("Order processed");
}
.instrument(span)
.await;
}Shutdown
Always call shutdown() before program exit to flush pending data:
// In main()
let shutdown_handler = logfire::configure().finish()?;
// ... app runs ...
// Before exit
shutdown_handler.shutdown()?;For web servers using tokio, handle shutdown via signal:
tokio::signal::ctrl_c().await?;
shutdown_handler.shutdown()?;Related skills
FAQ
Which languages does it support?
Python, JavaScript/TypeScript, and Rust, plus any language via OpenTelemetry.
Why does call ordering matter?
configure() must run before instrument_*(); if you instrument before configuring, hooks register but traces go nowhere.