instana 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +24 -2
  5. data/Gemfile +2 -22
  6. data/README.md +1 -1
  7. data/Rakefile +15 -0
  8. data/Tracing.md +29 -2
  9. data/gemfiles/libraries.gemfile +50 -0
  10. data/gemfiles/rails32.gemfile +45 -0
  11. data/gemfiles/rails42.gemfile +44 -0
  12. data/gemfiles/rails50.gemfile +44 -0
  13. data/instana.gemspec +6 -8
  14. data/lib/instana/config.rb +7 -5
  15. data/lib/instana/frameworks/instrumentation/abstract_mysql_adapter.rb +55 -0
  16. data/lib/instana/frameworks/instrumentation/action_controller.rb +105 -0
  17. data/lib/instana/frameworks/instrumentation/active_record.rb +22 -0
  18. data/lib/instana/frameworks/instrumentation/mysql2_adapter.rb +81 -0
  19. data/lib/instana/frameworks/instrumentation/mysql_adapter.rb +56 -0
  20. data/lib/instana/frameworks/instrumentation/postgresql_adapter.rb +71 -0
  21. data/lib/instana/frameworks/rails.rb +5 -0
  22. data/lib/instana/test.rb +40 -0
  23. data/lib/instana/tracer.rb +19 -0
  24. data/lib/instana/tracing/span.rb +3 -3
  25. data/lib/instana/version.rb +1 -1
  26. data/log/.keep +0 -0
  27. data/test/agent/agent_test.rb +139 -0
  28. data/test/apps/cuba.rb +15 -0
  29. data/test/apps/roda.rb +10 -0
  30. data/test/apps/sinatra.rb +5 -0
  31. data/test/config_test.rb +17 -0
  32. data/test/frameworks/cuba_test.rb +44 -0
  33. data/test/frameworks/rack_test.rb +152 -0
  34. data/test/frameworks/rails/actioncontroller_test.rb +123 -0
  35. data/test/frameworks/rails/activerecord3_test.rb +134 -0
  36. data/test/frameworks/rails/activerecord4_test.rb +134 -0
  37. data/test/frameworks/rails/activerecord5_test.rb +90 -0
  38. data/test/frameworks/roda_test.rb +44 -0
  39. data/test/frameworks/sinatra_test.rb +44 -0
  40. data/test/instana_test.rb +27 -0
  41. data/test/instrumentation/dalli_test.rb +274 -0
  42. data/test/instrumentation/excon_test.rb +171 -0
  43. data/test/instrumentation/net-http_test.rb +140 -0
  44. data/test/instrumentation/rest-client_test.rb +61 -0
  45. data/test/models/block.rb +18 -0
  46. data/test/servers/rackapp_6511.rb +20 -0
  47. data/test/servers/rails_3205.rb +95 -0
  48. data/test/test_helper.rb +39 -0
  49. data/test/tracing/custom_test.rb +143 -0
  50. data/test/tracing/id_management_test.rb +96 -0
  51. data/test/tracing/opentracing_test.rb +377 -0
  52. data/test/tracing/trace_test.rb +50 -0
  53. data/test/tracing/tracer_async_test.rb +298 -0
  54. data/test/tracing/tracer_test.rb +202 -0
  55. metadata +114 -4
