skylight 3.1.4 → 5.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +465 -294
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +11 -3
  5. data/ERRORS.md +3 -0
  6. data/LICENSE.md +8 -18
  7. data/README.md +1 -2
  8. data/bin/skylight +1 -1
  9. data/ext/extconf.rb +118 -122
  10. data/ext/libskylight.yml +8 -6
  11. data/ext/skylight_native.c +56 -100
  12. data/lib/skylight/api.rb +41 -27
  13. data/lib/skylight/cli/doctor.rb +68 -70
  14. data/lib/skylight/cli/helpers.rb +3 -5
  15. data/lib/skylight/cli/merger.rb +99 -92
  16. data/lib/skylight/cli.rb +40 -43
  17. data/lib/skylight/config.rb +656 -201
  18. data/lib/skylight/data/cacert.pem +730 -1023
  19. data/lib/skylight/deprecation.rb +17 -0
  20. data/lib/skylight/errors.rb +34 -16
  21. data/lib/skylight/extensions/source_location.rb +291 -0
  22. data/lib/skylight/extensions.rb +95 -0
  23. data/lib/skylight/formatters/http.rb +18 -0
  24. data/lib/skylight/gc.rb +99 -0
  25. data/lib/skylight/helpers.rb +82 -39
  26. data/lib/skylight/instrumenter.rb +339 -9
  27. data/lib/skylight/middleware.rb +147 -1
  28. data/lib/skylight/native.rb +71 -23
  29. data/lib/skylight/native_ext_fetcher.rb +39 -47
  30. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  31. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  32. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  33. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  34. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  35. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  36. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  37. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  38. data/lib/skylight/normalizers/active_job/perform.rb +87 -0
  39. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  40. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  41. data/lib/skylight/normalizers/active_record/sql.rb +20 -0
  42. data/lib/skylight/normalizers/active_storage.rb +28 -0
  43. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  44. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  53. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  54. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  55. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  56. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  57. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  58. data/lib/skylight/normalizers/default.rb +24 -0
  59. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  60. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  61. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  62. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  64. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  65. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  66. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  67. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  68. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  69. data/lib/skylight/normalizers/render.rb +79 -0
  70. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  71. data/lib/skylight/normalizers/shrine.rb +32 -0
  72. data/lib/skylight/normalizers/sql.rb +41 -0
  73. data/lib/skylight/normalizers.rb +157 -0
  74. data/lib/skylight/probes/action_controller.rb +52 -0
  75. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  76. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  77. data/lib/skylight/probes/action_dispatch.rb +2 -0
  78. data/lib/skylight/probes/action_view.rb +42 -0
  79. data/lib/skylight/probes/active_job.rb +27 -0
  80. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  81. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  82. data/lib/skylight/probes/active_record_async.rb +96 -0
  83. data/lib/skylight/probes/delayed_job.rb +144 -0
  84. data/lib/skylight/probes/elasticsearch.rb +45 -0
  85. data/lib/skylight/probes/excon/middleware.rb +65 -0
  86. data/lib/skylight/probes/excon.rb +25 -0
  87. data/lib/skylight/probes/faraday.rb +23 -0
  88. data/lib/skylight/probes/graphql.rb +38 -0
  89. data/lib/skylight/probes/httpclient.rb +44 -0
  90. data/lib/skylight/probes/middleware.rb +135 -0
  91. data/lib/skylight/probes/mongo.rb +169 -0
  92. data/lib/skylight/probes/mongoid.rb +6 -0
  93. data/lib/skylight/probes/net_http.rb +54 -0
  94. data/lib/skylight/probes/rack_builder.rb +37 -0
  95. data/lib/skylight/probes/redis.rb +68 -0
  96. data/lib/skylight/probes/sequel.rb +29 -0
  97. data/lib/skylight/probes/sinatra.rb +66 -0
  98. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  99. data/lib/skylight/probes/tilt.rb +25 -0
  100. data/lib/skylight/probes.rb +172 -0
  101. data/lib/skylight/railtie.rb +172 -15
  102. data/lib/skylight/sidekiq.rb +47 -0
  103. data/lib/skylight/sinatra.rb +2 -2
  104. data/lib/skylight/subscriber.rb +130 -0
  105. data/lib/skylight/test.rb +147 -0
  106. data/lib/skylight/trace.rb +331 -15
  107. data/lib/skylight/user_config.rb +60 -0
  108. data/lib/skylight/util/allocation_free.rb +26 -0
  109. data/lib/skylight/util/clock.rb +57 -0
  110. data/lib/skylight/util/component.rb +47 -9
  111. data/lib/skylight/util/deploy.rb +24 -40
  112. data/lib/skylight/util/gzip.rb +20 -0
  113. data/lib/skylight/util/hostname.rb +4 -4
  114. data/lib/skylight/util/http.rb +62 -71
  115. data/lib/skylight/util/instrumenter_method.rb +26 -0
  116. data/lib/skylight/util/logging.rb +136 -0
  117. data/lib/skylight/util/lru_cache.rb +36 -0
  118. data/lib/skylight/util/platform.rb +74 -0
  119. data/lib/skylight/util/proxy.rb +13 -0
  120. data/lib/skylight/util/ssl.rb +4 -28
  121. data/lib/skylight/util.rb +12 -0
  122. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  123. data/lib/skylight/version.rb +5 -1
  124. data/lib/skylight/vm/gc.rb +60 -0
  125. data/lib/skylight.rb +213 -24
  126. metadata +171 -53
