instana 1.193.5 → 1.195.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +20 -2
  3. data/Appraisals +17 -1
  4. data/docker-compose.yml +20 -0
  5. data/gemfiles/aws_30.gemfile +21 -0
  6. data/gemfiles/excon_021.gemfile +18 -0
  7. data/gemfiles/excon_079.gemfile +18 -0
  8. data/gemfiles/shoryuken_50.gemfile +19 -0
  9. data/lib/instana/activators/aws_sdk_dynamodb.rb +20 -0
  10. data/lib/instana/activators/aws_sdk_s3.rb +20 -0
  11. data/lib/instana/activators/aws_sdk_sns.rb +20 -0
  12. data/lib/instana/activators/aws_sdk_sqs.rb +20 -0
  13. data/lib/instana/activators/excon.rb +1 -1
  14. data/lib/instana/activators/shoryuken.rb +24 -0
  15. data/lib/instana/config.rb +3 -0
  16. data/lib/instana/instrumentation/aws_sdk_dynamodb.rb +48 -0
  17. data/lib/instana/instrumentation/aws_sdk_s3.rb +55 -0
  18. data/lib/instana/instrumentation/aws_sdk_sns.rb +29 -0
  19. data/lib/instana/instrumentation/aws_sdk_sqs.rb +98 -0
  20. data/lib/instana/instrumentation/excon.rb +10 -4
  21. data/lib/instana/instrumentation/instrumented_request.rb +64 -7
  22. data/lib/instana/instrumentation/net-http.rb +10 -3
  23. data/lib/instana/instrumentation/rack.rb +35 -15
  24. data/lib/instana/instrumentation/shoryuken.rb +44 -0
  25. data/lib/instana/instrumentation/sidekiq-client.rb +1 -1
  26. data/lib/instana/instrumentation/sidekiq-worker.rb +1 -1
  27. data/lib/instana/secrets.rb +2 -2
  28. data/lib/instana/tracer.rb +14 -11
  29. data/lib/instana/tracing/span.rb +9 -3
  30. data/lib/instana/tracing/span_context.rb +25 -1
  31. data/lib/instana/version.rb +1 -1
  32. data/test/instrumentation/aws_test.rb +161 -0
  33. data/test/instrumentation/excon_test.rb +1 -0
  34. data/test/instrumentation/net_http_test.rb +18 -0
  35. data/test/instrumentation/rack_instrumented_request_test.rb +50 -3
  36. data/test/instrumentation/rack_test.rb +141 -0
  37. data/test/instrumentation/shoryuken_test.rb +47 -0
  38. data/test/tracing/opentracing_test.rb +3 -3
  39. metadata +21 -3
  40. data/Dockerfile +0 -16
