instana 1.11.7 → 1.192.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +10 -0
  3. data/Rakefile +26 -37
  4. data/gemfiles/libraries.gemfile +2 -0
  5. data/lib/instana/agent.rb +6 -0
  6. data/lib/instana/base.rb +2 -0
  7. data/lib/instana/config.rb +11 -0
  8. data/lib/instana/frameworks/cuba.rb +33 -0
  9. data/lib/instana/frameworks/instrumentation/action_controller.rb +11 -0
  10. data/lib/instana/frameworks/instrumentation/mysql2_adapter.rb +7 -1
  11. data/lib/instana/frameworks/instrumentation/mysql_adapter.rb +7 -1
  12. data/lib/instana/frameworks/instrumentation/postgresql_adapter.rb +23 -5
  13. data/lib/instana/frameworks/roda.rb +41 -0
  14. data/lib/instana/frameworks/sinatra.rb +17 -0
  15. data/lib/instana/instrumentation/excon.rb +1 -1
  16. data/lib/instana/instrumentation/graphql.rb +77 -0
  17. data/lib/instana/instrumentation/instrumented_request.rb +68 -0
  18. data/lib/instana/instrumentation/net-http.rb +2 -0
  19. data/lib/instana/instrumentation/rack.rb +17 -52
  20. data/lib/instana/secrets.rb +42 -0
  21. data/lib/instana/setup.rb +1 -0
  22. data/lib/instana/tracer.rb +6 -0
  23. data/lib/instana/tracing/span.rb +23 -10
  24. data/lib/instana/util.rb +15 -29
  25. data/lib/instana/version.rb +1 -1
  26. data/test/apps/cuba.rb +4 -0
  27. data/test/apps/roda.rb +3 -0
  28. data/test/apps/sinatra.rb +4 -0
  29. data/test/config_test.rb +1 -17
  30. data/test/frameworks/cuba_test.rb +14 -1
  31. data/test/frameworks/rack_test.rb +52 -19
  32. data/test/frameworks/rails/actioncontroller_test.rb +12 -0
  33. data/test/frameworks/rails/activerecord_test.rb +80 -28
  34. data/test/frameworks/roda_test.rb +14 -0
  35. data/test/frameworks/sinatra_test.rb +14 -0
  36. data/test/instrumentation/excon_test.rb +0 -2
  37. data/test/instrumentation/graphql_test.rb +116 -0
  38. data/test/instrumentation/instrumented_request_test.rb +84 -0
  39. data/test/instrumentation/redis_test.rb +10 -0
  40. data/test/secrets_test.rb +73 -0
  41. data/test/tracing/id_management_test.rb +4 -66
  42. data/test/tracing/tracer_test.rb +31 -1
  43. metadata +16 -8
  44. data/test/tracing/trace_test.rb +0 -67
@@ -0,0 +1,68 @@
1
+ # Note: We really only need "cgi/util" here but Ruby 2.4.1 has an issue:
2
+ # https://bugs.ruby-lang.org/issues/13539
3
+ require "cgi"
4
+
5
+ module Instana
6
+ class InstrumentedRequest < Rack::Request
7
+ def skip_trace?
8
+ # Honor X-Instana-L
9
+ @env.has_key?('HTTP_X_INSTANA_L') && @env['HTTP_X_INSTANA_L'].start_with?('0')
10
+ end
11
+
12
+ def incoming_context
13
+ context = {}
14
+
15
+ if @env['HTTP_X_INSTANA_T']
16
+ context[:trace_id] = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T'])
17
+ context[:span_id] = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_S']) if @env['HTTP_X_INSTANA_S']
18
+ context[:level] = @env['HTTP_X_INSTANA_L'][0] if @env['HTTP_X_INSTANA_L']
19
+ end
20
+
21
+ context
22
+ end
23
+
24
+ def extra_header_tags
25
+ return nil unless ::Instana.agent.extra_headers
26
+ headers = {}
27
+
28
+ ::Instana.agent.extra_headers.each do |custom_header|
29
+ # Headers are available in this format: HTTP_X_CAPTURE_THIS
30
+ rack_header = 'HTTP_' + custom_header.upcase
31
+ rack_header.tr!('-', '_')
32
+
33
+ headers[custom_header.to_sym] = @env[rack_header] if @env.has_key?(rack_header)
34
+ end
35
+
36
+ headers
37
+ end
38
+
39
+ def request_tags
40
+ {
41
+ method: request_method,
42
+ url: CGI.unescape(path_info),
43
+ host: host_with_port,
44
+ header: extra_header_tags
45
+ }.compact
46
+ end
47
+
48
+ def correlation_data
49
+ @correlation_data ||= parse_correlation_data
50
+ end
51
+
52
+ private
53
+
54
+ def parse_correlation_data
55
+ return {} unless @env.has_key?('HTTP_X_INSTANA_L')
56
+ _level, *tokens = @env['HTTP_X_INSTANA_L'].split(/[,=;]/)
57
+ data = tokens
58
+ .map { |t| t.strip }
59
+ .each_slice(2)
60
+ .select { |a| a.length == 2 }.to_h
61
+
62
+ {
63
+ type: data['correlationType'],
64
+ id: data['correlationId']
65
+ }.compact
66
+ end
67
+ end
68
+ end
@@ -32,6 +32,8 @@ if defined?(::Net::HTTP) && ::Instana.config[:nethttp][:enabled]
32
32
  kv_payload[:http][:url] = "http://#{@address}:#{@port}#{request.path}"
