1+ from collections import defaultdict
2+ from datetime import datetime
13from 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
47from fastapi import APIRouter , FastAPI , Header
58from pydantic import BaseModel
9+ from starlette .datastructures import Headers
610from starlette .requests import Request
711from starlette .responses import Response
812
913from pyctuator .environment .environment_provider import EnvironmentData
1014from pyctuator .health .health_provider import HealthSummary
11- from pyctuator .httptrace . fastapi_http_tracer import FastApiHttpTracer
15+ from pyctuator .httptrace import TraceRecord , TraceRequest , TraceResponse
1216from 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
1518from pyctuator .impl .pyctuator_impl import PyctuatorImpl , AppInfo
1619from 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
1722from pyctuator .threads .thread_dump_provider import ThreadDump
1823
1924
@@ -24,6 +29,7 @@ class FastApiLoggerItem(BaseModel):
2429# pylint: disable=too-many-locals
2530class 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