grpc_interceptors 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5788b629b92902268c009af0d015ce195ea82ef6841f2adc75be44a80841acf
4
- data.tar.gz: 175875a825b39b6bbf21256c0a97310ffa83feff87a9c9e9ff6ad4a6ebbc83f5
3
+ metadata.gz: 2d7842d003c6ac5d3ed32a99b351e1c4637b2a9f2cc492ae4e53d7035c36e3e5
4
+ data.tar.gz: 67486ab61fb8f18703281e86233ec99fd501567aeb0887475e5cf5e07573e49a
5
5
  SHA512:
6
- metadata.gz: b1b68eeb8f1d1bd06e99153c160caea6353043fc14e2c3c61268246f7fe32bd87755ff0d509d4748c85128f04349e1ca61550fcef3f640b645672afe81c7bd84
7
- data.tar.gz: ca9653d07430adaecbe7ca6ed07e629703275435a0d0f067240ff93a13c3b5a61e80b704305f60365db70b3977f5d7b8f37bafd2529f8942d43c9899ae3e9a65
6
+ metadata.gz: dc309aaa264a867dc0e20bc6f657de562fd15e75f9ca1da2a9f05f955b64a62e0432b695b57c21584094ce8ad8ee9b2fd63a69d3327674f64670b811553706d3
7
+ data.tar.gz: b797809c323717c89c4ef73f61151748e0b2d0a26697684328bae47d9d263a9c5ea9f9d98b9aa9a32cdf381d7bed4af55f8e2960730b7cddfe1f3b4fd503d8b4
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'] = Common::GrpcHelper.proto_to_json(request)
61
+ payload['response'] = Common::GrpcHelper.proto_to_json(response)
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,54 @@
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
40
+
41
+ def self.method_name_from_client(method)
42
+ method_parts = method.to_s.sub(%r{^/}, '').split('/')
43
+ method_parts[1..].join('/')
44
+ end
8
45
 
9
- def self.tracer
10
- OpenTelemetry.tracer_provider.tracer('grpc')
46
+ def self.proto_to_json(proto)
47
+ proto.to_json(
48
+ emit_defaults: true,
49
+ preserve_proto_fieldnames: true
50
+ )
51
+ end
11
52
  end
12
53
  end
13
54
  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'] = Common::GrpcHelper.proto_to_json(request)
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,14 +1,14 @@
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.3
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
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-api
@@ -272,10 +272,13 @@ files:
272
272
  - LICENSE
273
273
  - README.md
274
274
  - lib/grpc_interceptors.rb
275
- - lib/grpc_interceptors/client/open_telemetry_tracing_instrument.rb
275
+ - lib/grpc_interceptors/client/logging.rb
276
+ - lib/grpc_interceptors/client/opentelemetry_tracing_instrument.rb
276
277
  - lib/grpc_interceptors/common/grpc_helper.rb
277
- - lib/grpc_interceptors/common/logger.rb
278
- - lib/grpc_interceptors/server/open_telemetry_tracing_instrument.rb
278
+ - lib/grpc_interceptors/common/log_payload.rb
279
+ - lib/grpc_interceptors/common/opentelemetry_helper.rb
280
+ - lib/grpc_interceptors/server/logging.rb
281
+ - lib/grpc_interceptors/server/opentelemetry_tracing_instrument.rb
279
282
  - lib/grpc_interceptors/server/statsd_metrics.rb
280
283
  homepage: https://github.com/michal-kazmierczak/ruby-grpc-interceptors
281
284
  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