33
33
  end
34
34
  end
35
+
36
+ kv_payload[:http][:url] = ::Instana.secrets.remove_from_query(kv_payload[:http][:url])
35
37
 
36
38
  # The core call
37
39
  response = request_without_instana(*args, &block)
@@ -1,6 +1,4 @@
1
- # Note: We really only need "cgi/util" here but Ruby 2.4.1 has an issue:
2
- # https://bugs.ruby-lang.org/issues/13539
3
- require "cgi"
1
+ require 'instana/instrumentation/instrumented_request'
4
2
 
5
3
  module Instana
6
4
  class Rack
@@ -8,58 +6,20 @@ module Instana
8
6
  @app = app
9
7
  end
10
8
 
11
- def collect_kvs(env)
12
- kvs = {}
13
- kvs[:http] = {}
14
- kvs[:http][:method] = env['REQUEST_METHOD']
15
- kvs[:http][:url] = ::CGI.unescape(env['PATH_INFO'])
16
-
17
- if env.key?('HTTP_HOST')
18
- kvs[:http][:host] = env['HTTP_HOST']
19
- elsif env.key?('SERVER_NAME')
20
- kvs[:http][:host] = env['SERVER_NAME']
21
- end
22
-
23
- if ENV.key?('INSTANA_SERVICE_NAME')
24
- kvs[:service] = ENV['INSTANA_SERVICE_NAME']
25
- end
26
-
27
- if ::Instana.agent.extra_headers
28
- ::Instana.agent.extra_headers.each { |custom_header|
29
- # Headers are available in this format: HTTP_X_CAPTURE_THIS
30
- rack_header = 'HTTP_' + custom_header.upcase
31
- rack_header.tr!('-', '_')
32
-
33
- if env.key?(rack_header)
34
- unless kvs[:http].key?(:header)
35
- kvs[:http][:header] = {}
36
- end
37
- kvs[:http][:header][custom_header.to_sym] = env[rack_header]
38
- end
39
- }
40
- end
41
- return kvs
42
- end
43
-
44
9
  def call(env)
45
- # Check incoming context
46
- incoming_context = {}
47
- if env.key?('HTTP_X_INSTANA_T')
48
- incoming_context[:trace_id] = ::Instana::Util.header_to_id(env['HTTP_X_INSTANA_T'])
49
- incoming_context[:span_id] = ::Instana::Util.header_to_id(env['HTTP_X_INSTANA_S']) if env.key?('HTTP_X_INSTANA_S')
50
- incoming_context[:level] = env['HTTP_X_INSTANA_L'] if env.key?('HTTP_X_INSTANA_L')
10
+ req = InstrumentedRequest.new(env)
11
+ return @app.call(env) if req.skip_trace?
12
+ kvs = {
13
+ http: req.request_tags,
14
+ service: ENV['INSTANA_SERVICE_NAME']
15
+ }.compact
51
16
 
