instana 1.193.6 → 1.195.4

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +16 -2
  3. data/Appraisals +13 -1
  4. data/docker-compose.yml +20 -0
  5. data/gemfiles/aws_30.gemfile +3 -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_s3.rb +20 -0
  10. data/lib/instana/activators/aws_sdk_sns.rb +20 -0
  11. data/lib/instana/activators/aws_sdk_sqs.rb +20 -0
  12. data/lib/instana/activators/excon.rb +1 -1
  13. data/lib/instana/activators/shoryuken.rb +24 -0
  14. data/lib/instana/config.rb +3 -0
  15. data/lib/instana/instrumentation/aws_sdk_dynamodb.rb +21 -2
  16. data/lib/instana/instrumentation/aws_sdk_s3.rb +55 -0
  17. data/lib/instana/instrumentation/aws_sdk_sns.rb +29 -0
  18. data/lib/instana/instrumentation/aws_sdk_sqs.rb +98 -0
  19. data/lib/instana/instrumentation/excon.rb +17 -4
  20. data/lib/instana/instrumentation/instrumented_request.rb +64 -7
  21. data/lib/instana/instrumentation/net-http.rb +9 -2
  22. data/lib/instana/instrumentation/rack.rb +35 -15
  23. data/lib/instana/instrumentation/shoryuken.rb +44 -0
  24. data/lib/instana/secrets.rb +2 -2
  25. data/lib/instana/tracer.rb +14 -11
  26. data/lib/instana/tracing/span.rb +3 -3
  27. data/lib/instana/tracing/span_context.rb +25 -1
  28. data/lib/instana/version.rb +1 -1
  29. data/test/instrumentation/aws_test.rb +130 -2
  30. data/test/instrumentation/excon_test.rb +16 -1
  31. data/test/instrumentation/net_http_test.rb +18 -0
  32. data/test/instrumentation/rack_instrumented_request_test.rb +50 -3
  33. data/test/instrumentation/rack_test.rb +141 -0
  34. data/test/instrumentation/shoryuken_test.rb +47 -0
  35. data/test/tracing/opentracing_test.rb +3 -3
  36. metadata +16 -3
  37. data/Dockerfile +0 -16
@@ -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? || !Instana.tracer.current_span.exit_span?
8
+ return @stack.request_call(datum) unless traceable?
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? || !Instana.tracer.current_span.exit_span?
39
+ return @stack.error_call(datum) unless traceable?
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? || !Instana.tracer.current_span.exit_span?
52
+ return @stack.response_call(datum) unless traceable?
47
53
 
48
54
  result = @stack.response_call(datum)
49
55
 
@@ -66,6 +72,13 @@ module Instana
66
72
  end
67
73
  result
68
74
  end
75
+
76
+ private
77
+
78
+ def traceable?
79
+ ::Instana.tracer.tracing? &&
80
+ (!Instana.tracer.current_span.exit_span? || Instana.tracer.current_span.name == :excon)
81
+ end
69
82
  end
70
83
  end
71
84
  end
@@ -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(/[,=;]/)
@@ -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
@@ -10,7 +10,7 @@ module Instana
10
10
  return str unless secret_values
11
11
 
12
12
  url = URI(str)
13
- params = CGI.parse(url.query || '')
13
+ params = url.scheme ? CGI.parse(url.query || '') : CGI.parse(url.to_s)
14
14
 
15
15
  redacted = params.map do |k, v|
16
16
  needs_redaction = secret_values['list']
@@ -19,7 +19,7 @@ module Instana
19
19
  end
20
20
 
21
21
  url.query = URI.encode_www_form(redacted)
22
- CGI.unescape(url.to_s)
22
+ url.scheme ? CGI.unescape(url.to_s) : CGI.unescape(url.query)
23
23
  end
24
24
 
25
25
  private
@@ -38,8 +38,8 @@ module Instana
38
38
  # :level specifies data collection level (optional)
39
39
  #
40
40
  def start_or_continue_trace(name, kvs = {}, incoming_context = nil, &block)
41
- log_start_or_continue(name, kvs, incoming_context)
42
- yield
41
+ span = log_start_or_continue(name, kvs, incoming_context)
42
+ yield(span)
43
43
  rescue Exception => e
44
44
  log_error(e)
45
45
  raise
@@ -59,8 +59,8 @@ module Instana
59
59
  # @param kvs [Hash] list of key values to be reported in this new span
60
60
  #
61
61
  def trace(name, kvs = {}, &block)
