From f89595c05d53f65b097fd62830434eb35f644cbb Mon Sep 17 00:00:00 2001 From: Ilya Makarov Date: Wed, 1 Apr 2026 18:57:12 +0300 Subject: [PATCH] suppress health access logs option --- README.rst | 7 +++++++ karapace.config.json | 1 + src/karapace/__main__.py | 2 ++ src/karapace/core/config.py | 1 + src/karapace/core/logging_setup.py | 11 +++++++++++ src/karapace/core/utils.py | 13 +++++++++++++ src/karapace/rapu.py | 20 ++++++++++++++++++-- tests/unit/test_config.py | 8 ++++++++ 8 files changed, 61 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index f93fc93b0..11de138c1 100644 --- a/README.rst +++ b/README.rst @@ -88,6 +88,13 @@ what you need to change. .. _`Here`: https://github.com/Aiven-Open/karapace/blob/master/karapace.config.json +To reduce log noise from Kubernetes or load balancer probes, enable:: + + suppress_health_access_logs = true + +This opt-in setting suppresses access logs for ``/_health`` while keeping all +other HTTP access logs unchanged. + Using Sources ------------- diff --git a/karapace.config.json b/karapace.config.json index 54b5b5b50..8479e1131 100644 --- a/karapace.config.json +++ b/karapace.config.json @@ -1,5 +1,6 @@ { "access_logs_debug": false, + "suppress_health_access_logs": false, "advertised_hostname": "localhost", "bootstrap_uri": "127.0.0.1:9092", "sasl_bootstrap_uri": "127.0.0.1:9094", diff --git a/src/karapace/__main__.py b/src/karapace/__main__.py index 6398dc79d..90553ef37 100644 --- a/src/karapace/__main__.py +++ b/src/karapace/__main__.py @@ -9,6 +9,7 @@ from karapace.core.auth_container import AuthContainer from karapace.core.config import ServerTLSClientAuth, get_client_auth_verify_mode from karapace.core.container import KarapaceContainer +from karapace.core.logging_setup import configure_uvicorn_access_logging import karapace.api.controller import karapace.api.factory @@ -96,6 +97,7 @@ ) config = karapace_container.config() + configure_uvicorn_access_logging(config=config) app = create_karapace_application(config=config, lifespan=karapace_schema_registry_lifespan) # Calculate SSL certificate requirements for uvicorn diff --git a/src/karapace/core/config.py b/src/karapace/core/config.py index 782dc4126..a16e3cdff 100644 --- a/src/karapace/core/config.py +++ b/src/karapace/core/config.py @@ -119,6 +119,7 @@ class Config(BaseSettings): log_handler: str | None = "stdout" log_level: str = "DEBUG" log_format: str = "%(name)-20s\t%(threadName)s\t%(levelname)-8s\t%(message)s" + suppress_health_access_logs: bool = False master_eligibility: bool = True replication_factor: int = 1 security_protocol: str = "PLAINTEXT" diff --git a/src/karapace/core/logging_setup.py b/src/karapace/core/logging_setup.py index 951510a6d..1be0f3a34 100644 --- a/src/karapace/core/logging_setup.py +++ b/src/karapace/core/logging_setup.py @@ -4,6 +4,7 @@ """ from karapace.core.config import Config +from karapace.core.access_logging import HealthRouteFilter import logging import sys @@ -43,3 +44,13 @@ def log_config_without_secrets(config: Config) -> None: value = "****" config_without_secrets[key] = value logging.log(logging.DEBUG, "Config %r", config_without_secrets) + + +def configure_uvicorn_access_logging(*, config: Config) -> None: + uvicorn_access_logger = logging.getLogger("uvicorn.access") + + if config.suppress_health_access_logs: + for existing_filter in uvicorn_access_logger.filters: + if isinstance(existing_filter, HealthRouteFilter): + return + uvicorn_access_logger.addFilter(HealthRouteFilter(suppress_health_access_logs=True)) diff --git a/src/karapace/core/utils.py b/src/karapace/core/utils.py index 4f4fe4a91..1f61195b7 100644 --- a/src/karapace/core/utils.py +++ b/src/karapace/core/utils.py @@ -7,6 +7,7 @@ from __future__ import annotations +from .access_logging import should_skip_access_log from .typing import ArgJsonData, JsonData from aiohttp.web_log import AccessLogger from aiohttp.web_request import BaseRequest @@ -273,6 +274,18 @@ def log( self.logger.exception("Error in logging") +class HealthSkippingDebugAccessLogger(DebugAccessLogger): + def log( + self, + request: BaseRequest, + response: StreamResponse, + time: float, + ) -> None: + if should_skip_access_log(path=request.path, suppress_health_access_logs=True): + return + super().log(request, response, time) + + def shutdown(): """ Send a SIGTERM into the current running application process, which should initiate shutdown logic. diff --git a/src/karapace/rapu.py b/src/karapace/rapu.py index 552e03f32..e719e45e6 100644 --- a/src/karapace/rapu.py +++ b/src/karapace/rapu.py @@ -10,9 +10,10 @@ from accept_types import get_best_match from collections.abc import Callable from http import HTTPStatus +from karapace.core.access_logging import HealthSkippingAccessLogger from karapace.core.config import Config, create_server_ssl_context from karapace.statsd import StatsClient -from karapace.core.utils import json_decode, json_encode +from karapace.core.utils import DebugAccessLogger, HealthSkippingDebugAccessLogger, json_decode, json_encode from karapace.version import __version__ from typing import NoReturn, overload @@ -25,6 +26,7 @@ import logging import re import time +from aiohttp.web_log import AccessLogger SERVER_NAME = f"Karapace/{__version__}" JSON_CONTENT_TYPE = "application/json" @@ -484,12 +486,26 @@ async def wrapped_cors(request): def run(self) -> None: ssl_context = create_server_ssl_context(self.config) + access_log_class = self.config.access_log_class + if self.config.suppress_health_access_logs: + if access_log_class is AccessLogger: + access_log_class = HealthSkippingAccessLogger + elif access_log_class is DebugAccessLogger: + access_log_class = HealthSkippingDebugAccessLogger + elif ( + access_log_class is not HealthSkippingAccessLogger + and access_log_class is not HealthSkippingDebugAccessLogger + ): + self.log.warning( + "suppress_health_access_logs is enabled but access_log_class=%s is custom; skipping health access-log suppression", + access_log_class, + ) aiohttp.web.run_app( app=self.app, host=self.config.host, port=self.config.port, ssl_context=ssl_context, - access_log_class=self.config.access_log_class, + access_log_class=access_log_class, access_log_format='%Tfs %{x-client-ip}i "%r" %s "%{user-agent}i" response=%bb request_body=%{content-length}ib', ) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index f89da8026..3a129114e 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -7,6 +7,7 @@ from karapace.core.config import Config from karapace.core.constants import DEFAULT_AIOHTTP_CLIENT_MAX_SIZE, DEFAULT_PRODUCER_MAX_REQUEST +import pytest def test_http_request_max_size() -> None: @@ -39,3 +40,10 @@ def test_http_request_max_size() -> None: config.http_request_max_size = 1024 config.producer_max_request_size = DEFAULT_PRODUCER_MAX_REQUEST + 1024 assert config.get_max_request_size() == 1024 + + +def test_suppress_health_access_logs_defaults_and_env(monkeypatch: pytest.MonkeyPatch) -> None: + assert Config().suppress_health_access_logs is False + + monkeypatch.setenv("KARAPACE_SUPPRESS_HEALTH_ACCESS_LOGS", "true") + assert Config().suppress_health_access_logs is True