52
- # Honor X-Instana-L
53
- if incoming_context[:level] and incoming_context[:level].length > 0
54
- if incoming_context[:level][0] == "0"
55
- return @app.call(env)
56
- end
57
- end
58
- end
17
+ current_span = ::Instana.tracer.log_start_or_continue(:rack, {}, req.incoming_context)
59
18
 
60
- kvs = collect_kvs(env)
61
-
62
- ::Instana.tracer.log_start_or_continue(:rack, {}, incoming_context)
19
+ unless req.correlation_data.empty?
20
+ current_span[:crid] = req.correlation_data[:id]
21
+ current_span[:crtp] = req.correlation_data[:type]
22
+ end
63
23
 
64
24
  status, headers, response = @app.call(env)
65
25
 
@@ -76,6 +36,11 @@ module Instana
76
36
  ::Instana.tracer.log_error(nil)
77
37
  end
78
38
 
39
+ # If the framework instrumentation provides a path template,
40
+ # pass it into the span here.
41
+ # See: https://www.instana.com/docs/tracing/custom-best-practices/#path-templates-visual-grouping-of-http-endpoints
42
+ kvs[:http][:path_tpl] = env['INSTANA_HTTP_PATH_TEMPLATE'] if env['INSTANA_HTTP_PATH_TEMPLATE']
43
+
79
44
  # Save the IDs before the trace ends so we can place
80
45
  # them in the response headers in the ensure block
81
46
  trace_id = ::Instana.tracer.current_span.trace_id
@@ -0,0 +1,42 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+
4
+ module Instana
5
+ class Secrets
6
+ def remove_from_query(str, secret_values = Instana.agent.secret_values)
7
+ return str unless secret_values
8
+
9
+ url = URI(str)
10
+ params = CGI.parse(url.query)
11
+
12
+ redacted = params.map do |k, v|
13
+ needs_redaction = secret_values['list']
14
+ .any? { |t| matcher(secret_values['matcher']).(t,k) }
15
+ [k, needs_redaction ? '<redacted>' : v]
16
+ end
17
+
18
+ url.query = URI.encode_www_form(redacted)
19
+ CGI.unescape(url.to_s)
20
+ end
21
+
22
+ private
23
+
24
+ def matcher(name)
25
+ case name
26
+ when 'equals-ignore-case'
27
+ ->(expected, actual) { expected.casecmp(actual) == 0 }
28
+ when 'equals'
29
+ ->(expected, actual) { (expected <=> actual) == 0 }
30
+ when 'contains-ignore-case'
31
+ ->(expected, actual) { actual.downcase.include?(expected) }
32
+ when 'contains'
33
+ ->(expected, actual) { actual.include?(expected) }
34
+ when 'regex'
35
+ ->(expected, actual) { !Regexp.new(expected).match(actual).nil? }
36
+ else
37
+ ::Instana.logger.warn("Matcher #{name} is not supported.")
38
+ lambda { false }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,6 +4,7 @@ require "instana/base"
4
4
  require "instana/config"
5
5
  require "instana/agent"
6
6
  require "instana/collector"
7
+ require "instana/secrets"
7
8
  require "instana/tracer"
8
9
  require "instana/tracing/processor"
9
10
  require "instana/instrumentation"
@@ -100,6 +100,12 @@ module Instana
100
100
  else
101
101
  self.current_span = Span.new(name)
102
102
  end
103
+
104
+ if incoming_context.is_a?(Hash) && incoming_context[:correlation] && !incoming_context[:correlation].empty?
105
+ self.current_span[:crid] = incoming_context[:correlation][:id]
106
+ self.current_span[:crtp] = incoming_context[:correlation][:type]
107
+ end
108
+
103
109
  self.current_span.set_tags(kvs) unless kvs.empty?
104
110
  self.current_span
105
111
  end
@@ -3,8 +3,8 @@ module Instana
3
3
  REGISTERED_SPANS = [ :actioncontroller, :actionview, :activerecord, :excon,
4
4
  :memcache, :'net-http', :rack, :render, :'rpc-client',
5
5
  :'rpc-server', :'sidekiq-client', :'sidekiq-worker',
6
- :redis, :'resque-client', :'resque-worker' ].freeze
7
- ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker' ].freeze
6
+ :redis, :'resque-client', :'resque-worker', :'graphql.server' ].freeze
7
+ ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server' ].freeze
8
8
  EXIT_SPANS = [ :activerecord, :excon, :'net-http', :'resque-client',
9
9
  :'rpc-client', :'sidekiq-client', :redis ].freeze
