sap-fastapi-logger 0.1.2
Async-safe structured JSON logging for FastAPI on SAP BTP
Requires Python: >=3.8

# SAP FastAPI Logger

**Async-safe, structured JSON logging for FastAPI applications running on SAP BTP (Business Technology Platform).**

This library solves the critical "Context Leakage" issue common when using standard logging libraries with ASGI frameworks like FastAPI. It ensures that **Correlation IDs** are correctly propagated across asynchronous requests and background tasks, making logs traceable in SAP Cloud Logging (Kibana).

---

## 🚨 The Problem

Standard Python logging libraries (including `sap-cf-logging`) rely on `threading.local` to store request context.

* **In Flask (WSGI):** One request = One thread. This works fine.
* **In FastAPI (ASGI):** One thread handles *multiple* concurrent requests via an event loop.

**The Consequence:** Using standard logging in FastAPI causes **Context Leakage**.

1. Logs from User A appear with User B's Correlation ID.
2. Correlation IDs are lost ("null") when `await` is called.
3. Background tasks lose context entirely.

## ✅ The Solution

`sap-fastapi-logger` replaces thread-local storage with Python's native **`contextvars`**. This attaches context to the **logical task** rather than the thread, ensuring 100% async safety.

### Features

* 🚀 **Zero Context Leakage:** Uses `contextvars` for safe async context propagation.
* 📊 **SAP BTP Compatible:** Outputs strict JSON format required by SAP Cloud Logging / Kibana (`msg`, `written_at`, `correlation_id`, `level`).
* 🔗 **Auto-Correlation:** Automatically extracts `X-CorrelationID` from headers or generates a UUID.
* ⚡ **Background Task Support:** Includes a drop-in replacement for FastAPI's `BackgroundTasks` that preserves context.

---

## 📦 Installation

```bash
pip install sap-fastapi-logger

```

---

## 🚀 Quick Start

### 1. Basic Setup (in `server.py` or `main.py`)

You only need to do two things: initialize the logger and add the middleware.

```python
import logging
from fastapi import FastAPI
from sap_fastapi_logger import setup_logging, CorrelationMiddleware

# 1. Initialize the JSON Logger (Run this before creating the app)
setup_logging()

app = FastAPI()

# 2. Add the Middleware to handle Correlation IDs
app.add_middleware(CorrelationMiddleware)

@app.get("/")
async def root():
    # Usage: Just use the standard python logger!
    logging.info("Processing request...") 
    return {"message": "Hello World"}

```

**Output in Console (and Kibana):**

```json
{"level": "INFO", "msg": "Processing request...", "logger": "root", "correlation_id": "a1b2-c3d4-e5f6...", "written_at": "2025-01-01 12:00:00,000"}

```

---

## ⚡ Handling Background Tasks

Standard `BackgroundTasks` in FastAPI will **lose** the correlation ID because the middleware finishes and resets the context before the background task runs.

Use `ContextAwareBackgroundTasks` provided by this library. It automatically captures the ID from the request and injects it into the background thread.

### Example

```python
from fastapi import FastAPI
import logging
# Import the custom class
from sap_fastapi_logger import ContextAwareBackgroundTasks 

app = FastAPI()

def long_running_process(contract_id: str):
    # This log will HAVE the correct correlation ID
    logging.info(f"Processing contract {contract_id} in background...")

@app.post("/extract")
def extract_data(
    # Type hint using the custom class
    background_tasks: ContextAwareBackgroundTasks 
):
    logging.info("Request received. Starting background task.")
    
    # Use exactly like standard FastAPI BackgroundTasks
    background_tasks.add_task(long_running_process, contract_id="999")
    
    return {"message": "Processing started"}

```

---

## 🛠 Advanced Usage

### Accessing the Correlation ID Manually

If you need to get the current Correlation ID string (e.g., to pass to a database query or an external API header), you can access the context variable directly.

```python
from sap_fastapi_logger import correlation_id_ctx

@app.get("/debug")
def debug():
    current_id = correlation_id_ctx.get()
    return {"my_id": current_id}

```

### How it works (Architecture)

1. **Middleware:** Intercepts request  Generates/Extracts ID  Sets `ContextVar`.
2. **Formatter:** `logging` calls Formatter  Formatter reads `ContextVar`  Injects into JSON.
3. **Background Tasks:** `ContextAwareBackgroundTasks` snapshots `ContextVar` during request  Re-hydrates it inside the worker thread.

---

## 📄 License

This project is licensed under the MIT License.