1
1
"""Handlers for logging."""
2
- import functools
3
2
import logging
4
3
from functools import cached_property
5
4
from typing import Type
@@ -42,6 +41,61 @@ def __call__(
42
41
self .logger .error ("exception during call chain: %s" , exception )
43
42
44
43
44
+ class _ResponseChunkLogger :
45
+ # TODO also use this logger for other types (f.e. Kinesis' SubscribeToShard event streamed responses)
46
+ def __init__ (
47
+ self ,
48
+ logger : logging .Logger ,
49
+ request : Request ,
50
+ response_status : int ,
51
+ response_headers : Headers ,
52
+ streaming : bool ,
53
+ ):
54
+ """
55
+ :param logger: HTTP logger to log the request onto
56
+ :param request: HTTP request data (containing useful metadata like the HTTP method and path)
57
+ :param response_status: HTTP status of the response to log
58
+ :param response_headers: HTTP headers of the response to log
59
+ :param streaming: true if this instance will be used to log streaming responses
60
+ """
61
+ self .logger = logger
62
+ self .request = request
63
+ self .response_status = response_status
64
+ self .response_headers = response_headers
65
+ self .streaming = streaming
66
+
67
+ def __call__ (
68
+ self ,
69
+ response_data ,
70
+ ):
71
+ """
72
+ Logs a given HTTP response on a defined logger.
73
+ The given response data is returned by this function, which allows the usage as a log interceptor for streamed
74
+ response data.
75
+
76
+ :param response_data: HTTP body of the response to log
77
+ :return: response data
78
+ """
79
+ self .logger .info (
80
+ "%s %s => %d" ,
81
+ self .request .method ,
82
+ self .request .path ,
83
+ self .response_status ,
84
+ extra = {
85
+ # request
86
+ "input_type" : "Request" ,
87
+ "input" : restore_payload (self .request ),
88
+ "request_headers" : dict (self .request .headers ),
89
+ # response
90
+ "output_type" : "Response" ,
91
+ "output_streaming" : self .streaming ,
92
+ "output" : response_data ,
93
+ "response_headers" : dict (self .response_headers ),
94
+ },
95
+ )
96
+ return response_data
97
+
98
+
45
99
class ResponseLogger :
46
100
def __call__ (self , _ : HandlerChain , context : RequestContext , response : Response ):
47
101
if context .request .path == "/health" or context .request .path == "/_localstack/health" :
@@ -82,44 +136,6 @@ def _prepare_logger(self, logger: logging.Logger, formatter: Type):
82
136
logger .addHandler (handler )
83
137
return logger
84
138
85
- def _log_http_response (
86
- self ,
87
- logger : logging .Logger ,
88
- request : Request ,
89
- response_status : int ,
90
- response_headers : Headers ,
91
- response_data ,
92
- ):
93
- """
94
- Logs a given HTTP response on a given logger.
95
- The given response data is returned by this function, which allows the usage as a log interceptor for streamed
96
- response data.
97
-
98
- :param logger: HTTP logger to log the request onto
99
- :param request: HTTP request data (containing useful metadata like the HTTP method and path)
100
- :param response_status: HTTP status of the response to log
101
- :param response_headers: HTTP headers of the response to log
102
- :param response_data: HTTP body of the response to log
103
- :return: response data
104
- """
105
- logger .info (
106
- "%s %s => %d" ,
107
- request .method ,
108
- request .path ,
109
- response_status ,
110
- extra = {
111
- # request
112
- "input_type" : "Request" ,
113
- "input" : restore_payload (request ),
114
- "request_headers" : dict (request .headers ),
115
- # response
116
- "output_type" : "Response" ,
117
- "output" : response_data ,
118
- "response_headers" : dict (response_headers ),
119
- },
120
- )
121
- return response_data
122
-
123
139
def _log (self , context : RequestContext , response : Response ):
124
140
aws_logger = self .aws_logger
125
141
http_logger = self .http_logger
@@ -146,6 +162,7 @@ def _log(self, context: RequestContext, response: Response):
146
162
# response
147
163
"output_type" : context .service_exception .code ,
148
164
"output" : context .service_exception .message ,
165
+ "output_streaming" : False ,
149
166
"response_headers" : dict (response .headers ),
150
167
},
151
168
)
@@ -167,28 +184,18 @@ def _log(self, context: RequestContext, response: Response):
167
184
if context .operation .output_shape
168
185
else "Response" ,
169
186
"output" : context .service_response ,
187
+ "output_streaming" : context .operation .has_event_stream_output ,
170
188
"response_headers" : dict (response .headers ),
171
189
},
172
190
)
173
191
else :
174
- # log any other HTTP response
175
- if hasattr (response .response , "__iter__" ):
176
- # If the response is streamed, wrap the response data's iterator which logs all values when they are consumed
177
- log_partial = functools .partial (
178
- self ._log_http_response ,
179
- http_logger ,
180
- context .request ,
181
- response .status_code ,
182
- response .headers ,
183
- )
184
- wrapped_response_iterator = map (log_partial , response .response )
185
- response .set_response (wrapped_response_iterator )
186
- else :
187
- # If the response is synchronous, we log the data directly
188
- self ._log_http_response (
189
- http_logger ,
190
- context .request ,
191
- response .status_code ,
192
- response .headers ,
193
- response .data ,
194
- )
192
+ streaming = hasattr (response .response , "__iter__" )
193
+ response_chunk_logger = _ResponseChunkLogger (
194
+ logger = http_logger ,
195
+ request = context .request ,
196
+ response_status = response .status_code ,
197
+ response_headers = response .headers ,
198
+ streaming = streaming ,
199
+ )
200
+ wrapped_response_iterator = map (response_chunk_logger , response .response )
201
+ response .set_response (wrapped_response_iterator )
0 commit comments