10
10
  HTTP_SPANS = [ :rack, :excon, :'net-http' ].freeze
@@ -57,9 +57,6 @@ module Instana
57
57
  end
58
58
 
59
59
  if ::Instana.config[:collect_backtraces]
60
- # For entry spans, add a backtrace fingerprint
61
- add_stack(limit: 2) if ENTRY_SPANS.include?(name)
62
-
63
60
  # Attach a backtrace to all exit spans
64
61
  add_stack if EXIT_SPANS.include?(name)
65
62
  end
@@ -76,9 +73,11 @@ module Instana
76
73
  #
77
74
  # @param limit [Integer] Limit the backtrace to the top <limit> frames
78
75
  #
79
- def add_stack(limit: nil, stack: Kernel.caller)
76
+ def add_stack(limit: 30, stack: Kernel.caller)
80
77
  frame_count = 0
78
+ sanitized_stack = []
81
79
  @data[:stack] = []
80
+ limit = 40 if limit > 40
82
81
 
83
82
  stack.each do |i|
84
83
  # If the stack has the full instana gem version in it's path
@@ -86,18 +85,23 @@ module Instana
86
85
  if !i.match(/instana\/instrumentation\/rack.rb/).nil? ||
87
86
  (i.match(::Instana::VERSION_FULL).nil? && i.match('lib/instana/').nil?)
88
87
 
89
- break if limit && frame_count >= limit
90
-
91
88
  x = i.split(':')
92
89
 
