freddy 1.7.0 → 2.2.1

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.
@@ -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