@@ -0,0 +1,172 @@
1
+ require "pathname"
2
+ require "active_support/inflector"
3
+
4
+ module Skylight
5
+ # @api private
6
+ module Probes
7
+ class ProbeRegistration
8
+ attr_reader :name, :const_name, :require_paths, :probe
9
+
10
+ def initialize(name, const_name, require_paths, probe)
11
+ @name = name
12
+ @const_name = const_name
13
+ @require_paths = Array(require_paths)
14
+ @probe = probe
15
+ end
16
+
17
+ def install
18
+ probe.install
19
+ rescue StandardError, LoadError => e
20
+ log_install_exception(e)
21
+ end
22
+
23
+ def constant_available?
24
+ Skylight::Probes.constant_available?(const_name)
25
+ end
26
+
27
+ private
28
+
29
+ def log_install_exception(err)
30
+ description = err.class.to_s
31
+ description << ": #{err.message}" unless err.message.empty?
32
+
33
+ backtrace = err.backtrace.map { |l| " #{l}" }.join("\n")
34
+
35
+ # rubocop:disable Lint/SuppressedException
36
+ gems =
37
+ begin
38
+ Bundler.locked_gems.dependencies.map { |d| [d.name, d.requirement.to_s] }
39
+ rescue StandardError
40
+ end
41
+
42
+ # rubocop:enable Lint/SuppressedException
43
+
44
+ error =
45
+ "[SKYLIGHT] [#{Skylight::VERSION}] Encountered an error while installing the " \
46
+ "probe for #{const_name}. Please notify support@skylight.io with the debugging " \
47
+ "information below. It's recommended that you disable this probe until the " \
48
+ "issue is resolved." \
49
+ "\n\nERROR: #{description}\n\n#{backtrace}\n\n"
50
+
51
+ if gems
52
+ gems_string = gems.map { |g| " #{g[0]} #{g[1]}" }.join("\n")
53
+ error << "GEMS:\n\n#{gems_string}\n\n"
54
+ end
55
+
56
+ $stderr.puts(error)
57
+ end
58
+ end
59
+
60
+ class << self
61
+ def constant_available?(const_name)
62
+ ::ActiveSupport::Inflector.safe_constantize(const_name).present?
63
+ end
64
+
65
+ def install!
66
+ pending = registered.values - installed.values
67
+
68
+ pending.each do |registration|
69
+ registration.constant_available? ? install_probe(registration) : register_require_hook(registration)
70
+ end
71
+ end
72
+
73
+ def install_probe(registration)
74
+ return if installed.key?(registration.name)
75
+
76
+ installed[registration.name] = registration
77
+ registration.install
78
+ end
79
+
80
+ def add_path(path)
81
+ Dir.glob("**/*.rb", base: path) do |f|
82
+ name = Pathname.new(f).sub_ext("").to_s
83
+ full_path = File.expand_path(f, path)
84
+
85
+ raise "duplicate probe name: #{name}; original=#{available[name]}; new=#{full_path}" if available.key?(name)
86
+
87
+ available[name] = full_path
88
+ end
89
+ end
90
+
91
+ def available
92
+ @available ||= {}
93
+ end
94
+
95
+ def probe(*probes)
96
+ unknown = probes.map(&:to_s) - available.keys
97
+ raise ArgumentError, "unknown probes: #{unknown.join(", ")}" unless unknown.empty?
98
+
99
+ probes.each { |p| require available[p.to_s] }
100
+ end
101
+
102
+ def registered
103
+ @registered ||= {}
104
+ end
105
+
106
+ def require_hooks
107
+ @require_hooks ||= {}
108
+ end
109
+
110
+ def installed
111
+ @installed ||= {}
112
+ end
113
+
114
+ def register(name, *args)
115
+ raise "already registered: #{name}" if registered.key?(name)
116
+
117
+ registered[name] = ProbeRegistration.new(name, *args)
118
+
119
+ true
120
+ end
121
+
122
+ def require_hook(require_path)
123
+ each_by_require_path(require_path) do |registration|
124
+ # Double check constant is available
125
+ next unless registration.constant_available?
126
+
127
+ install_probe(registration)
128
+
129
+ # Don't need this to be called again
130
+ unregister_require_hook(registration)
131
+ end
132
+ end
133
+
134
+ def register_require_hook(registration)
135
+ registration.require_paths.each do |p|
136
+ require_hooks[p] ||= []
137
+ require_hooks[p] << registration
138
+ end
139
+ end
140
+
141
+ def unregister_require_hook(registration)
142
+ registration.require_paths.each do |p|
143
+ require_hooks[p].delete(registration)
144
+ require_hooks.delete(p) if require_hooks[p].empty?
145
+ end
146
+ end
147
+
148
+ def each_by_require_path(require_path)
149
+ return unless require_hooks.key?(require_path)
150
+
151
+ # dup because we may be mutating the array
152
+ require_hooks[require_path].dup.each { |registration| yield registration }
153
+ end
154
+ end
155
+
156
+ add_path(File.expand_path("./probes", __dir__))
157
+ end
158
+ end
159
+
160
+ # @api private
161
+ module Kernel
162
+ # Unfortunately, we can't use prepend here, in part because RubyGems changes require with an alias
163
+ alias require_without_sk require
164
+
165
+ def require(name)
166
+ require_without_sk(name).tap do
167
+ Skylight::Probes.require_hook(name)
168
+ rescue Exception => e
169
+ warn("[SKYLIGHT] Rescued exception in require hook", e)
170
+ end
171
+ end
172
+ end
@@ -1,16 +1,13 @@
1
- require 'skylight/core/railtie'
1
+ require "skylight"
2
+ require "rails"
2
3
 
