44{% import "%namespace/%name_%version/%sub/services/%service/_shared_macros.j2" as shared_macros %}
55
66import inspect
7+ import json
8+ import pickle
9+ import logging as std_logging
710import warnings
811from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union
912
@@ -16,8 +19,11 @@ from google.api_core import operations_v1
1619{% endif %}
1720from google.auth import credentials as ga_credentials # type: ignore
1821from google.auth.transport.grpc import SslCredentials # type: ignore
22+ from google.protobuf.json_format import MessageToJson
23+ import google.protobuf.message
1924
2025import grpc # type: ignore
26+ import proto # type: ignores
2127from grpc.experimental import aio # type: ignore
2228
2329{% filter sort_lines %}
@@ -47,6 +53,76 @@ from google.longrunning import operations_pb2 # type: ignore
4753from .base import {{ service.name }}Transport, DEFAULT_CLIENT_INFO
4854from .grpc import {{ service.name }}GrpcTransport
4955
56+ try:
57+ from google.api_core import client_logging # type: ignore
58+ CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER
59+ except ImportError: # pragma: NO COVER
60+ CLIENT_LOGGING_SUPPORTED = False
61+
62+ _LOGGER = std_logging.getLogger(__name__)
63+
64+
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)}"
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": str(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+ )
94+ response = await continuation(client_call_details, request)
95+ if logging_enabled: # pragma: NO COVER
96+ response_metadata = await response.trailing_metadata()
97+ # Convert gRPC metadata `<class ' grpc.aio._metadata.Metadata' >` to list of tuples
98+ metadata = dict([(k, v) for k, v in response_metadata]) if response_metadata else None
99+ result = await response
100+ if isinstance(result, proto.Message):
101+ {# TODO(https://github.com/googleapis/gapic-generator-python/issues/2293): Investigate if we can improve this logic
102+ or wait for next gen protobuf.
103+ #}
104+ response_payload = type(result).to_json(result)
105+ elif isinstance(result, google.protobuf.message.Message):
106+ response_payload = MessageToJson(result)
107+ else:
108+ response_payload = f"{type(result).__name__}: {pickle.dumps(result)}"
109+ grpc_response = {
110+ "payload": response_payload,
111+ "metadata": metadata,
112+ "status": "OK",
113+ }
114+ _LOGGER.debug(
115+ f"Received response to rpc {client_call_details.method}.",
116+ extra = {
117+ "serviceName": "{{ service.meta.address.proto }}",
118+ "rpcName": str(client_call_details.method),
119+ "response": grpc_response,
120+ {# 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.' #}
121+ "metadata": grpc_response["metadata"],
122+ },
123+ )
124+ return response
125+
50126
51127class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
52128 """gRPC AsyncIO backend transport for {{ service.name }}.
@@ -171,7 +247,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
171247 google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
172248 and ``credentials_file`` are passed.
173249 """
174- self._grpc_channel = None
250+ self._logged_channel = None
175251 self._ssl_channel_credentials = ssl_channel_credentials
176252 self._stubs: Dict[str, Callable] = {}
177253 {% if service .has_lro %}
@@ -188,7 +264,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
188264 credentials = None
189265 self._ignore_credentials = True
190266 # If a channel was explicitly provided, set it.
191- self._grpc_channel = channel
267+ self._logged_channel = channel
192268 self._ssl_channel_credentials = None
193269 else:
194270 if api_mtls_endpoint:
@@ -223,10 +299,10 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
223299 api_audience=api_audience,
224300 )
225301
226- if not self._grpc_channel :
302+ if not self._logged_channel :
227303 # initialize with the provided callable or the default channel
228304 channel_init = channel or type(self).create_channel
229- self._grpc_channel = channel_init(
305+ self._logged_channel = channel_init(
230306 self._host,
231307 # use the credentials which are saved
232308 credentials=self._credentials,
@@ -242,8 +318,10 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
242318 ],
243319 )
244320
245- # Wrap messages. This must be done after self._grpc_channel exists
321+ self._interceptor = _LoggingClientAIOInterceptor()
322+ self._logged_channel._unary_unary_interceptors.append(self._interceptor)
246323 self._wrap_with_kind = "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
324+ # Wrap messages. This must be done after self._logged_channel exists
247325 self._prep_wrapped_messages(client_info)
248326
249327 @property
@@ -254,7 +332,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
254332 the same channel.
255333 """
256334 # Return the channel from cache.
257- return self._grpc_channel
335+ return self._logged_channel
258336 {% if service .has_lro %}
259337
260338 @property
@@ -267,7 +345,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
267345 # Quick check: Only create a new client if we do not already have one.
268346 if self._operations_client is None:
269347 self._operations_client = operations_v1.OperationsAsyncClient(
270- self.grpc_channel
348+ self._logged_channel
271349 )
272350
273351 # Return the client from cache.
@@ -297,7 +375,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
297375 # gRPC handles serialization and deserialization, so we just need
298376 # to pass in the functions for each.
299377 if '{{ method.transport_safe_name|snake_case }}' not in self._stubs:
300- self._stubs['{{ method.transport_safe_name|snake_case }}'] = self.grpc_channel .{{ method.grpc_stub_type }}(
378+ self._stubs['{{ method.transport_safe_name|snake_case }}'] = self._logged_channel .{{ method.grpc_stub_type }}(
301379 '/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}',
302380 request_serializer={{ method.input.ident }}.{% if method .input .ident .python_import .module .endswith ('_pb2' ) %} SerializeToString{% else %} serialize{% endif %} ,
303381 response_deserializer={{ method.output.ident }}.{% if method .output .ident .python_import .module .endswith ('_pb2' ) %} FromString{% else %} deserialize{% endif %} ,
@@ -325,7 +403,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
325403 # gRPC handles serialization and deserialization, so we just need
326404 # to pass in the functions for each.
327405 if "set_iam_policy" not in self._stubs:
328- self._stubs["set_iam_policy"] = self.grpc_channel .unary_unary(
406+ self._stubs["set_iam_policy"] = self._logged_channel .unary_unary(
329407 "/google.iam.v1.IAMPolicy/SetIamPolicy",
330408 request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
331409 response_deserializer=policy_pb2.Policy.FromString,
@@ -351,7 +429,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
351429 # gRPC handles serialization and deserialization, so we just need
352430 # to pass in the functions for each.
353431 if "get_iam_policy" not in self._stubs:
354- self._stubs["get_iam_policy"] = self.grpc_channel .unary_unary(
432+ self._stubs["get_iam_policy"] = self._logged_channel .unary_unary(
355433 "/google.iam.v1.IAMPolicy/GetIamPolicy",
356434 request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
357435 response_deserializer=policy_pb2.Policy.FromString,
@@ -380,7 +458,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
380458 # gRPC handles serialization and deserialization, so we just need
381459 # to pass in the functions for each.
382460 if "test_iam_permissions" not in self._stubs:
383- self._stubs["test_iam_permissions"] = self.grpc_channel .unary_unary(
461+ self._stubs["test_iam_permissions"] = self._logged_channel .unary_unary(
384462 "/google.iam.v1.IAMPolicy/TestIamPermissions",
385463 request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
386464 response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
@@ -393,7 +471,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
393471 {{ shared_macros.wrap_async_method_macro()|indent(4) }}
394472
395473 def close(self):
396- return self.grpc_channel .close()
474+ return self._logged_channel .close()
397475
398476 @property
399477 def kind(self) -> str:
@@ -405,4 +483,4 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
405483__all__ = (
406484 '{{ service.name }}GrpcAsyncIOTransport',
407485)
408- {% endblock %}
486+ {% endblock %}
0 commit comments