# SP-OBS: Spinal OpenTelemetry Integration
SP-OBS is Spinal's cost tracking library built on top of open telemetry. It works by adding isolated tracers to libraries that have not been instrumented
and attached a processor to libraries that aloready have been instrumented.
This means we can also play nice with other observability libraries out there.
## Features
- Seamlessly integrates with existing OpenTelemetry setups
- Works with Logfire, vanilla OpenTelemetry, or any OTEL-compatible framework
- Adds user and workflow context to spans for better tracking
- Selective span processing - only sends relevant AI/billing spans
## Installation
```bash
pip install sp-obs
```
### With AI Provider Support
```bash
# For OpenAI support
pip install sp-obs[openai]
# For Anthropic support
pip install sp-obs[anthropic]
# For all providers
pip install sp-obs[all]
```
## Quick Start
### Configuration
First, configure SP-OBS with your endpoint and API key:
```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`
### Instrumenting AI Providers
```python
import sp_obs
# Configure SP-OBS
sp_obs.configure()
# Instrument providers
sp_obs.instrument_openai()
sp_obs.instrument_anthropic()
sp_obs.instrument_httpx()
sp_obs.instrument_requests()
```
### Adding Context to Traces
Use the context manager to add user and workflow information:
```python
import sp_obs
# Add context using context manager
with sp_obs.add_context(
workflow_id="workflow-123",
user_id="user-456",
aggregation_id="session-789" # optional
):
# All spans created here will have this context
response = client.chat.completions.create(...)
```
## 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()
sp_obs.instrument_openai()
@app.post("/generate")
async def generate(user_id: str, workflow_id: str):
with sp_obs.add_context(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.
Raw data
{
"_id": null,
"home_page": null,
"name": "sp-obs",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "ai, llm, monitoring, observability, opentelemetry, tracing",
"author": null,
"author_email": "Andrew van Rensburg <andrew@withspinal.com>",
"download_url": "https://files.pythonhosted.org/packages/5c/bd/e3a0b2ce22144158aa0fed61bbcbad71a400469a3d07e838e4fa96b8b2de/sp_obs-0.1.3.tar.gz",
"platform": null,
"description": "# SP-OBS: Spinal OpenTelemetry Integration\n\nSP-OBS is Spinal's cost tracking library built on top of open telemetry. It works by adding isolated tracers to libraries that have not been instrumented\nand attached a processor to libraries that aloready have been instrumented. \nThis means we can also play nice with other observability libraries out there. \n## Features\n\n- Seamlessly integrates with existing OpenTelemetry setups\n- Works with Logfire, vanilla OpenTelemetry, or any OTEL-compatible framework\n- Adds user and workflow context to spans for better tracking\n- Selective span processing - only sends relevant AI/billing spans\n\n## Installation\n\n```bash\npip install sp-obs\n```\n\n### With AI Provider Support\n\n```bash\n# For OpenAI support\npip install sp-obs[openai]\n\n# For Anthropic support \npip install sp-obs[anthropic]\n\n# For all providers\npip install sp-obs[all]\n```\n\n## Quick Start\n\n### Configuration\n\nFirst, configure SP-OBS with your endpoint and API key:\n\n```python\nimport sp_obs\n\n# Configure globally (recommended)\nsp_obs.configure(\n api_key=\"your-api-key\"\n # endpoint defaults to \"https://cloud.withspinal.com\" if not specified\n)\n```\n\nOr use environment variables:\n- `SPINAL_TRACING_ENDPOINT` (defaults to \"https://cloud.withspinal.com\")\n- `SPINAL_API_KEY`\n\n### Instrumenting AI Providers\n\n```python\nimport sp_obs\n\n# Configure SP-OBS\nsp_obs.configure()\n\n# Instrument providers\nsp_obs.instrument_openai()\nsp_obs.instrument_anthropic()\nsp_obs.instrument_httpx()\nsp_obs.instrument_requests()\n```\n\n### Adding Context to Traces\n\nUse the context manager to add user and workflow information:\n\n```python\nimport sp_obs\n\n# Add context using context manager\nwith sp_obs.add_context(\n workflow_id=\"workflow-123\",\n user_id=\"user-456\",\n aggregation_id=\"session-789\" # optional\n):\n # All spans created here will have this context\n response = client.chat.completions.create(...)\n```\n\n## Configuration Options\n\n### Environment Variables\n\n- `SPINAL_TRACING_ENDPOINT`: HTTP endpoint to send spans to (default: \"https://cloud.withspinal.com\")\n- `SPINAL_API_KEY`: API key for authentication\n- `SPINAL_PROCESS_MAX_QUEUE_SIZE`: Max spans in queue (default: 2048)\n- `SPINAL_PROCESS_SCHEDULE_DELAY`: Export delay in ms (default: 5000)\n- `SPINAL_PROCESS_MAX_EXPORT_BATCH_SIZE`: Batch size (default: 512)\n- `SPINAL_PROCESS_EXPORT_TIMEOUT`: Export timeout in ms (default: 30000)\n\n### Advanced Configuration\n\n```python\nsp_obs.configure(\n api_key=\"your-api-key\",\n endpoint=\"https://cloud.withspinal.com\", # Optional - this is the default\n headers={\"Custom-Header\": \"value\"},\n timeout=5,\n max_queue_size=2048,\n max_export_batch_size=512,\n schedule_delay_millis=5000,\n export_timeout_millis=30000,\n scrubber=my_custom_scrubber # Optional\n)\n```\n\n## Data Scrubbing\n\nSP-OBS includes automatic scrubbing of sensitive data:\n\n```python\nfrom sp_obs import DefaultScrubber, NoOpScrubber\n\n# Use default scrubber (redacts tokens, keys, passwords)\nsp_obs.configure(scrubber=DefaultScrubber())\n\n# Or disable scrubbing\nsp_obs.configure(scrubber=NoOpScrubber())\n\n# Or implement custom scrubbing\nclass MyCustomScrubber:\n def scrub_attributes(self, attributes: dict) -> dict:\n # Your scrubbing logic\n return attributes\n\nsp_obs.configure(scrubber=MyCustomScrubber())\n```\n\n## Performance Considerations\n\nSP-OBS uses a BatchSpanProcessor to minimize performance impact:\n\n- Spans are batched and sent asynchronously in a background thread\n- Default batch size: 512 spans\n- Default flush interval: 5 seconds\n- Spans are dropped if queue exceeds max size (default: 2048)\n\nTo tune for high-volume applications:\n\n```python\nsp_obs.configure(\n max_queue_size=5000, # Increase queue size\n max_export_batch_size=1000, # Larger batches\n schedule_delay_millis=2000 # More frequent exports\n)\n```\n\n## What Spans Are Captured?\n\nSP-OBS automatically captures:\n- AI/LLM spans (identified by `gen_ai.system` attribute)\n- HTTPX and request spans\n- Explicitly created billing event spans\n- Spans with attached user/workflow context\n\nAll other spans are ignored to minimize overhead and data transfer.\n\n## Integration Examples\n\n### FastAPI Application\n\n```python\nfrom fastapi import FastAPI\nimport sp_obs\nfrom openai import AsyncOpenAI\n\napp = FastAPI()\nclient = AsyncOpenAI()\n\n# Configure on startup\n@app.on_event(\"startup\")\nasync def startup():\n sp_obs.configure()\n sp_obs.instrument_openai()\n\n@app.post(\"/generate\")\nasync def generate(user_id: str, workflow_id: str):\n with sp_obs.add_context(user_id=user_id, workflow_id=workflow_id):\n response = await client.chat.completions.create(\n model=\"gpt-4\",\n messages=[{\"role\": \"user\", \"content\": \"Hello\"}]\n )\n return response\n```\n\n## License\n\nMIT License - see LICENSE file for details.",
"bugtrack_url": null,
"license": null,
"summary": "Observability integration with Spinal",
"version": "0.1.3",
"project_urls": {
"Homepage": "https://github.com/withspinal/sp-obs",
"Issues": "https://github.com/withspinal/sp-obs/issues"
},
"split_keywords": [
"ai",
" llm",
" monitoring",
" observability",
" opentelemetry",
" tracing"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ac1ddd82c46aa1a390e89d9ee98ff88921cea23d6e21b029b43b108c7b200502",
"md5": "72e1d3c85555090e2de734b044bab925",
"sha256": "001f363a195ec695a367f976a3f0dcf5660e6cf9998078d46f6bd61007f9db0f"
},
"downloads": -1,
"filename": "sp_obs-0.1.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "72e1d3c85555090e2de734b044bab925",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 17182,
"upload_time": "2025-07-28T09:33:31",
"upload_time_iso_8601": "2025-07-28T09:33:31.351763Z",
"url": "https://files.pythonhosted.org/packages/ac/1d/dd82c46aa1a390e89d9ee98ff88921cea23d6e21b029b43b108c7b200502/sp_obs-0.1.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5cbde3a0b2ce22144158aa0fed61bbcbad71a400469a3d07e838e4fa96b8b2de",
"md5": "8d858120b6f7830e21ee283e3846892d",
"sha256": "64073311e5e3a1afec4b4e899cd5651b9276855739b19186268072f47f495cd8"
},
"downloads": -1,
"filename": "sp_obs-0.1.3.tar.gz",
"has_sig": false,
"md5_digest": "8d858120b6f7830e21ee283e3846892d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 58627,
"upload_time": "2025-07-28T09:33:33",
"upload_time_iso_8601": "2025-07-28T09:33:33.379077Z",
"url": "https://files.pythonhosted.org/packages/5c/bd/e3a0b2ce22144158aa0fed61bbcbad71a400469a3d07e838e4fa96b8b2de/sp_obs-0.1.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-28 09:33:33",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "withspinal",
"github_project": "sp-obs",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "sp-obs"
}