Metadata-Version: 2.4
Name: peeng
Version: 0.1.0
Summary: Official Python SDK for Peeng, an AI-powered observability platform
Project-URL: Homepage, https://peeng.dev
Project-URL: Documentation, https://docs.peeng.dev
Project-URL: Twitter, https://x.com/peengtech
Author-email: Peeng <hello@peeng.dev>
License-Expression: MIT
License-File: LICENSE
Keywords: logging,logs,observability,peeng,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: requests<3,>=2.25
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.23; extra == 'dev'
Requires-Dist: types-requests; extra == 'dev'
Description-Content-Type: text/markdown

# peeng

Official Python SDK for [Peeng](https://peeng.dev), an AI-powered observability
platform. This package provides `PeengClient`, a lightweight, thread-safe
client for shipping logs to the Peeng log ingestion API.

- Batches log calls in the background — `client.info(...)` returns
  immediately, never blocking on network I/O.
- Automatically retries transient failures (network errors, 429s, 5xx) with
  exponential backoff + jitter, reusing the same idempotency key so retries
  can't cause duplicate log entries server-side.
- Never raises out of a logging call and never crashes the host app —
  delivery failures are reported through an `on_error` callback instead.

## Install

```bash
pip install peeng
```

Requires Python 3.8+. The only runtime dependency is `requests`.

## Quick start

```python
from peeng import PeengClient

client = PeengClient(api_key="pk_test_xxx", environment="production")

client.info("user signed up", status_code=200, service="auth-api")
client.error(
    "payment failed",
    status_code=500,
    service="billing-api",
    metadata={"order_id": "ord_123"},
)

client.close()  # flush remaining logs and stop the background thread
```

If you never call `close()` explicitly, it still runs automatically at
process exit (via `atexit`), but calling it yourself gives you a
deterministic shutdown point.

## Convenience methods

One method per log level — `debug`, `info`, `warn`, `error`, `fatal` — plus a
generic `log(level, message, **opts)` for anything else:

```python
from peeng import PeengClient, LogLevel

client = PeengClient(api_key="pk_test_xxx", environment="production")

client.debug("cache miss", status_code=200)
client.info("request completed", status_code=200, request_id="req_abc")
client.warn("slow query", status_code=200, metadata={"duration_ms": 1400})
client.error("unhandled exception", status_code=500, stack_trace="Traceback ...")
client.fatal("out of memory", status_code=500, hostname="worker-3")

# equivalent generic form
client.log(LogLevel.INFO, "request completed", status_code=200)
```

`status_code` is required on every call (it models the outcome of whatever
operation the log is about — e.g. `200` for a routine success log, `500` for
an unhandled error). `environment` defaults to whatever was passed to
`PeengClient(...)`, but can be overridden per call. Other optional fields:
`service`, `metadata` (any JSON-serializable dict), `stack_trace`, `hostname`,
`request_id`, `user_id`, `timestamp` (a `datetime` or a pre-formatted
ISO-8601 string — omit it to let the server stamp receipt time).

## Context manager usage

```python
from peeng import PeengClient

with PeengClient(api_key="pk_test_xxx", environment="production") as client:
    client.info("service started", status_code=200)
    # ... application code ...
# client.close() is called automatically on exit, flushing buffered logs
```

## Custom batching configuration

```python
client = PeengClient(
    api_key="pk_test_xxx",
    base_url="https://api.peeng.dev",
    environment="production",
    service="checkout-api",       # default `service` for calls that don't override it
    max_batch_size=250,           # flush as soon as the buffer hits this size (hard-capped at 500)
    flush_interval=5.0,           # otherwise flush every 5 seconds
    max_retries=5,                # retry attempts for transient failures before giving up
)
```

## Handling delivery failures

By default, a failed flush (after retries are exhausted, or immediately for
a non-retryable 4xx) is written to stderr. Pass `on_error` to handle it
yourself instead — e.g. to feed your own metrics/alerting:

```python
def handle_delivery_failure(exc: Exception) -> None:
    print(f"failed to ship logs to Peeng: {exc}")

client = PeengClient(
    api_key="pk_test_xxx",
    environment="production",
    on_error=handle_delivery_failure,
)
```

`on_error` receives either a `peeng.PeengApiError` (carrying `status_code`,
`message`, and `error` from the server's error envelope) for HTTP failures,
or the underlying `requests` exception for network-level failures.

**Retry behavior**: a 429 or 5xx response, or a network/connection error, is
retried up to `max_retries` times with exponential backoff and jitter,
reusing the same `Idempotency-Key` on every retry of a given batch. A 4xx
response other than 429 (e.g. a 400 validation error) is treated as
non-retryable and reported to `on_error` immediately — retrying a malformed
batch would never succeed, so the SDK doesn't waste retries on it.

## Forcing a flush / graceful shutdown

```python
client.flush()             # block until currently buffered logs are sent
client.flush(timeout=2.0)  # give it up to 2 seconds, then return regardless

client.close()             # stop the background thread and flush remaining logs
```

Call `close()` (or use the context manager) before your process exits if you
want a deterministic guarantee that buffered logs were flushed — the
`atexit` hook is a safety net, not a replacement for explicit shutdown in
long-running services.

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

pytest
mypy src/peeng
```

## License

MIT