@@ -0,0 +1,105 @@
1
+ module Instana
2
+ module Instrumentation
3
+
4
+ # Contains the methods common to both ::Instana::Instrumentation::ActionController
5
+ # and ::Instana::Instrumentation::ActionControllerLegacy
6
+ #
7
+ module ActionControllerCommon
8
+
9
+ # Indicates whether a Controller rescue handler is in place. If so, this affects
10
+ # error logging and reporting. (Hence the need for this method).
11
+ #
12
+ # @return [Boolean]
13
+ #
14
+ def has_rails_handler?
15
+ found = false
16
+ rescue_handlers.detect do |klass_name, _handler|
17
+ # Rescue handlers can be specified as strings or constant names
18
+ klass = self.class.const_get(klass_name) rescue nil
19
+ klass ||= klass_name.constantize rescue nil
20
+ found = exception.is_a?(klass) if klass
21
+ end
22
+ found
23
+ rescue => e
24
+ ::Instana.logger.debug "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}"
25
+ return false
26
+ end
27
+ end
28
+
29
+ # Used in ActionPack versions 5 and beyond, this module provides
30
+ # instrumentation for ActionController (a part of ActionPack)
31
+ #
32
+ module ActionController
33
+ include ::Instana::Instrumentation::ActionControllerCommon
34
+
35
+ # This is the Rails 5 version of the process_action method where we use prepend to
36
+ # instrument the class method instead of using the older alias_method_chain.
37
+ #
38
+ def process_action(*args)
39
+ kv_payload = { :actioncontroller => {} }
40
+ kv_payload[:actioncontroller][:controller] = self.class.name
41
+ kv_payload[:actioncontroller][:action] = action_name
42
+
43
+ ::Instana.tracer.log_entry(:actioncontroller, kv_payload)
44
+
45
+ super(*args)
46
+ rescue Exception => e
47
+ ::Instana.tracer.log_error(e) unless has_rails_handler?
48
+ raise
49
+ ensure
50
+ ::Instana.tracer.log_exit(:actioncontroller)
51
+ end
52
+ end
53
+
54
+ # Used in ActionPack versions 4 and earlier, this module provides
55
+ # instrumentation for ActionController (a part of ActionPack)
56
+ #
57
+ module ActionControllerLegacy
58
+ include ::Instana::Instrumentation::ActionControllerCommon
59
+
60
+ def self.included(klass)
61
+ klass.class_eval do
62
+ alias_method_chain :process_action, :instana
63
+ end
64
+ end
65
+
66
+ # The Instana wrapper method for ActionController::Base.process_action
67
+ # for versions 3 and 4.
68
+ #
69
+ def process_action_with_instana(*args)
70
+ kv_payload = { :actioncontroller => {} }
71
+ kv_payload[:actioncontroller][:controller] = self.class.name
72
+ kv_payload[:actioncontroller][:action] = action_name
73
+
74
+ ::Instana.tracer.log_entry(:actioncontroller, kv_payload)
75
+
76
+ process_action_without_instana(*args)
77
+ rescue Exception => e
78
+ ::Instana.tracer.log_error(e) unless has_rails_handler?
79
+ raise
80
+ ensure
81
+ ::Instana.tracer.log_exit(:actioncontroller)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ if defined?(::ActionController) && ::Instana.config[:action_controller][:enabled] && ::ActionPack::VERSION::MAJOR >= 2
88
+ ::Instana.logger.warn "Instrumenting ActionController"
89
+ if ActionPack::VERSION::MAJOR >= 5
90
+ ::ActionController::Base.send(:prepend, ::Instana::Instrumentation::ActionController)
91
+ else
92
+ ::ActionController::Base.send(:include, ::Instana::Instrumentation::ActionControllerLegacy)
93
+ end
94
+ end
95
+
96
+ # ActionController::API was introduced in Ruby on Rails 5 but was originally an independent project before being
97
+ # rolled into the Rails ActionPack. In case, someone is using the independent project or potentially backported
98
+ # the rails version to and older Ruby on Rails version, we only limit in a minimal way re: version checking.
99
+ #
100
+ # We allow ActionController::API instrumentation in version of Ruby on Rails 3 and higher.
101
+ #
102
+ if defined?(::ActionController::API) && ::Instana.config[:action_controller][:enabled] && ::ActionPack::VERSION::MAJOR >= 3
103
+ ::Instana.logger.warn "Instrumenting ActionController API"
104
+ ::ActionController::API.send(:prepend, ::Instana::Instrumentation::ActionController)
105
+ end
@@ -0,0 +1,22 @@
1
+
2
+ require "instana/frameworks/instrumentation/mysql_adapter"
3
+ require "instana/frameworks/instrumentation/abstract_mysql_adapter"
4
+ require "instana/frameworks/instrumentation/mysql2_adapter"
5
+ require "instana/frameworks/instrumentation/postgresql_adapter"
6
+
7
+ if defined?(::ActiveRecord) && ::Instana.config[:active_record][:enabled]
8
+ case ActiveRecord::Base.connection.adapter_name.downcase
9
+ when 'mysql'
10
+ ::Instana.logger.warn "Instrumenting ActiveRecord (mysql)"
11
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, ::Instana::Instrumentation::MysqlAdapter)
12
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.send(:include, ::Instana::Instrumentation::AbstractMysqlAdapter)
13
+ when 'mysql2'
14
+ ::Instana.logger.warn "Instrumenting ActiveRecord (mysql2)"
15
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:include, ::Instana::Instrumentation::Mysql2Adapter)
16
+ when 'postgresql'
17
+ ::Instana.logger.warn "Instrumenting ActiveRecord (postgresql)"
18
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, ::Instana::Instrumentation::PostgreSQLAdapter)
19
+ else
20
+ ::Instana.logger.warn "Unsupported ActiveRecord adapter: #{ActiveRecord::Base.connection.adapter_name.downcase}"
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ module Instana
2
+ module Instrumentation
3
+ module Mysql2Adapter
4
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE).freeze
5
+ EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
6
+
7
+ # This module supports instrumenting ActiveRecord with the mysql2 adapter.
8
+ #
9
+ def self.included(klass)
10
+ # ActiveRecord 3.1 and up only (for now possibly)
11
+ if ActiveRecord::VERSION::STRING > '3.0'
12
+ Instana::Util.method_alias(klass, :exec_delete)
13
+ Instana::Util.method_alias(klass, :exec_insert)
14
+ Instana::Util.method_alias(klass, :exec_query)
15
+
16
+ @@sanitize_regexp = Regexp.new('(\'[\s\S][^\']*\'|\d*\.\d+|\d+|NULL)', Regexp::IGNORECASE)
17
+ end
18
+ end
19
+
20
+ # Collect up this DB connection info for reporting.
21
+ #
22
+ # @param sql [String]
23
+ # @return [Hash] Hash of collected KVs
24
+ #
25
+ def collect(sql)
26
+ payload = { :activerecord => {} }
27
+ payload[:activerecord][:sql] = sql.gsub(@@sanitize_regexp, '?')
28
+ payload[:activerecord][:adapter] = @config[:adapter]
29
+ payload[:activerecord][:host] = @config[:host]
30
+ payload[:activerecord][:db] = @config[:database]
31
+ payload[:activerecord][:username] = @config[:username]
32
+ payload
33
+ end
34
+
35
+ # In the spirit of ::ActiveRecord::ExplainSubscriber.ignore_payload? There are
36
+ # only certain calls that we're interested in tracing. e.g. No use to instrument
37
+ # framework caches.
38
+ #
39
+ # @param payload [String]
40
+ # @return [Boolean]
41
+ #
42
+ def ignore_payload?(name, sql)
43
+ IGNORED_PAYLOADS.include?(name) || sql !~ EXPLAINED_SQLS
44
+ end
45
+
46
+ def exec_delete_with_instana(sql, name = nil, binds = [])
47
+ if !::Instana.tracer.tracing? || ignore_payload?(name, sql)
48
+ return exec_delete_without_instana(sql, name, binds)
49
+ end
50
+
51
+ kv_payload = collect(sql)
52
+ ::Instana.tracer.trace(:activerecord, kv_payload) do
53
+ exec_delete_without_instana(sql, name, binds)
54
+ end
55
+ end
56
+
57
+ def exec_insert_with_instana(sql, name = 'SQL', binds = [], *args)
58
+ if !::Instana.tracer.tracing? || ignore_payload?(name, sql)
59
+ return exec_insert_without_instana(sql, name, binds, *args)
60
+ end
61
+
62
+ kv_payload = collect(sql)
63
+ ::Instana.tracer.trace(:activerecord, kv_payload) do
64
+ exec_insert_without_instana(sql, name, binds, *args)
65
+ end
66
+ end
67
+
68
+ def exec_query_with_instana(sql, name = 'SQL', binds = [], *args)
69
+ if !::Instana.tracer.tracing? || ignore_payload?(name, sql) ||
70
+ ::Instana.tracer.current_span_name?(:activerecord)
71
+ return exec_query_without_instana(sql, name, binds, *args)
72
+ end
73
+
74
+ kv_payload = collect(sql)
75
+ ::Instana.tracer.trace(:activerecord, kv_payload) do
76
+ exec_query_without_instana(sql, name, binds, *args)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,56 @@
1
+ module Instana
2
+ module Instrumentation
3
+ module MysqlAdapter
4
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE).freeze
5
+ EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
6
+
7
+ # This module supports instrumenting ActiveRecord with the mysql2 adapter.
8
+ #
9
+ def self.included(klass)
10
+ if ActiveRecord::VERSION::STRING >= '3.2'
11
+ Instana::Util.method_alias(klass, :exec_query)
12
+
13
+ @@sanitize_regexp = Regexp.new('(\'[\s\S][^\']*\'|\d*\.\d+|\d+|NULL)', Regexp::IGNORECASE)
14
+ end
15
+ end
16
+
17
+ # Collect up this DB connection info for reporting.
18
+ #
19
+ # @param sql [String]
20
+ # @return [Hash] Hash of collected KVs
21
+ #
22
+ def collect(sql)
23
+ payload = { :activerecord => {} }
24
+ payload[:activerecord][:sql] = sql.gsub(@@sanitize_regexp, '?')
25
+ payload[:activerecord][:adapter] = @config[:adapter]
26
+ payload[:activerecord][:host] = @config[:host]
27
+ payload[:activerecord][:db] = @config[:database]
28
+ payload[:activerecord][:username] = @config[:username]
29
+ payload
30
+ end
31
+
32
+ # In the spirit of ::ActiveRecord::ExplainSubscriber.ignore_payload? There are
33
+ # only certain calls that we're interested in tracing. e.g. No use to instrument
34
+ # framework caches.
35
+ #
36
+ # @param payload [String]
37
+ # @return [Boolean]
38
+ #
39
+ def ignore_payload?(name, sql)
40
+ IGNORED_PAYLOADS.include?(name) || sql !~ EXPLAINED_SQLS
41
+ end
42
+
43
+ def exec_query_with_instana(sql, name = 'SQL', binds = [], *args)
44
+ if !::Instana.tracer.tracing? || ignore_payload?(name, sql) ||
45
+ ::Instana.tracer.current_span_name?(:activerecord)
46
+ return exec_query_without_instana(sql, name, binds, *args)
47
+ end
48
+
49
+ kv_payload = collect(sql)
50
+ ::Instana.tracer.trace(:activerecord, kv_payload) do
51
+ exec_query_without_instana(sql, name, binds, *args)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,71 @@
1
+ module Instana
2
+ module Instrumentation
3
+ module PostgreSQLAdapter
4
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE).freeze
5
+ EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
6
+
7
+ # This module supports instrumenting ActiveRecord with the postgresql adapter. Only
8
+ # versions >= 3.1 are supported.
9
+ #
10
+ def self.included(klass)
11
+ if (::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR > 0) ||
12
+ ::ActiveRecord::VERSION::MAJOR >= 4
13
+
14
+ # ActiveRecord 3.1 and up
15
+ Instana::Util.method_alias(klass, :exec_query)
16
+ Instana::Util.method_alias(klass, :exec_delete)
17
+
18
+ @@sanitize_regexp = Regexp.new('(\'[\s\S][^\']*\'|\d*\.\d+|\d+|NULL)', Regexp::IGNORECASE)
19
+ end
20
+ end
21
+
22
+ # Collect up this DB connection info for reporting.
23
+ #
24
+ # @param sql [String]
25
+ # @return [Hash] Hash of collected KVs
26
+ #
27
+ def collect(sql)
28
+ payload = { :activerecord => {} }
29
+ payload[:activerecord][:sql] = sql.gsub(@@sanitize_regexp, '?')
30
+ payload[:activerecord][:adapter] = @config[:adapter]
31
+ payload[:activerecord][:host] = @config[:host]
32
+ payload[:activerecord][:db] = @config[:database]
33
+ payload[:activerecord][:username] = @config[:username]
34
+ payload
35
+ end
36
+
37
+ # In the spirit of ::ActiveRecord::ExplainSubscriber.ignore_payload? There are
38
+ # only certain calls that we're interested in tracing. e.g. No use to instrument
39
+ # framework caches.
40
+ #
41
+ # @param payload [String]
42
+ # @return [Boolean]
43
+ #
44
+ def ignore_payload?(name, sql)
45
+ IGNORED_PAYLOADS.include?(name) || sql !~ EXPLAINED_SQLS
46
+ end
47
+
48
+ def exec_query_with_instana(sql, name = 'SQL', binds = [], *args)
49
+ if !::Instana.tracer.tracing? || ignore_payload?(name, sql)
50
+ return exec_query_without_instana(sql, name, binds, *args)
51
+ end
52
+
53
+ kv_payload = collect(sql)
54
+ ::Instana.tracer.trace(:activerecord, kv_payload) do
55
+ exec_query_without_instana(sql, name, binds, *args)
56
+ end
57
+ end
58
+
59
+ def exec_delete_with_instana(sql, name = nil, binds = [])
60
+ if !::Instana.tracer.tracing? || ignore_payload?(name, sql)
61
+ return exec_delete_without_instana(sql, name, binds)
62
+ end
63
+
64
+ kv_payload = collect(sql)
65
+ ::Instana.tracer.trace(:activerecord, kv_payload) do
66
+ exec_delete_without_instana(sql, name, binds)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -16,6 +16,11 @@ if defined?(::Rails)
16
16
  ::Instana.logger.warn "Instrumenting Rack"