3
4
  module Skylight
5
+ # @api private
4
6
  class Railtie < Rails::Railtie
5
- include Skylight::Core::Railtie
6
-
7
- def self.config_class; Skylight::Config end
8
- def self.middleware_class; Skylight::Middleware end
9
-
10
7
  config.skylight = ActiveSupport::OrderedOptions.new
11
8
 
12
9
  # The environments in which skylight should be enabled
13
- config.skylight.environments = ['production']
10
+ config.skylight.environments = ["production"]
14
11
 
15
12
  # The path to the configuration file
16
13
  config.skylight.config_path = "config/skylight.yml"
@@ -19,29 +16,189 @@ module Skylight
19
16
  # net_http, action_controller, action_dispatch, action_view, and middleware are on by default
20
17
  # See https://www.skylight.io/support/getting-more-from-skylight#available-instrumentation-options
21
18
  # for a full list.
22
- config.skylight.probes = ['net_http', 'action_controller', 'action_dispatch', 'action_view', 'middleware', 'active_job_enqueue']
19
+ config.skylight.probes = %w[
20
+ net_http
21
+ action_controller
22
+ action_dispatch
23
+ action_view
24
+ middleware
25
+ active_job_enqueue
26
+ active_record_async
27
+ ]
23
28
 
24
29
  # The position in the middleware stack to place Skylight
25
30
  # Default is first, but can be `{ after: Middleware::Name }` or `{ before: Middleware::Name }`
26
31
  config.skylight.middleware_position = 0
27
32
 
28
- initializer 'skylight.configure' do |app|
33
+ initializer "skylight.configure" do |app|
29
34
  run_initializer(app)
30
35
  end
31
36
 
32
37
  private
33
38
 
