Skip to main content

Backend Setup: Python

This guide walks you through instrumenting a Python application with the SF Veritas SDK

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. The SDK activates when it detects the DEBUG=true environment variable, so it only runs during local development:

Setting DEBUG=true

You must set the DEBUG=true environment variable when running your app locally. This is how SF Veritas knows to activate. Add it as a prefix to your run command:

DEBUG=true python main.py
DEBUG=true uvicorn main:app --reload
DEBUG=true flask run
DEBUG=true python manage.py runserver
import os

# Initialize SF Veritas ONLY in development mode
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='my-python-service',
service_version='1.0.0',
)

# Your application code continues below...

Using a .env File

Instead of prefixing every command, create a .env file in your project root:

# .env
DEBUG=true

Then load it in your application (most frameworks support this automatically, or use python-dotenv):

pip install python-dotenv
from dotenv import load_dotenv
load_dotenv() # Loads .env file

import os
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='my-python-service',
)
tip

Add .env to your .gitignore to avoid committing development settings. Create a .env.example file for reference:

# .env.example
DEBUG=true

Configuration Options

OptionTypeRequiredDescription
api_keystrYesUse "sf-veritas-local" for local development
graphql_endpointstrYesURL of the local collector (default port 6776)
service_identifierstrYesUnique name for your service
service_versionstrNoVersion of your service

Framework Examples

Django

Critical: Placement matters

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
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='django-api',
service_version='1.0.0',
)

from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

if __name__ == '__main__':
main()
Add it to all three entry points

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 pointUsed by
manage.pypython manage.py runserver (local dev)
wsgi.pygunicorn myproject.wsgi:application
asgi.pyuvicorn 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
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='django-api',
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
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='django-api',
service_version='1.0.0',
)

application = get_asgi_application()

FastAPI

# main.py
import os

# Initialize SF Veritas only in development
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='fastapi-api',
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 SF Veritas Console
return {'users': []}

# Run with: DEBUG=true uvicorn main:app --reload

Flask

# app.py
import os

# Initialize SF Veritas only in development
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='flask-api',
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 SF Veritas Console
return {'users': []}

# Run with: DEBUG=true flask run

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)

LibraryProducerConsumer
Celerytask.apply_async() / .delay()task_prerun / task_postrun signals
confluent-kafkaProducer.produce()Consumer.poll()
aiokafkaAIOKafkaProducer.send() / send_and_wait()AIOKafkaConsumer __anext__ / getmany
kafka-pythonKafkaProducer.send()consumer iteration

Each works automatically — just import the library and the SDK's patches wire up on setup_interceptors().

Example

import os

if os.getenv('SF_DEV') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
service_identifier='order-service',
graphql_endpoint='http://localhost:6776/graphql/',
)

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)

# Run with: SF_DEV=true celery -A myapp worker

Open the Desktop App Console — you'll 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

Verifying the Setup

  1. Start your application with DEBUG=true:
    • Django: DEBUG=true python manage.py runserver
    • FastAPI: DEBUG=true uvicorn main:app --reload
    • Flask: DEBUG=true flask run
  2. In VS Code, open the Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
  3. Run SF Veritas: Show Console Logs
  4. Trigger some activity in your application
  5. You should see logs appearing in the Console panel

Troubleshooting

No logs appearing

  1. Check the local server: Run SF Veritas: Start Local Server if not running
  2. Verify the endpoint: Ensure graphql_endpoint matches your server port
  3. Check console output: Look for SF Veritas initialization messages in your terminal

Connection refused errors

  1. Verify the SF Veritas Desktop App is installed and running
  2. Check that the local server is running (look for server status in the Desktop App)
  3. Ensure port 6776 is not blocked by a firewall

Import errors

  1. Ensure sf-veritas is installed: pip show sf-veritas
  2. Check you're using Python 3.8+
  3. Verify you're in the correct virtual environment

Multi-Service Setup

When running multiple Python services locally, give each a unique service_identifier:

# user-service/main.py
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='user-service',
service_version='1.0.0',
)

# order-service/main.py
if os.environ.get('DEBUG') == 'true':
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='order-service',
service_version='1.0.0',
)

Use the service filter in the Console to switch between services.

How It Works

The environment variable controls when SF Veritas is active:

FrameworkDev CheckDev CommandSF Veritas
DjangoDEBUG=true or settings.DEBUGDEBUG=true python manage.py runserver✅ Active
FastAPIDEBUG=trueDEBUG=true uvicorn main:app --reload✅ Active
FlaskDEBUG=trueDEBUG=true flask run✅ Active
AnyProduction deploy (no DEBUG=true)❌ Not loaded

All frameworks use the same DEBUG=true environment variable for consistency:

  • Django: Also supports settings.DEBUG in settings.py
  • FastAPI: Use DEBUG=true prefix or .env file
  • Flask: Use DEBUG=true prefix or .env file

Enterprise Setup

Enterprise Only

This section applies to users with a Sailfish Enterprise account. If you're using the Desktop App for local development only, the basic setup above is all you need.

Enterprise users need two configurations — one for local development (Desktop App) and one for staging/production (Sailfish cloud). The SDK should detect which environment it's running in and configure itself accordingly.

Dual-Mode Configuration

import os

def setup_sailfish():
"""Configure SF Veritas for local dev or enterprise staging/production."""
if os.environ.get('DEBUG') == 'true':
# Local development — send to Desktop App
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='my-python-service',
service_version='1.0.0',
)
elif os.environ.get('SAILFISH_API_KEY'):
# Staging/Production — send to Sailfish cloud
# Do NOT set graphql_endpoint — the SDK defaults to the cloud endpoint
from sf_veritas import setup_interceptors
setup_interceptors(
api_key=os.environ['SAILFISH_API_KEY'],
service_identifier='my-python-service',
service_version='1.0.0',
)

setup_sailfish()

Django Example

As noted above, place this in manage.py (or wsgi.py/asgi.py), not in settings.py:

# manage.py — before execute_from_command_line()
import os

_debug = os.environ.get('DEBUG') == 'true'
_sailfish_api_key = os.environ.get('SAILFISH_API_KEY')

if _debug:
# Local development
from sf_veritas import setup_interceptors
setup_interceptors(
api_key='sf-veritas-local',
graphql_endpoint='http://localhost:6776/graphql/',
service_identifier='django-api',
)
elif _sailfish_api_key:
# Staging/Production (Enterprise)
from sf_veritas import setup_interceptors
setup_interceptors(
api_key=_sailfish_api_key,
service_identifier='django-api',
)

Environment Variables for Enterprise

Set these in your staging/production deployment:

# Required — your company's Enterprise API key
SAILFISH_API_KEY=your-enterprise-api-key

# Required — the git commit SHA for this build
GIT_SHA=$(git rev-parse HEAD)

# Optional — override the endpoint (only for local dev, do NOT set in production)
# SAILFISH_GRAPHQL_ENDPOINT=http://localhost:6776/graphql/

GIT_SHA

The GIT_SHA environment variable allows Sailfish to correlate telemetry with specific commits. Set it in your build pipeline:

# 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 }}

URL Override

You can override the GraphQL endpoint in two ways:

  1. Environment variable: SAILFISH_GRAPHQL_ENDPOINT
  2. Constructor argument: graphql_endpoint in setup_interceptors()
EnvironmentEndpointHow
Local devhttp://localhost:6776/graphql/Set graphql_endpoint or SAILFISH_GRAPHQL_ENDPOINT
Staging/ProductionDefault (cloud)Do not set either — the SDK uses https://api-service.sailfishqa.com/graphql/ automatically

Next Steps