Skip to content

Commit ae07484

Browse files
committed
feat: add client debug logging support for unary-stream gRPC calls
1 parent 7fbd5fd commit ae07484

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

google/api_core/grpc_helpers.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717

1818
import collections
1919
import functools
20+
import logging
21+
import pickle
2022
import warnings
2123

24+
import google.protobuf.json_format
2225
import grpc
26+
import proto
2327

2428
from google.api_core import exceptions
2529
import google.auth
@@ -48,6 +52,7 @@
4852
else:
4953
HAS_GRPC_GCP = False
5054

55+
_LOGGER = logging.getLogger(__name__)
5156

5257
# The list of gRPC Callable interfaces that return iterators.
5358
_STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)
@@ -113,7 +118,31 @@ def __next__(self) -> P:
113118
result = self._stored_first_result
114119
del self._stored_first_result
115120
return result
116-
return next(self._wrapped)
121+
result = next(self._wrapped)
122+
123+
logging_enabled = _LOGGER.isEnabledFor(
124+
logging.DEBUG
125+
)
126+
if logging_enabled: # pragma: NO COVER
127+
if isinstance(result, proto.Message):
128+
response_payload = type(result).to_json(result)
129+
elif isinstance(result, google.protobuf.message.Message):
130+
response_payload = google.protobuf.json_format.MessageToJson(result)
131+
else:
132+
response_payload = (
133+
f"{type(result).__name__}: {pickle.dumps(result)}"
134+
)
135+
grpc_response = {
136+
"payload": response_payload,
137+
"status": "OK",
138+
}
139+
_LOGGER.debug(
140+
f"Received response of type {type(result)} via gRPC stream",
141+
extra={
142+
"response": grpc_response,
143+
},
144+
)
145+
return result
117146
except grpc.RpcError as exc:
118147
# If the stream has already returned data, we cannot recover here.
119148
raise exceptions.from_grpc_error(exc) from exc

google/api_core/grpc_helpers_async.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,23 @@
2020

2121
import asyncio
2222
import functools
23+
import logging
24+
import pickle
2325

2426
from typing import AsyncGenerator, Generic, Iterator, Optional, TypeVar
2527

28+
import google.protobuf.json_format
2629
import grpc
2730
from grpc import aio
31+
import proto
2832

2933
from google.api_core import exceptions, grpc_helpers
3034

3135
# denotes the proto response type for grpc calls
3236
P = TypeVar("P")
3337

38+
_LOGGER = logging.getLogger(__name__)
39+
3440
# NOTE(lidiz) Alternatively, we can hack "__getattribute__" to perform
3541
# automatic patching for us. But that means the overhead of creating an
3642
# extra Python function spreads to every single send and receive.
@@ -94,7 +100,30 @@ def __init__(self):
94100

95101
async def read(self) -> P:
96102
try:
97-
return await self._call.read()
103+
result = await self._call.read()
104+
logging_enabled =_LOGGER.isEnabledFor(
105+
logging.DEBUG
106+
)
107+
if logging_enabled: # pragma: NO COVER
108+
if isinstance(result, proto.Message):
109+
response_payload = type(result).to_json(result)
110+
elif isinstance(result, google.protobuf.message.Message):
111+
response_payload = google.protobuf.json_format.MessageToJson(result)
112+
else:
113+
response_payload = (
114+
f"{type(result).__name__}: {pickle.dumps(result)}"
115+
)
116+
grpc_response = {
117+
"payload": response_payload,
118+
"status": "OK",
119+
}
120+
_LOGGER.debug(
121+
f"Received response of type {type(result)} via gRPC stream",
122+
extra={
123+
"response": grpc_response,
124+
},
125+
)
126+
return result
98127
except grpc.RpcError as rpc_error:
99128
raise exceptions.from_grpc_error(rpc_error) from rpc_error
100129

0 commit comments

Comments
 (0)