34
- def development_warning
35
- super + "\n(To disable this message for all local apps, run `skylight disable_dev_warning`.)"
39
+ # We must have an opt-in signal
40
+ def show_worker_activation_warning(sk_config)
41
+ reasons = []
42
+ reasons << "the 'active_job' probe is enabled" if sk_rails_config.probes.include?("active_job")
43
+ reasons << "the 'delayed_job' probe is enabled" if sk_rails_config.probes.include?("delayed_job")
44
+ reasons << "SKYLIGHT_ENABLE_SIDEKIQ is set" if sk_config.enable_sidekiq?
45
+
46
+ return if reasons.empty?
47
+
48
+ sk_config.logger.warn("Activating Skylight for Background Jobs because #{reasons.to_sentence}")
49
+ end
50
+
51
+ def log_prefix
52
+ "[SKYLIGHT] [#{Skylight::VERSION}]"
53
+ end
54
+
55
+ def development_warning
56
+ "#{log_prefix} Running Skylight in development mode. No data will be reported until you deploy your app.\n" \
57
+ "(To disable this message for all local apps, run `skylight disable_dev_warning`.)"
58
+ end
59
+
60
+ def run_initializer(app)
61
+ # Load probes even when agent is inactive to catch probe related bugs sooner
62
+ load_probes
63
+
64
+ config = load_skylight_config(app)
65
+
66
+ if activate?(config)
67
+ if config
68
+ if Skylight.start!(config)
69
+ set_middleware_position(app, config)
70
+ Rails.logger.info "#{log_prefix} Skylight agent enabled"
71
+ else
72
+ Rails.logger.info "#{log_prefix} Unable to start, see the Skylight logs for more details"
73
+ end
74
+ end
75
+ elsif Rails.env.development?
76
+ log_warning config, development_warning unless config.user_config.disable_dev_warning?
77
+ elsif !Rails.env.test?
78
+ unless config.user_config.disable_env_warning?
79
+ log_warning config,
80
+ "#{log_prefix} You are running in the #{Rails.env} environment but haven't added it " \
81
+ "to config.skylight.environments, so no data will be sent to Skylight servers."
82
+ end
83
+ end
84
+ rescue Skylight::ConfigError => e
85
+ Rails.logger.error "#{log_prefix} #{e.message}; disabling Skylight agent"
86
+ end
87
+
88
+ def log_warning(config, msg)
89
+ config ? config.alert_logger.warn(msg) : Rails.logger.warn(msg)
90
+ end
91
+
92
+ def existent_paths(paths)
93
+ paths.respond_to?(:existent) ? paths.existent : paths.select { |f| File.exist?(f) }
94
+ end
95
+
96
+ def load_skylight_config(app)
97
+ path = config_path(app)
98
+ path = nil unless File.exist?(path)
99
+
100
+ unless (tmp = app.config.paths["tmp"].first)
101
+ Rails.logger.error "#{log_prefix} tmp directory missing from rails configuration"
102
+ return nil
36
103
  end
37
104
 
38
- def load_skylight_config(app)
39
- super.tap do |config|
40
- if config[:report_rails_env]
41
- config[:env] ||= Rails.env.to_s
105
+ config = Config.load(file: path, priority_key: Rails.env.to_s)
106
+ config[:root] = Rails.root
107
+
108
+ configure_logging(config, app)
109
+
110
+ config[:"daemon.sockdir_path"] ||= tmp
111
+ config[:"normalizers.render.view_paths"] = existent_paths(app.config.paths["app/views"]) + [Rails.root.to_s]
112
+
113
+ config[:env] ||= Rails.env.to_s if config[:report_rails_env]
114
+
115
+ config
116
+ end
117
+
118
+ def configure_logging(config, app)
119
+ if (logger = sk_rails_config(app).logger)
120
+ config.logger = logger
121
+ else
122
+ # Configure the log file destination
123
+ if (log_file = sk_rails_config(app).log_file)
124
+ config["log_file"] = log_file
125
+ end
126
+
127
+ if (native_log_file = sk_rails_config(app).native_log_file)
128
+ config["native_log_file"] = native_log_file
129
+ end
130
+
131
+ config["log_file"] = File.join(Rails.root, "log/skylight.log") if !config.key?("log_file") && !config.on_heroku?
132
+
133
+ # Configure the log level
134
+ if (level = sk_rails_config(app).log_level)
135
+ config["log_level"] = level
136
+ elsif !config.key?("log_level")
137
+ if (level = app.config.log_level)
138
+ config["log_level"] = level
42
139
  end
