Backend Setup: Python
This guide walks you through instrumenting a Python application with the SF Veritas SDK for Sailfish Enterprise.
If you connected GitHub and received Auto-Installation PRs, the API key, service identifier, and graphql endpoint are already configured for you. Merge the PR and you're done.
Getting Your API Key
- Open the Sailfish dashboard
- Log in with your enterprise email
- Navigate to Settings > Configuration
- Copy your company's API key
Installation
Install the SF Veritas package using your preferred package manager:
pip
pip install sf-veritas
Poetry
poetry add sf-veritas
uv
uv add sf-veritas
Pipenv
pipenv install sf-veritas
pdm
pdm add sf-veritas
pyproject.toml (manual)
If you manage dependencies directly in pyproject.toml, add sf-veritas to your dependencies:
[project]
dependencies = [
"sf-veritas",
# ... your other dependencies
]
Then install with your tool of choice:
pip install . # pip
poetry install # Poetry
uv sync # uv
pdm install # pdm
Basic Setup
Add the following to your application's entry point:
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/web-api/src/main.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
# Your application code continues below...
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
api_key | str | Yes | Your Sailfish Enterprise API key |
service_identifier | str | No | Unique identifier in <org>/<repo>/<path> format. Falls back to SERVICE_VERSION or GIT_SHA env vars if not set |
service_version | str | No | Version of your service |
graphql_endpoint | str | No | Sailfish collector endpoint. Defaults to the Sailfish cloud endpoint. Override for self-hosted or local setups |
service_display_name | str | No | Human-readable name shown in the Sailfish dashboard |
git_sha | str | No | Git commit SHA. Auto-detected from CI/CD env vars (see below) |
git_org | str | No | Git organization name. Auto-detected from GITHUB_REPOSITORY |
git_repo | str | No | Git repository name. Auto-detected from GITHUB_REPOSITORY |
git_provider | str | No | Git provider (e.g. github, gitlab) |
domains_to_not_propagate_headers_to | list[str] | No | External domains where Sailfish headers should not be injected |
routes_to_skip_network_hops | list[str] | No | URL patterns to exclude from network hop capture |
Framework Examples
Django
setup_interceptors() must be called before Django initializes (before execute_from_command_line, get_wsgi_application(), or get_asgi_application()). This is because SF Veritas needs to monkey-patch the framework, logging, and HTTP clients before they are loaded.
Do NOT place setup_interceptors() at the bottom of settings.py — by that point Django's module loading is already underway and the interceptors cannot fully patch the framework. The correct location is manage.py (for runserver) or wsgi.py/asgi.py (for production deployments), before the Django application is created.
Recommended: manage.py (used by python manage.py runserver):
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
# Initialize SF Veritas BEFORE Django setup
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/django-api/manage.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
Most Django projects use different entry points in different environments — manage.py for local dev, wsgi.py for Gunicorn, asgi.py for Uvicorn/Daphne. Add setup_interceptors() to all three files so telemetry works regardless of how Django is started. The call is idempotent, so duplicate initialization is safe.
| Entry point | Used by |
|---|---|
manage.py | python manage.py runserver (local dev) |
wsgi.py | gunicorn myproject.wsgi:application |
asgi.py | uvicorn myproject.asgi:application, daphne myproject.asgi:application |
For WSGI deployments (Gunicorn), add it to wsgi.py before get_wsgi_application():
# wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
# Initialize SF Veritas BEFORE the WSGI app is created
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/django-api/wsgi.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
application = get_wsgi_application()
For ASGI deployments (Uvicorn, Daphne), add it to asgi.py before get_asgi_application():
# asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
# Initialize SF Veritas BEFORE the ASGI app is created
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/django-api/asgi.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
application = get_asgi_application()
FastAPI
# main.py
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/fastapi-api/src/main.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
from fastapi import FastAPI
app = FastAPI()
@app.get('/api/users')
async def get_users():
print('Fetching users') # This will appear in Sailfish
return {'users': []}
Flask
# app.py
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/flask-api/src/app.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
from flask import Flask
app = Flask(__name__)
@app.route('/api/users')
def get_users():
print('Fetching users') # This will appear in Sailfish
return {'users': []}
Async Queue Tracing
Sailfish automatically stitches producer → broker → consumer into a single trace when your code uses an instrumented async-queue library. Background jobs show up in the same trace as the HTTP request that enqueued them.
HTTP POST /enqueue ──► producer.apply_async() ──► broker ──► worker.task
(inbound hop) (ENQUEUE hop) (CONSUME hop)
│────────── shared session + pageVisit ──────────│
Supported libraries (zero-config)
| Library | Producer | Consumer |
|---|---|---|
| Celery | task.apply_async() / .delay() | task_prerun / task_postrun signals |
| confluent-kafka | Producer.produce() | Consumer.poll() |
| aiokafka | AIOKafkaProducer.send() / send_and_wait() | AIOKafkaConsumer __anext__ / getmany |
| kafka-python | KafkaProducer.send() | consumer iteration |
Each works automatically — just import the library and the SDK's patches
wire up on setup_interceptors().
Example
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/order-service/src/main.py',
service_version='1.0.0',
)
from celery import Celery
app = Celery('orders', broker='redis://localhost:6379/0')
@app.task
def send_receipt(order_id: int):
...
# In your HTTP handler:
send_receipt.delay(order.id)
In the Sailfish dashboard you will see one linked trace: the HTTP
request → an ENQUEUE hop for send_receipt → a CONSUME hop (from the
worker process) → any downstream HTTP calls the task makes.
Canvas, retry, and scheduled tasks
- Groups / chains / chords (
group,chain,chord) — every subtask is stitched into the same trace with a distinct requestId per hop. self.retry()/autoretry_for— each retry attempt gets its own requestId while preserving session + pageVisit.countdown=N/eta=...— the trace ID is captured at enqueue and flows through the broker unchanged; the worker's CONSUME hop lines up even if execution is delayed.
Runtime controls
Set SF_DISABLE_QUEUE_PATCHES=true to skip every queue-library patch
without disabling the rest of the SDK.
On startup, Sailfish logs one info line listing which libraries were patched, skipped (not installed), and failed — useful for confirming the integration.
Caveats
- Kafka transactional producers (if you use
producer.transaction()) emit their hop synchronously at.send(), not at.commit(). An aborted transaction still leaves its hop in the timeline.
Function Tracing
Use decorators to control which functions appear in the Flamechart:
from sf_veritas import capture_function_spans, skip_function_tracing
@capture_function_spans()
def important_business_logic():
"""This function will be traced in the Flamechart."""
process_data()
return result
@skip_function_tracing
def internal_helper():
"""This function won't be traced."""
pass
Decorator Options
@capture_function_spans(
include_arguments=True, # Capture function arguments
include_return_value=True, # Capture return values
sample_rate=1.0, # Sampling rate (0.0 to 1.0)
)
def traced_function():
pass
GIT_SHA
The GIT_SHA environment variable allows Sailfish to correlate telemetry with specific commits.
The SDK auto-detects GIT_SHA from common CI/CD platforms:
| CI Platform | Environment Variable |
|---|---|
| Generic | GIT_SHA |
| GitHub Actions | GITHUB_SHA |
| GitLab CI | CI_COMMIT_SHA |
| Bitbucket Pipelines | BITBUCKET_COMMIT |
| Vercel | VERCEL_GIT_COMMIT_SHA |
| CircleCI | CIRCLE_SHA1 |
| Heroku | HEROKU_SLUG_COMMIT |
| Render | RENDER_GIT_COMMIT |
| Railway | RAILWAY_GIT_COMMIT_SHA |
| AWS CodeBuild | CODEBUILD_RESOLVED_SOURCE_VERSION |
For custom CI systems, set it manually:
# Docker
docker build --build-arg GIT_SHA=$(git rev-parse HEAD) .
# In Dockerfile
ARG GIT_SHA
ENV GIT_SHA=$GIT_SHA
# CI/CD (GitHub Actions)
env:
GIT_SHA: ${{ github.sha }}
Verifying the Setup
- Deploy your application with the Sailfish SDK configured
- Trigger some activity in your application
- Open the Sailfish dashboard
- You should see telemetry appearing in your project
Troubleshooting
No logs appearing
- Check the API key: Ensure
api_keyis set to your Enterprise API key - Check the service identifier: Ensure
service_identifieruses the<org>/<repo>/<path>format - Check console output: Look for SF Veritas initialization messages in your terminal
Connection errors
- Ensure your deployment can reach
https://api-service.sailfish.ai(or your customgraphql_endpointif configured) - Check that outbound HTTPS (port 443) is not blocked by a firewall or network policy
Import errors
- Ensure
sf-veritasis installed:pip show sf-veritas - Check you're using Python 3.8+
- Verify you're in the correct virtual environment
Multi-Service Setup
When running multiple Python services, give each a unique service_identifier following the <org>/<repo>/<path> format:
# user-service/main.py
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<see-api-key-from-your-account-settings-page>",
service_identifier='acme-corp/user-service/src/main.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
# order-service/main.py
from sf_veritas import setup_interceptors
setup_interceptors(
api_key="<ApiKey />",
service_identifier='acme-corp/order-service/src/main.py', # Format: <org>/<repo>/<path-to-this-file>
service_version='1.0.0',
)
Use the service filter in the Sailfish dashboard to switch between services.
Next Steps
- Set up your frontend application (optional)
- Check the Sailfish dashboard to verify telemetry is flowing
Looking to set up SF Veritas for local development with the Desktop App? See the Desktop App Python guide.