freddy 1.4.2 → 2.0.0
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 +5 -5
 - data/.github/workflows/ci.yml +31 -0
 - data/.rubocop.yml +9 -28
 - data/.ruby-gemset +1 -1
 - data/.ruby-version +1 -1
 - data/Gemfile +3 -4
 - data/README.md +15 -10
 - data/freddy.gemspec +11 -18
 - data/lib/freddy.rb +21 -17
 - data/lib/freddy/adapters.rb +3 -28
 - data/lib/freddy/adapters/bunny_adapter.rb +20 -3
 - data/lib/freddy/consumers.rb +1 -1
 - data/lib/freddy/consumers/respond_to_consumer.rb +3 -13
 - data/lib/freddy/consumers/response_consumer.rb +2 -4
 - data/lib/freddy/consumers/tap_into_consumer.rb +41 -25
 - data/lib/freddy/delivery.rb +46 -13
 - data/lib/freddy/payload.rb +3 -34
 - data/lib/freddy/producers.rb +1 -1
 - data/lib/freddy/producers/reply_producer.rb +12 -5
 - data/lib/freddy/producers/send_and_forget_producer.rb +5 -11
 - data/lib/freddy/producers/send_and_wait_response_producer.rb +19 -33
 - data/lib/freddy/request_manager.rb +1 -9
 - data/lib/freddy/tracing.rb +37 -0
 - data/lib/freddy/version.rb +5 -0
 - data/spec/.rubocop.yml +26 -0
 - data/spec/freddy/error_response_spec.rb +6 -6
 - data/spec/freddy/payload_spec.rb +25 -16
 - data/spec/integration/concurrency_spec.rb +8 -12
 - data/spec/integration/tap_into_with_group_spec.rb +34 -0
 - data/spec/integration/tracing_spec.rb +15 -32
 - data/spec/spec_helper.rb +5 -13
 - metadata +31 -19
 - data/.npmignore +0 -8
 - data/.travis.yml +0 -13
 - data/lib/freddy/adapters/march_hare_adapter.rb +0 -64
 - data/lib/freddy/trace_carrier.rb +0 -28
 - data/spec/freddy/trace_carrier_spec.rb +0 -56
 
    
        data/lib/freddy/delivery.rb
    CHANGED
    
    | 
         @@ -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,52 @@ class Freddy 
     | 
|
| 
       23 
24 
     | 
    
         
             
                  @metadata.reply_to
         
     | 
| 
       24 
25 
     | 
    
         
             
                end
         
     | 
| 
       25 
26 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
                def  
     | 
| 
       27 
     | 
    
         
            -
                   
     | 
| 
       28 
     | 
    
         
            -
                   
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                     
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            +
                    # In general we should start a new trace here and just link two traces
         
     | 
| 
      
 39 
     | 
    
         
            +
                    # together. But Zipkin (which we currently use) doesn't support links.
         
     | 
| 
      
 40 
     | 
    
         
            +
                    # So even though the root trace could finish before anything here
         
     | 
| 
      
 41 
     | 
    
         
            +
                    # starts executing, we'll continue with the root trace here as well.
         
     | 
| 
      
 42 
     | 
    
         
            +
                    OpenTelemetry::Context.with_current(producer_context) do
         
     | 
| 
      
 43 
     | 
    
         
            +
                      Freddy.tracer.in_span(name, attributes: span_attributes, links: links, kind: kind, &block)
         
     | 
| 
       37 
44 
     | 
    
         
             
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  else
         
     | 
| 
      
 46 
     | 
    
         
            +
                    OpenTelemetry::Context.with_current(producer_context) do
         
     | 
| 
      
 47 
     | 
    
         
            +
                      Freddy.tracer.in_span(name, attributes: span_attributes, kind: kind, &block)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                private
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def span_attributes
         
     | 
| 
      
 55 
     | 
    
         
            +
                  destination_kind = @exchange == '' ? 'queue' : 'topic'
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  attributes = {
         
     | 
| 
      
 58 
     | 
    
         
            +
                    'payload.type' => (@payload[:type] || 'unknown').to_s,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM => 'rabbitmq',
         
     | 
| 
      
 60 
     | 
    
         
            +
                    OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION => @exchange,
         
     | 
| 
      
 61 
     | 
    
         
            +
                    OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION_KIND => destination_kind,
         
     | 
| 
      
 62 
     | 
    
         
            +
                    OpenTelemetry::SemanticConventions::Trace::MESSAGING_RABBITMQ_ROUTING_KEY => @routing_key,
         
     | 
| 
      
 63 
     | 
    
         
            +
                    OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION => 'receive'
         
     | 
| 
      
 64 
     | 
    
         
            +
                  }
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  # There's no correlation_id when a message was sent using
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # `Freddy#deliver`.
         
     | 