43
140
  end
44
141
  end
142
+ end
143
+
144
+ def config_path(app)
145
+ File.expand_path(sk_rails_config.config_path, app.root)
146
+ end
147
+
148
+ def environments
149
+ Array(sk_rails_config.environments).map { |e| e&.to_s }.compact
150
+ end
151
+
152
+ def activate?(sk_config)
153
+ return false unless sk_config
154
+
155
+ key = "SKYLIGHT_ENABLED"
156
+ activate = ENV.key?(key) ? ENV.fetch(key, nil) !~ /^false$/i : environments.include?(Rails.env.to_s)
157
+
158
+ show_worker_activation_warning(sk_config) if activate
159
+
160
+ activate
161
+ end
162
+
163
+ def load_probes
164
+ probes = sk_rails_config.probes || []
165
+ Skylight::Probes.probe(*probes)
166
+ end
167
+
168
+ def middleware_position
169
+ if sk_rails_config.middleware_position.is_a?(Hash)
170
+ sk_rails_config.middleware_position.symbolize_keys
171
+ else
172
+ sk_rails_config.middleware_position
173
+ end
174
+ end
175
+
176
+ def insert_middleware(app, config)
177
+ if middleware_position.key?(:after)
178
+ app.middleware.insert_after(middleware_position[:after], Skylight::Middleware, config: config)
179
+ elsif middleware_position.key?(:before)
180
+ app.middleware.insert_before(middleware_position[:before], Skylight::Middleware, config: config)
181
+ else
182
+ raise "The middleware position you have set is invalid. Please be sure " \
183
+ "`config.skylight.middleware_position` is set up correctly."
184
+ end
185
+ end
45
186
 
187
+ def set_middleware_position(app, config)
188
+ if middleware_position.is_a?(Integer)
189
+ app.middleware.insert middleware_position, Skylight::Middleware, config: config
190
+ elsif middleware_position.is_a?(Hash) && middleware_position.keys.count == 1
191
+ insert_middleware(app, config)
192
+ elsif middleware_position.nil?
193
+ app.middleware.insert 0, Skylight::Middleware, config: config
194
+ else
195
+ raise "The middleware position you have set is invalid. Please be sure " \
196
+ "`config.skylight.middleware_position` is set up correctly."
197
+ end
198
+ end
199
+
200
+ def sk_rails_config(target = self)
201
+ target.config.skylight
202
+ end
46
203
  end
47
204
  end
