freddy 1.7.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -47,33 +47,21 @@ class Freddy
47
47
 
48
48
  def process_message(_queue, delivery)
49
49
  @consume_thread_pool.process do
50
- begin
51
- scope = delivery.build_trace("freddy:observe:#{@pattern}",
52
- tags: {
53
- 'message_bus.destination' => @pattern,
54
- 'message_bus.correlation_id' => delivery.correlation_id,
55
- 'component' => 'freddy',
56
- 'span.kind' => 'consumer' # Message Bus
57
- },
58
- force_follows_from: true)
59
-
50
+ delivery.in_span(force_follows_from: true) do
60
51
  yield delivery.payload, delivery.routing_key
61
-
62
52
  @channel.acknowledge(delivery.tag)
63
- rescue StandardError
64
- case on_exception
65
- when :reject
66
- @channel.reject(delivery.tag)
67
- when :requeue
68
- @channel.reject(delivery.tag, true)
69
- else
70
- @channel.acknowledge(delivery.tag)
71
- end
72
-
73
- raise
74
- ensure
75
- scope.close
76
53
  end
54
+ rescue StandardError
55
+ case on_exception
56
+ when :reject
57
+ @channel.reject(delivery.tag)
58
+ when :requeue
59
+ @channel.reject(delivery.tag, true)
60
+ else
61
+ @channel.acknowledge(delivery.tag)
62
+ end
63
+
64
+ raise
77
65
  end
78
66
  end
79
67
 
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir[File.dirname(__FILE__) + '/consumers/*.rb'].each(&method(:require))
3
+ Dir["#{File.dirname(__FILE__)}/consumers/*.rb"].sort.each(&method(:require))
@@ -4,11 +4,12 @@ class Freddy
4
4
  class Delivery
5
5
  attr_reader :routing_key, :payload, :tag
6
6
 
7
- def initialize(payload, metadata, routing_key, tag)
7
+ def initialize(payload, metadata, routing_key, tag, exchange)
8
8
  @payload = payload
9
9
  @metadata = metadata
10
10
  @routing_key = routing_key
11
11
  @tag = tag
12
+ @exchange = exchange
12
13
  end
13
14
 
14
15
  def correlation_id
@@ -23,20 +24,51 @@ class Freddy
23
24
  @metadata.reply_to
24
25
  end
25
26
 
26
- def build_trace(operation_name, tags: {}, force_follows_from: false)
27
- carrier = TraceCarrier.new(@metadata)
28
- parent = OpenTracing.global_tracer.extract(OpenTracing::FORMAT_TEXT_MAP, carrier)
29
-
30
- references =
31
- if !parent
32
- []
33
- elsif force_follows_from
34
- [OpenTracing::Reference.follows_from(parent)]
35
- else
36
- [OpenTracing::Reference.child_of(parent)]
27
+ def in_span(force_follows_from: false, &block)
28
+ name = "#{@exchange}.#{@routing_key} receive"
29
+ kind = OpenTelemetry::Trace::SpanKind::CONSUMER
30
+ producer_context = OpenTelemetry.propagation.extract(@metadata[:headers] || {})
31
+
32
+ if force_follows_from
33
+ producer_span_context = OpenTelemetry::Trace.current_span(producer_context).context
34
+
35
+ links = []
36
+ links << OpenTelemetry::Trace::Link.new(producer_span_context) if producer_span_context.valid?
37
+
38
+ root_span = Freddy.tracer.start_root_span(name, attributes: span_attributes, links: links, kind: kind)
39
+ OpenTelemetry::Trace.with_span(root_span) do
40
+ Freddy.tracer.in_span(name, attributes: span_attributes, links: links, kind: kind, &block)
41
+ ensure
42
+ root_span.finish
37
43
  end
