grpc_interceptors 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5788b629b92902268c009af0d015ce195ea82ef6841f2adc75be44a80841acf
4
- data.tar.gz: 175875a825b39b6bbf21256c0a97310ffa83feff87a9c9e9ff6ad4a6ebbc83f5
3
+ metadata.gz: 3d25e64c56db11d1b34fd966a59da4ad97ca2883fdbb68f2e046731a0f46f538
4
+ data.tar.gz: 282ee4ba2bf4dafe62c5896697106c48e26e8d739ff62dc3f603bddbeba37049
5
5
  SHA512:
6
- metadata.gz: b1b68eeb8f1d1bd06e99153c160caea6353043fc14e2c3c61268246f7fe32bd87755ff0d509d4748c85128f04349e1ca61550fcef3f640b645672afe81c7bd84
7
- data.tar.gz: ca9653d07430adaecbe7ca6ed07e629703275435a0d0f067240ff93a13c3b5a61e80b704305f60365db70b3977f5d7b8f37bafd2529f8942d43c9899ae3e9a65
6
+ metadata.gz: c28c9eb7f8e08408a243ed15c400203165466c1c128f7552c97c12b87c996790c6a9c99751d011cb2cb317bdc9a62153f81fd86abbfba7601745a0ba8ab95878
7
+ data.tar.gz: 33279a6a49e2414e50e70a621fd5a1584ce87c06de408395e05f9442c5b4d4100a45e86aa589feee9083afffb1415b1d4979a94b218b45318f50c0be7ae2749f
data/README.md CHANGED
@@ -29,7 +29,11 @@ _WIP_
29
29
 
30
30
  ### Logging
31
31
 
