22
33{% block content %}
44
5+ import logging as std_logging
6+ import pickle
57import warnings
68from typing import Callable, Dict, Optional, Sequence, Tuple, Union
79
@@ -13,8 +15,11 @@ from google.api_core import gapic_v1
1315import google.auth # type: ignore
1416from google.auth import credentials as ga_credentials # type: ignore
1517from google.auth.transport.grpc import SslCredentials # type: ignore
18+ from google.protobuf.json_format import MessageToJson
19+ import google.protobuf.message
1620
1721import grpc # type: ignore
22+ import proto # type: ignore
1823
1924{% filter sort_lines %}
2025{% set import_ns = namespace (has_operations_mixin =false ) %}
@@ -42,6 +47,77 @@ from google.longrunning import operations_pb2 # type: ignore
4247{% endfilter %}
4348from .base import {{ service.name }}Transport, DEFAULT_CLIENT_INFO
4449
50+ try:
51+ from google.api_core import client_logging # type: ignore
52+ CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER
53+ except ImportError: # pragma: NO COVER
54+ CLIENT_LOGGING_SUPPORTED = False
55+
56+ _LOGGER = std_logging.getLogger(__name__)
57+
58+
59+ class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER
60+ def intercept_unary_unary(self, continuation, client_call_details, request):
61+ logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(std_logging.DEBUG)
62+ if logging_enabled: # pragma: NO COVER
63+ request_metadata = client_call_details.metadata
64+ if isinstance(request, proto.Message):
65+ {# TODO(https://github.com/googleapis/gapic-generator-python/issues/2293): Investigate if we can improve this logic
66+ or wait for next gen protobuf.
67+ #}
68+ request_payload = type(request).to_json(request)
69+ elif isinstance(request, google.protobuf.message.Message):
70+ request_payload = MessageToJson(request)
71+ else:
72+ request_payload = f"{type(result).__name__}: {pickle.dumps(request)}"
73+ grpc_request = {
74+ "payload": request_payload,
75+ "requestMethod": "grpc",
76+ "metadata": dict(request_metadata),
77+ }
78+ _LOGGER.debug(
79+ f"Sending request for {client_call_details.method}",
80+ extra = {
81+ "serviceName": "{{ service.meta.address.proto }}",
82+ "rpcName": client_call_details.method,
83+ "request": grpc_request,
84+ {# 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. #}
85+ "metadata": grpc_request["metadata"],
86+ },
87+ )
88+
89+ response = continuation(client_call_details, request)
90+ if logging_enabled: # pragma: NO COVER
91+ response_metadata = response.trailing_metadata()
92+ # Convert gRPC metadata `<class ' grpc.aio._metadata.Metadata' >` to list of tuples
93+ metadata = dict([(k, v) for k, v in response_metadata]) if response_metadata else None
94+ result = response.result()
95+ if isinstance(result, proto.Message):
96+ {# TODO(https://github.com/googleapis/gapic-generator-python/issues/2293): Investigate if we can improve this logic
97+ or wait for next gen protobuf.
98+ #}
99+ response_payload = type(result).to_json(result)
100+ elif isinstance(result, google.protobuf.message.Message):
101+ response_payload = MessageToJson(result)
102+ else:
103+ response_payload = f"{type(result).__name__}: {pickle.dumps(result)}"
104+ grpc_response = {
105+ "payload": response_payload,
106+ "metadata": metadata,
107+ "status": "OK",
108+ }
109+ _LOGGER.debug(
110+ f"Received response for {client_call_details.method}.",
111+ extra = {
112+ "serviceName": "{{ service.meta.address.proto }}",
113+ "rpcName": client_call_details.method,
114+ "response": grpc_response,
115+ {# 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. #}
116+ "metadata": grpc_response["metadata"],
117+ },
118+ )
119+ return response
120+
45121
46122class {{ service.name }}GrpcTransport({{ service.name }}Transport):
47123 """gRPC backend transport for {{ service.name }}.
@@ -195,7 +271,10 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
195271 ],
196272 )
197273
198- # Wrap messages. This must be done after self._grpc_channel exists
274+ self._interceptor = _LoggingClientInterceptor()
275+ self._logged_channel = grpc.intercept_channel(self._grpc_channel, self._interceptor)
276+
277+ # Wrap messages. This must be done after self._logged_channel exists
199278 self._prep_wrapped_messages(client_info)
200279
201280
@@ -262,7 +341,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
262341 # Quick check: Only create a new client if we do not already have one.
263342 if self._operations_client is None:
264343 self._operations_client = operations_v1.OperationsClient(
265- self.grpc_channel
344+ self._logged_channel
266345 )
267346
268347 # Return the client from cache.
@@ -292,7 +371,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
292371 # gRPC handles serialization and deserialization, so we just need
293372 # to pass in the functions for each.
294373 if '{{ method.transport_safe_name|snake_case }}' not in self._stubs:
295- self._stubs['{{ method.transport_safe_name|snake_case }}'] = self.grpc_channel .{{ method.grpc_stub_type }}(
374+ self._stubs['{{ method.transport_safe_name|snake_case }}'] = self._logged_channel .{{ method.grpc_stub_type }}(
296375 '/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}',
297376 request_serializer={{ method.input.ident }}.{% if method .input .ident .python_import .module .endswith ('_pb2' ) %} SerializeToString{% else %} serialize{% endif %} ,
298377 response_deserializer={{ method.output.ident }}.{% if method .output .ident .python_import .module .endswith ('_pb2' ) %} FromString{% else %} deserialize{% endif %} ,
@@ -320,7 +399,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
320399 # gRPC handles serialization and deserialization, so we just need
321400 # to pass in the functions for each.
322401 if "set_iam_policy" not in self._stubs:
323- self._stubs["set_iam_policy"] = self.grpc_channel .unary_unary(
402+ self._stubs["set_iam_policy"] = self._logged_channel .unary_unary(
324403 "/google.iam.v1.IAMPolicy/SetIamPolicy",
325404 request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
326405 response_deserializer=policy_pb2.Policy.FromString,
@@ -346,7 +425,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
346425 # gRPC handles serialization and deserialization, so we just need
347426 # to pass in the functions for each.
348427 if "get_iam_policy" not in self._stubs:
349- self._stubs["get_iam_policy"] = self.grpc_channel .unary_unary(
428+ self._stubs["get_iam_policy"] = self._logged_channel .unary_unary(
350429 "/google.iam.v1.IAMPolicy/GetIamPolicy",
351430 request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
352431 response_deserializer=policy_pb2.Policy.FromString,
@@ -374,7 +453,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
374453 # gRPC handles serialization and deserialization, so we just need
375454 # to pass in the functions for each.
376455 if "test_iam_permissions" not in self._stubs:
377- self._stubs["test_iam_permissions"] = self.grpc_channel .unary_unary(
456+ self._stubs["test_iam_permissions"] = self._logged_channel .unary_unary(
378457 "/google.iam.v1.IAMPolicy/TestIamPermissions",
379458 request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
380459 response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
@@ -383,7 +462,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
383462 {% endif %}
384463
385464 def close(self):
386- self.grpc_channel .close()
465+ self._logged_channel .close()
387466
388467 {% include '%namespace/%name_%version/%sub/services/%service/transports/_mixins.py.j2' %}
389468
0 commit comments