@@ -0,0 +1,29 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Instrumentation
6
+ class SNS < Seahorse::Client::Plugin
7
+ class Handler < Seahorse::Client::Handler
8
+ def call(context)
9
+ sns_tags = {
10
+ topic: context.params[:topic_arn],
11
+ target: context.params[:target_arn],
12
+ phone: context.params[:phone_number],
13
+ subject: context.params[:subject]
14
+ }.compact
15
+
16
+ if context.operation_name == :publish
17
+ ::Instana.tracer.trace(:sns, {sns: sns_tags}) { @handler.call(context) }
18
+ else
19
+ @handler.call(context)
20
+ end
21
+ end
22
+ end
23
+
24
+ def add_handlers(handlers, _config)
25
+ handlers.add(Handler, step: :initialize)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,98 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Instrumentation
6
+ class SQS < Seahorse::Client::Plugin
7
+ class Handler < Seahorse::Client::Handler
8
+ SPAN_FORMING_OPERATIONS = [:send_message, :send_message_batch, :get_queue_url, :create_queue, :delete_message, :delete_message_batch].freeze
9
+
10
+ def call(context)
11
+ is_tracing = ::Instana.tracer.tracing?
12
+ unless is_tracing && SPAN_FORMING_OPERATIONS.include?(context.operation_name)
13
+ return @handler.call(context)
14
+ end
15
+
16
+ span_tags = tags_for(context.operation_name, context.params).compact
17
+
18
+ ::Instana.tracer.trace(:sqs, {sqs: span_tags}) do |span|
19
+ case context.operation_name
20
+ when :send_message
21
+ inject_instana_headers(span, context.params)
22
+ when :send_message_batch
23
+ context.params[:entries].each { |e| inject_instana_headers(span, e) }
24
+ end
25
+
26
+ response = @handler.call(context)
27
+
28
+ span_tags[:queue] = response.queue_url if response.respond_to?(:queue_url)
29
+ span.set_tags(sqs: span_tags)
30
+
31
+ response
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def inject_instana_headers(span, params)
38
+ params[:message_attributes] ||= {}
39
+ params[:message_attributes].merge!({
40
+ "X_INSTANA_T" => {data_type: 'String', string_value: span.context.trace_id},
41
+ "X_INSTANA_S" => {data_type: 'String', string_value: span.context.span_id},
42
+ "X_INSTANA_L" => {data_type: 'String', string_value: span.context.level.to_s}
43
+ })
44
+ end
45
+
46
+ def tags_for(operation_name, params)
47
+ case operation_name
48
+ when :create_queue
49
+ {
50
+ sort: 'exit',
51
+ type: 'create.queue',
52
+ queue: params[:queue_name]
53
+ }
54
+ when :get_queue_url
55
+ {
56
+ sort: 'exit',
57
+ type: 'get.queue',
58
+ queue: params[:queue_name]
59
+ }
60
+ when :send_message
61
+ {
62
+ sort: 'exit',
63
+ type: 'single.sync',
64
+ queue: params[:queue_url],
65
+ group: params[:message_group_id]
66
+ }
67
+ when :send_message_batch
68
+ {
69
+ sort: 'exit',
70
+ type: 'single.sync',
71
+ queue: params[:queue_url],
72
+ size: params[:entries].count
73
+ }
74
+ when :delete_message
75
+ {
76
+ sort: 'exit',
77
+ type: 'delete.single.sync',
78
+ queue: params[:queue_url]
79
+ }
80
+ when :delete_message_batch
81
+ {
82
+ sort: 'exit',
83
+ type: 'delete.batch.sync',
84
+ queue: params[:queue_url],
85
+ size: params[:entries].count
86
+ }
87
+ else
88
+ {}
89
+ end
90
+ end
91
+ end
92
+
93
+ def add_handlers(handlers, _config)
94
+ handlers.add(Handler, step: :initialize)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -5,12 +5,13 @@ module Instana
5
5
  module Instrumentation
6
6
  class Excon < ::Excon::Middleware::Base
7
7
  def request_call(datum)
8
- return @stack.request_call(datum) unless ::Instana.tracer.tracing?
8
+ return @stack.request_call(datum) unless ::Instana.tracer.tracing? && !Instana.tracer.current_span.exit_span?
9
9
 
10
10
  payload = { :http => {} }
11
- path = datum[:path].split('?').first
11
+ path, query = datum[:path].split('?', 2)
12
12
  payload[:http][:url] = ::Instana.secrets.remove_from_query("#{datum[:connection].instance_variable_get(:@socket_key)}#{path}")
13
13
  payload[:http][:method] = datum[:method] if datum.key?(:method)
14
+ payload[:http][:params] = ::Instana.secrets.remove_from_query(query || '')
14
15
 
15
16
  if datum[:pipeline] == true
16
17
  # Pass the context along in the datum so we get back on response
@@ -26,11 +27,16 @@ module Instana
26
27
  datum[:headers]['X-Instana-T'] = t_context.trace_id_header
27
28
  datum[:headers]['X-Instana-S'] = t_context.span_id_header
28
29
 
30
+ if ::Instana.config[:w3_trace_correlation]
31
+ datum[:headers]['Traceparent'] = t_context.trace_parent_header
32
+ datum[:headers]['Tracestate'] = t_context.trace_state_header
33
+ end
34
+
29
35
  @stack.request_call(datum)
30
36
  end
31
37
 
32
38
  def error_call(datum)
33
- return @stack.error_call(datum) unless ::Instana.tracer.tracing?
39
+ return @stack.error_call(datum) unless ::Instana.tracer.tracing? || !Instana.tracer.current_span.exit_span?
34
40
 
35
41
  if datum[:pipeline] == true
36
42
  ::Instana.tracer.log_async_error(datum[:error], datum[:instana_span])
@@ -43,7 +49,7 @@ module Instana
43
49
  def response_call(datum)
44
50
  # FIXME: Will connect exceptions call a response?
45
51
  #
