grpc_interceptors 0.1.2 → 0.1.3

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: 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