17
17
  app.config.middleware.insert 0, ::Instana::Rack
18
18
  end
19
+
20
+ config.after_initialize do
21
+ require "instana/frameworks/instrumentation/active_record"
22
+ require "instana/frameworks/instrumentation/action_controller"
23
+ end
19
24
  end
20
25
  end
21
26
  end
@@ -0,0 +1,40 @@
1
+ module Instana
2
+ class Test
3
+ class << self
4
+ # Used at the start of the test suite to configure required environment
5
+ # variables (if missing)
6
+ #
7
+ def setup_environment
8
+ # Set defaults if not set
9
+ ENV['MEMCACHED_HOST'] ||= '127.0.0.1:11211'
10
+ ENV['TRAVIS_PSQL_HOST'] ||= "127.0.0.1"
11
+ ENV['TRAVIS_PSQL_USER'] ||= "postgres"
12
+ ENV['TRAVIS_MYSQL_HOST'] ||= "127.0.0.1"
13
+ ENV['TRAVIS_MYSQL_USER'] ||= "root"
14
+
15
+ if ENV['DB_FLAVOR'] == 'mysql2'
16
+ ENV['DATABASE_URL'] = "mysql2://#{ENV['TRAVIS_MYSQL_USER']}:#{ENV['TRAVIS_MYSQL_PASS']}@#{ENV['TRAVIS_MYSQL_HOST']}:3306/travis_ci_test"
17
+ elsif ENV['DB_FLAVOR'] == 'mysql'
18
+ ENV['DATABASE_URL'] = "mysql://#{ENV['TRAVIS_MYSQL_USER']}:#{ENV['TRAVIS_MYSQL_PASS']}@#{ENV['TRAVIS_MYSQL_HOST']}:3306/travis_ci_test"
19
+ else
20
+ ENV['DB_FLAVOR'] ||= 'postgresql'
21
+ ENV['DATABASE_URL'] = "postgresql://#{ENV['TRAVIS_PSQL_USER']}:#{ENV['TRAVIS_PSQL_PASS']}@#{ENV['TRAVIS_PSQL_HOST']}:5432/travis_ci_test"
22
+ end
23
+
24
+ Instana.logger.warn "Database connect string configured to: #{ENV['DATABASE_URL']}"
25
+ end
26
+
27
+ def postgresql?
28
+ ENV['DB_FLAVOR'] == 'postgresql'
29
+ end
30
+
31
+ def mysql2?
32
+ ENV['DB_FLAVOR'] == 'mysql2'
33
+ end
34
+
35
+ def mysql?
36
+ ENV['DB_FLAVOR'] == 'mysql'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -116,6 +116,13 @@ module Instana
116
116
  #