46
- return @stack.response_call(datum) unless ::Instana.tracer.tracing?
52
+ return @stack.response_call(datum) unless ::Instana.tracer.tracing? || !Instana.tracer.current_span.exit_span?
47
53
 
48
54
  result = @stack.response_call(datum)
49
55
 
@@ -9,19 +9,24 @@ require 'rack/request'
9
9
 
10
10
  module Instana
11
11
  class InstrumentedRequest < Rack::Request
12
+ W3_TRACE_PARENT_FORMAT = /00-(?<trace>[0-9a-f]+)-(?<parent>[0-9a-f]+)-(?<flags>[0-9a-f]+)/.freeze
13
+ INSTANA_TRACE_STATE = /in=(?<trace>[0-9a-f]+);(?<span>[0-9a-f]+)/.freeze
14
+
12
15
  def skip_trace?
13
16
  # Honor X-Instana-L
14
17
  @env.has_key?('HTTP_X_INSTANA_L') && @env['HTTP_X_INSTANA_L'].start_with?('0')
15
18
  end
16
19
 
17
20
  def incoming_context
18
- context = {}
21
+ context = if @env['HTTP_X_INSTANA_T']
22
+ context_from_instana_headers
23
+ elsif @env['HTTP_TRACEPARENT'] && ::Instana.config[:w3_trace_correlation]
24
+ context_from_trace_parent
25
+ else
26
+ {}
27
+ end
19
28
 
20
- if @env['HTTP_X_INSTANA_T']
21
- context[:trace_id] = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T'])
22
- context[:span_id] = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_S']) if @env['HTTP_X_INSTANA_S']
23
- context[:level] = @env['HTTP_X_INSTANA_L'][0] if @env['HTTP_X_INSTANA_L']
24
- end
29
+ context[:level] = @env['HTTP_X_INSTANA_L'][0] if @env['HTTP_X_INSTANA_L']
25
30
 
26
31
  context
27
32
  end
@@ -41,12 +46,17 @@ module Instana
41
46
  headers
42
47
  end
43
48
 
49
+ def request_params
50
+ ::Instana.secrets.remove_from_query(@env['QUERY_STRING'])
51
+ end
52
+
44
53
  def request_tags
45
54
  {
46
55
  method: request_method,
47
56
  url: CGI.unescape(path_info),
48
57
  host: host_with_port,
49
- header: extra_header_tags
58
+ header: extra_header_tags,
59
+ params: request_params
50
60
  }.compact
51
61
  end
52
62
 
@@ -54,8 +64,55 @@ module Instana
54
64
  @correlation_data ||= parse_correlation_data
55
65
  end
56
66
 
67
+ def instana_ancestor
68
+ @instana_ancestor ||= parse_trace_state
69
+ end
70
+
71
+ def continuing_from_trace_parent?
72
+ incoming_context.include?(:external_trace_id)
73
+ end
74
+
75
+ def synthetic?
76
+ @env.has_key?('HTTP_X_INSTANA_SYNTHETIC') && @env['HTTP_X_INSTANA_SYNTHETIC'].eql?('1')
77
+ end
78
+
57
79
  private
58
80
 
81
+ def context_from_instana_headers
82
+ {
83
+ trace_id: ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T']),
84
+ span_id: ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_S'])
85
+ }.compact
86
+ end
87
+
88
+ def context_from_trace_parent
89
+ return {} unless @env.has_key?('HTTP_TRACEPARENT')
90
+ matches = @env['HTTP_TRACEPARENT'].match(W3_TRACE_PARENT_FORMAT)
91
+ return {} unless matches
92
+
93
+ {
94
+ external_trace_id: matches['trace'],
95
+ external_state: @env['HTTP_TRACESTATE'],
96
+ trace_id: ::Instana::Util.header_to_id(matches['trace'][16..-1]), # rubocop:disable Style/SlicingWithRange, Lint/RedundantCopDisableDirective
97
+ span_id: ::Instana::Util.header_to_id(matches['parent'])
98
+ }
99
+ end
100
+
101
+ def parse_trace_state
102
+ return {} unless @env.has_key?('HTTP_TRACESTATE')
103
+ token = @env['HTTP_TRACESTATE']
104
+ .split(/,/)
105
+ .map { |t| t.match(INSTANA_TRACE_STATE) }
106
+ .reject { |t| t.nil? }
107
+ .first
108
+ return {} unless token
109
+
110
+ {
111
+ t: token['trace'],
112
+ p: token['span']
113
+ }
114
+ end
115
+
59
116
  def parse_correlation_data
