instana 1.11.8 → 1.193.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) 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/abstract_mysql_adapter.rb +5 -7
  10. data/lib/instana/frameworks/instrumentation/action_controller.rb +11 -0
  11. data/lib/instana/frameworks/instrumentation/action_view.rb +6 -10
  12. data/lib/instana/frameworks/instrumentation/active_record.rb +4 -4
  13. data/lib/instana/frameworks/instrumentation/mysql2_adapter.rb +17 -15
  14. data/lib/instana/frameworks/instrumentation/mysql_adapter.rb +11 -7
  15. data/lib/instana/frameworks/instrumentation/postgresql_adapter.rb +33 -19
  16. data/lib/instana/frameworks/roda.rb +41 -0
  17. data/lib/instana/frameworks/sinatra.rb +17 -0
  18. data/lib/instana/instrumentation/dalli.rb +9 -14
  19. data/lib/instana/instrumentation/excon.rb +1 -1
  20. data/lib/instana/instrumentation/graphql.rb +77 -0
  21. data/lib/instana/instrumentation/grpc.rb +72 -62
  22. data/lib/instana/instrumentation/instrumented_request.rb +68 -0
  23. data/lib/instana/instrumentation/net-http.rb +44 -43
  24. data/lib/instana/instrumentation/rack.rb +17 -52
  25. data/lib/instana/instrumentation/redis.rb +15 -18
  26. data/lib/instana/instrumentation/resque.rb +17 -28
  27. data/lib/instana/instrumentation/rest-client.rb +3 -13
  28. data/lib/instana/secrets.rb +42 -0
  29. data/lib/instana/setup.rb +1 -0
  30. data/lib/instana/tracer.rb +6 -0
  31. data/lib/instana/tracing/span.rb +14 -10
  32. data/lib/instana/util.rb +15 -69
  33. data/lib/instana/version.rb +1 -1
  34. data/test/apps/cuba.rb +4 -0
  35. data/test/apps/roda.rb +3 -0
  36. data/test/apps/sinatra.rb +4 -0
  37. data/test/config_test.rb +1 -17
  38. data/test/frameworks/cuba_test.rb +14 -1
  39. data/test/frameworks/rack_test.rb +52 -19
  40. data/test/frameworks/rails/actioncontroller_test.rb +12 -0
  41. data/test/frameworks/rails/activerecord_test.rb +80 -28
  42. data/test/frameworks/roda_test.rb +14 -0
  43. data/test/frameworks/sinatra_test.rb +14 -0
  44. data/test/instrumentation/excon_test.rb +0 -2
  45. data/test/instrumentation/graphql_test.rb +116 -0
  46. data/test/instrumentation/instrumented_request_test.rb +84 -0
  47. data/test/instrumentation/redis_test.rb +10 -0
  48. data/test/secrets_test.rb +73 -0
  49. data/test/test_helper.rb +3 -9
  50. data/test/tracing/id_management_test.rb +4 -66
  51. metadata +16 -6
@@ -1,34 +1,24 @@
1
1
  module Instana
2
2
  module Instrumentation
3
3
  module RestClientRequest
4
- def self.included(klass)
5
- if klass.method_defined?(:execute)
6
- klass.class_eval do
7
- alias execute_without_instana execute
8
- alias execute execute_with_instana
9
- end
10
- end
11
- end
12
-
13
- def execute_with_instana & block
4
+ def execute(&block)
14
5
  # Since RestClient uses net/http under the covers, we just
15
6
  # provide span visibility here. HTTP related KVs are reported
16
7
  # in the Net::HTTP instrumentation
17
8
  ::Instana.tracer.log_entry(:'rest-client')
18
9
 
19
- execute_without_instana(&block)
10
+ super(&block)
20
11
  rescue => e
21
12
  ::Instana.tracer.log_error(e)
22
13
  raise
23
14
  ensure
24
15
  ::Instana.tracer.log_exit(:'rest-client')
25
16
  end
26
-
27
17
  end
28
18
  end
29
19
  end
30
20
 
31
21
  if defined?(::RestClient::Request) && ::Instana.config[:'rest-client'][:enabled]
32
22
  ::Instana.logger.debug "Instrumenting RestClient"
33
- ::RestClient::Request.send(:include, ::Instana::Instrumentation::RestClientRequest)
23
+ ::RestClient::Request.send(:prepend, ::Instana::Instrumentation::RestClientRequest)
34
24
  end
@@ -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
@@ -2,46 +2,6 @@ module Instana
2
2
  module Util
3
3
  class << self
4
4
  ID_RANGE = -2**63..2**63-1
