Skip to content

Commit 27463b2

Browse files
committed
feat: add client debug logging support for unary-stream gRPC calls
1 parent a165ff3 commit 27463b2

File tree

17 files changed

+600
-109
lines changed

17 files changed

+600
-109
lines changed

gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,43 @@ def _get_http_options():
264264
{% endmacro %}
265265

266266

267+
{% macro unary_request_interceptor_common(service) %}
268+
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
269+
if logging_enabled: # pragma: NO COVER
270+
request_metadata = client_call_details.metadata
271+
if isinstance(request, proto.Message):
272+
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2293): Investigate if we can improve this logic
273+
or wait for next gen protobuf.
274+
#}
275+
request_payload = type(request).to_json(request)
276+
elif isinstance(request, google.protobuf.message.Message):
277+
request_payload = MessageToJson(request)
278+
else:
279+
request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
280+
281+
request_metadata = {
282+
key: value.decode("utf-8") if isinstance(value, bytes) else value
283+
for key, value in request_metadata
284+
}
285+
grpc_request = {
286+
"payload": request_payload,
287+
"requestMethod": "grpc",
288+
"metadata": dict(request_metadata),
289+
}
290+
method = str(client_call_details.method)
291+
_LOGGER.debug(
292+
f"Sending request for {method}",
293+
extra = {
294+
"serviceName": "{{ service.meta.address.proto }}",
295+
"rpcName": method,
296+
"request": grpc_request,
297+
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2275): logging `metadata` seems repetitive and may need to be cleaned up. We're including it within "request" for consistency with REST transport. #}
298+
"metadata": grpc_request["metadata"],
299+
},
300+
)
301+
{% endmacro %}
302+
303+
267304
{% macro prep_wrapped_messages_async_method(api, service) %}
268305
def _prep_wrapped_messages(self, client_info):
269306
""" Precompute the wrapped methods, overriding the base class method to use async wrappers."""

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{% extends '_base.py.j2' %}
22

3+
{% import "%namespace/%name_%version/%sub/services/%service/_shared_macros.j2" as shared_macros %}
4+
35
{% block content %}
46

57
import json
@@ -57,41 +59,14 @@ except ImportError: # pragma: NO COVER
5759
_LOGGER = std_logging.getLogger(__name__)
5860

5961

60-
class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER
61-
def intercept_unary_unary(self, continuation, client_call_details, request):
62-
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
63-
if logging_enabled: # pragma: NO COVER
64-
request_metadata = client_call_details.metadata
65-
if isinstance(request, proto.Message):
66-
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2293): Investigate if we can improve this logic
67-
or wait for next gen protobuf.
68-
#}
69-
request_payload = type(request).to_json(request)
70-
elif isinstance(request, google.protobuf.message.Message):
71-
request_payload = MessageToJson(request)
72-
else:
73-
request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
74-
75-
request_metadata = {
76-
key: value.decode("utf-8") if isinstance(value, bytes) else value
77-
for key, value in request_metadata
78-
}
79-
grpc_request = {
80-
"payload": request_payload,
81-
"requestMethod": "grpc",
82-
"metadata": dict(request_metadata),
83-
}
84-
_LOGGER.debug(
85-
f"Sending request for {client_call_details.method}",
86-
extra = {
87-
"serviceName": "{{ service.meta.address.proto }}",
88-
"rpcName": client_call_details.method,
89-
"request": grpc_request,
90-
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2275): logging `metadata` seems repetitive and may need to be cleaned up. We're including it within "request" for consistency with REST transport. #}
91-
"metadata": grpc_request["metadata"],
92-
},
93-
)
62+
class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor): # pragma: NO COVER
63+
def intercept_unary_stream(self, continuation, client_call_details, request):
64+
{{ shared_macros.unary_request_interceptor_common(service) }}
65+
response_it = continuation(client_call_details, request)
66+
return response_it
9467

68+
def intercept_unary_unary(self, continuation, client_call_details, request):
69+
{{ shared_macros.unary_request_interceptor_common(service) }}
9570
response = continuation(client_call_details, request)
9671
if logging_enabled: # pragma: NO COVER
9772
response_metadata = response.trailing_metadata()

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -62,40 +62,14 @@ except ImportError: # pragma: NO COVER
6262
_LOGGER = std_logging.getLogger(__name__)
6363

6464