60
117
  return {} unless @env.has_key?('HTTP_X_INSTANA_L')
61
118
  _level, *tokens = @env['HTTP_X_INSTANA_L'].split(/[,=;]/)
@@ -7,7 +7,7 @@ module Instana
7
7
  module Instrumentation
8
8
  module NetHTTPInstrumentation
9
9
  def request(*args, &block)
10
- if !Instana.tracer.tracing? || !started?
10
+ if !Instana.tracer.tracing? || Instana.tracer.current_span.exit_span? || !started?
11
11
  do_skip = true
12
12
  return super(*args, &block)
13
13
  end
@@ -22,12 +22,19 @@ module Instana
22
22
  request['X-Instana-T'] = t_context.trace_id_header
23
23
  request['X-Instana-S'] = t_context.span_id_header
24
24
 
25
+ if ::Instana.config[:w3_trace_correlation]
26
+ request['Traceparent'] = t_context.trace_parent_header
27
+ request['Tracestate'] = t_context.trace_state_header
28
+ end
29
+
25
30
  # Collect up KV info now in case any exception is raised
26
31
  kv_payload = { :http => {} }
27
32
  kv_payload[:http][:method] = request.method
28
33
 
29
34
  if request.uri
30
- kv_payload[:http][:url] = request.uri.to_s
35
+ uri_without_query = request.uri.dup.tap { |r| r.query = nil }
36
+ kv_payload[:http][:url] = uri_without_query.to_s.gsub(/\?\z/, '')
37
+ kv_payload[:http][:params] = ::Instana.secrets.remove_from_query(request.uri.query)
31
38
  else
32
39
  if use_ssl?
33
40
  kv_payload[:http][:url] = "https://#{@address}:#{@port}#{request.path}"
@@ -36,7 +43,7 @@ module Instana
36
43
  end
37
44
  end
38
45
 
39
- kv_payload[:http][:url] = ::Instana.secrets.remove_from_query(kv_payload[:http][:url])
46
+ kv_payload[:http][:url] = ::Instana.secrets.remove_from_query(kv_payload[:http][:url]).gsub(/\?\z/, '')
40
47
 
41
48
  # The core call
42
49
  response = super(*args, &block)
@@ -20,14 +20,26 @@ module Instana
20
20
 
21
21
  current_span = ::Instana.tracer.log_start_or_continue(:rack, {}, req.incoming_context)
22
22
 
23
- unless req.correlation_data.empty?
24
- current_span[:crid] = req.correlation_data[:id]
25
- current_span[:crtp] = req.correlation_data[:type]
26
- end
27
-
28
23
  status, headers, response = @app.call(env)
29
24
 
30
25
  if ::Instana.tracer.tracing?
26
+ unless req.correlation_data.empty?
27
+ current_span[:crid] = req.correlation_data[:id]
28
+ current_span[:crtp] = req.correlation_data[:type]
29
+ end
30
+
31
+ if !req.instana_ancestor.empty? && req.continuing_from_trace_parent?
32
+ current_span[:ia] = req.instana_ancestor
33
+ end
34
+
35
+ if req.continuing_from_trace_parent?
36
+ current_span[:tp] = true
37
+ current_span[:lt] = req.incoming_context[:external_trace_id]
38
+ end
39
+
40
+ if req.synthetic?
41
+ current_span[:sy] = true
42
+ end
31
43
  # In case some previous middleware returned a string status, make sure that we're dealing with
32
44
  # an integer. In Ruby nil.to_i, "asdfasdf".to_i will always return 0 from Ruby versions 1.8.7 and newer.
33
45
  # So if an 0 status is reported here, it indicates some other issue (e.g. no status from previous middleware)
@@ -45,23 +57,31 @@ module Instana
45
57
  # See: https://www.instana.com/docs/tracing/custom-best-practices/#path-templates-visual-grouping-of-http-endpoints
46
58
  kvs[:http][:path_tpl] = env['INSTANA_HTTP_PATH_TEMPLATE'] if env['INSTANA_HTTP_PATH_TEMPLATE']
47
59
 
48
- # Save the IDs before the trace ends so we can place
60
+ # Save the span context before the trace ends so we can place
49
61
  # them in the response headers in the ensure block