5
-
6
- # An agnostic approach to method aliasing.
7
- #
8
- # @param klass [Object] The class or module that holds the method to be alias'd.
9
- # @param method [Symbol] The name of the method to be aliased.
10
- #
11
- def method_alias(klass, method)
12
- if klass.method_defined?(method.to_sym) ||
13
- klass.private_method_defined?(method.to_sym)
14
-
15
- with = "#{method}_with_instana"
16
- without = "#{method}_without_instana"
17
-
18
- klass.class_eval do
19
- alias_method without, method.to_s
20
- alias_method method.to_s, with
21
- end
22
- else
23
- ::Instana.logger.debug "No such method (#{method}) to alias on #{klass}"
24
- end
25
- end
26
-
27
- # Calls on target_class to 'extend' cls
28
- #
29
- # @param target_cls [Object] the class/module to do the 'extending'
30
- # @param cls [Object] the class/module to be 'extended'
31
- #
32
- def send_extend(target_cls, cls)
33
- target_cls.send(:extend, cls) if defined?(target_cls)
34
- end
35
-
36
- # Calls on <target_cls> to include <cls> into itself.
37
- #
38
- # @param target_cls [Object] the class/module to do the 'including'
39
- # @param cls [Object] the class/module to be 'included'
40
- #
41
- def send_include(target_cls, cls)
42
- target_cls.send(:include, cls) if defined?(target_cls)
43
- end
44
-
45
5
  # Debugging helper method
46
6
  #
47
7
  def pry!
@@ -225,53 +185,39 @@ module Instana
225
185
  (time.to_f * 1000).floor
226
186
  end
227
187
 
228
- # Generate a random 64bit ID
188
+ # Generate a random 64bit/128bit ID
189
+ #
190
+ # @param size [Integer] Number of 64 bit integers used to generate the id
229
191
  #
230
- # @return [Integer] a random 64bit integer
192
+ # @return [String] a random 64bit/128bit hex encoded string
231
193
  #
232
- def generate_id
233
- # Max value is 9223372036854775807 (signed long in Java)
234
- rand(ID_RANGE)
194
+ def generate_id(size = 1)
195
+ Array.new(size) { rand(ID_RANGE) }
196
+ .pack('q>*')
197
+ .unpack('H*')
198
+ .first
235
199
  end
236
200
 
237
201
  # Convert an ID to a value appropriate to pass in a header.
238
202
  #
239
- # @param id [Integer] the id to be converted
203
+ # @param id [String] the id to be converted
240
204
  #
241
205
  # @return [String]
242
206
  #
243
207
  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") }
208
+ return '' unless id.is_a?(String)
209
+ # Only send 64bit IDs downstream for now
210
+ id.length == 32 ? id[16..-1] : id
252
211
  end
253
212
 
254
213
  # Convert a received header value into a valid ID
255
214
  #
256
215
  # @param header_id [String] the header value to be converted
257
216
  #
258
- # @return [Integer]
217
+ # @return [String]
259
218
  #
260
219
  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") }
220
+ header_id.is_a?(String) && header_id.match(/\A[a-z\d]{16,32}\z/i) ? header_id : ''
275
221
  end
276
222
  end
277
223
  end
@@ -1,4 +1,4 @@
1
1
  module Instana
2
- VERSION = "1.11.8"
2
+ VERSION = "1.193.2"
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
@@ -1,4 +1,3 @@
1
-
2
1
  if defined?(::Cuba)
3
2
  require 'test_helper'
4
3
  require File.expand_path(File.dirname(__FILE__) + '/../apps/cuba')
@@ -40,5 +39,19 @@ if defined?(::Cuba)
40
39
  assert first_span[:data][:http].key?(:host)
41
40
  assert_equal "example.org", first_span[:data][:http][:host]
42
41
  end
42
+
43
+ def test_path_template
44
+ clear_all!
45
+
46
+ r = get '/greet/instana'
47
+ assert last_response.ok?
48
+
49
+ spans = ::Instana.processor.queued_spans
50
+ assert_equal 1, spans.count
51
+
52
+ first_span = spans.first
53
+ assert_equal :rack, first_span[:n]
54
+ assert_equal '/greet/{name}', first_span[:data][:http][:path_tpl]
55
+ end
43
56
  end
44
57
  end
@@ -6,15 +6,21 @@ require "instana/rack"
6
6
  class RackTest < Minitest::Test
7
7
  include Rack::Test::Methods
8
8
 
