TraceKit APM for Python - Zero-config distributed tracing and code monitoring for Flask, FastAPI, and Django
Project description
TraceKit APM for Python
Zero-config distributed tracing and performance monitoring for Flask, FastAPI, and Django applications.
Features
- ✅ Zero Configuration - Works out of the box with sensible defaults
- ✅ Automatic Instrumentation - No code changes needed
- ✅ Flask Support - Simple middleware integration
- ✅ FastAPI Support - ASGI middleware-based tracing
- ✅ Django Support - Django middleware integration
- ✅ HTTP Request Tracing - Track every request, route, and handler
- ✅ Error Tracking - Capture exceptions with full Python stack traces
- ✅ Code Discovery - Automatically index code from exception stack traces
- ✅ Code Monitoring - Live debugging with breakpoints and variable inspection
- ✅ Nested Spans - Parent-child span relationships with OpenTelemetry
- ✅ Low Overhead - < 5% performance impact
- ✅ OpenTelemetry Standard - Built on industry-standard OpenTelemetry
Installation
pip install tracekit-apm
Framework-Specific Installation
# Flask
pip install tracekit-apm[flask]
# FastAPI
pip install tracekit-apm[fastapi]
# Django
pip install tracekit-apm[django]
# All frameworks
pip install tracekit-apm[all]
Quick Start
Flask
from flask import Flask
import tracekit
from tracekit.middleware.flask import init_flask_app
app = Flask(__name__)
# Initialize TraceKit with code monitoring enabled
client = tracekit.init(
api_key="your-api-key",
service_name="my-flask-app",
enable_code_monitoring=True # Enable live debugging
)
# Add middleware
init_flask_app(app, client)
@app.route('/')
def hello():
return 'Hello World!'
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
# Capture snapshot for debugging (synchronous call)
if client.get_snapshot_client():
client.capture_snapshot('get-user', {
'user_id': user_id,
'request_path': request.path,
'request_method': request.method
})
# Your business logic here
user = fetch_user_from_db(user_id)
return jsonify(user)
if __name__ == '__main__':
app.run()
FastAPI
from fastapi import FastAPI
import tracekit
from tracekit.middleware.fastapi import init_fastapi_app
app = FastAPI()
# Initialize TraceKit
client = tracekit.init(
api_key="your-api-key",
service_name="my-fastapi-app"
)
# Add middleware
init_fastapi_app(app, client)
@app.get("/")
async def root():
return {"message": "Hello World"}
Django
# settings.py
# Add middleware
MIDDLEWARE = [
'tracekit.middleware.django.TracekitDjangoMiddleware',
# ... other middleware
]
# Initialize TraceKit in your app's apps.py
# apps.py
from django.apps import AppConfig
import tracekit
import os
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
tracekit.init(
api_key=os.environ['TRACEKIT_API_KEY'],
service_name='my-django-app'
)
Code Monitoring (Live Debugging)
TraceKit includes production-safe code monitoring for live debugging without redeployment.
Enable Code Monitoring
import tracekit
# Enable code monitoring
client = tracekit.init(
api_key="your-api-key",
service_name="my-app",
enable_code_monitoring=True # Enable live debugging
)
Add Debug Points
Add checkpoints anywhere in your code to capture variable state and stack traces:
@app.post('/checkout')
async def checkout(request):
cart = await request.json()
user_id = cart['user_id']
# Capture snapshot at this point (synchronous - no await needed)
client.capture_snapshot('checkout-validation', {
'user_id': user_id,
'cart_items': len(cart.get('items', [])),
'total_amount': cart.get('total', 0),
})
# Process payment...
result = await process_payment(cart)
# Another checkpoint
client.capture_snapshot('payment-complete', {
'user_id': user_id,
'payment_id': result['payment_id'],
'success': result['success'],
})
return {'status': 'success', 'result': result}
Automatic Breakpoint Management
- Auto-Registration: First call to
capture_snapshot()automatically creates breakpoints in TraceKit - Smart Matching: Breakpoints match by function name + label (stable across code changes)
- Background Sync: SDK polls for active breakpoints every 30 seconds
- Production Safe: No performance impact when breakpoints are inactive
- Synchronous API:
capture_snapshot()is synchronous - noawaitneeded (works in both sync and async code)
Important Notes
🔑 Key Points:
capture_snapshot()is synchronous (noawaitneeded)- Works in both sync and async functions
- Automatically registers breakpoints on first call
- Captures are rate-limited (1 per second per breakpoint)
- Max 100 captures per breakpoint by default
View Captured Data
Snapshots include:
- Variables: Local variables at capture point
- Stack Trace: Full call stack with file/line numbers
- Request Context: HTTP method, URL, headers, query params
- Execution Time: When the snapshot was captured
Get your API key at https://app.tracekit.dev
Configuration
Basic Configuration
import tracekit
client = tracekit.init(
# Required: Your TraceKit API key
api_key="your-api-key",
# Optional: Service name (default: 'python-app')
service_name="my-service",
# Optional: TraceKit endpoint (default: 'https://app.tracekit.dev/v1/traces')
endpoint="https://app.tracekit.dev/v1/traces",
# Optional: Enable/disable tracing (default: True)
enabled=True,
# Optional: Sample rate 0.0-1.0 (default: 1.0 = 100%)
sample_rate=0.5, # Trace 50% of requests
# Optional: Enable live code debugging (default: False)
enable_code_monitoring=True
)
Environment Variables
Create a .env file or set these environment variables:
TRACEKIT_API_KEY=your_api_key_here
TRACEKIT_ENDPOINT=https://app.tracekit.dev/v1/traces
TRACEKIT_SERVICE_NAME=my-python-app
Then use them in your code:
import os
import tracekit
client = tracekit.init(
api_key=os.getenv('TRACEKIT_API_KEY'),
service_name=os.getenv('TRACEKIT_SERVICE_NAME', 'python-app'),
endpoint=os.getenv('TRACEKIT_ENDPOINT', 'https://app.tracekit.dev/v1/traces')
)
What Gets Traced?
HTTP Requests
Every HTTP request is automatically traced with:
- Route path and HTTP method
- Request URL and query parameters
- HTTP status code
- Request duration
- User agent and client IP
- Controller and handler names
Errors and Exceptions
All exceptions are automatically captured with:
- Exception type and message
- Full stack trace (Python format preserved)
- Request context
- Handler information
Automatic Code Discovery
TraceKit automatically discovers and indexes your Python code from exception stack traces:
Python Stack Trace Format (Native):
File "/path/to/app.py", line 123, in function_name
What Gets Indexed:
- File paths:
app.py,handlers.py, etc. - Function names:
get_user,process_payment, etc. - Line numbers: Exact location in your code
- First/last seen timestamps
- Occurrence counts
Example: When this exception occurs:
File "/app/handlers/users.py", line 42, in get_user
user = User.objects.get(id=user_id)
DoesNotExist: User matching query does not exist.
TraceKit indexes:
- File:
users.py - Function:
get_user - Line: 42
- Service: Your service name
View discovered code in the TraceKit UI under Code Inventory.
Advanced Usage
Manual Tracing
from opentelemetry import trace
@app.post('/process-order')
async def process_order(request):
# Get the client
client = tracekit.get_client()
# Start a custom span
span = client.start_span('process-order', {
'order.id': order_id,
'customer.id': customer_id
})
try:
# Your business logic
result = await process_order_logic(order_id)
# Add attributes
client.end_span(span, {
'order.status': result['status'],
'order.total': result['total']
})
return result
except Exception as error:
# Record exception
client.record_exception(span, error)
client.end_span(span, {}, status='ERROR')
raise
Custom Spans with Context Manager (Nested Spans)
Use OpenTelemetry's context manager for automatic parent-child span relationships:
from opentelemetry import trace
from flask import Flask, jsonify
import time
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
# Get the tracer
tracer = trace.get_tracer(__name__)
# Parent span is automatically managed by Flask middleware
# Create child span using context manager
with tracer.start_as_current_span('db.query.user') as span:
span.set_attributes({
'db.system': 'postgresql',
'db.operation': 'SELECT',
'db.table': 'users',
'db.statement': 'SELECT * FROM users WHERE id = ?',
'user.id': user_id
})
time.sleep(0.01) # Simulate DB query
user = fetch_user_from_db(user_id)
span.set_attributes({
'user.found': user is not None,
'user.role': user.get('role') if user else None
})
return jsonify(user)
This creates a nested trace with parent-child relationships:
GET /api/users/1 (parent span - auto-created by middleware)
└─ db.query.user (child span - manually created)
Database Query Tracing
@app.get('/users')
async def list_users():
client = tracekit.get_client()
# Start a database query span
span = client.start_span('db.query.users', {
'db.system': 'postgresql',
'db.operation': 'SELECT',
'db.table': 'users'
})
try:
users = await db.query('SELECT * FROM users')
client.end_span(span, {
'db.rows_affected': len(users)
})
return users
except Exception as error:
client.record_exception(span, error)
client.end_span(span, {}, status='ERROR')
raise
External API Call Tracing
import httpx
@app.get('/external-data')
async def fetch_external_data():
client = tracekit.get_client()
span = client.start_span('http.client.get', {
'http.url': 'https://api.example.com/data',
'http.method': 'GET'
})
try:
async with httpx.AsyncClient() as http_client:
response = await http_client.get('https://api.example.com/data')
client.end_span(span, {
'http.status_code': response.status_code,
'response.size': len(response.content)
})
return response.json()
except Exception as error:
client.record_exception(span, error)
client.end_span(span, {}, status='ERROR')
raise
Environment-Based Configuration
Disable tracing in development
import os
tracekit.init(
api_key=os.getenv('TRACEKIT_API_KEY'),
enabled=os.getenv('ENVIRONMENT') == 'production'
)
Sample only 10% of requests
tracekit.init(
api_key=os.getenv('TRACEKIT_API_KEY'),
sample_rate=0.1 # Trace 10% of requests
)
Performance
TraceKit APM is designed to have minimal performance impact:
- < 5% overhead on average request time
- Asynchronous trace sending (doesn't block responses)
- Automatic batching and compression
- Configurable sampling for high-traffic apps
Requirements
- Python 3.8 or higher
- Flask 2.0+ (for Flask support)
- FastAPI 0.100+ (for FastAPI support)
- Django 3.2+ (for Django support)
Examples
See the examples/ directory for complete working applications:
- Flask example: examples/flask_example.py
- FastAPI example: examples/fastapi_example.py
- Django example: examples/django_example/
Troubleshooting
Snapshots Not Capturing
If snapshots are registered but not capturing:
-
Check if code monitoring is enabled:
client = tracekit.init( api_key="your-api-key", enable_code_monitoring=True # Must be True )
-
Verify snapshot client is available:
if client.get_snapshot_client(): client.capture_snapshot('label', {'var': value})
-
Check backend logs for errors:
- Look for "Snapshot captured successfully" messages
- Check for datetime format errors (should use UTC with Z suffix)
Code Discovery Not Working
If Python code isn't being indexed:
- Trigger an exception - Code discovery works from exception stack traces
- Check stack trace format - Should be native Python format:
File "/path/to/file.py", line 123, in function_name - View backend logs - Look for "Parsed X frames from stack trace"
Context Token Errors
If you see RuntimeError: Token has already been used once:
- This was fixed in the latest version
- Make sure you're using the latest
tracekit-apmpackage - The error occurs when exceptions are not properly cleaned up
Nested Spans Not Showing
To create nested spans, use OpenTelemetry's context manager:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
# This creates a child span under the parent request span
with tracer.start_as_current_span('operation-name') as span:
span.set_attribute('key', 'value')
# Your code here
Support
- Documentation: https://app.tracekit.dev/docs/languages/python
- Issues: https://github.com/Tracekit-Dev/python-apm/issues
- Email: support@tracekit.dev
License
MIT License. See LICENSE for details.
Credits
Built with ❤️ by the TraceKit team using OpenTelemetry.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tracekit_apm-1.0.1.tar.gz.
File metadata
- Download URL: tracekit_apm-1.0.1.tar.gz
- Upload date:
- Size: 20.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f5663d463d80a4745d221257e80e77f1b629f55efeaf7895d7f0dea14f89859
|
|
| MD5 |
50543ee089081e88fb4e01034d8492cd
|
|
| BLAKE2b-256 |
dafdba839939ad5f9169195c0fe2b5696ee4ded8040cb3fd173dddcf1a02c316
|
File details
Details for the file tracekit_apm-1.0.1-py3-none-any.whl.
File metadata
- Download URL: tracekit_apm-1.0.1-py3-none-any.whl
- Upload date:
- Size: 18.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
006c554db54b86e8e3a4ee52bf6cf595d586f97ef375b10a07341bb55b1967a2
|
|
| MD5 |
84b8d715ab404d1fd6a08b2b9d7b542b
|
|
| BLAKE2b-256 |
a448ea0ab38d93ed318265b845b23b5a87c97aa85e1117158e107f3a0d4dd5d2
|