50
- trace_id = ::Instana.tracer.current_span.trace_id
51
- span_id = ::Instana.tracer.current_span.id
62
+ trace_context = ::Instana.tracer.current_span.context
52
63
  end
53
64
 
54
65
  [status, headers, response]
55
66
  rescue Exception => e
56
- ::Instana.tracer.log_error(e)
67
+ ::Instana.tracer.log_error(e) if ::Instana.tracer.tracing?
57
68
  raise
58
69
  ensure
59
- if headers && ::Instana.tracer.tracing?
60
- # Set reponse headers; encode as hex string
61
- headers['X-Instana-T'] = ::Instana::Util.id_to_header(trace_id)
62
- headers['X-Instana-S'] = ::Instana::Util.id_to_header(span_id)
63
- headers['X-Instana-L'] = '1'
64
- headers['Server-Timing'] = "intid;desc=#{::Instana::Util.id_to_header(trace_id)}"
70
+ if ::Instana.tracer.tracing?
71
+ if headers
72
+ # Set response headers; encode as hex string
73
+ headers['X-Instana-T'] = trace_context.trace_id_header
74
+ headers['X-Instana-S'] = trace_context.span_id_header
75
+ headers['X-Instana-L'] = '1'
76
+
77
+ if ::Instana.config[:w3_trace_correlation]
78
+ headers['Traceparent'] = trace_context.trace_parent_header
79
+ headers['Tracestate'] = trace_context.trace_state_header
80
+ end
81
+
82
+ headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
83
+ end
84
+
65
85
  ::Instana.tracer.log_end(:rack, kvs)
66
86
  end
67
87
  end
@@ -0,0 +1,44 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Instrumentation
6
+ class Shoryuken
7
+ def call(_worker_instance, _queue, sqs_message, _body, &block)
8
+ if sqs_message.is_a? Array
9
+ return yield
10
+ end
11
+
12
+ sqs_tags = {
13
+ sort: 'entry',
14
+ queue: sqs_message.queue_url
15
+ }
16
+
17
+ context = incomming_context_from(sqs_message.message_attributes)
18
+ ::Instana.tracer.start_or_continue_trace(:sqs, {sqs: sqs_tags}, context, &block)
19
+ end
20
+
21
+ private
22
+
23
+ def incomming_context_from(attributes)
24
+ trace_id = try(attributes, 'X_INSTANA_T', 'X_INSTANA_ST')
25
+ span_id = try(attributes, 'X_INSTANA_S', 'X_INSTANA_SS')
26
+ level = try(attributes, 'X_INSTANA_L', 'X_INSTANA_SL')
27
+
28
+ {
29
+ trace_id: trace_id,
30
+ span_id: span_id,
31
+ level: level
32
+ }.compact
33
+ end
34
+
35
+ def try(attributes, *args)
36
+ key = args.detect do |a|
37
+ attributes && attributes[a] && attributes[a].respond_to?(:string_value)
38
+ end
39
+
40
+ attributes[key].string_value if attributes && key
41
+ end
42
+ end
43
+ end
44
+ end
@@ -7,7 +7,7 @@ module Instana
7
7
  def call(worker_class, msg, queue, _redis_pool)
8
8
  kv_payload = { :'sidekiq-client' => {} }
9
9
  kv_payload[:'sidekiq-client'][:queue] = queue
10
- kv_payload[:'sidekiq-client'][:job] = worker_class
10
+ kv_payload[:'sidekiq-client'][:job] = worker_class.to_s
11
11
  kv_payload[:'sidekiq-client'][:retry] = msg['retry'].to_s
12
12
  ::Instana.tracer.log_entry(:'sidekiq-client', kv_payload)
13
13
 
@@ -8,7 +8,7 @@ module Instana
8
8
  kv_payload = { :'sidekiq-worker' => {} }
9
9
  kv_payload[:'sidekiq-worker'][:job_id] = msg['jid']
10
10
  kv_payload[:'sidekiq-worker'][:queue] = msg['queue']
11
- kv_payload[:'sidekiq-worker'][:job] = msg['class']
11
+ kv_payload[:'sidekiq-worker'][:job] = msg['class'].to_s
12
12
  kv_payload[:'sidekiq-worker'][:retry] = msg['retry'].to_s
13
13
 
14
14
  # Temporary until we move connection collection to redis