Skip to content

Commit 25e63d7

Browse files
author
michael.yak
committed
Set response content-type application/vnd.spring-boot.actuator.v2+json
Responses sent by Pyctuator should set content-type to `application/vnd.spring-boot.actuator.v2+json` - the existing mechanism was invoked *after* requests were intercepted so the content-type was missing tom "HTTP Traces" in SBA.
1 parent 22a04b3 commit 25e63d7

File tree

7 files changed

+133
-156
lines changed

7 files changed

+133
-156
lines changed

pyctuator/httptrace/fastapi_http_tracer.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

pyctuator/httptrace/flask_http_tracer.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

pyctuator/impl/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SBA_V2_CONTENT_TYPE = "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
from collections import defaultdict
2+
from datetime import datetime
13
from http import HTTPStatus
2-
from typing import Optional, Dict, Callable, Awaitable
4+
from typing import Mapping, List, Callable
5+
from typing import Optional, Dict, Awaitable
36

47
from fastapi import APIRouter, FastAPI, Header
58
from pydantic import BaseModel
9+
from starlette.datastructures import Headers
610
from starlette.requests import Request
711
from starlette.responses import Response
812

913
from pyctuator.environment.environment_provider import EnvironmentData
1014
from pyctuator.health.health_provider import HealthSummary
11-
from pyctuator.httptrace.fastapi_http_tracer import FastApiHttpTracer
15+
from pyctuator.httptrace import TraceRecord, TraceRequest, TraceResponse
1216
from pyctuator.httptrace.http_tracer import Traces
13-
from pyctuator.logging.pyctuator_logging import LoggersData, LoggerLevels
14-
from pyctuator.metrics.metrics_provider import Metric, MetricNames
17+
from pyctuator.impl import SBA_V2_CONTENT_TYPE
1518
from pyctuator.impl.pyctuator_impl import PyctuatorImpl, AppInfo
1619
from pyctuator.impl.pyctuator_router import PyctuatorRouter, EndpointsData
20+
from pyctuator.logging.pyctuator_logging import LoggersData, LoggerLevels
21+
from pyctuator.metrics.metrics_provider import Metric, MetricNames
1722
from pyctuator.threads.thread_dump_provider import ThreadDump
1823

1924

@@ -24,6 +29,7 @@ class FastApiLoggerItem(BaseModel):
2429
# pylint: disable=too-many-locals
2530
class FastApiPyctuator(PyctuatorRouter):
2631

