Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/powercontext/server/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ def filter(self, record: logging.LogRecord) -> bool:
return True


class _UvicornDisplayNameFilter(logging.Filter):
"""Rewrite uvicorn's lifecycle logger name for display.

uvicorn hardcodes "uvicorn.error" for its lifecycle logger, which mostly
carries INFO startup lines but reads like an error channel. Rewriting the
record name here is display-only: the logger tree, level routing, and any
Comment thread
AlexStocks marked this conversation as resolved.
Outdated
filtering keyed on the original name are untouched.
"""

@override
def filter(self, record: logging.LogRecord) -> bool:
if record.name == "uvicorn.error":
record.name = "uvicorn"
return True


def configure_server_logging(config: ServerLoggingConfig) -> None:
"""Configure process logging for the foreground Server command."""

Expand All @@ -106,7 +122,10 @@ def configure_server_logging(config: ServerLoggingConfig) -> None:
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
"filters": {"operational": {"()": _HumanContextFilter}},
"filters": {
"operational": {"()": _HumanContextFilter},
"uvicorn_display_name": {"()": _UvicornDisplayNameFilter},
},
"formatters": {"server": formatter},
"handlers": {
"server": {
Expand All @@ -126,6 +145,7 @@ def configure_server_logging(config: ServerLoggingConfig) -> None:
"handlers": ["server"],
"level": config.level,
"propagate": False,
"filters": ["uvicorn_display_name"],
},
},
})
Expand Down
30 changes: 29 additions & 1 deletion tests/test_server_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,38 @@

from powercontext.builtin.persistence.sqlite import SQLiteConfig
from powercontext.server.factory import create_server_app
from powercontext.server.logging import JsonFormatter, OperationalContextFilter
from powercontext.server.logging import JsonFormatter, OperationalContextFilter, _UvicornDisplayNameFilter
from powercontext.server.settings import McpConfig, ServerLoggingConfig, ServerSettings


def test_uvicorn_error_records_are_displayed_as_uvicorn() -> None:
record = logging.makeLogRecord({
"name": "uvicorn.error",
"levelno": logging.INFO,
"levelname": "INFO",
"msg": "Started server process",
})

_UvicornDisplayNameFilter().filter(record)
Comment thread
AlexStocks marked this conversation as resolved.
Outdated

payload = json.loads(JsonFormatter().format(record))
assert payload["logger"] == "uvicorn"
assert payload["message"] == "Started server process"


def test_uvicorn_display_name_filter_leaves_other_loggers_untouched() -> None:
record = logging.makeLogRecord({
"name": "powercontext.server.factory",
"levelno": logging.INFO,
"levelname": "INFO",
"msg": "PowerContext Server is ready",
})

_UvicornDisplayNameFilter().filter(record)

assert record.name == "powercontext.server.factory"


def test_json_formatter_emits_stable_operational_fields() -> None:
record = logging.makeLogRecord({
"name": "powercontext.server.access",
Expand Down
Loading