Skip to content

Commit fe9e0ec

Browse files
authored
feat(common): Log requests and responses (#1112)
1 parent 100b6e2 commit fe9e0ec

File tree

20 files changed

+962
-184
lines changed

20 files changed

+962
-184
lines changed

gapic-common/.rubocop.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ Metrics/ClassLength:
1111
Metrics/CyclomaticComplexity:
1212
Max: 15
1313

14+
Metrics/MethodLength:
15+
Max: 30
16+
1417
Metrics/PerceivedComplexity:
1518
Max: 15
1619

20+
Naming/AccessorMethodName:
21+
Enabled: false
22+
1723
Naming/FileName:
1824
Exclude:
1925
- "lib/gapic-common.rb"

gapic-common/Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ source "https://rubygems.org"
33
gemspec
44

55
gem "concurrent-ruby", "~> 1.3"
6+
gem "googleapis-common-protos-types", "~> 1.15"
67
gem "google-cloud-core", "~> 1.7"
78
gem "google-style", "~> 1.30.0"
89
gem "grpc-tools", "~> 1.65"

gapic-common/gapic-common.gemspec

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ Gem::Specification.new do |spec|
3939
spec.add_dependency "faraday-retry", ">= 1.0", "< 3.a"
4040
spec.add_dependency "googleapis-common-protos", "~> 1.6"
4141
spec.add_dependency "googleapis-common-protos-types", "~> 1.15"
42-
spec.add_dependency "googleauth", "~> 1.11"
42+
spec.add_dependency "googleauth", "~> 1.12"
43+
spec.add_dependency "google-cloud-env", "~> 2.2"
44+
spec.add_dependency "google-logging-utils", "~> 0.1"
4345
spec.add_dependency "google-protobuf", ">= 3.25", "< 5.a"
44-
spec.add_dependency "grpc", "~> 1.65"
46+
spec.add_dependency "grpc", "~> 1.66"
4547
end

gapic-common/lib/gapic/grpc/service_stub.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require "gapic/grpc/service_stub/rpc_call"
1818
require "gapic/grpc/service_stub/channel"
1919
require "gapic/grpc/service_stub/channel_pool"
20+
require "gapic/logging_concerns"
2021
require "gapic/universe_domain_concerns"
2122

2223
module Gapic
@@ -32,6 +33,7 @@ module Gapic
3233
#
3334
class ServiceStub
3435
include UniverseDomainConcerns
36+
include LoggingConcerns
3537

3638
attr_reader :grpc_stub
3739
attr_reader :channel_pool
@@ -64,6 +66,9 @@ class ServiceStub
6466
# be used for intercepting calls before they are executed Interceptors are an EXPERIMENTAL API.
6567
# @param channel_pool_config [::Gapic::ServiceStub:ChannelPool::Configuration] The configuration for channel
6668
# pool. This argument will raise error when `credentials` is provided as a `::GRPC::Core::Channel`.
69+
# @param logger [Logger,:default,nil] An explicit logger to use, or one
70+
# of the values `:default` (the default) to construct a default logger,
71+
# or `nil` to disable logging explicitly.
6772
#
6873
def initialize grpc_stub_class,
6974
credentials:,
@@ -72,13 +77,19 @@ def initialize grpc_stub_class,
7277
universe_domain: nil,
7378
channel_args: nil,
7479
interceptors: nil,
75-
channel_pool_config: nil
80+
channel_pool_config: nil,
81+
logger: :default
7682
raise ArgumentError, "grpc_stub_class is required" if grpc_stub_class.nil?
7783

7884
setup_universe_domain universe_domain: universe_domain,
7985
endpoint: endpoint,
8086
endpoint_template: endpoint_template,
8187
credentials: credentials
88+
setup_logging logger: logger,
89+
system_name: grpc_stub_class,
90+
service: grpc_stub_class,
91+
endpoint: self.endpoint,
92+
client_id: object_id
8293

8394
@channel_pool = nil
8495
@grpc_stub = nil
@@ -102,7 +113,7 @@ def create_channel_pool grpc_stub_class, endpoint:, credentials:, channel_args:
102113
end
103114
@channel_pool = ChannelPool.new grpc_stub_class, endpoint: endpoint, credentials: credentials,
104115
channel_args: channel_args, interceptors: interceptors,
105-
config: channel_pool_config
116+
config: channel_pool_config, stub_logger: stub_logger
106117
end
107118

108119
def create_grpc_stub grpc_stub_class, endpoint:, credentials:, channel_args: nil, interceptors: nil
@@ -201,7 +212,8 @@ def create_grpc_stub grpc_stub_class, endpoint:, credentials:, channel_args: nil
201212
#
202213
def call_rpc method_name, request, options: nil, &block
203214
if @channel_pool.nil?
204-
rpc_call = RpcCall.new @grpc_stub.method method_name
215+
meth = @grpc_stub.method method_name
216+
rpc_call = RpcCall.new meth, stub_logger: stub_logger, method_name: method_name
205217
rpc_call.call request, options: options, &block
206218
else
207219
@channel_pool.call_rpc method_name, request, options: options, &block

gapic-common/lib/gapic/grpc/service_stub/channel.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ class Channel
3232
# Creates a new Channel instance
3333
#
3434
def initialize grpc_stub_class, endpoint:, credentials:, channel_args: nil, interceptors: nil,
35-
on_channel_create: nil
35+
on_channel_create: nil, stub_logger: nil
3636
@grpc_stub_class = grpc_stub_class
3737
@endpoint = endpoint
3838
@credentials = credentials
3939
@channel_args = Hash channel_args
4040
@interceptors = Array interceptors
41+
@stub_logger = stub_logger
4142
@concurrent_streams = 0
4243
@mutex = Mutex.new
4344
setup_grpc_stub
@@ -88,7 +89,8 @@ def setup_grpc_stub
8889
def call_rpc method_name, request, options: nil, &block
8990
@mutex.synchronize { @concurrent_streams += 1 }
9091
begin
91-
rpc_call = RpcCall.new @grpc_stub.method method_name
92+
meth = @grpc_stub.method method_name
93+
rpc_call = RpcCall.new meth, stub_logger: @stub_logger, method_name: method_name
9294
response = rpc_call.call request, options: options, &block
9395
response
9496
ensure

gapic-common/lib/gapic/grpc/service_stub/channel_pool.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ class ChannelPool
3030
##
3131
# Initialize an instance of ServiceStub::ChannelPool
3232
#
33-
def initialize grpc_stub_class, endpoint:, credentials:, channel_args: nil, interceptors: nil, config: nil
33+
def initialize grpc_stub_class,
34+
endpoint:, credentials:,
35+
channel_args: nil,
36+
interceptors: nil,
37+
config: nil,
38+
stub_logger: nil
3439
if credentials.is_a? ::GRPC::Core::Channel
3540
raise ArgumentError, "Can't create a channel pool with GRPC::Core::Channel as credentials"
3641
end
@@ -41,6 +46,7 @@ def initialize grpc_stub_class, endpoint:, credentials:, channel_args: nil, inte
4146
@channel_args = channel_args
4247
@interceptors = interceptors
4348
@config = config || Configuration.new
49+
@stub_logger = stub_logger
4450

4551
@channels = (1..@config.channel_count).map { create_channel }
4652
end
@@ -49,7 +55,8 @@ def initialize grpc_stub_class, endpoint:, credentials:, channel_args: nil, inte
4955
# Creates a new channel.
5056
def create_channel
5157
Channel.new @grpc_stub_class, endpoint: @endpoint, credentials: @credentials, channel_args: @channel_args,
52-
interceptors: @interceptors, on_channel_create: @config.on_channel_create
58+
interceptors: @interceptors, on_channel_create: @config.on_channel_create,
59+
stub_logger: @stub_logger
5360
end
5461

5562
##

gapic-common/lib/gapic/grpc/service_stub/rpc_call.rb

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
require "gapic/call_options"
16+
require "gapic/logging_concerns"
1617
require "grpc/errors"
1718

1819
module Gapic
@@ -32,8 +33,11 @@ class RpcCall
3233
#
3334
# @param stub_method [Proc] Used to make a bare rpc call.
3435
#
35-
def initialize stub_method
36+
def initialize stub_method, stub_logger: nil, method_name: nil
3637
@stub_method = stub_method
38+
@stub_logger = stub_logger
39+
@method_name = method_name
40+
@request_id = LoggingConcerns.random_uuid4
3741
end
3842

3943
##
@@ -44,7 +48,8 @@ def initialize stub_method
4448
# customize the options object, using keys that match the arguments for {Gapic::CallOptions.new}. This object
4549
# should only be used once.
4650
#
47-
# @yield [response, operation] Access the response along with the RPC operation.
51+
# @yield [response, operation] Access the response along with the RPC operation. Additionally, throwing
52+
# `:response, obj` within the block will change the return value to `obj`.
4853
# @yieldparam response [Object] The response object.
4954
# @yieldparam operation [::GRPC::ActiveCall::Operation] The RPC operation for the response.
5055
#
@@ -91,7 +96,7 @@ def initialize stub_method
9196
# )
9297
# response = echo_call.call request, options: options
9398
#
94-
# @example Accessing the response and RPC operation using a block:
99+
# @example Accessing the RPC operation using a block:
95100
# require "google/showcase/v1beta1/echo_pb"
96101
# require "google/showcase/v1beta1/echo_services_pb"
97102
# require "gapic"
@@ -107,8 +112,8 @@ def initialize stub_method
107112
# echo_call = Gapic::ServiceStub::RpcCall.new echo_stub.method :echo
108113
#
109114
# request = Google::Showcase::V1beta1::EchoRequest.new
110-
# echo_call.call request do |response, operation|
111-
# operation.trailing_metadata
115+
# metadata = echo_call.call request do |_response, operation|
116+
# throw :response, operation.trailing_metadata
112117
# end
113118
#
114119
def call request, options: nil
@@ -117,21 +122,27 @@ def call request, options: nil
117122
deadline = calculate_deadline options
118123
metadata = options.metadata
119124

125+
try_number = 1
120126
retried_exception = nil
121127
begin
128+
request = log_request request, metadata, try_number
122129
operation = stub_method.call request, deadline: deadline, metadata: metadata, return_op: true
123130
response = operation.execute
124-
yield response, operation if block_given?
125-
response
131+
catch :response do
132+
response = log_response response, try_number
133+
yield response, operation if block_given?
134+
response
135+
end
126136
rescue ::GRPC::DeadlineExceeded => e
137+
log_response e, try_number
127138
raise Gapic::GRPC::DeadlineExceededError.new e.message, root_cause: retried_exception
128139
rescue StandardError => e
129-
if e.is_a?(::GRPC::Unavailable) && /Signet::AuthorizationError/ =~ e.message
130-
e = Gapic::GRPC::AuthorizationError.new e.message.gsub(%r{^\d+:}, "")
131-
end
140+
e = normalize_exception e
141+
log_response e, try_number
132142

133143
if check_retry?(deadline) && options.retry_policy.call(e)
134144
retried_exception = e
145+
try_number += 1
135146
retry
136147
end
137148

@@ -163,6 +174,93 @@ def current_time
163174
nsecs_part = nanos % 1_000_000_000
164175
Time.at secs_part, nsecs_part, :nanosecond
165176
end
177+
178+
def normalize_exception exception
179+
if exception.is_a?(::GRPC::Unavailable) && /Signet::AuthorizationError/ =~ exception.message
180+
exception = Gapic::GRPC::AuthorizationError.new exception.message.gsub(%r{^\d+:}, "")
181+
end
182+
exception
183+
end
184+
185+
def log_request request, metadata, try_number
186+
return request unless @stub_logger
187+
@stub_logger.info do |entry|
188+
entry.set_system_name
189+
entry.set_service
190+
entry.set "rpcName", @method_name
191+
entry.set "retryAttempt", try_number
192+
entry.set "requestId", @request_id
193+
entry.message =
194+
if request.is_a? Enumerable
195+
"Sending stream to #{entry.service}.#{@method_name} (try #{try_number})"
196+
else
197+
"Sending request to #{entry.service}.#{@method_name} (try #{try_number})"
198+
end
199+
end
200+
loggable_metadata = metadata.to_h rescue {}
201+
if request.is_a? Enumerable
202+
request.lazy.map do |req|
203+
log_single_request req, loggable_metadata
204+
end
205+
else
206+
log_single_request request, loggable_metadata
207+
end
208+
end
209+
210+
def log_single_request request, metadata
211+
request_content = request.respond_to?(:to_h) ? (request.to_h rescue {}) : request.to_s
212+
if !request_content.empty? || !metadata.empty?
213+
@stub_logger.debug do |entry|
214+
entry.set "requestId", @request_id
215+
entry.set "request", request_content
216+
entry.set "headers", metadata
217+
entry.message = "(request payload as #{request.class})"
218+
end
219+
end
220+
request
221+
end
222+
223+
def log_response response, try_number
224+
return response unless @stub_logger
225+
@stub_logger.info do |entry|
226+
entry.set_system_name
227+
entry.set_service
228+
entry.set "rpcName", @method_name
229+
entry.set "retryAttempt", try_number
230+
entry.set "requestId", @request_id
231+
case response
232+
when StandardError
233+
entry.set "exception", response.to_s
234+
entry.message = "Received error for #{entry.service}.#{@method_name} (try #{try_number}): #{response}"
235+
when Enumerable
236+
entry.message = "Receiving stream for #{entry.service}.#{@method_name} (try #{try_number})"
237+
else
238+
entry.message = "Received response for #{entry.service}.#{@method_name} (try #{try_number})"
239+
end
240+
end
241+
case response
242+
when StandardError
243+
response
244+
when Enumerable
245+
response.lazy.map do |resp|
246+
log_single_response resp
247+
end
248+
else
249+
log_single_response response
250+
end
251+
end
252+
253+
def log_single_response response
254+
response_content = response.respond_to?(:to_h) ? (response.to_h rescue {}) : response.to_s
255+
unless response_content.empty?
256+
@stub_logger.debug do |entry|
257+
entry.set "requestId", @request_id
258+
entry.set "response", response_content
259+
entry.message = "(response payload as #{response.class})"
260+
end
261+
end
262+
response
263+
end
166264
end
167265
end
168266
end

0 commit comments

Comments
 (0)