Metadata-Version: 2.4
Name: infinity-observability-sdk
Version: 0.4.3
Summary: Unified observability SDK for Infinity Constellation services — tracing, structured logging, error tracking.
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: opentelemetry-api>=1.39.1
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.39.1
Requires-Dist: opentelemetry-sdk>=1.39.1
Requires-Dist: structlog>=25.1.0
Provides-Extra: django
Requires-Dist: opentelemetry-instrumentation-django>=0.48b0; extra == 'django'
Provides-Extra: logfire
Requires-Dist: logfire>=4.18.0; extra == 'logfire'
Provides-Extra: sentry
Requires-Dist: sentry-sdk>=2.0.0; extra == 'sentry'
Description-Content-Type: text/markdown

# Infinity Observability SDK

[![CI](https://github.com/infinity-constellation/infinity-observability-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/infinity-constellation/infinity-observability-sdk/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/infinity-observability-sdk)](https://pypi.org/project/infinity-observability-sdk/)
[![Python](https://img.shields.io/pypi/pyversions/infinity-observability-sdk)](https://pypi.org/project/infinity-observability-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![basedpyright](https://img.shields.io/badge/type_checker-basedpyright-blue)](https://github.com/DetachHead/basedpyright)

Unified observability for Infinity Constellation services — OTel tracing, structured logging, and optional error tracking in a single `configure_observability()` call.

## Installation

```bash
pip install infinity-observability-sdk
```

Optional extras:

```bash
pip install infinity-observability-sdk[sentry]    # Sentry error tracking
pip install infinity-observability-sdk[logfire]   # pydantic-ai / httpx auto-instrumentation
pip install infinity-observability-sdk[django]    # Django OTel auto-instrumentation
```

## Quick start

```python
from infinity_observability_sdk import configure_observability, ObservabilityConfig

configure_observability(ObservabilityConfig(
    business_unit_name="my_bu",
    service_name="my_service",
))
```

Set the required environment variables before calling `configure_observability`:

| Variable | Description |
|---|---|
| `INFINITY_OBSERVABILITY_ENDPOINT` | OTLP collector endpoint (e.g. `https://collector.example.com`) |
| `INFINITY_OBSERVABILITY_API_KEY` | API key for collector authentication |
| `ENVIRONMENT` | Deployment environment — controls log format. `development` → human-readable console output; anything else → JSON. Defaults to `production`. |

## Configuration

```python
ObservabilityConfig(
    business_unit_name="my_bu",       # required
    service_name="my_service",        # required

    # Integrations
    instrument_pydantic_ai=True,      # default: True
    instrument_httpx=True,            # default: True
    sentry_dsn=None,                  # optional — enables Sentry if set
    sentry_environment=None,          # falls back to ENVIRONMENT env var
    sentry_traces_sample_rate=0.0,    # 0.0–1.0, default: 0.0

    # Logging
    log_level="INFO",                 # DEBUG | INFO | WARNING | ERROR | CRITICAL

    # Advanced
    additional_resource_attributes={},  # extra OTel resource attributes
)
```

`configure_observability()` is idempotent — safe to call multiple times; subsequent calls are no-ops.

## Agent context

Wrap AI agent runs to emit a structured span with cost and identity metadata:

```python
from infinity_observability_sdk import agent_context

with agent_context(
    agent_employee_equivalent="data_engineer",
    hourly_rate=75.0,
    agent_name="my_agent",
    task_description="summarise quarterly report",
    task_instance_identifier="run-abc-123",
    approximate_person_hours=2.0,
    business_unit_name="my_bu",
    service_name="my_service",
) as span:
    # your agent logic here
    ...
```

Extra keyword arguments are forwarded as span attributes.

## Django integration

Call `instrument_django()` **before** the ASGI/WSGI app is loaded — typically at the top of `core/asgi.py`:

```python
from infinity_observability_sdk import configure_observability, ObservabilityConfig, instrument_django

configure_observability(ObservabilityConfig(
    business_unit_name="my_bu",
    service_name="my_api",
))
instrument_django()
```

Requires `infinity-observability-sdk[django]`.

## Standalone logging

For services that only need structured logging without full OTel tracing:

```python
from infinity_observability_sdk import configure_logging

configure_logging(service_name="my_bu.my_service", log_level="INFO")
```

## Development

```bash
uv sync --dev   # install all dependencies
make test       # run tests
make lint       # ruff lint
make ci         # full CI suite (lint, format check, tests, build, package check)
```

## Release cycle

Releases are **fully automated** via [python-semantic-release](https://python-semantic-release.readthedocs.io/) on every merge to `main`. No manual version bumps or GitHub Releases are needed.

### How it works

1. **Every merge to `main`** triggers the release workflow, which first runs the full CI suite (lint, format, tests, build).
2. `python-semantic-release` analyses commit messages since the last release using [Conventional Commits](https://www.conventionalcommits.org/) to determine whether a release is warranted and what the version bump should be.
3. If a release is warranted, it updates the version in `pyproject.toml`, creates a `vX.Y.Z` tag, and publishes a GitHub Release with generated release notes.
4. The GitHub Release triggers the deploy workflow, which builds the package and publishes to PyPI via OIDC (no stored secrets).

### Version bump rules

| Commit type | Example | Bump |
|---|---|---|
| `fix:`, `perf:` | `fix(sdk): handle missing env var` | patch |
| `feat:` | `feat(sdk): add configure_metrics()` | minor |
| `feat!:` or `BREAKING CHANGE:` footer | `feat(sdk)!: remove logfire hard dep` | major |
| `docs:`, `chore:`, `refactor:`, etc. | `docs: update README` | none |

> While the version is `0.x`, breaking changes produce a **minor** bump rather than jumping to `1.0.0`.

### Commit message format

```
<type>(<scope>): <short summary>

[optional body]

[optional footer]
```

All commit messages merged to `main` should follow this format. PR titles are used as the squash-merge commit message and must follow the same convention.