65-
class _LoggingClientAIOInterceptor(grpc.aio.UnaryUnaryClientInterceptor): # pragma: NO COVER
66-
async def intercept_unary_unary(self, continuation, client_call_details, request):
67-
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
68-
if logging_enabled: # pragma: NO COVER
69-
request_metadata = client_call_details.metadata
70-
if isinstance(request, proto.Message):
71-
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2293): Investigate if we can improve this logic
72-
or wait for next gen protobuf.
73-
#}
74-
request_payload = type(request).to_json(request)
75-
elif isinstance(request, google.protobuf.message.Message):
76-
request_payload = MessageToJson(request)
77-
else:
78-
request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
65+
class _LoggingClientAIOInterceptor(grpc.aio.UnaryUnaryClientInterceptor, grpc.aio.UnaryStreamClientInterceptor): # pragma: NO COVER
66+
async def intercept_unary_stream(self, continuation, client_call_details, request):
67+
{{ shared_macros.unary_request_interceptor_common(service) }}
68+
response_it = await continuation(client_call_details, request)
69+
return response_it
7970

80-
request_metadata = {
81-
key: value.decode("utf-8") if isinstance(value, bytes) else value
82-
for key, value in request_metadata
83-
}
84-
grpc_request = {
85-
"payload": request_payload,
86-
"requestMethod": "grpc",
87-
"metadata": dict(request_metadata),
88-
}
89-
_LOGGER.debug(
90-
f"Sending request for {client_call_details.method}",
91-
extra = {
92-
"serviceName": "{{ service.meta.address.proto }}",
93-
"rpcName": str(client_call_details.method),
94-
"request": grpc_request,
95-
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2275): logging `metadata` seems repetitive and may need to be cleaned up. We're including it within "request" for consistency with REST transport.' #}
96-
"metadata": grpc_request["metadata"],
97-
},
98-
)
71+
async def intercept_unary_unary(self, continuation, client_call_details, request):
72+
{{ shared_macros.unary_request_interceptor_common(service) }}
9973
response = await continuation(client_call_details, request)
10074
if logging_enabled: # pragma: NO COVER
10175
response_metadata = await response.trailing_metadata()
@@ -325,6 +299,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
325299

326300
self._interceptor = _LoggingClientAIOInterceptor()
327301
self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
302+
self._grpc_channel._unary_stream_interceptors.append(self._interceptor)
328303
self._logged_channel = self._grpc_channel
329304
self._wrap_with_kind = "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
330305
# Wrap messages. This must be done after self._logged_channel exists

tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/grpc.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,41 @@
4545
_LOGGER = std_logging.getLogger(__name__)
4646

4747

