<!-- *** GENERATED FILE - DO NOT EDIT *** -->
<!-- This file was generated by jinja-tree (https://github.com/fabien-marty/jinja-tree) from the template file: README.md.jinja -->

# stlog-fastapi-middlewares

![Python Badge](https://raw.githubusercontent.com/fabien-marty/common/refs/heads/main/badges/python310plus.svg)
[![UV Badge](https://raw.githubusercontent.com/fabien-marty/common/refs/heads/main/badges/uv.svg)](https://docs.astral.sh/uv/)
[![Mergify Badge](https://raw.githubusercontent.com/fabien-marty/common/refs/heads/main/badges/mergify.svg)](https://mergify.com/)
[![Renovate Badge](https://raw.githubusercontent.com/fabien-marty/common/refs/heads/main/badges/renovate.svg)](https://docs.renovatebot.com/)
[![MIT Licensed](https://raw.githubusercontent.com/fabien-marty/common/refs/heads/main/badges/mit.svg)](https://en.wikipedia.org/wiki/MIT_License)

FastAPI/starlette middlewares to improve logging with [stlog](https://github.com/fabien-marty/stlog) library.

## Installation

`pip install stlog-fastapi-middlewares`

(or equivalent command for your favorite package manager)

## Usage (with FastAPI)

```python
import stlog
from fastapi import FastAPI
from fastapi.concurrency import asynccontextmanager
from stlog_fastapi_middlewares import (
    AccessLogMiddleware,
    CustomExceptionHandlingMiddleware,
    LogContextMiddleware,
)


@asynccontextmanager
async def lifespan(app: FastAPI):
    stlog.setup()
    yield


logger = stlog.getLogger(__name__)
app = FastAPI(lifespan=lifespan)
app.add_middleware(CustomExceptionHandlingMiddleware, logger=logger)
app.add_middleware(AccessLogMiddleware, logger=logger)
app.add_middleware(LogContextMiddleware, logger=logger)


@app.get("/")
async def root():
    return {"message": "Hello World"}

```

## Reference

We provide 3 independent middlewares.

### `CustomExceptionHandlingMiddleware`

This middleware logs exceptions to structured logs.

No extra option is available.

Example:

```python
import stlog
from fastapi import FastAPI
from fastapi.concurrency import asynccontextmanager
from stlog_fastapi_middlewares import (
    CustomExceptionHandlingMiddleware,
)


@asynccontextmanager
async def lifespan(app: FastAPI):
    stlog.setup()
    yield


logger = stlog.getLogger(__name__)
app = FastAPI(lifespan=lifespan)
app.add_middleware(CustomExceptionHandlingMiddleware, logger=logger)


@app.get("/exception")
async def exception():
    raise Exception("test")

```

### `AccessLogMiddleware`

This middleware logs access to the application (to get access logs in JSON format for example).

You can optionally provide an `ignore_hook` option to ignore (for access log only!) some URLs.

Example:

```python
import stlog
from fastapi import FastAPI
from fastapi.concurrency import asynccontextmanager
from stlog_fastapi_middlewares import (
    AccessLogMiddleware,
)


@asynccontextmanager
async def lifespan(app: FastAPI):
    stlog.setup()
    yield


logger = stlog.getLogger(__name__)
app = FastAPI(lifespan=lifespan)
app.add_middleware(AccessLogMiddleware, logger=logger)


@app.get("/")
async def root():
    return {"message": "Hello World"}

```

> [!NOTE]
> As `FastAPI` or `uvicorn` already outputs some access logs, you may want to disable them!
> For example, with `uvicorn`, you can use the `--no-access-log` flag.

### `LogContextMiddleware`

This middleware adds some request context to structured logs (think of automatic request id, process id...).

There are multiple options to customize the middleware. See [the corresponding code for reference](stlog_fastapi_middlewares/context.py).

Example:

```python
import os
import stlog
from fastapi import FastAPI
from fastapi.concurrency import asynccontextmanager
from stlog_fastapi_middlewares import (
    LogContextMiddleware,
)


@asynccontextmanager
async def lifespan(app: FastAPI):
    stlog.setup()
    yield


logger = stlog.getLogger(__name__)
app = FastAPI(lifespan=lifespan)
app.add_middleware(
    LogContextMiddleware,
    logger=logger,
    add_pid=True,
    add_request_id=True,
    envs_to_kvs={"FOO": "foo", "BAR": "bar"},
    headers_to_kvs={"X-Test-Id": "test_id"},
)

# Let's add some environment variables to test the middleware
os.environ["FOO"] = "foo2"
os.environ["BAR"] = "bar2"


@app.get("/")
async def root():
    logger.info("hello world")
    # => this call will output a log with the following extra keys:
    # - foo
    # - bar
    # - pid
    # - request_id
    # - test_id (if the call is made with a X-Test-Id header)
    return {"message": "Hello World"}

```