skylight 3.1.4 → 5.3.4

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