mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-29 09:47:17 +00:00
filter out logs posting http logs
This commit is contained in:
parent
abd77e655b
commit
718ff8dd50
61
src/ahriman/core/log/filtered_access_logger.py
Normal file
61
src/ahriman/core/log/filtered_access_logger.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2022 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import re
|
||||||
|
|
||||||
|
from aiohttp.abc import BaseRequest, StreamResponse
|
||||||
|
from aiohttp.web_log import AccessLogger
|
||||||
|
|
||||||
|
|
||||||
|
class FilteredAccessLogger(AccessLogger):
|
||||||
|
"""
|
||||||
|
access logger implementation with log filter enabled
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
LOG_PATH_REGEX(re.Pattern): (class attribute) regex for logs uri
|
||||||
|
"""
|
||||||
|
|
||||||
|
# official packages have only ``[A-Za-z0-9_.+-]`` regex
|
||||||
|
LOG_PATH_REGEX = re.compile(r"^/api/v1/packages/[A-Za-z0-9_.+%-]+/logs$")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_logs_post(request: BaseRequest) -> bool:
|
||||||
|
"""
|
||||||
|
check if request looks lie logs posting
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request(BaseRequest): http reqeust descriptor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True in case if request looks like logs positing and False otherwise
|
||||||
|
"""
|
||||||
|
return request.method == "POST" and FilteredAccessLogger.LOG_PATH_REGEX.match(request.path) is not None
|
||||||
|
|
||||||
|
def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
|
||||||
|
"""
|
||||||
|
access log with enabled filter by request path
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request(BaseRequest): http reqeust descriptor
|
||||||
|
response(StreamResponse): streaming response object
|
||||||
|
time(float):
|
||||||
|
"""
|
||||||
|
if self.is_logs_post(request):
|
||||||
|
return
|
||||||
|
AccessLogger.log(self, request, response, time)
|
@ -27,6 +27,7 @@ from ahriman.core.auth import Auth
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.exceptions import InitializeError
|
from ahriman.core.exceptions import InitializeError
|
||||||
|
from ahriman.core.log.filtered_access_logger import FilteredAccessLogger
|
||||||
from ahriman.core.spawn import Spawn
|
from ahriman.core.spawn import Spawn
|
||||||
from ahriman.core.status.watcher import Watcher
|
from ahriman.core.status.watcher import Watcher
|
||||||
from ahriman.web.middlewares.exception_handler import exception_handler
|
from ahriman.web.middlewares.exception_handler import exception_handler
|
||||||
@ -79,7 +80,7 @@ def run_server(application: web.Application) -> None:
|
|||||||
port = configuration.getint("web", "port")
|
port = configuration.getint("web", "port")
|
||||||
|
|
||||||
web.run_app(application, host=host, port=port, handle_signals=False,
|
web.run_app(application, host=host, port=port, handle_signals=False,
|
||||||
access_log=logging.getLogger("http"))
|
access_log=logging.getLogger("http"), access_log_class=FilteredAccessLogger)
|
||||||
|
|
||||||
|
|
||||||
def setup_service(architecture: str, configuration: Configuration, spawner: Spawn) -> web.Application:
|
def setup_service(architecture: str, configuration: Configuration, spawner: Spawn) -> web.Application:
|
||||||
|
16
tests/ahriman/core/log/conftest.py
Normal file
16
tests/ahriman/core/log/conftest.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ahriman.core.log.filtered_access_logger import FilteredAccessLogger
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def filtered_access_logger() -> FilteredAccessLogger:
|
||||||
|
"""
|
||||||
|
fixture for custom access logger
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
FilteredAccessLogger: custom access logger test instance
|
||||||
|
"""
|
||||||
|
logger = logging.getLogger()
|
||||||
|
return FilteredAccessLogger(logger)
|
71
tests/ahriman/core/log/test_filtered_access_logger.py
Normal file
71
tests/ahriman/core/log/test_filtered_access_logger.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
from pytest_mock import MockerFixture
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from ahriman.core.log.filtered_access_logger import FilteredAccessLogger
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_logs_post() -> None:
|
||||||
|
"""
|
||||||
|
must correctly define if request belongs to logs posting
|
||||||
|
"""
|
||||||
|
request = MagicMock()
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/ahriman/logs"
|
||||||
|
assert FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/linux-headers/logs"
|
||||||
|
assert FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/memtest86+/logs"
|
||||||
|
assert FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/memtest86%2B/logs"
|
||||||
|
assert FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/python2.7/logs"
|
||||||
|
assert FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "GET"
|
||||||
|
request.path = "/api/v1/packages/ahriman/logs"
|
||||||
|
assert not FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/ahriman"
|
||||||
|
assert not FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
request.method = "POST"
|
||||||
|
request.path = "/api/v1/packages/ahriman/logs/random/path/after"
|
||||||
|
assert not FilteredAccessLogger.is_logs_post(request)
|
||||||
|
|
||||||
|
|
||||||
|
def test_log(filtered_access_logger: FilteredAccessLogger, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must emit log record
|
||||||
|
"""
|
||||||
|
request_mock = MagicMock()
|
||||||
|
response_mock = MagicMock()
|
||||||
|
is_log_path_mock = mocker.patch("ahriman.core.log.filtered_access_logger.FilteredAccessLogger.is_logs_post",
|
||||||
|
return_value=False)
|
||||||
|
log_mock = mocker.patch("aiohttp.web_log.AccessLogger.log")
|
||||||
|
|
||||||
|
filtered_access_logger.log(request_mock, response_mock, 0.001)
|
||||||
|
is_log_path_mock.assert_called_once_with(request_mock)
|
||||||
|
log_mock.assert_called_once_with(filtered_access_logger, request_mock, response_mock, 0.001)
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_filter_logs(filtered_access_logger: FilteredAccessLogger, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must skip log record in case if it is from logs posting
|
||||||
|
"""
|
||||||
|
request_mock = MagicMock()
|
||||||
|
response_mock = MagicMock()
|
||||||
|
mocker.patch("ahriman.core.log.filtered_access_logger.FilteredAccessLogger.is_logs_post", return_value=True)
|
||||||
|
log_mock = mocker.patch("aiohttp.web_log.AccessLogger.log")
|
||||||
|
|
||||||
|
filtered_access_logger.log(request_mock, response_mock, 0.001)
|
||||||
|
log_mock.assert_not_called()
|
@ -4,6 +4,7 @@ from aiohttp import web
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.core.exceptions import InitializeError
|
from ahriman.core.exceptions import InitializeError
|
||||||
|
from ahriman.core.log.filtered_access_logger import FilteredAccessLogger
|
||||||
from ahriman.core.status.watcher import Watcher
|
from ahriman.core.status.watcher import Watcher
|
||||||
from ahriman.web.web import on_shutdown, on_startup, run_server
|
from ahriman.web.web import on_shutdown, on_startup, run_server
|
||||||
|
|
||||||
@ -48,8 +49,10 @@ def test_run(application: web.Application, mocker: MockerFixture) -> None:
|
|||||||
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
||||||
|
|
||||||
run_server(application)
|
run_server(application)
|
||||||
run_application_mock.assert_called_once_with(application, host="127.0.0.1", port=port,
|
run_application_mock.assert_called_once_with(
|
||||||
handle_signals=False, access_log=pytest.helpers.anyvar(int))
|
application, host="127.0.0.1", port=port, handle_signals=False,
|
||||||
|
access_log=pytest.helpers.anyvar(int), access_log_class=FilteredAccessLogger
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_with_auth(application_with_auth: web.Application, mocker: MockerFixture) -> None:
|
def test_run_with_auth(application_with_auth: web.Application, mocker: MockerFixture) -> None:
|
||||||
@ -61,8 +64,10 @@ def test_run_with_auth(application_with_auth: web.Application, mocker: MockerFix
|
|||||||
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
||||||
|
|
||||||
run_server(application_with_auth)
|
run_server(application_with_auth)
|
||||||
run_application_mock.assert_called_once_with(application_with_auth, host="127.0.0.1", port=port,
|
run_application_mock.assert_called_once_with(
|
||||||
handle_signals=False, access_log=pytest.helpers.anyvar(int))
|
application_with_auth, host="127.0.0.1", port=port, handle_signals=False,
|
||||||
|
access_log=pytest.helpers.anyvar(int), access_log_class=FilteredAccessLogger
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_with_debug(application_with_debug: web.Application, mocker: MockerFixture) -> None:
|
def test_run_with_debug(application_with_debug: web.Application, mocker: MockerFixture) -> None:
|
||||||
@ -74,5 +79,7 @@ def test_run_with_debug(application_with_debug: web.Application, mocker: MockerF
|
|||||||
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
||||||
|
|
||||||
run_server(application_with_debug)
|
run_server(application_with_debug)
|
||||||
run_application_mock.assert_called_once_with(application_with_debug, host="127.0.0.1", port=port,
|
run_application_mock.assert_called_once_with(
|
||||||
handle_signals=False, access_log=pytest.helpers.anyvar(int))
|
application_with_debug, host="127.0.0.1", port=port, handle_signals=False,
|
||||||
|
access_log=pytest.helpers.anyvar(int), access_log_class=FilteredAccessLogger
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user