diff --git a/src/programming/Ligare/programming/patterns/dependency_injection.py b/src/programming/Ligare/programming/patterns/dependency_injection.py index 2a6be25b..8f99f8e6 100644 --- a/src/programming/Ligare/programming/patterns/dependency_injection.py +++ b/src/programming/Ligare/programming/patterns/dependency_injection.py @@ -137,10 +137,10 @@ def __init__( "timestamp": "asctime", }) - handler = logging.StreamHandler() - handler.formatter = formatter + json_handler = logging.StreamHandler() + json_handler.formatter = formatter if logging.lastResort is None: - logging.lastResort = handler + logging.lastResort = json_handler else: logging.lastResort.setFormatter(formatter) @@ -149,11 +149,13 @@ def __init__( def force_json_format(*args: Any, **kwargs: Any): logger = original_get_logger(*args, **kwargs) - if handler in logger.handlers: + if json_handler in logger.handlers: return logger - logger.handlers.clear() - logger.addHandler(handler) + for handler in logger.handlers: + handler.setFormatter(formatter) + + logger.addHandler(json_handler) return logger logging.getLogger = force_json_format diff --git a/src/web/Ligare/web/application.py b/src/web/Ligare/web/application.py index fb02b6f5..e8dea9bb 100644 --- a/src/web/Ligare/web/application.py +++ b/src/web/Ligare/web/application.py @@ -18,7 +18,6 @@ overload, ) -import json_logging.util from connexion import FlaskApp from connexion.options import SwaggerUIOptions from flask import Blueprint, Flask @@ -454,8 +453,6 @@ def configure_openapi(config: Config, name: Optional[str] = None): validate_responses=config.flask.openapi.validate_responses, ) - # enable_json_logging = config.logging.format == "JSON" - # if enable_json_logging: # FIXME what's the new way to get this URL? # if config.flask.openapi.use_swagger: # # App context needed for url_for. @@ -483,16 +480,6 @@ def configure_blueprint_routes( app = Flask(config.flask.app_name) config.update_flask_config(app.config) - enable_json_logging = config.logging.format == "JSON" - if enable_json_logging: - json_logging.init_flask( # pyright: ignore[reportUnknownMemberType] - enable_json=enable_json_logging - ) - json_logging.init_request_instrument( # pyright: ignore[reportUnknownMemberType] - app - ) - json_logging.config_root_logger() # pyright: ignore[reportUnknownMemberType] - blueprint_modules = _import_blueprint_modules(app, blueprint_import_subdir) _register_blueprint_modules(app, blueprint_modules) return app diff --git a/src/web/Ligare/web/middleware/context.py b/src/web/Ligare/web/middleware/context.py new file mode 100644 index 00000000..bcda0e14 --- /dev/null +++ b/src/web/Ligare/web/middleware/context.py @@ -0,0 +1,211 @@ +import uuid +from collections.abc import Collection +from contextvars import ContextVar +from logging import Logger +from typing import Any, NamedTuple, NewType, cast +from uuid import uuid4 + +from injector import inject +from Ligare.web.middleware.consts import REQUEST_ID_HEADER +from Ligare.web.middleware.openapi import MiddlewareRequestDict, MiddlewareResponseDict +from starlette.types import ASGIApp, Receive, Scope, Send +from typing_extensions import final + + +# copied from connexion.utils +def extract_content_type( + headers: list[tuple[bytes, bytes]] | dict[str, str], +) -> str | None: + """Extract the mime type and encoding from the content type headers. + + :param headers: Headers from ASGI scope + + :return: The content type if available in headers, otherwise None + """ + content_type: str | None = None + + header_pairs: Collection[tuple[str | bytes, str | bytes]] = ( + headers.items() if isinstance(headers, dict) else headers + ) + for key, value in header_pairs: + # Headers can always be decoded using latin-1: + # https://stackoverflow.com/a/27357138/4098821 + if isinstance(key, bytes): + decoded_key: str = key.decode("latin-1") + else: + decoded_key = key + + if decoded_key.lower() == "content-type": + if isinstance(value, bytes): + content_type = value.decode("latin-1") + else: + content_type = value + break + + return content_type + + +# copied from connexion.utils +def split_content_type(content_type: str | None) -> tuple[str | None, str | None]: + """Split the content type in mime_type and encoding. Other parameters are ignored.""" + mime_type, encoding = None, None + + if content_type is None: + return mime_type, encoding + + # Check for parameters + if ";" in content_type: + mime_type, parameters = content_type.split(";", maxsplit=1) + + # Find parameter describing the charset + prefix = "charset=" + for parameter in parameters.split(";"): + if parameter.startswith(prefix): + encoding = parameter[len(prefix) :] + else: + mime_type = content_type + return mime_type, encoding + + +CorrelationId = NewType("CorrelationId", str) +RequestId = NewType("RequestId", str) + +CORRELATION_ID_CTX_KEY = "correlationId" +REQUEST_ID_CTX_KEY = "requestId" + +_correlation_id_ctx_var: ContextVar[CorrelationId | None] = ContextVar( + CORRELATION_ID_CTX_KEY, default=None +) +_request_id_ctx_var: ContextVar[RequestId | None] = ContextVar( + REQUEST_ID_CTX_KEY, default=None +) + + +class TraceId(NamedTuple): + CorrelationId: CorrelationId | None + RequestId: RequestId | None + + +def get_trace_id() -> TraceId: + return TraceId(_correlation_id_ctx_var.get(), _request_id_ctx_var.get()) + + +@final +class CorrelationIdMiddleware: + """ + Generate a Correlation ID for each request. + + https://github.com/encode/starlette/issues/420 + """ + + def __init__( + self, + app: ASGIApp, + ) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] not in ["http", "websocket"]: + await self.app(scope, receive, send) + return + + correlation_id = _correlation_id_ctx_var.set(CorrelationId(str(uuid4()))) + + await self.app(scope, receive, send) + + _correlation_id_ctx_var.reset(correlation_id) + + +@final +class RequestIdMiddleware: + """ + Generate a Trace ID for each request. + If X-Correlation-Id is set in the request headers, that ID is used instead. + """ + + _app: ASGIApp + + def __init__(self, app: ASGIApp): + super().__init__() + self._app = app + + @inject + async def __call__( + self, scope: Scope, receive: Receive, send: Send, log: Logger + ) -> None: + if scope["type"] not in ["http", "websocket"]: + return await self._app(scope, receive, send) + + # extract the request ID from the request headers if it is set + + request = cast(MiddlewareRequestDict, scope) + request_headers = request.get("headers") + + content_type = extract_content_type(request_headers) + _, encoding = split_content_type(content_type) + if encoding is None: + encoding = "utf-8" + + try: + request_id_header_encoded = REQUEST_ID_HEADER.lower().encode(encoding) + + request_id: bytes | None = next( + ( + request_id + for (header, request_id) in request_headers + if header == request_id_header_encoded + ), + None, + ) + + if request_id: + # validate format + request_id_decoded = request_id.decode(encoding) + _ = uuid.UUID(request_id_decoded) + request_id_token = _request_id_ctx_var.set( + RequestId(request_id_decoded) + ) + else: + request_id_decoded = str(uuid4()) + request_id = request_id_decoded.encode(encoding) + request_headers.append(( + request_id_header_encoded, + request_id, + )) + request_id_token = _request_id_ctx_var.set( + RequestId(request_id_decoded) + ) + log.info( + f'Generated new UUID "{request_id}" for {REQUEST_ID_HEADER} request header.' + ) + except ValueError as e: + log.warning(f"Badly formatted {REQUEST_ID_HEADER} received in request.") + raise e + + async def wrapped_send(message: Any) -> None: + nonlocal scope + nonlocal send + + if message["type"] != "http.response.start": + return await send(message) + + # include the request ID in response headers + + response = cast(MiddlewareResponseDict, message) + response_headers = response["headers"] + + content_type = extract_content_type(response_headers) + _, encoding = split_content_type(content_type) + if encoding is None: + encoding = "utf-8" + + response_headers.append(( + request_id_header_encoded, + request_id, + )) + + return await send(message) + + await self._app(scope, receive, wrapped_send) + + _request_id_ctx_var.reset(request_id_token) diff --git a/src/web/Ligare/web/middleware/dependency_injection.py b/src/web/Ligare/web/middleware/dependency_injection.py index 4c58702b..13362a71 100644 --- a/src/web/Ligare/web/middleware/dependency_injection.py +++ b/src/web/Ligare/web/middleware/dependency_injection.py @@ -20,7 +20,7 @@ LoggerModule, ) from Ligare.web.application import Config as AppConfig -from Ligare.web.middleware.openapi import ( +from Ligare.web.middleware.context import ( CorrelationIdMiddleware, RequestIdMiddleware, get_trace_id, diff --git a/src/web/Ligare/web/middleware/flask/__init__.py b/src/web/Ligare/web/middleware/flask/__init__.py index c0d067f2..0daec2d2 100644 --- a/src/web/Ligare/web/middleware/flask/__init__.py +++ b/src/web/Ligare/web/middleware/flask/__init__.py @@ -8,11 +8,11 @@ from typing import Awaitable, Callable, Dict, TypeAlias, TypeVar from uuid import uuid4 -import json_logging from connexion import FlaskApp from flask import Flask, Request, Response, request from flask.typing import ResponseReturnValue from injector import inject +from Ligare.web.middleware.context import get_trace_id from ...config import Config from ..consts import ( @@ -71,7 +71,7 @@ def wrapper(request_callable: Callable[..., Response | None]) -> T_request_calla def _get_correlation_id(log: Logger) -> str: - correlation_id = _get_correlation_id_from_json_logging(log) + correlation_id = get_trace_id().CorrelationId if not correlation_id: correlation_id = _get_correlation_id_from_headers(log) @@ -99,23 +99,6 @@ def _get_correlation_id_from_headers(log: Logger) -> str: raise e -def _get_correlation_id_from_json_logging(log: Logger) -> str | None: - correlation_id: None | str - try: - correlation_id = json_logging.get_correlation_id(request) - # validate format - _ = uuid.UUID(correlation_id) - return correlation_id - except ValueError as e: - log.warning(f"Badly formatted {REQUEST_ID_HEADER} received in request.") - raise e - except Exception as e: - log.debug( - f"Error received when getting {REQUEST_ID_HEADER} header from `json_logging`. Possibly `json_logging` is not configured, and this is not an error.", - exc_info=e, - ) - - @inject def _log_all_api_requests( request: Request, diff --git a/src/web/Ligare/web/middleware/openapi/__init__.py b/src/web/Ligare/web/middleware/openapi/__init__.py index 406259c0..555c1a94 100644 --- a/src/web/Ligare/web/middleware/openapi/__init__.py +++ b/src/web/Ligare/web/middleware/openapi/__init__.py @@ -6,19 +6,9 @@ import uuid from collections.abc import Iterable from contextlib import ExitStack -from contextvars import ContextVar, Token +from contextvars import Token from logging import Logger -from typing import ( - Any, - Awaitable, - Callable, - Literal, - NamedTuple, - NewType, - TypeAlias, - TypeVar, - cast, -) +from typing import Any, Awaitable, Callable, Literal, TypeAlias, TypeVar, cast from uuid import uuid4 import starlette @@ -34,6 +24,7 @@ from flask_login import AnonymousUserMixin, current_user from injector import inject from Ligare.programming.collections.dict import AnyDict, merge +from Ligare.web.middleware.context import get_trace_id from starlette.datastructures import Address from starlette.types import ASGIApp, Receive, Scope, Send from typing_extensions import TypedDict, final @@ -123,148 +114,19 @@ }, ) -CorrelationId = NewType("CorrelationId", str) -RequestId = NewType("RequestId", str) - -CORRELATION_ID_CTX_KEY = "correlationId" -REQUEST_ID_CTX_KEY = "requestId" - -_correlation_id_ctx_var: ContextVar[CorrelationId | None] = ContextVar( - CORRELATION_ID_CTX_KEY, default=None -) -_request_id_ctx_var: ContextVar[RequestId | None] = ContextVar( - REQUEST_ID_CTX_KEY, default=None -) - - -class TraceId(NamedTuple): - CorrelationId: CorrelationId | None - RequestId: RequestId | None - - -def get_trace_id() -> TraceId: - return TraceId(_correlation_id_ctx_var.get(), _request_id_ctx_var.get()) - - -@final -class CorrelationIdMiddleware: - """ - Generate a Correlation ID for each request. - - https://github.com/encode/starlette/issues/420 - """ - - def __init__( - self, - app: ASGIApp, - ) -> None: - self.app = app - - async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: - if scope["type"] not in ["http", "websocket"]: - await self.app(scope, receive, send) - return - - correlation_id = _correlation_id_ctx_var.set(CorrelationId(str(uuid4()))) - - await self.app(scope, receive, send) - - _correlation_id_ctx_var.reset(correlation_id) - - -@final -class RequestIdMiddleware: - """ - Generate a Trace ID for each request. - If X-Correlation-Id is set in the request headers, that ID is used instead. - """ - - _app: ASGIApp - - def __init__(self, app: ASGIApp): - super().__init__() - self._app = app - - @inject - async def __call__( - self, scope: Scope, receive: Receive, send: Send, log: Logger - ) -> None: - if scope["type"] not in ["http", "websocket"]: - return await self._app(scope, receive, send) - - # extract the request ID from the request headers if it is set - - request = cast(MiddlewareRequestDict, scope) - request_headers = request.get("headers") - - content_type = utils.extract_content_type(request_headers) - _, encoding = utils.split_content_type(content_type) - if encoding is None: - encoding = "utf-8" - try: - request_id_header_encoded = REQUEST_ID_HEADER.lower().encode(encoding) - - request_id: bytes | None = next( - ( - request_id - for (header, request_id) in request_headers - if header == request_id_header_encoded - ), - None, - ) - - if request_id: - # validate format - request_id_decoded = request_id.decode(encoding) - _ = uuid.UUID(request_id_decoded) - request_id_token = _request_id_ctx_var.set( - RequestId(request_id_decoded) - ) - else: - request_id_decoded = str(uuid4()) - request_id = request_id_decoded.encode(encoding) - request_headers.append(( - request_id_header_encoded, - request_id, - )) - request_id_token = _request_id_ctx_var.set( - RequestId(request_id_decoded) - ) - log.info( - f'Generated new UUID "{request_id}" for {REQUEST_ID_HEADER} request header.' - ) - except ValueError as e: - log.warning(f"Badly formatted {REQUEST_ID_HEADER} received in request.") - raise e - - async def wrapped_send(message: Any) -> None: - nonlocal scope - nonlocal send - - if message["type"] != "http.response.start": - return await send(message) - - # include the request ID in response headers - - response = cast(MiddlewareResponseDict, message) - response_headers = response["headers"] - - content_type = utils.extract_content_type(response_headers) - _, encoding = utils.split_content_type(content_type) - if encoding is None: - encoding = "utf-8" - - response_headers.append(( - request_id_header_encoded, - request_id, - )) - - return await send(message) - - await self._app(scope, receive, wrapped_send) - - _request_id_ctx_var.reset(request_id_token) +def headers_as_dict( + request_response: MiddlewareRequestDict | MiddlewareResponseDict, +) -> dict[str, str]: + if ( + isinstance(request_response, dict) # pyright: ignore[reportUnnecessaryIsInstance] + and "headers" in request_response.keys() + ): + return { + key: value for (key, value) in decode_headers(request_response["headers"]) + } + else: + raise Exception("Unable to extract headers from request when logging request.") def _get_correlation_id( @@ -281,11 +143,11 @@ def _get_correlation_id_from_headers( request: MiddlewareRequestDict, response: MiddlewareResponseDict, log: Logger ) -> str: try: - headers = _headers_as_dict(request) + headers = headers_as_dict(request) correlation_id = headers.get(REQUEST_ID_HEADER.lower()) if not correlation_id: - headers = _headers_as_dict(response) + headers = headers_as_dict(response) correlation_id = headers.get(REQUEST_ID_HEADER.lower()) if correlation_id: @@ -304,20 +166,6 @@ def _get_correlation_id_from_headers( raise e -def _headers_as_dict( - request_response: MiddlewareRequestDict | MiddlewareResponseDict, -): - if ( - isinstance(request_response, dict) # pyright: ignore[reportUnnecessaryIsInstance] - and "headers" in request_response.keys() - ): - return { - key: value for (key, value) in decode_headers(request_response["headers"]) - } - else: - raise Exception("Unable to extract headers from request when logging request.") - - @inject def _log_all_api_requests( request: MiddlewareRequestDict, @@ -325,7 +173,7 @@ def _log_all_api_requests( config: Config, log: Logger, ): - request_headers_safe: dict[str, str] = _headers_as_dict(request) + request_headers_safe: dict[str, str] = headers_as_dict(request) correlation_id = get_trace_id().CorrelationId @@ -367,7 +215,7 @@ def _log_all_api_requests( def _wrap_all_api_responses(response: MiddlewareResponseDict, config: Config): correlation_id = get_trace_id().CorrelationId - response_headers = _headers_as_dict(response) + response_headers = headers_as_dict(response) response_headers[REQUEST_ID_HEADER] = str(correlation_id) @@ -392,7 +240,7 @@ def _log_all_api_responses( config: Config, log: Logger, ): - response_headers_safe: dict[str, str] = _headers_as_dict(response) + response_headers_safe: dict[str, str] = headers_as_dict(response) correlation_id = _get_correlation_id(request, response, log) diff --git a/src/web/Ligare/web/testing/create_app.py b/src/web/Ligare/web/testing/create_app.py index 8821b485..fb135941 100644 --- a/src/web/Ligare/web/testing/create_app.py +++ b/src/web/Ligare/web/testing/create_app.py @@ -23,7 +23,6 @@ ) from unittest.mock import AsyncMock, MagicMock, NonCallableMagicMock -import json_logging import pytest from _pytest.fixtures import SubRequest from connexion import FlaskApp @@ -40,6 +39,7 @@ from Ligare.platform.identity.user_loader import TRole, UserId, UserMixin from Ligare.programming.collections.dict import NestedDict from Ligare.programming.config import AbstractConfig, ConfigBuilder +from Ligare.programming.patterns.dependency_injection import JSONFormatter from Ligare.programming.str import get_random_str from Ligare.web.application import ( ApplicationBuilder, @@ -340,16 +340,11 @@ def _client_getter( # then tells pytest to use it for every test in the class @pytest.fixture(autouse=True) def setup_method_fixture(self, mocker: MockerFixture): - setup_artifacts = self._pre_test_setup(mocker) + self._pre_test_setup(mocker) yield - self._post_test_teardown(setup_artifacts) + self._post_test_teardown() def _pre_test_setup(self, mocker: MockerFixture): - # the pytest log formatters need to be restored - # in the event json_logging changes them, otherwise - # some tests may fail - log_formatters = [handler.formatter for handler in logging.getLogger().handlers] - self._automatic_mocks = {} mock_targets: list[tuple[str] | tuple[str, Any]] = [ @@ -372,18 +367,16 @@ def _pre_test_setup(self, mocker: MockerFixture): self._automatic_mocks[target_name] = mock - return log_formatters - - def _post_test_teardown(self, log_formatters: list[logging.Formatter | None]): + def _post_test_teardown(self): self._automatic_mocks = {} - for i, handler in enumerate(logging.getLogger().handlers): - # this assumes handlers are in the same order - # so is prone to breakage - handler.formatter = log_formatters[i] - - # json_logging relies on global, so they must be reset between each test - _ = importlib.reload(json_logging) + handlers = logging.getLogger().handlers + for handler in [ + handler + for handler in handlers + if isinstance(handler.formatter, JSONFormatter) + ]: + handlers.remove(handler) class CreateFlaskApp(CreateApp[Flask]): @@ -477,6 +470,9 @@ def _flask_client( yield ClientInjector(client, result.app_injector.flask_injector) + def get_app(self, flask_app_getter: AppGetter[Flask]): + return next(self._flask_client(flask_app_getter)) + @pytest.fixture() def flask_client(self, _get_basic_flask_app: FlaskAppResult) -> FlaskClientInjector: return next(self._flask_client(lambda: _get_basic_flask_app)) @@ -585,7 +581,6 @@ def _get_openapi_app( mocker: MockerFixture, openapi_mock_controller: OpenAPIMockController, ) -> OpenAPIAppResult: - _ = mocker.patch("Ligare.web.application.json_logging") openapi_mock_controller.begin() return next(self._get_real_openapi_app(openapi_config, mocker)) @@ -678,8 +673,6 @@ def openapi_client( def openapi_client_configurable( self, mocker: MockerFixture ) -> OpenAPIClientInjectorConfigurable: - # FIXME some day json_logging needs to be fixed - _ = mocker.patch("Ligare.web.application.json_logging") return self._client_configurable( mocker, self._get_real_openapi_app, self._openapi_client ) diff --git a/src/web/test/unit/application/test_create_flask_app.py b/src/web/test/unit/application/test_create_flask_app.py index d82d2a7d..9a5cac59 100644 --- a/src/web/test/unit/application/test_create_flask_app.py +++ b/src/web/test/unit/application/test_create_flask_app.py @@ -26,7 +26,6 @@ def test__CreateFlaskApp__configure_blueprint_routes__requires_flask_config(self def test__CreateFlaskApp__configure_blueprint_routes__creates_flask_app_using_config( self, mocker: MockerFixture ): - _ = mocker.patch("Ligare.web.application.json_logging") flask_mock = mocker.patch("Ligare.web.application.Flask") app_name = f"{TestCreateFlaskApp.test__CreateFlaskApp__configure_blueprint_routes__creates_flask_app_using_config.__name__}-app_name" diff --git a/src/web/test/unit/application/test_create_openapi_app.py b/src/web/test/unit/application/test_create_openapi_app.py index daf593c5..0039f800 100644 --- a/src/web/test/unit/application/test_create_openapi_app.py +++ b/src/web/test/unit/application/test_create_openapi_app.py @@ -18,7 +18,6 @@ def test__CreateOpenAPIApp__configure_openapi__requires_flask_config(self): def test__CreateOpenAPIApp__configure_openapi__creates_flask_app_using_config( self, mocker: MockerFixture ): - _ = mocker.patch("Ligare.web.application.json_logging") connexion_mock = mocker.patch("Ligare.web.application.FlaskApp") app_name = f"{TestCreateOpenAPIApp.test__CreateOpenAPIApp__configure_openapi__creates_flask_app_using_config.__name__}-app_name" diff --git a/src/web/test/unit/middleware/test_flask_middleware.py b/src/web/test/unit/middleware/test_flask_middleware.py index 3d60476c..352da2a4 100644 --- a/src/web/test/unit/middleware/test_flask_middleware.py +++ b/src/web/test/unit/middleware/test_flask_middleware.py @@ -6,7 +6,7 @@ from flask import Flask, Response, abort from Ligare.web.config import Config from Ligare.web.middleware import bind_errorhandler -from Ligare.web.middleware.consts import CORRELATION_ID_HEADER +from Ligare.web.middleware.consts import REQUEST_ID_HEADER from Ligare.web.middleware.flask import ( _get_correlation_id, # pyright: ignore[reportPrivateUsage] ) @@ -34,8 +34,8 @@ def test___register_api_response_handlers__sets_correlation_id_response_header_w flask_client = next(flask_client_configurable(basic_config)) response = flask_client.client.get("/") - assert response.headers[CORRELATION_ID_HEADER] - _ = uuid.UUID(response.headers[CORRELATION_ID_HEADER]) + assert response.headers[REQUEST_ID_HEADER] + _ = uuid.UUID(response.headers[REQUEST_ID_HEADER]) @pytest.mark.parametrize("format", ["plaintext", "JSON"]) def test___register_api_response_handlers__sets_correlation_id_response_header_when_set_in_request_header( @@ -48,10 +48,10 @@ def test___register_api_response_handlers__sets_correlation_id_response_header_w flask_client = next(flask_client_configurable(basic_config)) correlation_id = str(uuid4()) response = flask_client.client.get( - "/", headers={CORRELATION_ID_HEADER: correlation_id} + "/", headers={REQUEST_ID_HEADER: correlation_id} ) - assert response.headers[CORRELATION_ID_HEADER] == correlation_id + assert response.headers[REQUEST_ID_HEADER] == correlation_id @pytest.mark.parametrize("format", ["plaintext", "JSON"]) def test___get_correlation_id__validates_correlation_id_when_set_in_request_headers( @@ -63,7 +63,7 @@ def test___get_correlation_id__validates_correlation_id_when_set_in_request_head basic_config.logging.format = format correlation_id = "abc123" with flask_request_configurable( - basic_config, {"headers": {CORRELATION_ID_HEADER: correlation_id}} + basic_config, {"headers": {REQUEST_ID_HEADER: correlation_id}} ): with pytest.raises( ValueError, match="^badly formed hexadecimal UUID string$" @@ -80,7 +80,7 @@ def test___get_correlation_id__uses_existing_correlation_id_when_set_in_request_ basic_config.logging.format = format correlation_id = str(uuid4()) with flask_request_configurable( - basic_config, {"headers": {CORRELATION_ID_HEADER: correlation_id}} + basic_config, {"headers": {REQUEST_ID_HEADER: correlation_id}} ): returned_correlation_id = _get_correlation_id(MagicMock()) assert correlation_id == returned_correlation_id diff --git a/src/web/test/unit/middleware/test_openapi_middleware.py b/src/web/test/unit/middleware/test_openapi_middleware.py index af27bc93..dafce3e9 100644 --- a/src/web/test/unit/middleware/test_openapi_middleware.py +++ b/src/web/test/unit/middleware/test_openapi_middleware.py @@ -13,7 +13,7 @@ from Ligare.web.application import OpenAPIAppResult from Ligare.web.config import Config from Ligare.web.middleware import bind_errorhandler -from Ligare.web.middleware.consts import CORRELATION_ID_HEADER +from Ligare.web.middleware.consts import REQUEST_ID_HEADER from Ligare.web.middleware.flask import ( _get_correlation_id, # pyright: ignore[reportPrivateUsage] ) @@ -44,8 +44,8 @@ def test___register_api_response_handlers__sets_correlation_id_response_header_w response = flask_client.client.get("/") - assert response.headers[CORRELATION_ID_HEADER] - _ = uuid.UUID(response.headers[CORRELATION_ID_HEADER]) + assert response.headers[REQUEST_ID_HEADER] + _ = uuid.UUID(response.headers[REQUEST_ID_HEADER]) @pytest.mark.parametrize("format", ["plaintext", "JSON"]) def test___register_api_response_handlers__sets_correlation_id_response_header_when_set_in_request_header( @@ -60,10 +60,10 @@ def test___register_api_response_handlers__sets_correlation_id_response_header_w flask_client = next(openapi_client_configurable(openapi_config)) correlation_id = str(uuid.uuid4()) response = flask_client.client.get( - "/", headers={CORRELATION_ID_HEADER: correlation_id} + "/", headers={REQUEST_ID_HEADER: correlation_id} ) - assert response.headers[CORRELATION_ID_HEADER] == correlation_id + assert response.headers[REQUEST_ID_HEADER] == correlation_id @pytest.mark.parametrize("format", ["plaintext", "JSON"]) def test___register_api_response_handler__validates_correlation_id_when_set_in_request_headers( @@ -79,7 +79,7 @@ def test___register_api_response_handler__validates_correlation_id_when_set_in_r flask_client = next(openapi_client_configurable(openapi_config)) response = flask_client.client.get( - "/", headers={CORRELATION_ID_HEADER: correlation_id} + "/", headers={REQUEST_ID_HEADER: correlation_id} ) assert response.status_code == 500 @@ -96,7 +96,7 @@ def test___get_correlation_id__validates_correlation_id_when_set_in_request_head correlation_id = "abc123" openapi_mock_controller.begin() with openapi_request_configurable( - openapi_config, {"headers": {CORRELATION_ID_HEADER: correlation_id}} + openapi_config, {"headers": {REQUEST_ID_HEADER: correlation_id}} ): with pytest.raises( ValueError, match="^badly formed hexadecimal UUID string$" @@ -115,7 +115,7 @@ def test___get_correlation_id__uses_existing_correlation_id_when_set_in_request_ correlation_id = str(uuid.uuid4()) openapi_mock_controller.begin() with openapi_request_configurable( - openapi_config, {"headers": {CORRELATION_ID_HEADER: correlation_id}} + openapi_config, {"headers": {REQUEST_ID_HEADER: correlation_id}} ): returned_correlation_id = _get_correlation_id(MagicMock()) assert correlation_id == returned_correlation_id diff --git a/src/web/typings/json_logging/__init__.pyi b/src/web/typings/json_logging/__init__.pyi deleted file mode 100644 index fde80768..00000000 --- a/src/web/typings/json_logging/__init__.pyi +++ /dev/null @@ -1,166 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import json -import logging -import sys -import traceback -import uuid -from datetime import datetime - -import json_logging.framework.connexion as connexion_support -import json_logging.framework.fastapi as fastapi_support -import json_logging.framework.flask as flask_support -import json_logging.framework.quart as quart_support -from json_logging import util -from json_logging.framework.sanic import ( - SanicAppConfigurator, - SanicAppRequestInstrumentationConfigurator, - SanicRequestAdapter, - SanicResponseAdapter, -) -from json_logging.framework_base import ( - AppRequestInstrumentationConfigurator, - FrameworkConfigurator, - RequestAdapter, - ResponseAdapter, -) -from json_logging.util import get_library_logger, is_env_var_toggle - -CORRELATION_ID_GENERATOR = ... -ENABLE_JSON_LOGGING = ... -if is_env_var_toggle("ENABLE_JSON_LOGGING"): - ENABLE_JSON_LOGGING = ... -ENABLE_JSON_LOGGING_DEBUG = ... -EMPTY_VALUE = ... -CREATE_CORRELATION_ID_IF_NOT_EXISTS = ... -JSON_SERIALIZER = ... -CORRELATION_ID_HEADERS = ... -COMPONENT_ID = ... -COMPONENT_NAME = ... -COMPONENT_INSTANCE_INDEX = ... -_framework_support_map = ... -_current_framework = ... -_logger = ... -_request_util = ... -_default_formatter = ... - -def get_correlation_id(request=...) -> str: - """ - Get current request correlation-id. If one is not present, a new one might be generated - depends on CREATE_CORRELATION_ID_IF_NOT_EXISTS setting value. - - :return: correlation-id string - """ - ... - -def register_framework_support( - name, - app_configurator, - app_request_instrumentation_configurator, - request_adapter_class, - response_adapter_class, -): # -> None: - """ - register support for a framework - - :param name: name of framework - :param app_configurator: app pre-configurator class - :param app_request_instrumentation_configurator: app configurator class - :param request_adapter_class: request adapter class - :param response_adapter_class: response adapter class - """ - ... - -def config_root_logger(): # -> None: - """ - You must call this if you are using root logger. - Make all root logger' handlers produce JSON format - & remove duplicate handlers for request instrumentation logging. - Please made sure that you call this after you called "logging.basicConfig() or logging.getLogger() - """ - ... - -def init_non_web(*args, **kw): # -> None: - ... - -def init_request_instrument( - app=..., custom_formatter=..., exclude_url_patterns=... -): # -> None: - """ - Configure the request instrumentation logging configuration for given web app. Must be called after init method - - If **custom_formatter** is passed, it will use this formatter over the default. - - :param app: current web application instance - :param custom_formatter: formatter to override default JSONRequestLogFormatter. - """ - ... - -def get_request_logger(): ... - -class RequestInfo(dict): - """ - class that keep HTTP request information for request instrumentation logging - """ - - def __init__(self, request, **kwargs) -> None: ... - def update_response_status(self, response): # -> None: - """ - update response information into this object, must be called before invoke request logging statement - :param response: - """ - ... - -class BaseJSONFormatter(logging.Formatter): - """ - Base class for JSON formatters - """ - - base_object_common = ... - def __init__(self, *args, **kw) -> None: ... - def format(self, record): # -> str: - ... - -class JSONRequestLogFormatter(BaseJSONFormatter): - """ - Formatter for HTTP request instrumentation logging - """ - - ... - -class JSONLogFormatter(BaseJSONFormatter): - """ - Formatter for non-web application log - """ - - def get_exc_fields(self, record): # -> dict[str, Unknown]: - ... - @classmethod - def format_exception(cls, exc_info): # -> str: - ... - -class JSONLogWebFormatter(JSONLogFormatter): - """ - Formatter for web application log - """ - - ... - -def init_flask(custom_formatter=..., enable_json=...): # -> None: - ... - -def init_sanic(custom_formatter=..., enable_json=...): # -> None: - ... - -def init_quart(custom_formatter=..., enable_json=...): # -> None: - ... - -def init_connexion(custom_formatter=..., enable_json=...): # -> None: - ... - -if fastapi_support.is_fastapi_present(): ... - -def init_fastapi(custom_formatter=..., enable_json=...): # -> None: - ... diff --git a/src/web/typings/json_logging/framework/__init__.pyi b/src/web/typings/json_logging/framework/__init__.pyi deleted file mode 100644 index 006bc274..00000000 --- a/src/web/typings/json_logging/framework/__init__.pyi +++ /dev/null @@ -1,4 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - diff --git a/src/web/typings/json_logging/framework/connexion/__init__.pyi b/src/web/typings/json_logging/framework/connexion/__init__.pyi deleted file mode 100644 index ee1483c2..00000000 --- a/src/web/typings/json_logging/framework/connexion/__init__.pyi +++ /dev/null @@ -1,86 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import logging -import sys -import json_logging -import json_logging.framework -import connexion as connexion -import flask as flask -from json_logging import JSONLogWebFormatter -from json_logging.framework_base import AppRequestInstrumentationConfigurator, RequestAdapter, ResponseAdapter -from json_logging.util import is_not_match_any_pattern -from connexion import request as request_obj -from flask import g - -def is_connexion_present(): # -> bool: - ... - -if is_connexion_present(): - _current_request = ... - _connexion = connexion - _flask = flask -class ConnexionAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator): - def config(self, app, exclude_url_patterns=...): # -> None: - ... - - - -class ConnexionRequestAdapter(RequestAdapter): - @staticmethod - def get_request_class_type(): - ... - - @staticmethod - def support_global_request_object(): # -> Literal[True]: - ... - - @staticmethod - def get_current_request(): # -> LocalProxy[ConnexionRequest]: - ... - - def get_remote_user(self, request): # -> Literal['-']: - ... - - def get_http_header(self, request, header_name, default=...): # -> None: - ... - - def set_correlation_id(self, request_, value): # -> None: - ... - - def get_correlation_id_in_request_context(self, request): # -> None: - ... - - def get_protocol(self, request): - ... - - def get_path(self, request): - ... - - def get_content_length(self, request): - ... - - def get_method(self, request): - ... - - def get_remote_ip(self, request): - ... - - def get_remote_port(self, request): - ... - - - -class ConnexionResponseAdapter(ResponseAdapter): - def get_status_code(self, response): - ... - - def get_response_size(self, response): - ... - - def get_content_type(self, response): - ... - - - diff --git a/src/web/typings/json_logging/framework/fastapi/__init__.pyi b/src/web/typings/json_logging/framework/fastapi/__init__.pyi deleted file mode 100644 index e8d9a8c3..00000000 --- a/src/web/typings/json_logging/framework/fastapi/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from .implementation import FastAPIAppRequestInstrumentationConfigurator, FastAPIRequestAdapter, FastAPIResponseAdapter - -def is_fastapi_present(): # -> bool: - ... - -if is_fastapi_present(): - ... diff --git a/src/web/typings/json_logging/framework/fastapi/implementation.pyi b/src/web/typings/json_logging/framework/fastapi/implementation.pyi deleted file mode 100644 index e281ec09..00000000 --- a/src/web/typings/json_logging/framework/fastapi/implementation.pyi +++ /dev/null @@ -1,84 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import starlette.requests -import starlette.responses -from json_logging.framework_base import AppRequestInstrumentationConfigurator, RequestAdapter, ResponseAdapter -from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint -from starlette.requests import Request -from starlette.responses import Response -from starlette.types import ASGIApp - -class JSONLoggingASGIMiddleware(BaseHTTPMiddleware): - def __init__(self, app: ASGIApp, exclude_url_patterns=...) -> None: - ... - - async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: - ... - - - -class FastAPIAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator): - def config(self, app, exclude_url_patterns=...): # -> None: - ... - - - -class FastAPIRequestAdapter(RequestAdapter): - @staticmethod - def get_request_class_type(): # -> type[Request]: - ... - - @staticmethod - def support_global_request_object(): # -> Literal[False]: - ... - - @staticmethod - def get_current_request(): - ... - - def get_remote_user(self, request: starlette.requests.Request): # -> Any | Literal['-']: - ... - - def get_http_header(self, request: starlette.requests.Request, header_name, default=...): # -> str | None: - ... - - def set_correlation_id(self, request_, value): # -> None: - ... - - def get_correlation_id_in_request_context(self, request: starlette.requests.Request): # -> Any | None: - ... - - def get_protocol(self, request: starlette.requests.Request): # -> str: - ... - - def get_path(self, request: starlette.requests.Request): # -> str: - ... - - def get_content_length(self, request: starlette.requests.Request): # -> str: - ... - - def get_method(self, request: starlette.requests.Request): # -> str: - ... - - def get_remote_ip(self, request: starlette.requests.Request): # -> str: - ... - - def get_remote_port(self, request: starlette.requests.Request): # -> int: - ... - - - -class FastAPIResponseAdapter(ResponseAdapter): - def get_status_code(self, response: starlette.responses.Response): # -> int: - ... - - def get_response_size(self, response: starlette.responses.Response): # -> str: - ... - - def get_content_type(self, response: starlette.responses.Response): # -> str: - ... - - - diff --git a/src/web/typings/json_logging/framework/flask/__init__.pyi b/src/web/typings/json_logging/framework/flask/__init__.pyi deleted file mode 100644 index 2f2c2489..00000000 --- a/src/web/typings/json_logging/framework/flask/__init__.pyi +++ /dev/null @@ -1,81 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import logging -import json_logging -import json_logging.framework -import flask as flask -from json_logging.framework_base import AppRequestInstrumentationConfigurator, RequestAdapter, ResponseAdapter -from json_logging.util import is_not_match_any_pattern -from flask import request as request_obj - -def is_flask_present(): # -> bool: - ... - -if is_flask_present(): - _current_request = ... - _flask = flask -class FlaskAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator): - def config(self, app, exclude_url_patterns=...): # -> None: - ... - - - -class FlaskRequestAdapter(RequestAdapter): - @staticmethod - def get_request_class_type(): - ... - - @staticmethod - def support_global_request_object(): # -> Literal[True]: - ... - - @staticmethod - def get_current_request(): # -> Request: - ... - - def get_remote_user(self, request): # -> Literal['-']: - ... - - def get_http_header(self, request, header_name, default=...): # -> None: - ... - - def set_correlation_id(self, request_, value): # -> None: - ... - - def get_correlation_id_in_request_context(self, request): # -> Any | None: - ... - - def get_protocol(self, request): - ... - - def get_path(self, request): - ... - - def get_content_length(self, request): - ... - - def get_method(self, request): - ... - - def get_remote_ip(self, request): - ... - - def get_remote_port(self, request): - ... - - - -class FlaskResponseAdapter(ResponseAdapter): - def get_status_code(self, response): - ... - - def get_response_size(self, response): - ... - - def get_content_type(self, response): - ... - - - diff --git a/src/web/typings/json_logging/framework/quart/__init__.pyi b/src/web/typings/json_logging/framework/quart/__init__.pyi deleted file mode 100644 index 46f1a198..00000000 --- a/src/web/typings/json_logging/framework/quart/__init__.pyi +++ /dev/null @@ -1,83 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import logging -import sys -import json_logging -import json_logging.framework -import quart as quart -from json_logging import JSONLogWebFormatter -from json_logging.framework_base import AppRequestInstrumentationConfigurator, RequestAdapter, ResponseAdapter -from json_logging.util import is_not_match_any_pattern -from quart import request as request_obj - -def is_quart_present(): # -> bool: - ... - -if is_quart_present(): - _current_request = ... - _quart = quart -class QuartAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator): - def config(self, app, exclude_url_patterns=...): # -> None: - ... - - - -class QuartRequestAdapter(RequestAdapter): - @staticmethod - def get_request_class_type(): - ... - - @staticmethod - def support_global_request_object(): # -> Literal[True]: - ... - - @staticmethod - def get_current_request(): - ... - - def get_remote_user(self, request): # -> Literal['-']: - ... - - def get_http_header(self, request, header_name, default=...): # -> None: - ... - - def set_correlation_id(self, request_, value): # -> None: - ... - - def get_correlation_id_in_request_context(self, request): # -> None: - ... - - def get_protocol(self, request): - ... - - def get_path(self, request): - ... - - def get_content_length(self, request): - ... - - def get_method(self, request): - ... - - def get_remote_ip(self, request): - ... - - def get_remote_port(self, request): # -> None: - ... - - - -class QuartResponseAdapter(ResponseAdapter): - def get_status_code(self, response): - ... - - def get_response_size(self, response): - ... - - def get_content_type(self, response): - ... - - - diff --git a/src/web/typings/json_logging/framework/sanic/__init__.pyi b/src/web/typings/json_logging/framework/sanic/__init__.pyi deleted file mode 100644 index f65b0e32..00000000 --- a/src/web/typings/json_logging/framework/sanic/__init__.pyi +++ /dev/null @@ -1,87 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import logging -import logging.config -import sys -import json_logging -import json_logging.framework -from json_logging.framework_base import AppRequestInstrumentationConfigurator, FrameworkConfigurator, RequestAdapter, ResponseAdapter -from json_logging.util import is_not_match_any_pattern - -def is_sanic_present(): # -> bool: - ... - -class SanicAppConfigurator(FrameworkConfigurator): - def config(self): # -> None: - ... - - - -class SanicAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator): - def config(self, app, exclude_url_patterns=...): # -> None: - ... - - def get_request_logger(self): # -> Logger: - ... - - - -class SanicRequestAdapter(RequestAdapter): - @staticmethod - def get_current_request(): - ... - - @staticmethod - def support_global_request_object(): # -> Literal[False]: - ... - - @staticmethod - def get_request_class_type(): - ... - - def get_remote_user(self, request): # -> None: - ... - - def get_http_header(self, request, header_name, default=...): # -> None: - ... - - def set_correlation_id(self, request, value): # -> None: - ... - - def get_correlation_id_in_request_context(self, request): # -> None: - ... - - def get_protocol(self, request): # -> Literal['-']: - ... - - def get_path(self, request): - ... - - def get_content_length(self, request): # -> Literal['-']: - ... - - def get_method(self, request): - ... - - def get_remote_ip(self, request): - ... - - def get_remote_port(self, request): # -> Literal['-']: - ... - - - -class SanicResponseAdapter(ResponseAdapter): - def get_status_code(self, response): - ... - - def get_response_size(self, response): # -> Literal['-']: - ... - - def get_content_type(self, response): - ... - - - diff --git a/src/web/typings/json_logging/framework_base.pyi b/src/web/typings/json_logging/framework_base.pyi deleted file mode 100644 index 7bf72f00..00000000 --- a/src/web/typings/json_logging/framework_base.pyi +++ /dev/null @@ -1,200 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class RequestAdapter: - """ - Helper class help to extract logging-relevant information from HTTP request object - """ - def __new__(cls, *arg, **kwargs): # -> Self@RequestAdapter: - ... - - @staticmethod - def support_global_request_object(): - """ - whether current framework supports global request object like Flask - """ - ... - - @staticmethod - def get_current_request(): - """ - get current request, should only implement for framework that support global request object - """ - ... - - @staticmethod - def get_request_class_type(): - """ - class type of request object, only need to specify in case the framework dont support global request object - """ - ... - - def get_http_header(self, request, header_name, default=...): - """ - get HTTP header value given it value name - - :param request: request object - :param header_name: name of header - :param default: default value if header value is not present - :return: - """ - ... - - def get_remote_user(self, request): - """ - - :param request: request object - """ - ... - - def set_correlation_id(self, request, value): - """ - We can safely assume that request is valid request object.\n - Set correlation to request context. e.g Flask.g in Flask - Made sure that we can access it later from get_correlation_id_in_request_context given the same request. - - :param value: correlation id string - :param request: request object - """ - ... - - def get_correlation_id_in_request_context(self, request): - """ - We can safely assume that request is valid request object. - - :param request: request object - """ - ... - - def get_protocol(self, request): - """ - We can safely assume that request is valid request object.\n - Gets the request protocol (e.g. HTTP/1.1). - - :return: The request protocol or '-' if it cannot be determined - """ - ... - - def get_path(self, request): - """ - We can safely assume that request is valid request object.\n - Gets the request path. - - :return: the request path (e.g. /index.html) - """ - ... - - def get_content_length(self, request): - """ - We can safely assume that request is valid request object.\n - The content length of the request. - - :return: the content length of the request or '-' if it cannot be determined - """ - ... - - def get_method(self, request): - """ - We can safely assume that request is valid request object.\n - Gets the request method (e.g. GET, POST, etc.). - - :return: The request method or '-' if it cannot be determined - """ - ... - - def get_remote_ip(self, request): - """ - We can safely assume that request is valid request object.\n - Gets the remote ip of the request initiator. - - :return: An ip address or '-' if it cannot be determined - """ - ... - - def get_remote_port(self, request): - """ - We can safely assume that request is valid request object.\n - Gets the remote port of the request initiator. - - :return: A port or '-' if it cannot be determined - """ - ... - - - -class ResponseAdapter: - """ - Helper class help to extract logging-relevant information from HTTP response object - """ - def __new__(cls, *arg, **kwargs): # -> Self@ResponseAdapter: - ... - - def get_status_code(self, response): - """ - get response's integer status code - - :param response: response object - """ - ... - - def get_response_size(self, response): - """ - get response's size in bytes - - :param response: response object - """ - ... - - def get_content_type(self, response): - """ - get response's MIME/media type - - :param response: response object - """ - ... - - - -class FrameworkConfigurator: - """ - Class to perform logging configuration for given framework as needed, like disable built in request logging and other utils logging - """ - def __new__(cls, *args, **kw): # -> Self@FrameworkConfigurator: - ... - - def config(self): - """ - app logging configuration logic - """ - ... - - - -class AppRequestInstrumentationConfigurator: - """ - Class to perform request instrumentation logging configuration. Should at least contains: - 1- register before-request hook and create a RequestInfo object, store it to request context - 2- register after-request hook and update response to stored RequestInfo object - 3 - re-configure framework loggers. - NOTE: logger that is used to emit request instrumentation logs will need to assign to **self.request_logger** - """ - def __new__(cls, *args, **kw): # -> Self@AppRequestInstrumentationConfigurator: - ... - - def config(self, app, exclude_url_patterns=...): - """ - configuration logic - - :param app: - """ - ... - - def get_request_logger(self): - """ - get the current logger that is used to logger the request instrumentation information - """ - ... - - - diff --git a/src/web/typings/json_logging/util.pyi b/src/web/typings/json_logging/util.pyi deleted file mode 100644 index d3a8c8a8..00000000 --- a/src/web/typings/json_logging/util.pyi +++ /dev/null @@ -1,79 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -import sys - -def is_env_var_toggle(var_name): # -> bool: - ... - -def get_library_logger(logger_name): # -> Logger: - """ - - :param logger_name: name - :return: logger - """ - ... - -def update_formatter_for_loggers(loggers_iter, formatter): # -> None: - """ - :param formatter: - :param loggers_iter: - """ - ... - -def parse_int(input_int, default): # -> int: - ... - -def validate_subclass(subclass, superclass): # -> Literal[True]: - """ - - :param subclass - :param superclass - :return: bool - """ - ... - -_epoch = ... -def epoch_nano_second(datetime_): - ... - -def iso_time_format(datetime_): # -> str: - ... - -if hasattr(sys, '_getframe'): - currentframe = ... -else: - def currentframe(_no_of_go_up_level): - """Return the frame object for the caller's stack frame.""" - ... - -class RequestUtil: - """ - util for extract request's information - """ - def __new__(cls, *args, **kw): # -> Self@RequestUtil: - ... - - def get_correlation_id(self, request=..., within_formatter=...): # -> str: - """ - Gets the correlation id from the header of the request. \ - It tries to search from json_logging.CORRELATION_ID_HEADERS list, one by one.\n - If found no value, new id will be generated by default.\n - :param request: request object - :return: correlation id string - """ - ... - - def get_request_from_call_stack(self, within_formatter=...): # -> Any | None: - """ - - :return: get request object from call stack - """ - ... - - - -def is_not_match_any_pattern(path, patterns): # -> bool: - ... - diff --git a/typings/json_logging b/typings/json_logging deleted file mode 120000 index b84a22cd..00000000 --- a/typings/json_logging +++ /dev/null @@ -1 +0,0 @@ -../src/web/typings/json_logging/ \ No newline at end of file