| 
      
 68 
     | 
    
         
            +
                  if correlation_id
         
     | 
| 
      
 69 
     | 
    
         
            +
                    attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_CONVERSATION_ID] = correlation_id
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
       38 
71 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
                   
     | 
| 
      
 72 
     | 
    
         
            +
                  attributes
         
     | 
| 
       40 
73 
     | 
    
         
             
                end
         
     | 
| 
       41 
74 
     | 
    
         
             
              end
         
     | 
| 
       42 
75 
     | 
    
         
             
            end
         
     | 
    
        data/lib/freddy/payload.rb
    CHANGED
    
    | 
         @@ -1,11 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       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
         
     | 
| 
         @@ -20,12 +15,12 @@ class Freddy 
     | 
|
| 
       20 
15 
     | 
    
         
             
                end
         
     | 
| 
       21 
16 
     | 
    
         | 
| 
       22 
17 
     | 
    
         
             
                def self.json_handler
         
     | 
| 
       23 
     | 
    
         
            -
                  @json_handler ||=  
     | 
| 
      
 18 
     | 
    
         
            +
                  @json_handler ||= OjAdapter
         
     | 
| 
       24 
19 
     | 
    
         
             
                end
         
     | 
| 
       25 
20 
     | 
    
         | 
| 
       26 
21 
     | 
    
         
             
                class OjAdapter
         
     | 
| 
       27 
22 
     | 
    
         
             
                  PARSE_OPTIONS = { symbol_keys: true }.freeze
         
     | 