@@ -0,0 +1,47 @@
1
+ module Skylight
2
+ module Sidekiq
3
+ def self.add_middleware
4
+ unless defined?(::Sidekiq)
5
+ Skylight.warn "Skylight for Sidekiq is active, but Sidekiq is not defined."
6
+ return
7
+ end
8
+
9
+ ::Sidekiq.configure_server do |sidekiq_config|
10
+ Skylight.debug "Adding Sidekiq Middleware"
11
+
12
+ sidekiq_config.server_middleware do |chain|
13
+ # Put it at the front
14
+ chain.prepend ServerMiddleware
15
+ end
16
+ end
17
+ end
18
+
19
+ class ServerMiddleware
20
+ include Util::Logging
21
+
22
+ def call(worker, job, queue)
23
+ t { "Sidekiq middleware beginning trace" }
24
+ title = job["display_class"] || job["wrapped"] || job["class"]
25
+
26
+ # TODO: Using hints here would be ideal but requires further refactoring
27
+ meta =
28
+ if (source_location = worker.method(:perform).source_location)
29
+ { source_file: source_location[0], source_line: source_location[1] }
30
+ end
31
+
32
+ Skylight.trace(title, "app.sidekiq.worker", title, meta: meta, segment: queue, component: :worker) do |trace|
33
+ yield
34
+ rescue Exception # includes Sidekiq::Shutdown
35
+ trace.segment = "error" if trace
36
+ raise
37
+ end
38
+ end
39
+ end
40
+
41
+ ActiveSupport::Notifications.subscribe(
42
+ "started_instrumenter.skylight"
43
+ ) do |_name, _started, _finished, _unique_id, payload|
44
+ add_middleware if payload[:instrumenter].config.enable_sidekiq?
45
+ end
46
+ end
47
+ end
@@ -1,2 +1,2 @@
1
- require 'skylight'
2
- Skylight.probe(:sinatra_add_middleware, :sinatra, :tilt, :sequel)
1
+ require "skylight"
2
+ Skylight.probe(:sinatra_add_middleware, :sinatra, :tilt, :sequel, :rack_builder)
@@ -0,0 +1,130 @@
1
+ module Skylight
2
+ # @api private
3
+ class Subscriber
4
+ include Util::Logging
5
+
6
+ attr_reader :config, :normalizers
7
+
8
+ def initialize(config, instrumenter)
9
+ @config = config
10
+ @normalizers = Normalizers.build(config)
11
+ @instrumenter = instrumenter
12
+ @subscribers = []
13
+ end
14
+
15
+ def register!
16
+ unregister!
17
+ @normalizers.each_key { |key| @subscribers << ActiveSupport::Notifications.subscribe(key, self) }
18
+ end
19
+
20
+ def unregister!
21
+ ActiveSupport::Notifications.unsubscribe @subscribers.shift until @subscribers.empty?
22
+ end
23
+
24
+ #
25
+ #
26
+ # ===== ActiveSupport::Notifications API
27
+ #
28
+ #
29
+
30
+ class Notification
31
+ attr_reader :name, :span
32
+
33
+ def initialize(name, span)
34
+ @name = name
35
+ @span = span
36
+ end
37
+ end
38
+
39
+ # cargo-culted from Rails's ConnectionAdapter
40
+ EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
41
+ EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc:
42
+ private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
43
+ def with_trace(trace, &block) # :nodoc:
44
+ Thread.handle_interrupt(EXCEPTION_NEVER) do
45
+ previous_trace = @trace
46
+ @trace = trace
47
+ Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
48
+ ensure
49
+ @trace = previous_trace
50
+ end
51
+ end
52
+
53
+ def start(name, _id, payload)
54
+ return if @instrumenter.disabled?
55
+ return unless (trace = current_trace)
56
+
57
+ _start(trace, name, payload)
58
+ end
59
+
60
+ def finish(name, _id, payload)
61
+ return if @instrumenter.disabled?
62
+ return unless (trace = current_trace)
63
+
64
+ while (curr = trace.notifications.pop)
65
+ next unless curr.name == name
66
+
67
+ meta = {}
68
+ meta[:exception] = payload[:exception] if payload[:exception]
69
+ meta[:exception_object] = payload[:exception_object] if payload[:exception_object]
70
+ trace.done(curr.span, meta) if curr.span
71
+ normalize_after(trace, curr.span, name, payload)
72
+ return
73
+ end
74
+ rescue Exception => e
75
+ error "Subscriber#finish error; msg=%s", e.message
76
+ debug "trace=%s", trace.inspect
77
+ debug "in: name=%s", name.inspect
78
+ debug "in: payload=%s", payload.inspect
79
+ t { e.backtrace.join("\n") }
80
+ nil
81
+ end
82
+
83
+ def publish(name, *args)
84
+ # Ignored for now because nothing in rails uses it
85
+ end
86
+
87
+ def publish_event(event)
88
+ # Ignored for now because only ActiveRecord::FutureResult uses it and we handle that with probes
89
+ end
90
+
91
+ private
92
+
93
+ def current_trace
94
+ @trace || @instrumenter.current_trace
95
+ end
96
+
97
+ def normalize(*args)
98
+ @normalizers.normalize(*args)
99
+ end
100
+
101
+ def normalize_after(*args)
102
+ @normalizers.normalize_after(*args)
103
+ end
104
+
105
+ def _start(trace, name, payload)
106
+ result = normalize(trace, name, payload)
107
+
108
+ unless result == :skip
109
+ case result.size
110
+ when 3, 4
111
+ cat, title, desc, meta = result
112
+ else
113
+ raise "Invalid normalizer result: #{result.inspect}"
114
+ end
115
+
116
+ span = trace.instrument(cat, title, desc, meta)
117
+ end
118
+ rescue Exception => e
119
+ error "Subscriber#start error; msg=%s", e.message
120
+ debug "trace=%s", trace.inspect
121
+ debug "in: name=%s", name.inspect
122
+ debug "in: payload=%s", payload.inspect
123
+ debug "out: cat=%s, title=%s, desc=%s", cat.inspect, name.inspect, desc.inspect
124
+ t { e.backtrace.join("\n") }
125
+ nil
126
+ ensure
127
+ trace.notifications << Notification.new(name, span)
128
+ end
129
+ end
130
+ end