FastAPI application with automatic OpenTelemetry instrumentation, JWT authentication, and PostgreSQL integration with base14 Scout.
| Component | Version | EOL Status | Current Version |
|---|---|---|---|
| Python | 3.13 | Active | 3.13.9 |
| FastAPI | 0.128.0 | Stable | 0.128.0 |
| PostgreSQL | 18 | Active | 18.1 |
| OpenTelemetry | 1.39.1 | N/A | 1.39.1 |
| SQLAlchemy | 2.0.45 | Stable | 2.0.45 |
Why This Matters: Modern Python stack with FastAPI's high performance and automatic OpenTelemetry instrumentation for comprehensive observability.
- ✅ HTTP requests and responses (FastAPI automatic instrumentation)
- ✅ Database queries (SQLAlchemy automatic instrumentation)
- ✅ Distributed trace propagation (W3C Trace Context)
- ✅ Error tracking with automatic exception capture
- Traces: Post and user CRUD operations with custom spans
- Attributes: User data, post metadata, SQL operations
- Logs: Structured logs with trace correlation
- Metrics: Custom HTTP metrics middleware (request count, duration, errors)
- Business-specific custom spans and attributes
- Advanced metrics beyond HTTP basics
- Custom log correlation patterns
| Component | Package | Version |
|---|---|---|
| Python | python | 3.13 |
| FastAPI | fastapi[all] | 0.128.0 |
| PostgreSQL Driver | psycopg2-binary | 2.9.10 |
| SQLAlchemy | SQLAlchemy | 2.0.45 |
| Pydantic | pydantic | 2.12.5 |
| Authentication | PyJWT, passlib, bcrypt | 2.10.1, 1.7.4, 4.2.1 |
| OTel SDK | opentelemetry-sdk | 1.39.1 |
| OTel Instrumentation | opentelemetry-instrumentation-fastapi | 0.60b0 |
| OTel Exporter | opentelemetry-exporter-otlp | 1.39.1 |
| Database Migrations | alembic | 1.14.0 |
- Docker & Docker Compose - Install Docker
- base14 Scout Account - Sign up
- Python 3.13+ (for local development)
# Clone and navigate
git clone https://github.com/base-14/examples.git
cd examples/python/fastapi-postgres
# Copy environment template
cp .env.example .envEdit .env and update the required values:
# Generate a secure SECRET_KEY
SECRET_KEY=$(openssl rand -hex 32)
# Set database password
DB_PASSWORD=your_database_password
# Configure CORS (comma-separated origins)
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080Add these to your .env file:
SCOUT_ENDPOINT=https://your-tenant.base14.io:4318
SCOUT_CLIENT_ID=your_client_id
SCOUT_CLIENT_SECRET=your_client_secret
SCOUT_TOKEN_URL=https://your-tenant.base14.io/oauth/tokenSee the base14 Collector Setup Guide for obtaining credentials.
docker compose up --buildThis starts:
- app: FastAPI application on port 8000
- postgres: PostgreSQL 18 database
- otel-collector: OpenTelemetry Collector 0.144.0
docker compose exec app alembic upgrade head./scripts/test-api.shThe test script exercises all endpoints and verifies telemetry.
Navigate to your Scout dashboard to view traces and metrics:
https://your-tenant.base14.io
The OpenTelemetry Collector requires base14 Scout credentials to export telemetry data:
| Variable | Required | Description |
|---|---|---|
SCOUT_ENDPOINT |
Yes | base14 Scout OTLP endpoint |
SCOUT_CLIENT_ID |
Yes | OAuth2 client ID from base14 Scout |
SCOUT_CLIENT_SECRET |
Yes | OAuth2 client secret from base14 Scout |
SCOUT_TOKEN_URL |
Yes | OAuth2 token endpoint |
See .env.example for all available configuration options:
| Variable | Default | Description |
|---|---|---|
DB_HOSTNAME |
postgres |
PostgreSQL hostname |
DB_PORT |
5432 |
PostgreSQL port |
DB_PASSWORD |
Required | PostgreSQL password |
DB_NAME |
fastapi |
Database name |
DB_USERNAME |
postgres |
Database username |
SECRET_KEY |
Required | JWT secret key (openssl rand -hex 32) |
ALGORITHM |
HS256 |
JWT algorithm |
ACCESS_TOKEN_EXPIRE_MINUTES |
60 |
JWT token expiration |
ALLOWED_ORIGINS |
http://localhost:3000 |
CORS allowed origins |
OTEL_SERVICE_NAME |
fastapi-postgres-app |
Service name |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://otel-collector:4318 |
Collector |
Automatically included in telemetry:
service.name=fastapi-postgres-app
service.version=1.0.0
deployment.environment=development| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/ |
Root endpoint | No |
POST |
/users |
Create user | No |
GET |
/users/{id} |
Get user by ID | Yes |
POST |
/login |
User login | No |
GET |
/posts |
List all posts | Yes |
GET |
/posts/{id} |
Get post by ID | Yes |
POST |
/posts |
Create post | Yes |
PUT |
/posts/{id} |
Update post | Yes |
DELETE |
/posts/{id} |
Delete post | Yes |
POST |
/vote |
Vote on post | Yes |
DELETE |
/vote |
Remove vote | Yes |
# Create a user
curl -X POST http://localhost:8000/users \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com", "password": "securepass123"}'
# Login
curl -X POST http://localhost:8000/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=alice@example.com&password=securepass123"
# Create a post (with JWT token)
curl -X POST http://localhost:8000/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"title": "My First Post", "content": "This is the content", "published": true}'
# List posts
curl http://localhost:8000/posts \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Vote on a post
curl -X POST http://localhost:8000/vote \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"post_id": 1, "dir": 1}'- HTTP requests: Method, path, status code, duration
- Database queries: Full SQL statements with parameters
- Authentication: Login and token validation spans
- Business operations: Post CRUD, user registration, voting
- Span hierarchy: HTTP → Route Handler → Database operations
- Attributes: User IDs, post IDs, SQL table names
- Errors: Automatic exception capture with stack traces
Structured logs with automatic trace correlation (to be implemented):
{
"timestamp": "2025-12-02T14:20:42Z",
"severity": "info",
"message": "Post created successfully",
"trace_id": "6ec1f6ce672d342770671880fbf89ab9",
"span_id": "cc5e4bb6c023c846",
"service.name": "fastapi-postgres-app",
"post.id": 42,
"user.id": 1
}Custom metrics via MetricsMiddleware:
- http.server.request.count: Total HTTP requests by method, route, and status
- http.server.request.duration: Request duration histogram
- http.server.request.errors: Error count by route
fastapi[all]
opentelemetry-instrumentation-fastapi
opentelemetry-sdk
opentelemetry-instrumentation-requests
opentelemetry-exporter-otlp
opentelemetry-apiSee app/telemetry.py for complete implementation.
Key aspects:
- Automatic FastAPI instrumentation
- OTLP HTTP exporter to collector
- Resource attributes configuration
- Batch span processor for efficient export
- Custom metrics middleware
FastAPI endpoints automatically create spans. Custom metrics are added via middleware.
See app/MetricsMiddleware.py for custom metrics implementation:
from opentelemetry import metrics
class MetricsMiddleware:
def __init__(self, app):
self.app = app
meter = metrics.get_meter(__name__)
self.request_counter = meter.create_counter(
"http.server.request.count",
description="Total HTTP requests"
)
self.duration_histogram = meter.create_histogram(
"http.server.request.duration",
description="HTTP request duration"
)See app/routers/post.py for automatic span creation examples.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE,
password VARCHAR NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
phone_number VARCHAR
);CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR NOT NULL,
content VARCHAR NOT NULL,
published BOOLEAN DEFAULT TRUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);CREATE TABLE votes (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, post_id)
);# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Set environment variables
export DB_HOSTNAME=localhost
export DB_PASSWORD=your_password
# ... other variables from .env.example
# Run migrations
alembic upgrade head
# Start server
uvicorn app.main:app --reload# Build and start
docker compose up --build
# Start in background
docker compose up -d
# View logs
docker compose logs -f app
docker compose logs -f otel-collector
# Stop services
docker compose down
# Rebuild
docker compose build# Application logs
docker compose logs -f app
# API documentation (Swagger UI)
open http://localhost:8000/docs
# Database access
docker exec -it fastapi-postgres psql -U postgres -d fastapi
# OTel collector zpages
open http://localhost:55679/debug/servicez-
Check OTel collector logs:
docker compose logs otel-collector
-
Verify Scout credentials in environment variables
-
Test collector health:
curl http://localhost:55679/debug/servicez
-
Verify telemetry setup in application logs
-
Verify PostgreSQL is running:
docker compose ps postgres
-
Check database credentials in
.envor environment variables -
Test connection:
docker exec fastapi-postgres pg_isready -U postgres
-
Ensure JWT secret is set in environment:
echo $SECRET_KEY
-
Verify token format in Authorization header:
Authorization: Bearer YOUR_JWT_TOKEN -
Check token expiration (default 60 minutes)
- OpenTelemetry Python Documentation - Python SDK reference
- FastAPI Documentation - FastAPI framework
- SQLAlchemy - Python SQL toolkit and ORM
- base14 Scout - Observability platform
- base14 Documentation - Full instrumentation guides