Metadata-Version: 2.5
Name: dlogify
Version: 0.1.0
Summary: Send Python logs and uncaught exceptions to dlogify.
Project-URL: Homepage, https://dlogify.com
Project-URL: Documentation, https://docs.dlogify.com/send-logs/python
Author: Logify
License-Expression: MIT
License-File: LICENSE
Keywords: dlogify,errors,exceptions,logging,opentelemetry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Logging
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# dlogify

Send your Python logs and uncaught exceptions to [Logify](https://dlogify.com), which groups errors, classifies them with AI and delivers tickets with the full context.

Requires Python 3.10 or later. No dependencies.

## Install

```bash
pip install dlogify
export LOGIFY_API_KEY=lgf_live_…
```

## With the SDK

```python
import logging

import dlogify

dlogify.init(service="billing")
logging.basicConfig(level=logging.INFO)
```

`init()` sends every record that passes your `logging` configuration, and every uncaught exception (main thread and other threads). It changes no logger's level and adds no handler, so `logging.basicConfig()` works before or after it and your console output stays as it was. Python's root logger shows `WARNING` and above unless you configure it, as above.

```python
log = logging.getLogger("billing")

try:
    charge(order_id)
except PaymentError:
    log.exception("charge failed", extra={"order_id": order_id})
```

Keys passed with `extra=` become attributes; `log.exception()` (or `exc_info=True`) attaches the traceback.

You can also log directly:

```python
dlogify.info("order placed", attributes={"order_id": order_id})
dlogify.error("charge failed", exc=err, attributes={"order_id": order_id})
dlogify.log("warning", "disk almost full")  # any level name, any case
dlogify.capture_exception()  # inside an except block
dlogify.flush()              # before os._exit()
```

`log()` takes `trace`, `debug`, `info`, `warn`, `error` and `fatal`, and also Python's `warning` and `critical`, in any case; an unknown name logs at `info`.

- `init(capture_logging=False)` or `init(capture_uncaught=False)` turns a capture off.
- Calls before `init()` are dropped, and a second `init()` is ignored.
- `dlogify.Client(**options)` creates an independent client.
- `dictConfig` users can name the handler instead: `{"class": "dlogify.LogifyHandler"}` with `init(capture_logging=False)`; it sends through the `init()` client. With the capture left on, each record is still sent once.
- `structlog` and `loguru` can write to the standard `logging` module, which dlogify captures.

## Any process: logify run

```bash
LOGIFY_API_KEY=lgf_live_… pipx run dlogify run -- python manage.py runserver
```

With the package installed, the command is `logify run -- <cmd>`. The output appears in your terminal exactly as without dlogify, and the exit code is the command's. Python tracebacks, stack traces from other languages and Go panics become single events; JSON lines are read as structured records. A non-zero exit or a death by signal is reported as one more error, except a `SIGPIPE` after the reader of the output went away, as in `| head`. Not available on Windows.

## Configuration

Options passed to `init()` win over environment variables, which win over the defaults.

| Variable | Option | Default |
|---|---|---|
| `LOGIFY_API_KEY` | `api_key` | Required; without it the client is disabled. |
| `LOGIFY_ENDPOINT` | `endpoint` | `https://api.dlogify.com` |
| `LOGIFY_SERVICE` | `service` | `OTEL_SERVICE_NAME`, then the `[project] name` of the nearest `pyproject.toml` from the working directory, then `unknown_service` |
| `LOGIFY_ENVIRONMENT` | `environment` | `default` |
| `LOGIFY_RELEASE` | `release` | empty |
| `LOGIFY_MIN_LEVEL` | `min_level` | `info` (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) |
| `LOGIFY_DISABLED` | `disabled` | off (`1` or `true` turns the client into a no-op) |
| `LOGIFY_DEBUG` | `debug` | off (`1` or `true` prints the client's diagnostics to stderr) |

Also `attributes` (added to every record) and `max_queue_size` (default 2000). `disabled`, `debug`, `capture_logging` and `capture_uncaught` take `True` or `False`; any other value is ignored with a warning.

## Guarantees

- No call raises, and logging never waits for the network: records are sent by a background daemon thread, in compressed batches, with retries for up to 5 minutes.
- If the key is rejected, the client stops for the rest of the process with one warning. Redirects are not followed: one warning names the URL to put in `LOGIFY_ENDPOINT`.
- If the endpoint's TLS certificate cannot be verified, one warning says how to fix it: with Python from python.org on macOS, run `Install Certificates.command`; behind a proxy that inspects TLS, set `SSL_CERT_FILE` to its CA bundle.
- After an uncaught exception the client sends what is queued, then Python reports the exception as usual. At normal exit it flushes too. An exit spends at most 2 seconds on this in total. `os._exit()` skips both, so call `dlogify.flush()` first.
- Works with pre-fork servers such as gunicorn, and with `multiprocessing` children, which flush when their target returns. `Pool.terminate()` (also run when a `with Pool()` block ends) stops workers before they flush: call `pool.close()` and `pool.join()` instead. Other worker pools that end with `os._exit()` should call `dlogify.flush()` first.
- The client's own messages go to stderr, never through `logging`.

## What is sent

Only log records: message, level, time, logger name, the attributes you pass, the exception's type, message and traceback, and the service name, environment, release, host name and SDK version. The SDK makes no other network calls and opens no ports. Personal data is redacted by Logify on arrival.

Documentation: https://docs.dlogify.com/send-logs/python