44
+ else
45
+ OpenTelemetry::Context.with_current(producer_context) do
46
+ Freddy.tracer.in_span(name, attributes: span_attributes, kind: kind, &block)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def span_attributes
54
+ destination_kind = @exchange == '' ? 'queue' : 'topic'
55
+
56
+ attributes = {
57
+ 'payload.type' => (@payload[:type] || 'unknown').to_s,
58
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM => 'rabbitmq',
59
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION => @exchange,
60
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION_KIND => destination_kind,
61
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_RABBITMQ_ROUTING_KEY => @routing_key,
62
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION => 'process'
63
+ }
64
+
65
+ # There's no correlation_id when a message was sent using
66
+ # `Freddy#deliver`.
67
+ if correlation_id
68
+ attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_CONVERSATION_ID] = correlation_id
69
+ end
38
70
 
39
- OpenTracing.start_active_span(operation_name, references: references, tags: tags)
71
+ attributes
40
72
  end
41
73
  end
42
74
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+
5
+ class Freddy
6
+ class Encoding
7
+ ZLIB_CONTENT_ENCODING = 'zlib'
8
+
9
+ def self.compress(data, encoding)
10
+ case encoding
11
+ when ZLIB_CONTENT_ENCODING
12
+ ::Zlib::Deflate.deflate(data)
13
+ else
14
+ data
15
+ end
16
+ end
17
+
18
+ def self.uncompress(data, encoding)
19
+ case encoding
20
+ when ZLIB_CONTENT_ENCODING
21
+ ::Zlib::Inflate.inflate(data)
22
+ else
23
+ data
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require 'oj'
5
- rescue LoadError
6
- require 'symbolizer'
7
- require 'json'
8
- end
3
+ require 'oj'
9
4
 
10
5
  class Freddy
11
6
  class Payload
12
- def self.parse(payload)
7
+ def self.parse(payload, encoding)
13
8
  return {} if payload == 'null'
14
9
 
10
+ payload = Freddy::Encoding.uncompress(payload, encoding)
15
11
  json_handler.parse(payload)
16
12
  end
17
13
 
@@ -20,7 +16,7 @@ class Freddy
20
16
  end
21
17
 
22
18
  def self.json_handler
23
- @json_handler ||= defined?(Oj) ? OjAdapter : JsonAdapter
19
+ @json_handler ||= OjAdapter
24
20
  end
25
21
 
26
22
  class OjAdapter
@@ -35,31 +31,5 @@ class Freddy
35
31
  Oj.dump(payload, DUMP_OPTIONS)
36
32
  end
37
33
  end
38
-
39
- class JsonAdapter
40
- def self.parse(payload)
41
- # MRI has :symbolize_keys, but JRuby does not. Not adding it at the
42
- # moment.
43
- Symbolizer.symbolize(JSON.parse(payload))
44
- end
45
-
46
- def self.dump(payload)
47
- JSON.dump(serialize_time_objects(payload))
48
- end
49
-
50
- def self.serialize_time_objects(object)
51
- if object.is_a?(Hash)
52
- object.reduce({}) do |hash, (key, value)|
53
- hash.merge(key => serialize_time_objects(value))
54
- end
55
- elsif object.is_a?(Array)
56
- object.map(&method(:serialize_time_objects))
57
- elsif object.is_a?(Time) || object.is_a?(Date)
58
- object.iso8601
59
- else
60
- object
61
- end
62
- end
63
- end
64
34
  end
65
35
  end
@@ -10,17 +10,24 @@ class Freddy
10
10
  @exchange = channel.default_exchange
11
11
  end
12
12
 
13
- def produce(destination, payload, properties)
14
- if (span = OpenTracing.active_span)
15
- span.set_tag('message_bus.destination', destination)
16
- end
13
+ def produce(routing_key, payload, properties)
14
+ span = Tracing.span_for_produce(
15
+ @exchange,
16
+ routing_key,
17
+ payload,
18
+ correlation_id: properties[:correlation_id]
19
+ )
17
20
 
18
21
  properties = properties.merge(
19
- routing_key: destination,
22
+ routing_key: routing_key,
20
23
  content_type: CONTENT_TYPE
21
24
  )
25
+ Tracing.inject_tracing_information_to_properties!(properties)
22
26
 
23
27
  @exchange.publish Payload.dump(payload), properties
28
+ ensure
29
+ # We won't wait for a reply. Just finish the span immediately.
30
+ span.finish
24
31
  end
25
32
  end
26
33
  end
