diff --git a/docs/how_to_guides/logging.rst b/docs/how_to_guides/logging.rst index 9e16e07..b2860b3 100644 --- a/docs/how_to_guides/logging.rst +++ b/docs/how_to_guides/logging.rst @@ -66,8 +66,14 @@ p process ID {Header}i request header {Header}o response header {Variable}e environment variable +{Variable}s string or byte value from the ASGI scope =========== =========== +For example, ``%({myapp.trace_id}s)s`` logs a trace ID added to the +request's ASGI scope by the application. Byte values are decoded as +Latin-1; other value types and missing keys appear as ``-``. The +``{Variable}e`` atom continues to read process environment variables. + Customising the logger ---------------------- diff --git a/src/hypercorn/logging.py b/src/hypercorn/logging.py index 04f84ab..e11a531 100644 --- a/src/hypercorn/logging.py +++ b/src/hypercorn/logging.py @@ -140,6 +140,11 @@ def __init__( self[f"{{{name.decode('latin1').lower()}}}i"] = value.decode("latin1") for name, value in os.environ.items(): self[f"{{{name.lower()}}}e"] = value + for name, value in request.items(): + if isinstance(value, str): + self[f"{{{name.lower()}}}s"] = value + elif isinstance(value, bytes): + self[f"{{{name.lower()}}}s"] = value.decode("latin1") protocol = request.get("http_version", "ws") client = request.get("client") if client is None: diff --git a/tests/test_logging.py b/tests/test_logging.py index bb6f32b..d541591 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -3,6 +3,7 @@ import logging import os import time +from typing import cast import pytest @@ -110,6 +111,29 @@ def test_access_log_environ_atoms(http_scope: HTTPScope, response: ResponseSumma assert atoms["{random}e"] == "Environ" +def test_access_log_scope_atoms( + http_scope: HTTPScope, response: ResponseSummary, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("MYAPP_TRACE_ID", "process-value") + scope = cast( + HTTPScope, + { + **http_scope, + "myapp_trace_id": "request-value", + "myapp_bytes": b"binary-\xff", + "myapp_object": object(), + }, + ) + + atoms = AccessLogAtoms(scope, response, 0) + + assert atoms["{MYAPP_TRACE_ID}s"] == "request-value" + assert "%({myapp_trace_id}s)s" % atoms == "request-value" + assert atoms["{myapp_bytes}s"] == "binary-\xff" + assert atoms["{myapp_object}s"] == "-" + assert atoms["{myapp_trace_id}e"] == "process-value" + + def test_nonstandard_status_code(http_scope: HTTPScope) -> None: atoms = AccessLogAtoms(http_scope, {"status": 441, "headers": []}, 0) assert atoms["st"] == ""