instana 1.11.8 → 1.193.2

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 (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
@@ -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
@@ -1,59 +1,60 @@
1
1
  require 'net/http'
2
2
 
3
3
  if defined?(::Net::HTTP) && ::Instana.config[:nethttp][:enabled]
4
- Net::HTTP.class_eval {
5
-
6
- def request_with_instana(*args, &block)
7
- if !Instana.tracer.tracing? || !started?
8
- do_skip = true
9
- return request_without_instana(*args, &block)
10
- end
4
+ module Instana
5
+ module NetHTTPInstrumentation
6
+ def request(*args, &block)
7
+ if !Instana.tracer.tracing? || !started?
8
+ do_skip = true
9
+ return super(*args, &block)
10
+ end
11
11
 
12
- ::Instana.tracer.log_entry(:'net-http')
12
+ ::Instana.tracer.log_entry(:'net-http')
13
13
 
14
- # Send out the tracing context with the request
15
- request = args[0]
14
+ # Send out the tracing context with the request
15
+ request = args[0]
16
16
 
17
- # Set request headers; encode IDs as hexadecimal strings
18
- t_context = ::Instana.tracer.context
19
- request['X-Instana-T'] = t_context.trace_id_header
20
- request['X-Instana-S'] = t_context.span_id_header
17
+ # Set request headers; encode IDs as hexadecimal strings
18
+ t_context = ::Instana.tracer.context
19
+ request['X-Instana-T'] = t_context.trace_id_header
20
+ request['X-Instana-S'] = t_context.span_id_header
21
21
 
22
- # Collect up KV info now in case any exception is raised
23
- kv_payload = { :http => {} }
24
- kv_payload[:http][:method] = request.method
22
+ # Collect up KV info now in case any exception is raised
23
+ kv_payload = { :http => {} }
24
+ kv_payload[:http][:method] = request.method
25
25
 
26
- if request.uri
27
- kv_payload[:http][:url] = request.uri.to_s
28
- else
29
- if use_ssl?
30
- kv_payload[:http][:url] = "https://#{@address}:#{@port}#{request.path}"
26
+ if request.uri
27
+ kv_payload[:http][:url] = request.uri.to_s
31
28
  else
32
- kv_payload[:http][:url] = "http://#{@address}:#{@port}#{request.path}"
29
+ if use_ssl?
30
+ kv_payload[:http][:url] = "https://#{@address}:#{@port}#{request.path}"
31
+ else
32
+ kv_payload[:http][:url] = "http://#{@address}:#{@port}#{request.path}"
33
+ end
33
34
  end
34
- end
35
35
 
36
- # The core call
37
- response = request_without_instana(*args, &block)
36
+ kv_payload[:http][:url] = ::Instana.secrets.remove_from_query(kv_payload[:http][:url])
38
37
 
39
- kv_payload[:http][:status] = response.code
40
- if response.code.to_i.between?(500, 511)
41
- # Because of the 5xx response, we flag this span as errored but
42
- # without a backtrace (no exception)
43
- ::Instana.tracer.log_error(nil)
44
- end
38
+ # The core call
39
+ response = super(*args, &block)
45
40
 
46
- response
47
- rescue => e
48
- ::Instana.tracer.log_error(e)
49
- raise
50
- ensure
51
- ::Instana.tracer.log_exit(:'net-http', kv_payload) unless do_skip
52
- end
41
+ kv_payload[:http][:status] = response.code
42
+ if response.code.to_i.between?(500, 511)
43
+ # Because of the 5xx response, we flag this span as errored but
44
+ # without a backtrace (no exception)
45
+ ::Instana.tracer.log_error(nil)
46
+ end
53
47
 
54
- ::Instana.logger.debug "Instrumenting Net::HTTP"
48
+ response
49
+ rescue => e
50
+ ::Instana.tracer.log_error(e)
51
+ raise
52
+ ensure
53
+ ::Instana.tracer.log_exit(:'net-http', kv_payload) unless do_skip
54
+ end
55
+ end
56
+ end
55
57
 
56
- alias request_without_instana request
57
- alias request request_with_instana
58
- }
58
+ ::Instana.logger.debug "Instrumenting Net::HTTP"
59
+ Net::HTTP.prepend(::Instana::NetHTTPInstrumentation)
59
60
  end
@@ -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
@@ -1,11 +1,11 @@
1
- if defined?(::Redis) && ::Instana.config[:redis][:enabled]
2
- ::Redis::Client.class_eval do
3
- def call_with_instana(*args, &block)
1
+ module Instana
2
+ module RedisInstrumentation
3
+ def call(*args, &block)
4
4
  kv_payload = { redis: {} }
5
5
  dnt_spans = [:redis, :'resque-client', :'sidekiq-client']
6
6
 
7
- if !Instana.tracer.tracing? || dnt_spans.include?(::Instana.tracer.current_span.name)
8
- return call_without_instana(*args, &block)
7
+ if !Instana.tracer.tracing? || dnt_spans.include?(::Instana.tracer.current_span.name) || !Instana.config[:redis][:enabled]
8
+ return super(*args, &block)
9
9
  end
10
10
 
11
11
  begin
@@ -19,7 +19,7 @@ if defined?(::Redis) && ::Instana.config[:redis][:enabled]
19
19
  nil
20
20
  end
21
21
 
22
- call_without_instana(*args, &block)
22
+ super(*args, &block)
23
23
  rescue => e
24
24
  ::Instana.tracer.log_info({ redis: {error: true} })
25
25
  ::Instana.tracer.log_error(e)
@@ -29,17 +29,12 @@ if defined?(::Redis) && ::Instana.config[:redis][:enabled]
29
29
  end
30
30
  end
31
31
 
32
- ::Instana.logger.debug "Instrumenting Redis"
33
-
34
- alias call_without_instana call
35
- alias call call_with_instana
36
-
37
- def call_pipeline_with_instana(*args, &block)
32
+ def call_pipeline(*args, &block)
38
33
  kv_payload = { redis: {} }
39
34
  dnt_spans = [:redis, :'resque-client', :'sidekiq-client']
40
35
 
41
- if !Instana.tracer.tracing? || dnt_spans.include?(::Instana.tracer.current_span.name)
42
- return call_pipeline_without_instana(*args, &block)
36
+ if !Instana.tracer.tracing? || dnt_spans.include?(::Instana.tracer.current_span.name) || !Instana.config[:redis][:enabled]
37
+ return super(*args, &block)
43
38
  end
44
39
 
45
40
  begin
@@ -54,7 +49,7 @@ if defined?(::Redis) && ::Instana.config[:redis][:enabled]
54
49
  nil
55
50
  end
56
51
 
57
- call_pipeline_without_instana(*args, &block)
52
+ super(*args, &block)
58
53
  rescue => e
59
54
  ::Instana.tracer.log_info({ redis: {error: true} })
60
55
  ::Instana.tracer.log_error(e)
@@ -63,8 +58,10 @@ if defined?(::Redis) && ::Instana.config[:redis][:enabled]
63
58
  ::Instana.tracer.log_exit(:redis, kv_payload)
64
59
  end
65
60
  end
66
-
67
- alias call_pipeline_without_instana call_pipeline
68
- alias call_pipeline call_pipeline_with_instana
69
61
  end
70
62
  end
63
+
64
+ if defined?(::Redis) && ::Instana.config[:redis][:enabled]
65
+ ::Instana.logger.debug "Instrumenting Redis"
66
+ Redis::Client.prepend(::Instana::RedisInstrumentation)
67
+ end
@@ -3,11 +3,8 @@ require 'socket'
3
3
  module Instana
4
4
  module Instrumentation
5
5
  module ResqueClient
6
- def self.included(klass)
6
+ def self.prepended(klass)
7
7
  klass.send :extend, ::Resque
8
- ::Instana::Util.method_alias(klass, :enqueue)
9
- ::Instana::Util.method_alias(klass, :enqueue_to)
10
- ::Instana::Util.method_alias(klass, :dequeue)
11
8
  end
12
9
 
13
10
  def collect_kvs(op, klass, args)
@@ -23,50 +20,46 @@ module Instana
23
20
  { :'resque-client' => kvs }
24
21
  end
25
22
 
26
- def enqueue_with_instana(klass, *args)
23
+ def enqueue(klass, *args)
27
24
  if Instana.tracer.tracing?
28
25
  kvs = collect_kvs(:enqueue, klass, args)
29
26
 
30
27
  Instana.tracer.trace(:'resque-client', kvs) do
31
- enqueue_without_instana(klass, *args)
28
+ super(klass, *args)
32
29
  end
33
30
  else
34
- enqueue_without_instana(klass, *args)
31
+ super(klass, *args)
35
32
  end
36
33
  end
37
34
 
38
- def enqueue_to_with_instana(queue, klass, *args)
35
+ def enqueue_to(queue, klass, *args)
39
36
  if Instana.tracer.tracing? && !Instana.tracer.tracing_span?(:'resque-client')
40
37
  kvs = collect_kvs(:enqueue_to, klass, args)
41
38
  kvs[:Queue] = queue.to_s if queue
42
39
 
43
40
  Instana.tracer.trace(:'resque-client', kvs) do
44
- enqueue_to_without_instana(queue, klass, *args)
41
+ super(queue, klass, *args)
45
42
  end
46
43
  else
47
- enqueue_to_without_instana(queue, klass, *args)
44
+ super(queue, klass, *args)
48
45
  end
49
46
  end
50
47
 
51
- def dequeue_with_instana(klass, *args)
48
+ def dequeue(klass, *args)
52
49
  if Instana.tracer.tracing?
53
50
  kvs = collect_kvs(:dequeue, klass, args)
54
51
 
55
52
  Instana.tracer.trace(:'resque-client', kvs) do
56
- dequeue_without_instana(klass, *args)
53
+ super(klass, *args)
57
54
  end
58
55
  else
59
- dequeue_without_instana(klass, *args)
56
+ super(klass, *args)
60
57
  end
61
58
  end
62
59
  end
63
60
 
64
61
  module ResqueWorker
65
- def self.included(klass)
66
- ::Instana::Util.method_alias(klass, :perform)
67
- end
68
-
69
- def perform_with_instana(job)
62
+ def perform(job)
70
63
  kvs = {}
71
64
  kvs[:'resque-worker'] = {}
72
65
 
@@ -81,17 +74,13 @@ module Instana
81
74
  end
82
75
 
83
76
  Instana.tracer.start_or_continue_trace(:'resque-worker', kvs) do
84
- perform_without_instana(job)
77
+ super(job)
85
78
  end
86
79
  end
87
80
  end
88
81
 
89
82
  module ResqueJob
90
- def self.included(klass)
91
- ::Instana::Util.method_alias(klass, :fail)
92
- end
93
-
94
- def fail_with_instana(exception)
83
+ def fail(exception)
95
84
  if Instana.tracer.tracing?
96
85
  ::Instana.tracer.log_info(:'resque-worker' => { :error => "#{exception.class}: #{exception}"})
97
86
  ::Instana.tracer.log_error(exception)
@@ -99,7 +88,7 @@ module Instana
99
88
  rescue Exception => e
100
89
  ::Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" } if Instana::Config[:verbose]
101
90
  ensure
102
- fail_without_instana(exception)
91
+ super(exception)
103
92
  end
104
93
  end
105
94
  end
@@ -109,14 +98,14 @@ if defined?(::Resque) && RUBY_VERSION >= '1.9.3'
109
98
 
110
99
  if ::Instana.config[:'resque-client'][:enabled]
111
100
  ::Instana.logger.debug 'Instrumenting Resque Client'
112
- ::Instana::Util.send_include(::Resque, ::Instana::Instrumentation::ResqueClient)
101
+ ::Resque.prepend(::Instana::Instrumentation::ResqueClient)
113
102
  end
114
103
 
115
104
  if ::Instana.config[:'resque-worker'][:enabled]
116
105
  ::Instana.logger.debug 'Instrumenting Resque Worker'
117
106
 
118
- ::Instana::Util.send_include(::Resque::Worker, ::Instana::Instrumentation::ResqueWorker)
119
- ::Instana::Util.send_include(::Resque::Job, ::Instana::Instrumentation::ResqueJob)
107
+ ::Resque::Worker.prepend(::Instana::Instrumentation::ResqueWorker)
108
+ ::Resque::Job.prepend(::Instana::Instrumentation::ResqueJob)
120
109
 
121
110
  ::Resque.before_fork do |job|
122
111
  ::Instana.agent.before_resque_fork