jstverify-tracing 0.10.4
Python distributed tracing SDK for JstVerify application mapping
Requires Python: >=3.9

# jstverify-tracing

Python distributed tracing SDK for [JstVerify](https://jstverify.com) application mapping. Auto-instruments your backend to produce trace spans that connect with the JstVerify JavaScript SDK, giving you a full frontend-to-backend service map.

## Installation

```bash
pip install jstverify-tracing
```

With framework extras:

```bash
pip install jstverify-tracing[flask]
pip install jstverify-tracing[django]
pip install jstverify-tracing[fastapi]
```

## Quick Start

### 1. Initialize (once at startup)

```python
import jstverify_tracing

jstverify_tracing.init(
    api_key="your-sdk-key",
    service_name="my-backend",
)
```

> The endpoint defaults to the production ingestion URL (`https://sdkapi.jstverify.com/v1/tracing/spans`). Override for dev environments:
>
> ```python
> jstverify_tracing.init(
>     api_key="your-sdk-key",
>     endpoint="https://sdkapi.dev.jstverify.com/v1/tracing/spans",
>     service_name="my-backend",
> )
> ```

### 2. Add Framework Middleware

**Flask:**

```python
from jstverify_tracing.integrations.flask import JstVerifyTracingMiddleware
JstVerifyTracingMiddleware(app)
```

**Django (settings.py):**

```python
MIDDLEWARE = [
    "jstverify_tracing.integrations.django.JstVerifyTracingMiddleware",
    ...
]
```

**FastAPI:**

```python
from jstverify_tracing.integrations.fastapi import JstVerifyTracingMiddleware
app.add_middleware(JstVerifyTracingMiddleware)
```

**AWS Lambda (API Gateway):**

```python
from jstverify_tracing.integrations.awslambda import JstVerifyTracingMiddleware

@JstVerifyTracingMiddleware
def lambda_handler(event, context):
    return {"statusCode": 200, "body": "ok"}
```

**AWS AppSync Lambda Resolver:**

```python
from jstverify_tracing.integrations.appsync import JstVerifyAppSyncMiddleware

@JstVerifyAppSyncMiddleware
def handler(event, context):
    return [{"id": "1", "name": "Alice"}]
```

The AppSync middleware extracts trace context from `event["request"]["headers"]` and derives the operation name from `event["info"]["parentTypeName"]` and `event["info"]["fieldName"]` (e.g. `Query.listUsers`). Only direct transport mode is supported — relay mode is not available for AppSync since GraphQL responses cannot carry custom HTTP headers.

### 3. Manual Instrumentation (optional)

```python
from jstverify_tracing import trace, trace_span

@trace("process-payment")
def process_payment(order_id):
    ...

def handle_order(order_id):
    with trace_span("validate-order") as span:
        ...
        span.set_status(200)
    with trace_span("charge-card") as span:
        ...
        span.set_http_metadata(method="POST", url="/payments/charge", status_code=201)
```

### 4. DynamoDB Tracing

The `patch_requests=True` option only patches the `requests` HTTP library. AWS SDK calls via `boto3` use `urllib3` directly, so DynamoDB, S3, and SQS operations are **not** auto-traced.

Use the `trace_dynamodb()` helper to wrap individual DynamoDB operations:

```python
from jstverify_tracing import trace_dynamodb

# Instead of: table.get_item(Key={"UserID": "123"})
result = trace_dynamodb("GetItem", table, Key={"UserID": "123"})

# Works with any DynamoDB operation
result = trace_dynamodb("Query", table, KeyConditionExpression="pk = :pk",
                        ExpressionAttributeValues={":pk": org_id})
result = trace_dynamodb("PutItem", table, Item={"UserID": "456", "name": "Alice"})
```

Each call creates a child span with the operation name (e.g. `DynamoDB.GetItem`) and the table name in metadata.

### 5. Shutdown

```python
jstverify_tracing.shutdown()
```

Shutdown is also registered via `atexit` automatically.

## Configuration Options

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `api_key` | str | required | Your JstVerify SDK API key |
| `endpoint` | str | production URL | Span ingestion endpoint URL (defaults to `https://sdkapi.jstverify.com/v1/tracing/spans`) |
| `service_name` | str | required | Service name shown in the service map |
| `service_type` | str | `"http"` | Service type identifier |
| `transport` | str | `"direct"` | `"direct"` sends spans via HTTP; `"relay"` encodes spans into response headers |
| `flush_interval` | float | `5.0` | Seconds between background flushes |
| `max_queue_size` | int | `200` | Max buffered spans (circular buffer) |
| `max_batch_size` | int | `50` | Max spans per API request |
| `debug` | bool | `False` | Enable debug logging |
| `patch_requests` | bool | `True` | Auto-patch `requests` library for outgoing HTTP tracing |

## How It Works

### Direct Mode (default)

1. The middleware reads `X-JstVerify-Trace-Id` and `X-JstVerify-Parent-Span-Id` headers from incoming requests (injected by the JS SDK).
2. A root span is created for each request, with nested child spans for `@trace` decorated functions and `trace_span` context managers.
3. Outgoing `requests` library calls are automatically instrumented — trace headers are injected so downstream services can continue the trace.
4. Spans are buffered in a thread-safe queue and flushed to the JstVerify API in batches by a background daemon thread.

### Relay Mode

For backends without outbound internet access (private VPC, strict firewalls), relay mode encodes spans into the `X-JstVerify-Spans` response header. The JstVerify JS SDK reads this header and relays the spans to the ingestion API on behalf of the backend.

```python
jstverify_tracing.init(
    api_key="your-sdk-key",
    service_name="my-backend",
    transport="relay",  # No endpoint needed
)
```

**How it works:**

1. Each request collects spans in a per-request buffer (async-safe via `contextvars`).
2. When the response is sent, all spans are base64url-encoded into the `X-JstVerify-Spans` header.
3. The JS SDK decodes the header and merges the spans into its own flush queue.

**Limitations:**

- ~20-30 spans per response max due to the 7500-byte header size limit.
- Only works for request-response flows — async background jobs have no response to carry spans.
- Cross-origin requests require the `Access-Control-Expose-Headers` header (set automatically by the middleware).

## License

MIT