@@ -11,20 +11,19 @@ class Freddy
11
11
  @topic_exchange = channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
12
12
  end
13
13
 
14
- def produce(destination, payload, properties)
15
- span = OpenTracing.start_span("freddy:notify:#{destination}",
16
- tags: {
17
- 'message_bus.destination' => destination,
18
- 'component' => 'freddy',
19
- 'span.kind' => 'producer' # Message Bus
20
- })
14
+ def produce(routing_key, payload, properties)
15
+ span = Tracing.span_for_produce(@topic_exchange, routing_key, payload)
21
16
 
22
17
  properties = properties.merge(
23
- routing_key: destination,
18
+ routing_key: routing_key,
24
19
  content_type: CONTENT_TYPE
25
20
  )
26
- OpenTracing.global_tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, TraceCarrier.new(properties))
27
- json_payload = Payload.dump(payload)
21
+ Tracing.inject_tracing_information_to_properties!(properties)
22
+
23
+ json_payload = Freddy::Encoding.compress(
24
+ Payload.dump(payload),
25
+ properties[:content_encoding]
26
+ )
28
27
 
29
28
  # Connection adapters handle thread safety for #publish themselves. No
30
29
  # need to lock these.
@@ -33,8 +32,6 @@ class Freddy
33
32
  ensure
34
33
  # We don't know how many listeners there are and we do not know when
35
34
  # this message gets processed. Instead we close the span immediately.
36
- # Listeners should use FollowsFrom to add trace information.
37
- # https://github.com/opentracing/specification/blob/master/specification.md
38
35
  span.finish
39
36
  end
40
37
  end
@@ -12,7 +12,6 @@ class Freddy
12
12
  @request_manager = RequestManager.new(@logger)
13
13
 
14
14
  @exchange = @channel.default_exchange
15
- @topic_exchange = @channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
16
15
 
17
16
  @channel.on_no_route do |correlation_id|
18
17
  @request_manager.no_route(correlation_id)
@@ -24,43 +23,37 @@ class Freddy
24
23
  @response_consumer.consume(@channel, @response_queue, &method(:handle_response))
25
24
  end
26
25
 
27
- def produce(destination, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
26
+ def produce(routing_key, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
28
27
  correlation_id = SecureRandom.uuid
29
28
 
30
- span = OpenTracing.start_span("freddy:request:#{destination}",
31
- tags: {
32
- 'component' => 'freddy',
33
- 'span.kind' => 'client', # RPC
34
- 'payload.type' => payload[:type] || 'unknown',
35
- 'message_bus.destination' => destination,
36
- 'message_bus.response_queue' => @response_queue.name,
37
- 'message_bus.correlation_id' => correlation_id,
38
- 'freddy.timeout_in_seconds' => timeout_in_seconds
39
- })
29
+ span = Tracing.span_for_produce(
30
+ @exchange,
31
+ routing_key,
32
+ payload,
33
+ correlation_id: correlation_id, timeout_in_seconds: timeout_in_seconds
34
+ )
40
35
 
41
36
  container = SyncResponseContainer.new(
42
- on_timeout(correlation_id, destination, timeout_in_seconds, span)
37
+ on_timeout(correlation_id, routing_key, timeout_in_seconds, span)
43
38
  )
44
39
 
45
40
  @request_manager.store(correlation_id,
46
41
  callback: container,
47
42
  span: span,
48
- destination: destination)
43
+ destination: routing_key)
49
44
 
50
45
  properties[:expiration] = (timeout_in_seconds * 1000).to_i if delete_on_timeout
51
46
 
52
47
  properties = properties.merge(
53
- routing_key: destination, content_type: CONTENT_TYPE,
48
+ routing_key: routing_key, content_type: CONTENT_TYPE,
54
49
  correlation_id: correlation_id, reply_to: @response_queue.name,
55
50
  mandatory: true, type: 'request'
56
51
  )
57
- OpenTracing.global_tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, TraceCarrier.new(properties))
58
- json_payload = Payload.dump(payload)
52
+ Tracing.inject_tracing_information_to_properties!(properties)
59
53
 
60
54
  # Connection adapters handle thread safety for #publish themselves. No
