instana 1.1.0 → 1.2.0

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 (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