62
- log_entry(name, kvs)
63
- yield
62
+ span = log_entry(name, kvs)
63
+ yield(span)
64
64
  rescue Exception => e
65
65
  log_error(e)
66
66
  raise
@@ -91,7 +91,15 @@ module Instana
91
91
  if incoming_context
92
92
  if incoming_context.is_a?(Hash)
93
93
  if !incoming_context.empty?
94
- parent_context = SpanContext.new(incoming_context[:trace_id], incoming_context[:span_id], incoming_context[:level])
94
+ parent_context = SpanContext.new(
95
+ incoming_context[:trace_id],
96
+ incoming_context[:span_id],
97
+ incoming_context[:level],
98
+ {
99
+ external_trace_id: incoming_context[:external_trace_id],
100
+ external_state: incoming_context[:external_state]
101
+ }
102
+ )
95
103
  end
96
104
  else
97
105
  parent_context = incoming_context
@@ -103,12 +111,7 @@ module Instana
103
111
  else
104
112
  self.current_span = Span.new(name)
105
113
  end
106
-
107
- if incoming_context.is_a?(Hash) && incoming_context[:correlation] && !incoming_context[:correlation].empty?
108
- self.current_span[:crid] = incoming_context[:correlation][:id]
109
- self.current_span[:crtp] = incoming_context[:correlation][:type]
110
- end
111
-
114
+
112
115
  self.current_span.set_tags(kvs) unless kvs.empty?
113
116
  self.current_span
114
117
  end
@@ -6,10 +6,10 @@ module Instana
6
6
  REGISTERED_SPANS = [ :actioncontroller, :actionview, :activerecord, :excon,
7
7
  :memcache, :'net-http', :rack, :render, :'rpc-client',
8
8
  :'rpc-server', :'sidekiq-client', :'sidekiq-worker',
9
- :redis, :'resque-client', :'resque-worker', :'graphql.server', :dynamodb ].freeze
10
- ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server' ].freeze
9
+ :redis, :'resque-client', :'resque-worker', :'graphql.server', :dynamodb, :s3, :sns, :sqs ].freeze
10
+ ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server', :sqs ].freeze
11
11
  EXIT_SPANS = [ :activerecord, :excon, :'net-http', :'resque-client',
12
- :'rpc-client', :'sidekiq-client', :redis, :dynamodb ].freeze
12
+ :'rpc-client', :'sidekiq-client', :redis, :dynamodb, :s3, :sns, :sqs ].freeze
13
13
  HTTP_SPANS = [ :rack, :excon, :'net-http' ].freeze
14
14
 
15
15
  attr_accessor :parent
@@ -6,6 +6,7 @@ module Instana
6
6
  attr_accessor :trace_id
7
7
  attr_accessor :span_id
8
8
  attr_accessor :baggage
9
+ attr_reader :level
9
10
 
10
11
  # Create a new SpanContext
11
12
  #
@@ -18,7 +19,7 @@ module Instana
18
19
  @trace_id = tid
19
20
  @span_id = sid
20
21
  @level = level
21
- @baggage = baggage
22
+ @baggage = baggage || {}
22
23
  end
23
24
 
24
25
  def trace_id_header
@@ -29,8 +30,31 @@ module Instana
29
30
  ::Instana::Util.id_to_header(@span_id)
30
31
  end
31
32
 
33
+ def trace_parent_header
34
+ return '' unless valid?
35
+
36
+ trace = (@baggage[:external_trace_id] || @trace_id).rjust(32, '0')
37
+ parent = @span_id.rjust(16, '0')
38
+ flags = @level == 1 ? "01" : "00"
39
+
40
+ "00-#{trace}-#{parent}-#{flags}"
41
+ end
42
+
43
+ def trace_state_header
44
+ return '' unless valid?
45
+
46
+ state = ["in=#{trace_id_header};#{span_id_header}", @baggage[:external_state]]
47
+ state.compact.join(',')
48
+ end
49
+
32
50
  def to_hash
33
51
  { :trace_id => @trace_id, :span_id => @span_id }
34
52
  end
53
+
54
+ private
55
+
56
+ def valid?
57
+ @baggage && @trace_id && @span_id
58
+ end
35
59
  end
36
60
  end
@@ -2,6 +2,6 @@
2
2
  # (c) Copyright Instana Inc. 2016
3
3
 
4
4
  module Instana
5
- VERSION = "1.193.6"
5
+ VERSION = "1.195.4"
6
6
  VERSION_FULL = "instana-#{VERSION}"
7
7
  end