32
- When the `LOG_LEVEL` env variable is set to `INFO` then the server logs out
32
+ When the `LOG_LEVEL` env variable is set to `INFO` then the server logs out.
33
+
34
+ When the `LOG_LEVEL` env variable is set to `DEBUG` then the server additionally adds the request to the log message. (Note, adding the response is currently blocked by [this gRPC issue](https://github.com/grpc/grpc/pull/26547).)
35
+
36
+
33
37
 
34
38
  ### StatsD metrics
35
39
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../common/grpc_helper'
4
+ require_relative '../common/log_payload'
5
+
6
+ module GrpcInterceptors
7
+ module Client
8
+ class Logging < ::GRPC::ServerInterceptor
9
+ def initialize(logger)
10
+ @logger = logger
11
+
12
+ super()
13
+ end
14
+
15
+ def request_response(
16
+ request: nil, call: nil, method: nil, metadata: nil, &block
17
+ )
18
+ log(request, method, 'unary', &block)
19
+ end
20
+
21
+ # def client_streamer(_requests: nil, call: nil, method: nil, metadata: nil)
22
+ # yield
23
+ # end
24
+
25
+ # def server_streamer(_request: nil, call: nil, method: nil, metadata: nil)
26
+ # yield
27
+ # end
28
+
29
+ # def bidi_streamer(_requests: nil, call: nil, method: nil, metadata: nil)
30
+ # yield
31
+ # end
32
+
33
+ private
34
+
35
+ # if the server responds with error, then the error is attached to the log
36
+ # if the current level is INFO, then it logs out basic facts
37
+ # if the current level is DEBUG, then it additionally includes the request
38
+ def log(request, method, method_type)
39
+ grpc_code = ::GRPC::Core::StatusCodes::OK
40
+
41
+ response = yield
42
+ rescue StandardError => e
43
+ grpc_code = e.is_a?(::GRPC::BadStatus) ? e.code : ::GRPC::Core::StatusCodes::UNKNOWN
44
+
45
+ raise
46
+ ensure
47
+ payload = Common::LogPayload.build(
48
+ method, method_type, grpc_code, 'client'
49
+ )
50
+
51
+ if e
52
+ payload['error'] = e.class.to_s
53
+ payload['error_message'] = e.message
54
+ payload['backtrace'] = e.backtrace
55
+ end
56
+
57
+ if @logger.level == Logger::Severity::INFO
58
+ @logger.info(payload)
59
+ elsif @logger.level == Logger::Severity::DEBUG
60
+ payload['request'] = request.to_h
61
+ payload['response'] = response.to_h
62
+ @logger.debug(payload)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,15 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../common/grpc_helper'
4
+ require_relative '../common/opentelemetry_helper'
4
5
 
5
6
  module GrpcInterceptors
6
7
  module Client
7
8
  class OpenTelemetryTracingInstrument < ::GRPC::ClientInterceptor
8
9
  def request_response(request: nil, call: nil, method: nil, metadata: nil)
9
10
  kind = OpenTelemetry::Trace::SpanKind::CLIENT
10
- attributes = tracing_attributes(method)
11
+ attributes = Common::OpenTelemetryHelper.tracing_attributes(method)
11
12
 
12
- GrpcHelper.tracer.in_span(method, kind: kind, attributes: attributes) do
13
+ Common::OpenTelemetryHelper.tracer.in_span(
14
+ method, kind: kind, attributes: attributes
15
+ ) do
13
16
  OpenTelemetry.propagation.inject(metadata)
14
17
  yield
15
18
  end
@@ -26,20 +29,6 @@ module GrpcInterceptors
26
29
  # def bidi_streamer(_requests: nil, call: nil, method: nil, metadata: nil)
27
30
  # yield
28
31
  # end
29
-
30
- private
31
-
32
- def tracing_attributes(method)
33
- method_parts = method.to_s.sub(%r{^/}, '').split('/')
34
- service_name = method_parts.shift
35
- method_name = method_parts.join('/')
36
-
37
- {
38
- OpenTelemetry::SemanticConventions::Trace::RPC_SYSTEM => 'grpc',
39
- OpenTelemetry::SemanticConventions::Trace::RPC_SERVICE => service_name,
40
- OpenTelemetry::SemanticConventions::Trace::RPC_METHOD => method_name,
41
- }
42
- end
43
32
  end
44
33
  end
45
34
  end
@@ -1,13 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrpcInterceptors
4
- module GrpcHelper
5
- def self.route_name(method)
6
- "/#{method.owner.service_name}/#{method.original_name}"
7
- end
4
+ module Common
5
+ module GrpcHelper
6
+ def self.route_name(method)
7
+ return method unless method.is_a?(Method) # client case
8
+
9
+ # server case
10
+ service_name = service_name_from_server(method)
11
+ method_name = method_name_from_server(method)
12
+
13
+ "/#{service_name}/#{method_name}"
14
+ end
15
+
16
+ def self.service_name(method)
17
+ return service_name_from_server(method) if method.is_a?(Method) # server case
18
+
19
+ service_name_from_client(method) # client case
20
+ end
21
+
22
+ def self.method_name(method)
23
+ return method_name_from_server(method) if method.is_a?(Method) # server case
24
+
25
+ method_name_from_client(method) # client case
26
+ end
27
+
28
+ def self.service_name_from_server(method)
29
+ method.owner.service_name.to_s
30
+ end
31
+
32
+ def self.method_name_from_server(method)
33
+ method.original_name.to_s
34
+ end
35
+
36
+ def self.service_name_from_client(method)
37
+ method_parts = method.to_s.sub(%r{^/}, '').split('/')
38
+ method_parts.first
39
+ end
8
40
 
9
- def self.tracer
10
- OpenTelemetry.tracer_provider.tracer('grpc')
41
+ def self.method_name_from_client(method)
42
+ method_parts = method.to_s.sub(%r{^/}, '').split('/')
43
+ method_parts[1..].join('/')
44
+ end
11
45
  end
12
46
  end
13
47
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrpcInterceptors
4
+ module Common
5
+ module LogPayload
6
+ def self.build(method, method_type, grpc_code, kind)
7
+ service = GrpcHelper.service_name(method)
8
+ method = GrpcHelper.method_name(method)
9
+
10
+ payload = {
11
+ 'pid' => Process.pid,
12
+ 'grpc.component' => kind, # the caller, server or client
13
+ 'grpc.service' => service,
14
+ 'grpc.method' => method,
15
+ 'grpc.method_type' => method_type,
16
+ 'grpc.code' => grpc_code
17
+ }
18
+
19
+ if defined?(OpenTelemetry) && OpenTelemetry::Trace.current_span.recording?
20
+ tracing_context = OpenTelemetry::Trace.current_span.context
21
+ payload['span_id'] = tracing_context.hex_span_id
22
+ payload['trace_id'] = tracing_context.hex_trace_id
23
+ end
24
+
25
+ payload
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrpcInterceptors
4
+ module Common
5
+ module OpenTelemetryHelper
6
+ def self.tracer
7
+ OpenTelemetry.tracer_provider.tracer('grpc')
8
+ end
9
+
10
+ def self.tracing_attributes(method)
11
+ service_name = Common::GrpcHelper.service_name(method)
12
+ method_name = Common::GrpcHelper.method_name(method)
13
+
14
+ {
15
+ OpenTelemetry::SemanticConventions::Trace::RPC_SYSTEM => 'grpc',
16
+ OpenTelemetry::SemanticConventions::Trace::RPC_SERVICE => service_name,
17
+ OpenTelemetry::SemanticConventions::Trace::RPC_METHOD => method_name
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../common/grpc_helper'
4
+ require_relative '../common/log_payload'
5
+
6
+ module GrpcInterceptors
7
+ module Server
8
+ class Logging < ::GRPC::ServerInterceptor
9
+ def initialize(logger)
10
+ @logger = logger
11
+
12
+ super()
13
+ end
14
+
15
+ def request_response(request: nil, call: nil, method: nil, &block)
16
+ log(request, method, 'unary', &block)
17
+ end
18
+
19
+ # def client_streamer(call: nil, method: nil)
20
+ # yield
21
+ # end
22
+
23
+ # def server_streamer(_request: nil, call: nil, method: nil)
24
+ # yield
25
+ # end
26
+
27
+ # def bidi_streamer(_requests: nil, call: nil, method: nil)
28
+ # yield
29
+ # end
30
+
31
+ private
32
+
33
+ # in case of an exception, it logs out on the ERROR level including error details
34
+ # if the current level is INFO, then it logs out basic facts
35
+ # if the current level is DEBUG, then it additionally includes the request
36
+ def log(request, method, method_type)
37
+ grpc_code = ::GRPC::Core::StatusCodes::OK
38
+
39
+ yield
40
+ rescue StandardError => e
41
+ grpc_code = e.is_a?(::GRPC::BadStatus) ? e.code : ::GRPC::Core::StatusCodes::UNKNOWN
42
+
43
+ raise
44
+ ensure
45
+ payload = Common::LogPayload.build(
46
+ method, method_type, grpc_code, 'server'
47
+ )
48
+
49
+ if e
50
+ payload['error'] = e.class.to_s
51
+ payload['error_message'] = e.message
52
+ payload['backtrace'] = e.backtrace
53
+ @logger.error(payload)
54
+ elsif @logger.level == Logger::Severity::INFO
55
+ @logger.info(payload)
56
+ elsif @logger.level == Logger::Severity::DEBUG
57
+ payload['request'] = request.to_h
58
+ @logger.debug(payload)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../common/grpc_helper'
4
+ require_relative '../common/opentelemetry_helper'
5
+
6
+ module GrpcInterceptors
7
+ module Server
8
+ # https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/generic/interceptors.rb
9
+ class OpenTelemetryTracingInstrument < ::GRPC::ServerInterceptor
10
+ def request_response(request: nil, call: nil, method: nil, &block)
11
+ context = OpenTelemetry.propagation.extract(call.metadata)
12
+ route_name = Common::GrpcHelper.route_name(method)
13
+ attributes = Common::OpenTelemetryHelper.tracing_attributes(method)
14
+ kind = OpenTelemetry::Trace::SpanKind::SERVER
15
+
16
+ OpenTelemetry::Context.with_current(context) do
17
+ Common::OpenTelemetryHelper.tracer.in_span(
18
+ route_name,
19
+ attributes: attributes,
20
+ kind: kind,
21
+ &block
22
+ )
23
+ end
24
+ end
25
+
26
+ # def client_streamer(call: nil, method: nil)
27
+ # yield
28
+ # end
29
+
30
+ # def server_streamer(_request: nil, call: nil, method: nil)
31
+ # yield
32
+ # end
33
+
34
+ # def bidi_streamer(_requests: nil, call: nil, method: nil)
35
+ # yield
36
+ # end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../common/grpc_helper'
4
+
3
5
  module GrpcInterceptors
4
6
  module Server
5
7
  class StatsDMetrics < ::GRPC::ServerInterceptor
@@ -32,8 +34,8 @@ module GrpcInterceptors
32
34
 
33
35
  def common_labels(method)
34
36
  {
35
- grpc_method: method.original_name.to_s,
36
- grpc_service: method.owner.service_name
37
+ grpc_method: Common::GrpcHelper.method_name(method),
38
+ grpc_service: Common::GrpcHelper.service_name(method)
37
39
  }
38
40
  end
39
41
  end
metadata CHANGED
@@ -1,57 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grpc_interceptors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - michal-kazmierczak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-01 00:00:00.000000000 Z
11
+ date: 2024-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: opentelemetry-api
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.0.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 1.0.0
27
- - !ruby/object:Gem::Dependency
28
- name: opentelemetry-instrumentation-base
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.22.3
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.22.3
41
- - !ruby/object:Gem::Dependency
42
- name: statsd-instrument
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.6'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.6'
55
13
  - !ruby/object:Gem::Dependency
56
14
  name: grpc
57
15
  requirement: !ruby/object:Gem::Requirement
@@ -262,6 +220,20 @@ dependencies:
262
220
  - - "~>"
263
221
  - !ruby/object:Gem::Version
264
222
  version: '0.22'
223
+ - !ruby/object:Gem::Dependency
224
+ name: statsd-instrument
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '3.6'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: '3.6'
265
237
  description: A collection of Ruby interceptors (middlewares) for gRPC servers and
266
238
  clients.
267
239
  email:
@@ -272,10 +244,13 @@ files:
272
244
  - LICENSE
273
245
  - README.md
274
246
  - lib/grpc_interceptors.rb
275
- - lib/grpc_interceptors/client/open_telemetry_tracing_instrument.rb
247
+ - lib/grpc_interceptors/client/logging.rb
248
+ - lib/grpc_interceptors/client/opentelemetry_tracing_instrument.rb
276
249
  - lib/grpc_interceptors/common/grpc_helper.rb
277
- - lib/grpc_interceptors/common/logger.rb
278
- - lib/grpc_interceptors/server/open_telemetry_tracing_instrument.rb
250
+ - lib/grpc_interceptors/common/log_payload.rb
251
+ - lib/grpc_interceptors/common/opentelemetry_helper.rb
252
+ - lib/grpc_interceptors/server/logging.rb
253
+ - lib/grpc_interceptors/server/opentelemetry_tracing_instrument.rb
279
254
  - lib/grpc_interceptors/server/statsd_metrics.rb
280
255
  homepage: https://github.com/michal-kazmierczak/ruby-grpc-interceptors
281
256
  licenses:
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GrpcInterceptors
4
- module Common
5
- class Logger
6
- # extend self
7
-
8
- def log; end
9
- end
10
- end
11
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../common/grpc_helper'
4
-
5
- module GrpcInterceptors
6
- module Server
7
- # https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/generic/interceptors.rb
8
- class OpenTelemetryTracingInstrument < ::GRPC::ServerInterceptor
9
- def request_response(request: nil, call: nil, method: nil)
10
- parent_context = OpenTelemetry.propagation.extract(call.metadata)
11
- route_name = GrpcHelper.route_name(method)
12
- attributes = tracing_attributes(method)
13
- kind = OpenTelemetry::Trace::SpanKind::SERVER
14
- span = GrpcHelper.tracer.start_span(
15
- route_name,
16
- with_parent: parent_context,
17
- attributes: attributes,
18
- kind: kind
19
- )
20
-
21
- yield
22
-
23
- span.finish
24
- rescue StandardError => e
25
- OpenTelemetry.handle_error(exception: e)
26
-
27
- raise e
28
- ensure
29
- span.finish if span.recording?
30
- end
31
-
32
- # def client_streamer(call: nil, method: nil)
33
- # yield
34
- # end
35
-
36
- # def server_streamer(_request: nil, call: nil, method: nil)
37
- # yield
38
- # end
39
-
40
- # def bidi_streamer(_requests: nil, call: nil, method: nil)
41
- # yield
42
- # end
43
-
44
- private
45
-
46
- def tracing_attributes(method)
47
- {
48
- OpenTelemetry::SemanticConventions::Trace::RPC_SYSTEM => 'grpc',
49
- OpenTelemetry::SemanticConventions::Trace::RPC_SERVICE => method.owner.service_name,
50
- OpenTelemetry::SemanticConventions::Trace::RPC_METHOD => method.original_name.to_s,
51
- }
52
- end
53
- end
54
- end
55
- end