117
117
  def log_exit(name, kvs = {})
118
118
  return unless tracing?
119
+
120
+ if ::Instana.debug? || ::Instana.test?
121
+ unless current_span_name?(name)
122
+ ::Instana.logger.debug "Span mismatch: Attempt to exit #{name} span but #{current_span.name} is active."
123
+ end
124
+ end
125
+
119
126
  self.current_trace.end_span(kvs)
120
127
  end
121
128
 
@@ -131,6 +138,12 @@ module Instana
131
138
  def log_end(name, kvs = {}, end_time = Time.now)
132
139
  return unless tracing?
133
140
 
141
+ if ::Instana.debug? || ::Instana.test?
142
+ unless current_span_name?(name)
143
+ ::Instana.logger.debug "Span mismatch: Attempt to end #{name} span but #{current_span.name} is active."
144
+ end
145
+ end
146
+
134
147
  self.current_trace.finish(kvs, end_time)
135
148
 
136
149
  if !self.current_trace.has_async? ||
@@ -369,6 +382,12 @@ module Instana
369
382
  self.current_trace ? self.current_trace.current_span : nil
370
383
  end
371
384
 
385
+ # Indicates if the name of the current span matches <candidate>
386
+ #
387
+ def current_span_name?(candidate)
388
+ self.current_trace && self.current_trace.current_span.name == candidate
389
+ end
390
+
372
391
  # Used in the test suite, this resets the tracer to non-tracing state.
373
392
  #
374
393
  def clear!
@@ -1,8 +1,8 @@
1
1
  module Instana
2
2
  class Span
3
- REGISTERED_SPANS = [ :rack, :'net-http', :excon, :memcache ].freeze
3
+ REGISTERED_SPANS = [ :actioncontroller, :activerecord, :excon, :memcache, :'net-http', :rack ].freeze
4
4
  ENTRY_SPANS = [ :rack ].freeze
5
- EXIT_SPANS = [ :'net-http', :excon ].freeze
5
+ EXIT_SPANS = [ :'net-http', :excon, :activerecord ].freeze
6
6
  HTTP_SPANS = ENTRY_SPANS + EXIT_SPANS
7
7
 
8
8
  attr_accessor :parent
@@ -90,7 +90,7 @@ module Instana
90
90
  if HTTP_SPANS.include?(@data[:n])
91
91
  set_tags(:http => { :error => "#{e.class}: #{e.message}" })
92
92
  else
93
- set_tags(:log => { :message => e.message, :parameters => e.class })
93
+ set_tags(:log => { :message => e.message, :parameters => e.class.to_s })
94
94
  end
95
95
  e.instance_variable_set(:@instana_logged, true)
96
96
  end