9
+ class PathTemplateApp
10
+ def call(env)
11
+ env['INSTANA_HTTP_PATH_TEMPLATE'] = 'sample_template'
12
+ [200, {}, ['Ok']]
13
+ end
14
+ end
15
+
9
16
  def app
10
- @app = Rack::Builder.new {
17
+ @app = Rack::Builder.new do
11
18
  use Rack::CommonLogger
12
19
  use Rack::ShowExceptions
13
20
  use Instana::Rack
14
- map "/mrlobster" do
15
- run Rack::Lobster.new
16
- end
17
- }
21
+ map("/mrlobster") { run Rack::Lobster.new }
22
+ map("/path_tpl") { run PathTemplateApp.new }
23
+ end
18
24
  end
19
25
 
20
26
  def test_basic_get
@@ -51,11 +57,7 @@ class RackTest < Minitest::Test
51
57
  assert rack_span[:f].key?(:e)
52
58
  assert rack_span[:f].key?(:h)
53
59
  assert_equal ::Instana.agent.agent_uuid, rack_span[:f][:h]
54
-
55
- # Backtrace fingerprint validation
56
- assert rack_span.key?(:stack)
57
- assert_equal 2, rack_span[:stack].count
58
- refute_nil rack_span[:stack].first[:c].match(/instana\/instrumentation\/rack.rb/)
60
+ assert !rack_span.key?(:stack)
59
61
 
60
62
  # Restore to default
61
63
  ::Instana.config[:collect_backtraces] = false
@@ -145,8 +147,9 @@ class RackTest < Minitest::Test
145
147
 
146
148
  def test_context_continuation
147
149
  clear_all!
148
- header 'X-INSTANA-T', Instana::Util.id_to_header(1234)
149
- header 'X-INSTANA-S', Instana::Util.id_to_header(4321)
150
+ continuation_id = Instana::Util.generate_id
151
+ header 'X-INSTANA-T', continuation_id
152
+ header 'X-INSTANA-S', continuation_id
150
153
 
151
154
  get '/mrlobster'
152
155
  assert last_response.ok?
@@ -180,10 +183,30 @@ class RackTest < Minitest::Test
180
183
  # Context validation
181
184
  # The first span should have the passed in trace ID
182
185
  # and specify the passed in span ID as it's parent.
183
- assert_equal 1234, rack_span[:t]
184
- assert_equal 4321, rack_span[:p]
186
+ assert_equal continuation_id, rack_span[:t]
187
+ assert_equal continuation_id, rack_span[:p]
185
188
  end
186
189
 
190
+ def test_correlation_information
191
+ clear_all!
192
+
193
+ header 'X-INSTANA-L', '1,correlationType=test;correlationId=abcdefh123'
194
+
195
+ get '/mrlobster'
196
+ assert last_response.ok?
197
+
198
+ spans = ::Instana.processor.queued_spans
199
+
200
+ # Span validation
201
+ assert_equal 1, spans.count
202
+ rack_span = spans.first
203
+ assert_equal :rack, rack_span[:n]
204
+
205
+ assert_equal 'abcdefh123', rack_span[:crid]
206
+ assert_equal 'test', rack_span[:crtp]
207
+ end
208
+
209
+
187
210
  def test_instana_response_headers
188
211
  clear_all!
189
212
  get '/mrlobster'
@@ -227,14 +250,24 @@ class RackTest < Minitest::Test
227
250
  assert rack_span[:data][:http][:header].key?(:"X-Capture-This")
228
251
  assert !rack_span[:data][:http][:header].key?(:"X-Capture-That")
229
252
  assert_equal "ThereYouGo", rack_span[:data][:http][:header][:"X-Capture-This"]
230
-
231
- # Backtrace fingerprint validation
232
- assert rack_span.key?(:stack)
233
- assert_equal 2, rack_span[:stack].count
234
- refute_nil rack_span[:stack].first[:c].match(/instana\/instrumentation\/rack.rb/)
253
+ assert !rack_span.key?(:stack)
235
254
 
236
255
  # Restore to default
237
256
  ::Instana.config[:collect_backtraces] = false
238
257
  ::Instana.agent.extra_headers = nil
239
258
  end
259
+
260
+ def test_capture_http_path_template
261
+ clear_all!
262
+
263
+ get '/path_tpl'
264
+ assert last_response.ok?
265
+
266
+ spans = ::Instana.processor.queued_spans
267
+ assert_equal 1, spans.length
268
+
269
+ rack_span = spans.first
270
+ assert_equal :rack, rack_span[:n]
271
+ assert_equal 'sample_template', rack_span[:data][:http][:path_tpl]
272
+ end
240
273
  end