61
- # need to lock these.
62
- @topic_exchange.publish json_payload, properties.dup
63
- @exchange.publish json_payload, properties.dup
55
+ # need to lock this.
56
+ @exchange.publish Payload.dump(payload), properties.dup
64
57
 
65
58
  container.wait_for_response(timeout_in_seconds)
66
59
  end
@@ -82,28 +75,21 @@ class Freddy
82
75
  "with correlation_id #{delivery.correlation_id}"
83
76
  request[:callback].call(delivery.payload, delivery)
84
77
  rescue InvalidRequestError => e
85
- request[:span].set_tag('error', true)
86
- request[:span].log_kv(
87
- event: 'invalid request',
88
- message: e.message,
89
- 'error.object': e
90
- )
78
+ request[:span].record_exception(e)
79
+ request[:span].status = OpenTelemetry::Trace::Status.error
91
80
  raise e
92
81
  ensure
93
82
  request[:span].finish
94
83
  end
95
84
 
96
- def on_timeout(correlation_id, destination, timeout_in_seconds, span)
85
+ def on_timeout(correlation_id, routing_key, timeout_in_seconds, span)
97
86
  proc do
98
- @logger.warn "Request timed out waiting response from #{destination}"\
87
+ @logger.warn "Request timed out waiting response from #{routing_key}"\
99
88
  ", correlation id #{correlation_id}, timeout #{timeout_in_seconds}s"
100
89
 
101
90
  @request_manager.delete(correlation_id)
102
- span.set_tag('error', true)
103
- span.log_kv(
104
- event: 'timed out',
105
- message: "Timed out waiting response from #{destination}"
106
- )
91
+ span.add_event('timeout')
92
+ span.status = OpenTelemetry::Trace::Status.error("Timed out waiting response from #{routing_key}")
107
93
  span.finish
108
94
  end
109
95
  end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir[File.dirname(__FILE__) + '/producers/*.rb'].each(&method(:require))
3
+ Dir["#{File.dirname(__FILE__)}/producers/*.rb"].sort.each(&method(:require))
@@ -3,7 +3,7 @@
3
3
  class Freddy
4
4
  class RequestManager
5
5
  def initialize(logger)
6
- @requests = ConcurrentHash.new
6
+ @requests = {}
7
7
  @logger = logger
8
8
  end
9
9
 
@@ -22,13 +22,5 @@ class Freddy
22
22
  def delete(correlation_id)
23
23
  @requests.delete(correlation_id)
24
24
  end
25
-
26
- class ConcurrentHash < Hash
27
- # CRuby hash does not need any locks. Only adding when using JRuby.
28
- if RUBY_PLATFORM == 'java'
29
- require 'jruby/synchronized'
30
- include JRuby::Synchronized
31
- end
32
- end
33
25
  end
34
26
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Freddy
4
+ module Tracing
5
+ # NOTE: Make sure you finish the span youself.
6
+ def self.span_for_produce(exchange, routing_key, payload, correlation_id: nil, timeout_in_seconds: nil)
7
+ destination = exchange.name
8
+ destination_kind = exchange.type == :direct ? 'queue' : 'topic'
9
+
10
+ attributes = {
11
+ 'payload.type' => (payload[:type] || 'unknown').to_s,
12
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM => 'rabbitmq',
13
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_RABBITMQ_ROUTING_KEY => routing_key,
14
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION => destination,
15
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION_KIND => destination_kind,
16
+ OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION => 'send'
17
+ }
18
+
19
+ attributes['freddy.timeout_in_seconds'] = timeout_in_seconds if timeout_in_seconds
20
+
21
+ if correlation_id
22
+ attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_CONVERSATION_ID] = correlation_id
23
+ end
24
+
25
+ Freddy.tracer.start_span(
26
+ ".#{routing_key} send",
27
+ kind: OpenTelemetry::Trace::SpanKind::PRODUCER,
28
+ attributes: attributes
29
+ )
30
+ end
31
+
32
+ def self.inject_tracing_information_to_properties!(properties)
33
+ properties[:headers] ||= {}
34
+ OpenTelemetry.propagation.inject(properties[:headers])
35
+ end
36
+ end
37
+ end