8000 extract log wrapper to class, add streaming log extra · localstack/localstack@7415771 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

8000
Appearance settings

Commit 7415771

Browse files
committed
extract log wrapper to class, add streaming log extra
1 parent 2c593b8 commit 7415771

File tree

2 files changed

+68
-61
lines changed

2 files changed

+68
-61
lines changed

localstack/aws/handlers/logging.py

Lines changed: 67 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Handlers for logging."""
2-
import functools
32
import logging
43
from functools import cached_property
54
from typing import Type
@@ -42,6 +41,61 @@ def __call__(
4241
self.logger.error("exception during call chain: %s", exception)
4342

4443

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+
4599
class ResponseLogger:
46100
def __call__(self, _: HandlerChain, context: RequestContext, response: Response):
47101
if context.request.path == "/health" or context.request.path == "/_localstack/health":
@@ -82,44 +136,6 @@ def _prepare_logger(self, logger: logging.Logger, formatter: Type):
82136
logger.addHandler(handler)
83137
return logger
84138

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-
123139
def _log(self, context: RequestContext, response: Response):
124140
aws_logger = self.aws_logger
125141
http_logger = self.http_logger
@@ -146,6 +162,7 @@ def _log(self, context: RequestContext, response: Response):
146162
# response
147163
"output_type": context.service_exception.code,
148164
"output": context.service_exception.message,
165+
"output_streaming": False,
149166
"response_headers": dict(response.headers),
150167
},
151168
)
@@ -167,28 +184,18 @@ def _log(self, context: RequestContext, response: Response):
167184
if context.operation.output_shape
168185
else "Response",
169186
"output": context.service_response,
187+
"output_streaming": context.operation.has_event_stream_output,
170188
"response_headers": dict(response.headers),
171189
},
172190
)
173191
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)

localstack/logging/format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def compress_logger_name(name: str, length: int) -> str:
108108
class TraceLoggingFormatter(logging.Formatter):
109109
aws_trace_log_format = (
110110
LOG_FORMAT
111-
+ "; %(input_type)s(%(input)s, headers=%(request_headers)s); %(output_type)s(%(output)s, headers=%(response_headers)s)"
111+
+ "; %(input_type)s(%(input)s, headers=%(request_headers)s); %(output_type)s(%(output)s, headers=%(response_headers)s, streaming=%(output_streaming)s)"
112112
)
113113

114114
def __init__(self):

0 commit comments

Comments
 (0)
0