| 
       28 
     | 
    
         
            -
                  DUMP_OPTIONS = { mode: : 
     | 
| 
      
 23 
     | 
    
         
            +
                  DUMP_OPTIONS = { mode: :custom, time_format: :xmlschema, second_precision: 6 }.freeze
         
     | 
| 
       29 
24 
     | 
    
         | 
| 
       30 
25 
     | 
    
         
             
                  def self.parse(payload)
         
     | 
| 
       31 
26 
     | 
    
         
             
                    Oj.strict_load(payload, PARSE_OPTIONS)
         
     | 
| 
         @@ -35,31 +30,5 @@ class Freddy 
     | 
|
| 
       35 
30 
     | 
    
         
             
                    Oj.dump(payload, DUMP_OPTIONS)
         
     | 
| 
       36 
31 
     | 
    
         
             
                  end
         
     | 
| 
       37 
32 
     | 
    
         
             
                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 
33 
     | 
    
         
             
              end
         
     | 
| 
       65 
34 
     | 
    
         
             
            end
         
     | 
    
        data/lib/freddy/producers.rb
    CHANGED
    
    
| 
         @@ -10,17 +10,24 @@ class Freddy 
     | 
|
| 
       10 
10 
     | 
    
         
             
                    @exchange = channel.default_exchange
         
     | 
| 
       11 
11 
     | 
    
         
             
                  end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                  def produce( 
     | 
| 
       14 
     | 
    
         
            -
                     
     | 
| 
       15 
     | 
    
         
            -
                       
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
      
 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:  
     | 
| 
      
 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,19 +11,15 @@ class Freddy 
     | 
|
| 
       11 
11 
     | 
    
         
             
                    @topic_exchange = channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
         
     | 
| 
       12 
12 
     | 
    
         
             
                  end
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                  def produce( 
     | 
| 
       15 
     | 
    
         
            -
                    span =  
     | 
| 
       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:  
     | 
| 
      
 18 
     | 
    
         
            +
                      routing_key: routing_key,
         
     | 
| 
       24 
19 
     | 
    
         
             
                      content_type: CONTENT_TYPE
         
     | 
| 
       25 
20 
     | 
    
         
             
                    )
         
     | 
| 
       26 
     | 
    
         
            -
                     
     | 
| 
      
 21 
     | 
    
         
            +
                    Tracing.inject_tracing_information_to_properties!(properties)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
       27 
23 
     | 
    
         
             
                    json_payload = Payload.dump(payload)
         
     | 
| 
       28 
24 
     | 
    
         | 
| 
       29 
25 
     | 
    
         
             
                    # Connection adapters handle thread safety for #publish themselves. No
         
     | 
| 
         @@ -33,8 +29,6 @@ class Freddy 
     | 
|
| 
       33 
29 
     | 
    
         
             
                  ensure
         
     | 
| 
       34 
30 
     | 
    
         
             
                    # We don't know how many listeners there are and we do not know when
         
     | 
| 
       35 
31 
     | 
    
         
             
                    # 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 
32 
     | 
    
         
             
                    span.finish
         
     | 
| 
       39 
33 
     | 
    
         
             
                  end
         
     | 
| 
       40 
34 
     | 
    
         
             
                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( 
     | 
| 
      
 26 
     | 
    
         
            +
                  def produce(routing_key, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
         
     | 
| 
       28 
27 
     | 
    
         
             
                    correlation_id = SecureRandom.uuid
         
     | 
| 
       29 
28 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                    span =  
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       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,  
     | 
| 
      
 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:  
     | 
| 
      
 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:  
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
       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  
     | 
| 
       62 
     | 
    
         
            -
                    @ 
     | 
| 
       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]. 
     | 
| 
       86 
     | 
    
         
            -
                    request[:span]. 
     | 
| 
       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,  
     | 
| 
      
 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 #{ 
     | 
| 
      
 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. 
     | 
| 
       103 
     | 
    
         
            -
                      span. 
     | 
| 
       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
         
     | 
| 
         @@ -3,7 +3,7 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            class Freddy
         
     | 
| 
       4 
4 
     | 
    
         
             
              class RequestManager
         
     | 
| 
       5 
5 
     | 
    
         
             
                def initialize(logger)
         
     | 
| 
       6 
     | 
    
         
            -
                  @requests =  
     | 
| 
      
 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
         
     | 
    
        data/spec/.rubocop.yml
    ADDED
    
    | 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require: rubocop-rspec
         
     | 
| 
      
 2 
     | 
    
         
            +
            inherit_from: ../.rubocop.yml
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            RSpec/ExampleLength:
         
     | 
| 
      
 5 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            RSpec/MultipleExpectations:
         
     | 
| 
      
 8 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            RSpec/MessageSpies:
         
     | 
| 
      
 11 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            RSpec/VerifiedDoubles:
         
     | 
| 
      
 14 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            RSpec/InstanceVariable:
         
     | 
| 
      
 17 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            RSpec/NestedGroups:
         
     | 
| 
      
 20 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            RSpec/DescribeClass:
         
     | 
| 
      
 23 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            RSpec/MultipleMemoizedHelpers:
         
     | 
| 
      
 26 
     | 
    
         
            +
              Enabled: no
         
     | 
| 
         @@ -13,10 +13,10 @@ describe Freddy::ErrorResponse do 
     | 
|
| 
       13 
13 
     | 
    
         
             
                end
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
                describe '#message' do
         
     | 
| 
       16 
     | 
    
         
            -
                  subject { error.message }
         
     | 
| 
      
 16 
     | 
    
         
            +
                  subject(:message) { error.message }
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                  it 'uses error type as a message' do
         
     | 
| 
       19 
     | 
    
         
            -
                     
     | 
| 
      
 19 
     | 
    
         
            +
                    expect(message).to eq('SomeError')
         
     | 
| 
       20 
20 
     | 
    
         
             
                  end
         
     | 
| 
       21 
21 
     | 
    
         
             
                end
         
     | 
| 
       22 
22 
     | 
    
         
             
              end
         
     | 
| 
         @@ -31,10 +31,10 @@ describe Freddy::ErrorResponse do 
     | 
|
| 
       31 
31 
     | 
    
         
             
                end
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
                describe '#message' do
         
     | 
| 
       34 
     | 
    
         
            -
                  subject { error.message }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  subject(:message) { error.message }
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
                  it 'uses error type as a message' do
         
     | 
| 
       37 
     | 
    
         
            -
                     
     | 
| 
      
 37 
     | 
    
         
            +
                    expect(message).to eq('SomeError: extra info')
         
     | 
| 
       38 
38 
     | 
    
         
             
                  end
         
     | 
| 
       39 
39 
     | 
    
         
             
                end
         
     | 
| 
       40 
40 
     | 
    
         
             
              end
         
     | 
| 
         @@ -49,10 +49,10 @@ describe Freddy::ErrorResponse do 
     | 
|
| 
       49 
49 
     | 
    
         
             
                end
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
51 
     | 
    
         
             
                describe '#message' do
         
     | 
| 
       52 
     | 
    
         
            -
                  subject { error.message }
         
     | 
| 
      
 52 
     | 
    
         
            +
                  subject(:message) { error.message }
         
     | 
| 
       53 
53 
     | 
    
         | 
| 
       54 
54 
     | 
    
         
             
                  it 'uses default error message as a message' do
         
     | 
| 
       55 
     | 
    
         
            -
                     
     | 
| 
      
 55 
     | 
    
         
            +
                    expect(message).to eq('Use #response to get the error response')
         
     | 
| 
       56 
56 
     | 
    
         
             
                  end
         
     | 
| 
       57 
57 
     | 
    
         
             
                end
         
     | 
| 
       58 
58 
     | 
    
         
             
              end
         
     | 
    
        data/spec/freddy/payload_spec.rb
    CHANGED
    
    | 
         @@ -2,19 +2,28 @@ require 'spec_helper' 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            describe Freddy::Payload do
         
     | 
| 
       4 
4 
     | 
    
         
             
              describe '#dump' do
         
     | 
| 
       5 
     | 
    
         
            -
                 
     | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
       7 
     | 
    
         
            -
                     
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                   
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                   
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 5 
     | 
    
         
            +
                context 'with a given Ruby engine' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                  let(:ts) do
         
     | 
| 
      
 7 
     | 
    
         
            +
                    RUBY_ENGINE == 'jruby' ? '{"time":"2016-01-04T20:18:00Z"}' : '{"time":"2016-01-04T20:18:00.000000Z"}'
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
                  let(:ts_array) do
         
     | 
| 
      
 10 
     | 
    
         
            +
                    RUBY_ENGINE == 'jruby' ? '{"time":["2016-01-04T20:18:00Z"]}' : '{"time":["2016-01-04T20:18:00.000000Z"]}'
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  it 'serializes time objects as iso8601 format strings' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                    expect(dump(time: Time.utc(2016, 1, 4, 20, 18)))
         
     | 
| 
      
 15 
     | 
    
         
            +
                      .to eq(ts)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  it 'serializes time objects in an array as iso8601 format strings' do
         
     | 
| 
      
 19 
     | 
    
         
            +
                    expect(dump(time: [Time.utc(2016, 1, 4, 20, 18)]))
         
     | 
| 
      
 20 
     | 
    
         
            +
                      .to eq(ts_array)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  it 'serializes time objects in a nested hash as iso8601 format strings' do
         
     | 
| 
      
 24 
     | 
    
         
            +
                    expect(dump(x: { time: Time.utc(2016, 1, 4, 20, 18) }))
         
     | 
| 
      
 25 
     | 
    
         
            +
                      .to eq("{\"x\":#{ts}}")
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
       18 
27 
     | 
    
         
             
                end
         
     | 
| 
       19 
28 
     | 
    
         | 
| 
       20 
29 
     | 
    
         
             
                it 'serializes date objects as iso8601 format strings' do
         
     | 
| 
         @@ -33,17 +42,17 @@ describe Freddy::Payload do 
     | 
|
| 
       33 
42 
     | 
    
         
             
                end
         
     | 
| 
       34 
43 
     | 
    
         | 
| 
       35 
44 
     | 
    
         
             
                it 'serializes datetime objects as iso8601 format strings' do
         
     | 
| 
       36 
     | 
    
         
            -
                  expect(dump(datetime: DateTime.new(2016, 1, 4, 20, 18))) 
     | 
| 
      
 45 
     | 
    
         
            +
                  expect(dump(datetime: DateTime.new(2016, 1, 4, 20, 18)))
         
     | 
| 
       37 
46 
     | 
    
         
             
                    .to eq('{"datetime":"2016-01-04T20:18:00+00:00"}')
         
     | 
| 
       38 
47 
     | 
    
         
             
                end
         
     | 
| 
       39 
48 
     | 
    
         | 
| 
       40 
49 
     | 
    
         
             
                it 'serializes datetime objects in an array as iso8601 format strings' do
         
     | 
| 
       41 
     | 
    
         
            -
                  expect(dump(datetime: [DateTime.new(2016, 1, 4, 20, 18)])) 
     | 
| 
      
 50 
     | 
    
         
            +
                  expect(dump(datetime: [DateTime.new(2016, 1, 4, 20, 18)]))
         
     | 
| 
       42 
51 
     | 
    
         
             
                    .to eq('{"datetime":["2016-01-04T20:18:00+00:00"]}')
         
     | 
| 
       43 
52 
     | 
    
         
             
                end
         
     | 
| 
       44 
53 
     | 
    
         | 
| 
       45 
54 
     | 
    
         
             
                it 'serializes datetime objects in a nested hash as iso8601 format strings' do
         
     | 
| 
       46 
     | 
    
         
            -
                  expect(dump(x: { datetime: DateTime.new(2016, 1, 4, 20, 18) })) 
     | 
| 
      
 55 
     | 
    
         
            +
                  expect(dump(x: { datetime: DateTime.new(2016, 1, 4, 20, 18) }))
         
     | 
| 
       47 
56 
     | 
    
         
             
                    .to eq('{"x":{"datetime":"2016-01-04T20:18:00+00:00"}}')
         
     | 
| 
       48 
57 
     | 
    
         
             
                end
         
     | 
| 
       49 
58 
     | 
    
         |