> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peeng.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Peeng Python SDK — Batched Log Delivery for Python 3.8+

> Official Peeng Python SDK — thread-safe, non-blocking log delivery with automatic batching, retries, and idempotency. Requires Python 3.8+ and requests.

The official Peeng Python SDK (`pip install peeng`) is a thread-safe client that delivers logs to Peeng in the background via a daemon thread. Every log call enqueues the entry and returns immediately — the SDK handles batching, retries, and idempotency automatically — so logging never blocks your application or crashes it when Peeng is unavailable.

<Info>
  MIT licensed. Requires Python 3.8+. The only runtime dependency is
  `requests`.
</Info>

## Install

<CodeGroup>
  ```bash pip theme={null}
  pip install peeng
  ```

  ```bash pip3 theme={null}
  pip3 install peeng
  ```
</CodeGroup>

## Quick start

Construct a client, call any logging method, and call `close()` when you're done:

```python theme={null}
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
```

<Note>
  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.
</Note>

## Why use the SDK

<CardGroup cols={2}>
  <Card title="Non-blocking" icon="layer-group">
    Log calls enqueue onto an in-memory buffer and return immediately. A
    background daemon thread flushes it — every `flush_interval` seconds,
    or as soon as the buffer hits `max_batch_size`.
  </Card>

  <Card title="Automatic retries" icon="rotate">
    Network errors, `429`s, and `5xx`s are retried with exponential backoff
    and jitter, reusing one `Idempotency-Key` per batch so retries can't
    double-insert.
  </Card>

  <Card title="Never crashes your app" icon="shield">
    Delivery failures are reported through `on_error` (or written to
    `stderr`) instead of raising out of a logging call.
  </Card>

  <Card title="Thread-safe" icon="lock">
    Safe to call from multiple threads simultaneously — the internal buffer
    is protected by a lock.
  </Card>
</CardGroup>

## Constructor parameters

Pass keyword arguments to `PeengClient(...)` to configure the client.

| Parameter         | Type                          | Default                 | Description                                                           |
| ----------------- | ----------------------------- | ----------------------- | --------------------------------------------------------------------- |
| `api_key`         | `str`                         | required                | Your Peeng project API key.                                           |
| `base_url`        | `str`                         | `http://localhost:3000` | Peeng API base URL — set to `https://api.peeng.dev` for real traffic. |
| `environment`     | `str`                         | `None`                  | Default `environment` for calls that don't override it.               |
| `service`         | `str`                         | `None`                  | Default `service` for calls that don't override it.                   |
| `max_batch_size`  | `int`                         | `100`                   | Flush as soon as the buffer hits this size. Hard-capped at `500`.     |
| `flush_interval`  | `float`                       | `2.0`                   | Seconds between background flushes.                                   |
| `max_retries`     | `int`                         | `3`                     | Retry attempts before giving up on a batch.                           |
| `on_error`        | `Callable[[Exception], None]` | writes to stderr        | Called with delivery failures instead of raising.                     |
| `request_timeout` | `float`                       | `10.0`                  | Per-request timeout in seconds.                                       |

## Logging methods

The SDK exposes one convenience method per log level — `debug`, `info`, `warn`, `error`, and `fatal` — plus a generic `log()` form for dynamic level selection:

```python theme={null}
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 (100–599). The following keyword arguments are optional: `service`, `metadata` (any JSON-serializable `dict`), `stack_trace`, `hostname`, `request_id`, `user_id`, and `timestamp` (a `datetime` object or an ISO-8601 string). See the [field reference](/api-reference/logs/ingest) for what each one means.

## Context manager

Use `PeengClient` as a context manager to have `close()` called automatically when the block exits:

```python theme={null}
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() called automatically on exit
```

## Graceful shutdown

Call `flush()` to block until currently buffered logs are sent, and `close()` to stop the background thread and flush any remaining logs:

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

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

<Note>
  Call `close()` (or use the context manager) before your process exits for a
  deterministic delivery guarantee — the `atexit` hook is a safety net, not a
  replacement for explicit shutdown in long-running services.
</Note>

## Custom batching configuration

Override the default batching and retry behaviour at construction time:

```python theme={null}
client = PeengClient(
    api_key="pk_test_xxx",
    base_url="https://api.peeng.dev",
    environment="production",
    service="checkout-api",
    max_batch_size=250,
    flush_interval=5.0,
    max_retries=5,
)
```

## Error handling

Supply an `on_error` callback to receive delivery failures without letting them surface as exceptions in your application code:

```python theme={null}
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,
)
```

Your `on_error` handler receives one of two exception types:

* **`peeng.PeengApiError`** — the server responded with an HTTP error envelope. Carries `status_code`, `message`, and `error` (from the server envelope). Raised for non-retryable errors (e.g. `400`) or after retries are exhausted (e.g. repeated `5xx`).
* **`requests` exception** — a network-level failure with no HTTP response at all (DNS failure, connection refused), surfaced after retries are exhausted.

**Retry behaviour:** `429`, `5xx`, and network/connection errors are retried up to `max_retries` times with exponential backoff and jitter, reusing the same `Idempotency-Key` on every retry of a given batch. Any other `4xx` (e.g. a `400` validation error) is non-retryable and reported to `on_error` immediately.

See [Errors](/essentials/errors) for the full retryable/non-retryable rules.
