swapped docs symlink
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
# HTTPX Logging Handler Example
|
||||
|
||||
Use this reference when an application should emit JSON logs to an HTTP collector while keeping startup logging configuration declarative.
|
||||
|
||||
This page follows the top-level skill pattern:
|
||||
|
||||
- define one `LOGGING` dictionary
|
||||
- apply it once with `logging.config.dictConfig(LOGGING)`
|
||||
- keep modules focused on logger calls
|
||||
|
||||
Source docs to keep nearby:
|
||||
|
||||
- [HTTPX clients](https://www.python-httpx.org/advanced/clients/)
|
||||
- [HTTPX timeouts](https://www.python-httpx.org/advanced/timeouts/)
|
||||
- [`logging.config.dictConfig`](https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig)
|
||||
|
||||
## Minimal Topology
|
||||
|
||||
```text
|
||||
application code -> named logger -> HttpxJsonLogHandler -> HTTP collector
|
||||
```
|
||||
|
||||
## Reusable Handler Type
|
||||
|
||||
Keep transport behavior in one handler class and wire it declaratively through `dictConfig`.
|
||||
|
||||
```python title="httpx_json_handler.py"
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class HttpxJsonLogHandler(logging.Handler):
|
||||
def __init__(self, collector_url: str, timeout_seconds: float = 2.0, token: str | None = None) -> None:
|
||||
super().__init__()
|
||||
headers = {"content-type": "application/json"}
|
||||
if token is not None:
|
||||
headers["authorization"] = f"Bearer {token}"
|
||||
timeout = httpx.Timeout(timeout_seconds)
|
||||
self.client = httpx.Client(base_url=collector_url, headers=headers, timeout=timeout)
|
||||
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
payload = {
|
||||
"name": record.name,
|
||||
"levelname": record.levelname,
|
||||
"levelno": record.levelno,
|
||||
"pathname": record.pathname,
|
||||
"lineno": record.lineno,
|
||||
"funcName": record.funcName,
|
||||
"created": record.created,
|
||||
"message": record.getMessage(),
|
||||
}
|
||||
try:
|
||||
response = self.client.post("/logs", json=payload)
|
||||
response.raise_for_status()
|
||||
except httpx.HTTPError:
|
||||
self.handleError(record)
|
||||
|
||||
def close(self) -> None:
|
||||
self.client.close()
|
||||
super().close()
|
||||
```
|
||||
|
||||
## Application Logging Configuration (Declarative)
|
||||
|
||||
```python title="logging_config.py"
|
||||
import logging.config
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"handlers": {
|
||||
"httpx": {
|
||||
"class": "httpx_json_handler.HttpxJsonLogHandler",
|
||||
"collector_url": "http://127.0.0.1:9021",
|
||||
"timeout_seconds": 2.0,
|
||||
"token": None,
|
||||
},
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "INFO",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"level": "INFO",
|
||||
"handlers": ["httpx", "console"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def configure_logging() -> None:
|
||||
logging.config.dictConfig(LOGGING)
|
||||
```
|
||||
|
||||
```python title="feature.py"
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def sync_customer(customer_id: str) -> None:
|
||||
logger.info("Syncing customer %s", customer_id)
|
||||
```
|
||||
|
||||
```python title="main.py"
|
||||
from feature import sync_customer
|
||||
from logging_config import configure_logging
|
||||
|
||||
configure_logging()
|
||||
sync_customer("C-101")
|
||||
```
|
||||
|
||||
## Collector-Side Configuration (Declarative)
|
||||
|
||||
Whether you use an internal HTTP endpoint or a managed collector, keep receiver-side formatting and routing declared on the receiver side, not in application modules.
|
||||
|
||||
## Why This Pattern
|
||||
|
||||
- Logging wiring is declared once and applied once.
|
||||
- Runtime behavior changes by editing config fields, not scattered root mutations.
|
||||
- Feature modules stay independent from transport details.
|
||||
- HTTP connection details remain encapsulated in one handler type.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
1. Is there one `LOGGING` dict for the application process?
|
||||
2. Is `dictConfig` called once at startup?
|
||||
3. Are module loggers created via `logging.getLogger(__name__)`?
|
||||
4. Are HTTP endpoint, timeout, and auth token inputs declared in handler config?
|
||||
5. Are final routing/retention decisions handled by the collector side?
|
||||
Reference in New Issue
Block a user