93
- @data[:stack] << {
90
+ sanitized_stack << {
94
91
  :c => x[0],
95
92
  :n => x[1],
96
93
  :m => x[2]
97
94
  }
98
- frame_count = frame_count + 1 if limit
99
95
  end
100
96
  end
97
+
98
+ if sanitized_stack.length > limit
99
+ # (limit * -1) gives us negative form of <limit> used for
100
+ # slicing from the end of the list. e.g. stack[-30, 30]
101
+ @data[:stack] = sanitized_stack[limit*-1, limit]
102
+ else
103
+ @data[:stack] = sanitized_stack
104
+ end
101
105
  end
102
106
 
103
107
  # Log an error into the span
@@ -303,6 +307,15 @@ module Instana
303
307
  # a String, Numeric, or Boolean it will be encoded with to_s
304
308
  #
305
309
  def set_tag(key, value)
310
+ if ![Symbol, String].include?(key.class)
311
+ key = key.to_s
312
+ end
313
+
314
+ # If <value> is not a Symbol, String, Array, Hash or Numeric - convert to string
315
+ if ![Symbol, String, Array, TrueClass, FalseClass, Hash].include?(value.class) && !value.is_a?(Numeric)
316
+ value = value.to_s
317
+ end
318
+
306
319
  if custom?
307
320
  @data[:data][:sdk][:custom] ||= {}
308
321
  @data[:data][:sdk][:custom][:tags] ||= {}
@@ -225,53 +225,39 @@ module Instana
225
225
  (time.to_f * 1000).floor
226
226
  end
227
227
 
228
- # Generate a random 64bit ID
228
+ # Generate a random 64bit/128bit ID
229
229
  #
230
- # @return [Integer] a random 64bit integer
230
+ # @param size [Integer] Number of 64 bit integers used to generate the id
231
231
  #
232
- def generate_id
233
- # Max value is 9223372036854775807 (signed long in Java)
234
- rand(ID_RANGE)
232
+ # @return [String] a random 64bit/128bit hex encoded string
233
+ #
234
+ def generate_id(size = 1)
235
+ Array.new(size) { rand(ID_RANGE) }
236
+ .pack('q>*')
237
+ .unpack('H*')
238
+ .first
235
239
  end
236
240
 
237
241
  # Convert an ID to a value appropriate to pass in a header.
238
242
  #
239
- # @param id [Integer] the id to be converted
243
+ # @param id [String] the id to be converted
240
244
  #
241
245
  # @return [String]
242
246
  #
243
247
  def id_to_header(id)
244
- unless id.is_a?(Integer) || id.is_a?(String)
245
- Instana.logger.debug "id_to_header received a #{id.class}: returning empty string"
246
- return String.new
247
- end
248
- [id.to_i].pack('q>').unpack('H*')[0].gsub(/^0+/, '')
249
- rescue => e
250
- Instana.logger.info "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}"
251
- Instana.logger.debug { e.backtrace.join("\r\n") }
248
+ return '' unless id.is_a?(String)
249
+ # Only send 64bit IDs downstream for now
250
+ id.length == 32 ? id[16..-1] : id
252
251
  end
253
252
 
254
253
  # Convert a received header value into a valid ID
255
254
  #
256
255
  # @param header_id [String] the header value to be converted
257
256
  #
258
- # @return [Integer]
257
+ # @return [String]
259
258
  #
260
259
  def header_to_id(header_id)
261
- if !header_id.is_a?(String)
262
- Instana.logger.debug "header_to_id received a #{header_id.class}: returning 0"
263
- return 0
264
- end
265
- if header_id.length < 16
266
- # The header is less than 16 chars. Prepend
267
- # zeros so we can convert correctly
268
- missing = 16 - header_id.length
269
- header_id = ("0" * missing) + header_id
270
- end
271
- [header_id].pack("H*").unpack("q>")[0]
272
- rescue => e
273
- Instana.logger.info "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}"
274
- Instana.logger.debug { e.backtrace.join("\r\n") }
260
+ header_id.is_a?(String) && header_id.match(/\A[a-z\d]{16,32}\z/i) ? header_id : ''
275
261
  end
276
262
  end
277
263
  end
@@ -1,4 +1,4 @@
1
1
  module Instana
2
- VERSION = "1.11.7"
2
+ VERSION = "1.192.1"
3
3
  VERSION_FULL = "instana-#{VERSION}"
4
4
  end
@@ -7,6 +7,10 @@ Cuba.define do
7
7
  on "hello" do
8
8
  res.write "Hello Instana!"
9
9
  end
10
+
11
+ on "greet/:name" do |name|
12
+ res.write "Hello, #{name}"
13
+ end
10
14
 
11
15
  on root do
12
16
  res.redirect '/hello'
@@ -6,5 +6,8 @@ class InstanaRodaApp < Roda
6
6
  r.get "hello" do
7
7
  "Hello Roda + Instana"
8
8
  end
9
+ r.get "greet", String do |name|
10
+ "Hello, #{name}!"
11
+ end
9
12
  end
10
13
  end
@@ -2,4 +2,8 @@ class InstanaSinatraApp < ::Sinatra::Base
2
2
  get '/' do
3
3
  "Hello Sinatra!"
4
4
  end
5
+
6
+ get '/greet/:name' do
7
+ "Hello, #{params[:name]}!"
8
+ end
5
9
  end
@@ -10,28 +10,12 @@ class ConfigTest < Minitest::Test
10
10
  assert_equal '127.0.0.1', ::Instana.config[:agent_host]
11
11
  assert_equal 42699, ::Instana.config[:agent_port]
12
12
 
13
- assert ::Instana.config[:enabled]
14
13
  assert ::Instana.config[:tracing][:enabled]
15
14
  assert ::Instana.config[:metrics][:enabled]
16
15
 
17
16
  ::Instana.config[:metrics].each do |k, v|
17
+ next unless v.is_a? Hash
18
18
  assert_equal true, ::Instana.config[:metrics][k].key?(:enabled)
19
19
  end
20
20
  end
21
-
22
- def test_that_global_affects_children
23
- # Disabling the gem should explicitly disable
24
- # metrics and tracing flags
25
- ::Instana.config[:enabled] = false
26
-
27
- assert_equal false, ::Instana.config[:tracing][:enabled]
28
- assert_equal false, ::Instana.config[:metrics][:enabled]
29
-
30
- # Enabling the gem should explicitly enable
31
- # metrics and tracing flags
32
- ::Instana.config[:enabled] = true
33
-
34
- assert_equal ::Instana.config[:tracing][:enabled]
35
- assert_equal ::Instana.config[:metrics][:enabled]
36
- end
37
21
  end