32+
# pylint: disable=unused-variable
2733
def __init__(
2834
self,
2935
app: FastAPI,
@@ -32,10 +38,8 @@ def __init__(
3238
) -> None:
3339
super().__init__(app, pyctuator_impl)
3440
router = APIRouter()
35-
self.fastapi_http_tracer = FastApiHttpTracer(pyctuator_impl.http_tracer)
3641

3742
@router.get("/", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
38-
# pylint: disable=unused-variable
3943
def get_endpoints() -> EndpointsData:
4044
return self.get_endpoints_data()
4145

@@ -49,7 +53,6 @@ def get_endpoints() -> EndpointsData:
4953
@router.options("/logfile", include_in_schema=include_in_openapi_schema)
5054
@router.options("/trace", include_in_schema=include_in_openapi_schema)
5155
@router.options("/httptrace", include_in_schema=include_in_openapi_schema)
52-
# pylint: disable=unused-variable
5356
def options() -> None:
5457
"""
5558
Spring boot admin, after registration, issues multiple OPTIONS request to the monitored application in order
@@ -60,55 +63,45 @@ def options() -> None:
6063
"""
6164

6265
@router.get("/env", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
63-
# pylint: disable=unused-variable
6466
def get_environment() -> EnvironmentData:
6567
return pyctuator_impl.get_environment()
6668

6769
@router.get("/info", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
68-
# pylint: disable=unused-variable
6970
def get_info() -> AppInfo:
7071
return pyctuator_impl.app_info
7172

7273
@router.get("/health", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
73-
# pylint: disable=unused-variable
7474
def get_health() -> HealthSummary:
7575
return pyctuator_impl.get_health()
7676

7777
@router.get("/metrics", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
78-
# pylint: disable=unused-variable
7978
def get_metric_names() -> MetricNames:
8079
return pyctuator_impl.get_metric_names()
8180

8281
@router.get("/metrics/{metric_name}", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
83-
# pylint: disable=unused-variable
8482
def get_metric_measurement(metric_name: str) -> Metric:
8583
return pyctuator_impl.get_metric_measurement(metric_name)
8684

8785
# Retrieving All Loggers
8886
@router.get("/loggers", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
89-
# pylint: disable=unused-variable
9087
def get_loggers() -> LoggersData:
9188
return pyctuator_impl.logging.get_loggers()
9289

9390
@router.post("/loggers/{logger_name}", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
94-
# pylint: disable=unused-variable
9591
def set_logger_level(item: FastApiLoggerItem, logger_name: str) -> Dict:
9692
pyctuator_impl.logging.set_logger_level(logger_name, item.configuredLevel)
9793
return {}
9894

9995
@router.get("/loggers/{logger_name}", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
100-
# pylint: disable=unused-variable
10196
def get_logger(logger_name: str) -> LoggerLevels:
10297
return pyctuator_impl.logging.get_logger(logger_name)
10398

10499
@router.get("/dump", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
105100
@router.get("/threaddump", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
106-
# pylint: disable=unused-variable
107101
def get_thread_dump() -> ThreadDump:
108102
return pyctuator_impl.get_thread_dump()
109103

110104
@router.get("/logfile", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
111-
# pylint: disable=unused-variable
112105
def get_logfile(range_header: str = Header(default=None,
113106
alias="range")) -> Response: # pylint: disable=redefined-builtin
114107
if not range_header:
@@ -129,23 +122,50 @@ def get_logfile(range_header: str = Header(default=None,
129122

130123
@router.get("/trace", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
131124
@router.get("/httptrace", include_in_schema=include_in_openapi_schema, tags=["pyctuator"])
132-
# pylint: disable=unused-variable
133125
def get_httptrace() -> Traces:
134126
return pyctuator_impl.http_tracer.get_httptrace()
135127

136128
@app.middleware("http")
137-
# pylint: disable=unused-variable
138-
async def record_httptrace(
129+
async def intercept_requests_and_responses(
139130
request: Request,
140-
call_next: Callable[[Request], Awaitable[Response]]) -> Response:
141-
return await self.fastapi_http_tracer.record_httptrace(request, call_next)
142-
143-
@app.middleware("http")
144-
# pylint: disable=unused-variable
145-
async def add_sba2_support(request: Request, call_next: Callable) -> Response:
131+
call_next: Callable[[Request], Awaitable[Response]]
132+
) -> Response:
133+
request_time = datetime.now()
146134
response: Response = await call_next(request)
147-
if request.url.path.startswith(pyctuator_impl.pyctuator_endpoint_path_prefix):
148-
response.headers["Content-Type"] = pyctuator_impl.sba_v2_content_type
135+
response_time = datetime.now()
136+
137+
# Set the SBA-V2 content type for responses from Pyctuator
138+
if request.url.path.startswith(self.pyctuator_impl.pyctuator_endpoint_path_prefix):
139+
response.headers["Content-Type"] = SBA_V2_CONTENT_TYPE
140+
141+
# Record the request and response
142+
new_record = self._create_record(request, response, request_time, response_time)
143+
self.pyctuator_impl.http_tracer.add_record(record=new_record)
144+
149145
return response
150146

151-
app.include_router(router, prefix=(pyctuator_impl.pyctuator_endpoint_path_prefix))
147+
app.include_router(router, prefix=pyctuator_impl.pyctuator_endpoint_path_prefix)
148+
149+
def _create_headers_dictionary(self, headers: Headers) -> Mapping[str, List[str]]:
150+
headers_dict: Mapping[str, List[str]] = defaultdict(list)
151+
for (key, value) in headers.items():
152+
headers_dict[key].append(value)
153+
return headers_dict
154+
155+
def _create_record(
156+
self,
157+
request: Request,
158+
response: Response,
159+
request_time: datetime,
160+
response_time: datetime,
161+
) -> TraceRecord:
162+
response_delta_time = response_time - request_time
163+
new_record: TraceRecord = TraceRecord(
164+
request_time,
165+
None,
166+
None,
167+
TraceRequest(request.method, str(request.url), self._create_headers_dictionary(request.headers)),
168+
TraceResponse(response.status_code, self._create_headers_dictionary(response.headers)),
169+
int(response_delta_time.microseconds / 1000),
170+
)
171+
return new_record

0 commit comments

Comments
 (0)