48-
class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER
48+
class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor): # pragma: NO COVER
49+
def intercept_unary_stream(self, continuation, client_call_details, request):
50+
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
51+
if logging_enabled: # pragma: NO COVER
52+
request_metadata = client_call_details.metadata
53+
if isinstance(request, proto.Message):
54+
request_payload = type(request).to_json(request)
55+
elif isinstance(request, google.protobuf.message.Message):
56+
request_payload = MessageToJson(request)
57+
else:
58+
request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
59+
60+
request_metadata = {
61+
key: value.decode("utf-8") if isinstance(value, bytes) else value
62+
for key, value in request_metadata
63+
}
64+
grpc_request = {
65+
"payload": request_payload,
66+
"requestMethod": "grpc",
67+
"metadata": dict(request_metadata),
68+
}
69+
method = str(client_call_details.method)
70+
_LOGGER.debug(
71+
f"Sending request for {method}",
72+
extra = {
73+
"serviceName": "google.cloud.asset.v1.AssetService",
74+
"rpcName": method,
75+
"request": grpc_request,
76+
"metadata": grpc_request["metadata"],
77+
},
78+
)
79+
80+
response_it = continuation(client_call_details, request)
81+
return response_it
82+
4983
def intercept_unary_unary(self, continuation, client_call_details, request):
5084
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
5185
if logging_enabled: # pragma: NO COVER
@@ -66,11 +100,12 @@ def intercept_unary_unary(self, continuation, client_call_details, request):
66100
"requestMethod": "grpc",
67101
"metadata": dict(request_metadata),
68102
}
103+
method = str(client_call_details.method)
69104
_LOGGER.debug(
70-
f"Sending request for {client_call_details.method}",
105+
f"Sending request for {method}",
71106
extra = {
72107
"serviceName": "google.cloud.asset.v1.AssetService",
73-
"rpcName": client_call_details.method,
108+
"rpcName": method,
74109
"request": grpc_request,
75110
"metadata": grpc_request["metadata"],
76111
},

tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/grpc_asyncio.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,41 @@
4949
_LOGGER = std_logging.getLogger(__name__)
5050

5151

52-
class _LoggingClientAIOInterceptor(grpc.aio.UnaryUnaryClientInterceptor): # pragma: NO COVER
52+
class _LoggingClientAIOInterceptor(grpc.aio.UnaryUnaryClientInterceptor, grpc.aio.UnaryStreamClientInterceptor): # pragma: NO COVER
53+
async def intercept_unary_stream(self, continuation, client_call_details, request):
54+
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
55+
if logging_enabled: # pragma: NO COVER
56+
request_metadata = client_call_details.metadata
57+
if isinstance(request, proto.Message):
58+
request_payload = type(request).to_json(request)
59+
elif isinstance(request, google.protobuf.message.Message):
60+
request_payload = MessageToJson(request)
61+
else:
62+
request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
63+
64+
request_metadata = {
65+
key: value.decode("utf-8") if isinstance(value, bytes) else value
66+
for key, value in request_metadata
67+
}
68+
grpc_request = {
69+
"payload": request_payload,
70+
"requestMethod": "grpc",
71+
"metadata": dict(request_metadata),
72+
}
73+
method = str(client_call_details.method)
74+
_LOGGER.debug(
75+
f"Sending request for {method}",
76+
extra = {
77+
"serviceName": "google.cloud.asset.v1.AssetService",
78+
"rpcName": method,
79+
"request": grpc_request,
80+
"metadata": grpc_request["metadata"],
81+
},
82+
)
83+
84+
response_it = await continuation(client_call_details, request)
85+
return response_it
86+
5387
async def intercept_unary_unary(self, continuation, client_call_details, request):
5488
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
5589
if logging_enabled: # pragma: NO COVER
@@ -70,15 +104,17 @@ async def intercept_unary_unary(self, continuation, client_call_details, request
70104
"requestMethod": "grpc",
71105
"metadata": dict(request_metadata),
72106
}
107+
method = str(client_call_details.method)
73108
_LOGGER.debug(
74-
f"Sending request for {client_call_details.method}",
109+
f"Sending request for {method}",
75110
extra = {
76111
"serviceName": "google.cloud.asset.v1.AssetService",
77-
"rpcName": str(client_call_details.method),
112+
"rpcName": method,
78113
"request": grpc_request,
79114
"metadata": grpc_request["metadata"],
80115
},
81116
)
117+
82118
response = await continuation(client_call_details, request)
83119
if logging_enabled: # pragma: NO COVER
84120
response_metadata = await response.trailing_metadata()
@@ -302,6 +338,7 @@ def __init__(self, *,
302338

303339
self._interceptor = _LoggingClientAIOInterceptor()
304340
self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
341+
self._grpc_channel._unary_stream_interceptors.append(self._interceptor)
305342
self._logged_channel = self._grpc_channel
306343
self._wrap_with_kind = "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
307344
# Wrap messages. This must be done after self._logged_channel exists

tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/grpc.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,41 @@
4242
_LOGGER = std_logging.getLogger(__name__)
4343

4444

45-
class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER
45+
class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor): # pragma: NO COVER
46+
def intercept_unary_stream(self, continuation, client_call_details, request):
47+
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
48+
if logging_enabled: # pragma: NO COVER
49+
request_metadata = client_call_details.metadata
50+
if isinstance(request, proto.Message):
51+
request_payload = type(request).to_json(request)
52+
elif isinstance(request, google.protobuf.message.Message):
53+
request_payload = MessageToJson(request)
54+
else:
55+
request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
56+
57+
request_metadata = {
58+
key: value.decode("utf-8") if isinstance(value, bytes) else value
59+
for key, value in request_metadata
60+
}
61+
grpc_request = {
62+
"payload": request_payload,
63+
"requestMethod": "grpc",
64+
"metadata": dict(request_metadata),
65+
}
66+
method = str(client_call_details.method)
67+
_LOGGER.debug(
68+
f"Sending request for {method}",
69+
extra = {
70+
"serviceName": "google.iam.credentials.v1.IAMCredentials",
71+
"rpcName": method,
72+
"request": grpc_request,
73+
"metadata": grpc_request["metadata"],
74+
},
75+
)
76+
77+
response_it = continuation(client_call_details, request)
78+
return response_it
79+
4680
def intercept_unary_unary(self, continuation, client_call_details, request):
4781
logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
4882
if logging_enabled: # pragma: NO COVER
@@ -63,11 +97,12 @@ def intercept_unary_unary(self, continuation, client_call_details, request):
6397
"requestMethod": "grpc",
6498
"metadata": dict(request_metadata),
6599
}
100+
method = str(client_call_details.method)
66101
_LOGGER.debug(
67-
f"Sending request for {client_call_details.method}",
102+
f"Sending request for {method}",
68103
extra = {
69104
"serviceName": "google.iam.credentials.v1.IAMCredentials",
70-
"rpcName": client_call_details.method,
105+
"rpcName": method,
71106
"request": grpc_request,
72107
"metadata": grpc_request["metadata"],
73108
},

0 commit comments

Comments
 (0)