Metadata-Version: 2.5
Name: idelop_logging
Version: 0.2.1
Summary: Centralized structured JSON logging package for Idelop projects
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# idelop_logging

پکیج متمرکز و یکپارچه‌ی **Structured JSON Logging** برای تمام پروژه‌های سازمان Idelop.

هدف: انتشار لاگ‌های ساختاریافته تک‌خطی به صورت JSON صرفاً بر روی **stdout** جهت جمع‌آوری خودکار توسط Grafana Alloy و انتقال به Grafana Loki. این پکیج کاملاً **Zero External Dependencies** بوده و صرفاً با کتابخانه استاندارد پایتون پیاده‌سازی شده است.

---

## ویژگی‌های نسخه v0.2.1

- ✅ **خروجی استاندارد JSON**: خروجی استاندارد و تک‌خطی روی `stdout` مطابق با اسکیما قفل‌شده‌ی سازمان.
- ✅ **صفر وابستگی خارجی (stdlib-only)**: بدون نیاز به کتابخانه‌های سنگین خارجی یا درایورهای شخص ثالث.
- ✅ **مدیریت کانتکست با `contextvars`**: انتشار خودکار فیلدهای سطح ریکوئست/تسک (مانند `request_id`, `user_id`) از طریق `bind_context` و `clear_context`.
- ✅ **استثناهای ساختاریافته (Structured Exceptions)**: تفکیک خطاهای پایتون به آبجکت `{type, message, stacktrace}` جهت کوئری‌های سریع و دقیق در Loki/Grafana به جای استک‌تریس‌های متنی خام.
- ✅ **الگوی Enricher (اصل Open/Closed)**: امکان افزودن متادیتاهای اختصاصی هر پروژه با `register_enricher` بدون تغییر در کدهای core پکیج.
- ✅ **ایمنی در برابر خطا**: بروز خطا در یک Enricher هرگز برنامه مصرف‌کننده را متوقف نمی‌کند و لاگ را از بین نمی‌برد.

---

## ساختار اسکیمای JSON

نمونه لاگ تولید شده:

```json
{
  "timestamp": "2026-09-03T10:30:00.123Z",
  "level": "ERROR",
  "action": "http_request_failed",
  "service": "flight-api",
  "environment": "production",
  "module": "booking",
  "message": "Comment added successfully",
  "trace_id": null,
  "span_id": null,
  "exception": {
    "type": "KeyError",
    "message": "'key'",
    "stacktrace": "Traceback (most recent call last):\n  ..."
  },
  "extra": {
    "request_id": "abc-123",
    "user_id": 1422,
    "status_code": 500
  }
}
```

---

## نحوه نصب

با ابزار `uv`:

```bash
uv add idelop-logging
```

---

## راهنمای استفاده

### ۱. مقداردهی اولیه Logger

سه پارامتر `service_name`، `environment` و `module` اجباری (keyword-only) هستند و مقدار پیش‌فرض ندارند:

```python
from idelop_logging import get_logger

logger = get_logger(
    service_name="flight-api",
    environment="production",
    module="booking",
)
```

### ۲. لاگ کردن رویدادها (Actions)

آرگومان اول همیشه نام `action` است. فیلدهای جانبی از طریق دیکشنری `extra` ارسال می‌شوند. فیلد اختیاری `message` را نیز می‌توانید در صورت نیاز درون `extra` قرار دهید (به صورت خودکار به سطح ریشه لاگ منتقل می‌شود):

```python
# لاگ ساده
logger.info("order_created", extra={"order_id": 1001, "total": 120.5})

# همراه با فیلد اختیاری message
logger.info(
    "booking_confirmed",
    extra={"message": "Seat confirmed", "user_id": 1422, "seat": "12B"},
)
```

### ۳. مدیریت Context (مانند Request ID در Middleware)

```python
from idelop_logging import bind_context, clear_context

def handle_request(request):
    # مقادیر مرتبط با ریکوئست جاری را بایند کنید
    bind_context(request_id=request.headers.get("X-Request-ID"), user_id=request.user.id)
    try:
        logger.info("request_started")
        process(request)
        logger.info("request_finished")
    finally:
        # حتماً در بلوک finally کانتکست را پاک کنید تا به ریکوئست‌های بعدی نشت نکند
        clear_context()
```

### ۴. ثبت استثناها (Structured Exceptions)

```python
try:
    raise ValueError("Invalid currency code")
except ValueError:
    logger.exception("payment_processing_failed", extra={"merchant_id": "m-123"})
```

### ۵. فیلتر کردن مقادیر None با `drop_none`

برای ساختن یک دیکشنری `extra` تمیز بدون مقادیر `None`:

```python
from idelop_logging import drop_none

logger.info(
    "user_updated",
    extra=drop_none(
        user_id=1422,
        email="user@example.com",
        phone_number=None,  # حذف می‌شود
    ),
)
```

### ۶. افزودن Enricher اختصاصی پروژه

```python
from typing import Any
from idelop_logging import LogEnricher, register_enricher

class RegionEnricher(LogEnricher):
    def enrich(self, record: dict[str, Any]) -> dict[str, Any]:
        record["extra"]["cluster_region"] = "eu-west-1"
        return record

register_enricher(RegionEnricher())
```

> **ترتیب تقدم اولویت‌ها در صورت تداخل کلیدها:**
> `Core Context` -> `Context Filter (bind_context)` -> `Registered Enrichers` -> `Call-site extra`
> مقادیری که مستقیماً در محل فراخوانی (`extra=...`) پاس داده شوند، بالاترین اولویت را دارند و مقادیر Enricher یا Context را بازنویسی می‌کنند.

---

## استفاده در محیط‌های چندرشته‌ای (ThreadPoolExecutor)

در پایتون، مقدار `contextvars` به صورت خودکار به ترد‌های جدید ارسال نمی‌شود. در صورت استفاده از `ThreadPoolExecutor`، کانتکست را به شکل صریح کپی و اجرا کنید:

```python
import contextvars

ctx = contextvars.copy_context()
executor.submit(ctx.run, worker_function, *args)
```

---

## توسعه و تست

```bash
uv sync               # همگام‌سازی محیط مجازی
uv run pytest         # اجرای کل تست‌ها
uv run ruff check .   # اجرای لینتر
```
