Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------

Expand Down
1 change: 1 addition & 0 deletions karapace.config.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/karapace/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/karapace/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 11 additions & 0 deletions src/karapace/core/logging_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from karapace.core.config import Config
from karapace.core.access_logging import HealthRouteFilter

import logging
import sys
Expand Down Expand Up @@ -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))
13 changes: 13 additions & 0 deletions src/karapace/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 18 additions & 2 deletions src/karapace/rapu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"
Expand Down Expand Up @@ -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',
)
8 changes: 8 additions & 0 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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