Metadata-Version: 2.4
Name: sp-obs
Version: 0.2.4
Summary: Observability integration with Spinal
Project-URL: Homepage, https://github.com/withspinal/sp-obs
Project-URL: Issues, https://github.com/withspinal/sp-obs/issues
Author-email: Andrew van Rensburg <andrew@withspinal.com>
License-Expression: MIT
License-File: LICENSE
Keywords: ai,llm,monitoring,observability,opentelemetry,spinal,tracing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.11
Requires-Dist: httpx<1.0.0,>=0.28.0
Requires-Dist: opentelemetry-api<2,>=1.35.0
Requires-Dist: opentelemetry-instrumentation-httpx<1,>=0.57b0
Requires-Dist: opentelemetry-instrumentation-requests<1,>=0.57b0
Requires-Dist: opentelemetry-sdk<2,>=1.35.0
Requires-Dist: opentelemetry-semantic-conventions-ai>=0.4.12
Requires-Dist: orjson<4,>=3.11.1
Description-Content-Type: text/markdown

# SP-OBS: Spinal OpenTelemetry Integration

SP-OBS is Spinal's cost tracking library built on top of OpenTelemetry. It works by automatically instrumenting HTTP libraries (httpx, requests) and attaching a processor to existing OpenTelemetry setups. This dual approach allows it to integrate seamlessly with existing observability frameworks while selectively forwarding AI/LLM operations and billing events to Spinal's platform.

## Features

- Seamlessly integrates with existing OpenTelemetry setups
- Works with Logfire, vanilla OpenTelemetry, or any OTEL-compatible framework
- Automatic instrumentation of httpx and requests libraries
- Adds user and workflow context to spans for better tracking
- Selective span processing - only sends relevant AI/billing spans
- Built-in data scrubbing for sensitive information

## Installation

```bash
pip install sp-obs
```

## Quick Start

### Configuration

Configure SP-OBS with your endpoint and API key. Instrumentation happens automatically when you call `configure()`:

```python
import sp_obs

# Configure globally (recommended)
sp_obs.configure(
    api_key="your-api-key"
    # endpoint defaults to "https://cloud.withspinal.com" if not specified
)
```

Or use environment variables:
- `SPINAL_TRACING_ENDPOINT` (defaults to "https://cloud.withspinal.com")
- `SPINAL_API_KEY`

That's it! SP-OBS will automatically instrument httpx and requests to capture AI/LLM operations and HTTP requests.

### Adding Tags to Traces

Use the `tag` class to add user, workflow, and custom information to traces:

```python
import sp_obs

# As a context manager
with sp_obs.tag(
    workflow_id="workflow-123",
    user_id="user-456",
    aggregation_id="session-789",  # optional, reserved keyword
    custom_field="value",          # any additional tags
    environment="production"
):
    # All spans created here will have these tags
    response = client.chat.completions.create(...)

# As a function call (applies tags to current context)
sp_obs.tag(
    workflow_id="workflow-123", 
    user_id="user-456",
    custom_metadata="example"
)
```

**Note**: Only `aggregation_id` is a reserved keyword parameter. All other keyword arguments are added as custom tags with the `spinal.` prefix.

## Configuration Options

### Environment Variables

- `SPINAL_TRACING_ENDPOINT`: HTTP endpoint to send spans to (default: "https://cloud.withspinal.com")
- `SPINAL_API_KEY`: API key for authentication
- `SPINAL_PROCESS_MAX_QUEUE_SIZE`: Max spans in queue (default: 2048)
- `SPINAL_PROCESS_SCHEDULE_DELAY`: Export delay in ms (default: 5000)
- `SPINAL_PROCESS_MAX_EXPORT_BATCH_SIZE`: Batch size (default: 512)
- `SPINAL_PROCESS_EXPORT_TIMEOUT`: Export timeout in ms (default: 30000)

### Advanced Configuration

```python
sp_obs.configure(
    api_key="your-api-key",
    endpoint="https://cloud.withspinal.com",  # Optional - this is the default
    headers={"Custom-Header": "value"},
    timeout=5,
    max_queue_size=2048,
    max_export_batch_size=512,
    schedule_delay_millis=5000,
    export_timeout_millis=30000,
    scrubber=my_custom_scrubber  # Optional
)
```

## Data Scrubbing

SP-OBS includes automatic scrubbing of sensitive data:

```python
from sp_obs import DefaultScrubber, NoOpScrubber

# Use default scrubber (redacts tokens, keys, passwords)
sp_obs.configure(scrubber=DefaultScrubber())

# Or disable scrubbing
sp_obs.configure(scrubber=NoOpScrubber())

# Or implement custom scrubbing
class MyCustomScrubber:
    def scrub_attributes(self, attributes: dict) -> dict:
        # Your scrubbing logic
        return attributes

sp_obs.configure(scrubber=MyCustomScrubber())
```

## Performance Considerations

SP-OBS uses a BatchSpanProcessor to minimize performance impact:

- Spans are batched and sent asynchronously in a background thread
- Default batch size: 512 spans
- Default flush interval: 5 seconds
- Spans are dropped if queue exceeds max size (default: 2048)

To tune for high-volume applications:

```python
sp_obs.configure(
    max_queue_size=5000,          # Increase queue size
    max_export_batch_size=1000,   # Larger batches
    schedule_delay_millis=2000    # More frequent exports
)
```

## What Spans Are Captured?

SP-OBS automatically captures:
- AI/LLM spans (identified by `gen_ai.system` attribute)
- HTTPX and request spans
- Explicitly created billing event spans
- Spans with attached user/workflow context

All other spans are ignored to minimize overhead and data transfer.

## Integration Examples

### FastAPI Application

```python
from fastapi import FastAPI
import sp_obs
from openai import AsyncOpenAI

app = FastAPI()
client = AsyncOpenAI()

# Configure on startup
@app.on_event("startup")
async def startup():
    sp_obs.configure()

@app.post("/generate")
async def generate(user_id: str, workflow_id: str):
    with sp_obs.tag(user_id=user_id, workflow_id=workflow_id):
        response = await client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": "Hello"}]
        )
        return response
```

## License

MIT License - see LICENSE file for details.