skylight 4.3.2 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +399 -336
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +2 -8
  5. data/LICENSE.md +7 -17
  6. data/README.md +1 -1
  7. data/ext/extconf.rb +45 -56
  8. data/ext/libskylight.yml +10 -6
  9. data/ext/skylight_native.c +22 -99
  10. data/lib/skylight.rb +201 -14
  11. data/lib/skylight/api.rb +32 -21
  12. data/lib/skylight/cli.rb +48 -46
  13. data/lib/skylight/cli/doctor.rb +62 -63
  14. data/lib/skylight/cli/helpers.rb +19 -19
  15. data/lib/skylight/cli/merger.rb +142 -138
  16. data/lib/skylight/config.rb +634 -199
  17. data/lib/skylight/deprecation.rb +17 -0
  18. data/lib/skylight/errors.rb +23 -9
  19. data/lib/skylight/extensions.rb +95 -0
  20. data/lib/skylight/extensions/source_location.rb +291 -0
  21. data/lib/skylight/formatters/http.rb +18 -0
  22. data/lib/skylight/gc.rb +99 -0
  23. data/lib/skylight/helpers.rb +81 -36
  24. data/lib/skylight/instrumenter.rb +336 -18
  25. data/lib/skylight/middleware.rb +134 -1
  26. data/lib/skylight/native.rb +60 -12
  27. data/lib/skylight/native_ext_fetcher.rb +13 -14
  28. data/lib/skylight/normalizers.rb +157 -0
  29. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  30. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  31. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  32. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  33. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  34. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  35. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  36. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  37. data/lib/skylight/normalizers/active_job/perform.rb +90 -0
  38. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  39. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  40. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  41. data/lib/skylight/normalizers/active_storage.rb +28 -0
  42. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  43. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  53. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  54. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  55. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  56. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  57. data/lib/skylight/normalizers/default.rb +24 -0
  58. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  59. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  60. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  61. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  62. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  64. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  65. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  66. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  67. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  68. data/lib/skylight/normalizers/render.rb +79 -0
  69. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  70. data/lib/skylight/normalizers/shrine.rb +32 -0
  71. data/lib/skylight/normalizers/sql.rb +45 -0
  72. data/lib/skylight/probes.rb +173 -0
  73. data/lib/skylight/probes/action_controller.rb +52 -0
  74. data/lib/skylight/probes/action_dispatch.rb +2 -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_view.rb +42 -0
  78. data/lib/skylight/probes/active_job.rb +27 -0
  79. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  80. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  81. data/lib/skylight/probes/delayed_job.rb +144 -0
  82. data/lib/skylight/probes/elasticsearch.rb +36 -0
  83. data/lib/skylight/probes/excon.rb +25 -0
  84. data/lib/skylight/probes/excon/middleware.rb +65 -0
  85. data/lib/skylight/probes/faraday.rb +23 -0
  86. data/lib/skylight/probes/graphql.rb +38 -0
  87. data/lib/skylight/probes/httpclient.rb +44 -0
  88. data/lib/skylight/probes/middleware.rb +135 -0
  89. data/lib/skylight/probes/mongo.rb +156 -0
  90. data/lib/skylight/probes/mongoid.rb +13 -0
  91. data/lib/skylight/probes/net_http.rb +54 -0
  92. data/lib/skylight/probes/redis.rb +51 -0
  93. data/lib/skylight/probes/sequel.rb +29 -0
  94. data/lib/skylight/probes/sinatra.rb +66 -0
  95. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  96. data/lib/skylight/probes/tilt.rb +25 -0
  97. data/lib/skylight/railtie.rb +157 -27
  98. data/lib/skylight/sidekiq.rb +47 -0
  99. data/lib/skylight/subscriber.rb +108 -0
  100. data/lib/skylight/test.rb +151 -0
  101. data/lib/skylight/trace.rb +325 -22
  102. data/lib/skylight/user_config.rb +58 -0
  103. data/lib/skylight/util.rb +12 -0
  104. data/lib/skylight/util/allocation_free.rb +26 -0
  105. data/lib/skylight/util/clock.rb +57 -0
  106. data/lib/skylight/util/component.rb +22 -22
  107. data/lib/skylight/util/deploy.rb +16 -21
  108. data/lib/skylight/util/gzip.rb +20 -0
  109. data/lib/skylight/util/http.rb +106 -113
  110. data/lib/skylight/util/instrumenter_method.rb +26 -0
  111. data/lib/skylight/util/logging.rb +136 -0
  112. data/lib/skylight/util/lru_cache.rb +36 -0
  113. data/lib/skylight/util/platform.rb +1 -5
  114. data/lib/skylight/util/ssl.rb +1 -25
  115. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  116. data/lib/skylight/version.rb +5 -1
  117. data/lib/skylight/vm/gc.rb +60 -0
  118. metadata +126 -13
data/lib/skylight.rb CHANGED
@@ -1,36 +1,223 @@
1
1
  require "skylight/version"
2
- require "skylight/core"
3
2
  require "skylight/trace"
4
3
  require "skylight/instrumenter"
5
4
  require "skylight/middleware"
6
5
  require "skylight/api"
7
6
  require "skylight/helpers"
8
7
  require "skylight/config"
8
+ require "skylight/user_config"
9
9
  require "skylight/errors"
10
10
  require "skylight/native"
11
+ require "skylight/gc"
12
+ require "skylight/vm/gc"
13
+ require "skylight/util"
14
+ require "skylight/deprecation"
15
+ require "skylight/subscriber"
16
+ require "skylight/sidekiq"
17
+ require "skylight/probes"
11
18
 
12
19
  # For prettier global names
13
20
  require "English"
14
21
 
22
+ require "active_support/notifications"
23
+
24
+ # Specifically check for Railtie since we've had at least one case of a
25
+ # customer having Rails defined without having all of Rails loaded.
26
+ require "skylight/railtie" if defined?(Rails::Railtie)
27
+
15
28
  module Skylight
16
29
  # Used from the CLI
17
30
  autoload :CLI, "skylight/cli"
18
31
 
19
- # Specifically check for Railtie since we've had at least one case of a
20
- # customer having Rails defined without having all of Rails loaded.
21
- if defined?(Rails::Railtie)
22
- require "skylight/railtie"
23
- end
32
+ # Is this autoload even useful?
33
+ autoload :Normalizers, "skylight/normalizers"
24
34
 
25
- include Core::Instrumentable
35
+ extend Util::Logging
26
36
 
27
- def self.instrumenter_class
28
- Instrumenter
29
- end
37
+ LOCK = Mutex.new
30
38
 
31
- def self.config_class
32
- Config
33
- end
39
+ # @api private
40
+ TIERS = %w[rack api app view db noise other].freeze
41
+
42
+ # @api private
43
+ TIER_REGEX = /^(?:#{TIERS.join("|")})(?:\.|$)/u.freeze
44
+
45
+ # @api private
46
+ CATEGORY_REGEX = /^[a-z0-9_-]+(?:\.[a-z0-9_-]+)*$/iu.freeze
47
+
48
+ # @api private
49
+ DEFAULT_CATEGORY = "app.block".freeze
50
+
51
+ # @api private
52
+ DEFAULT_OPTIONS = { category: DEFAULT_CATEGORY }.freeze
53
+
54
+ at_exit { stop! }
55
+
56
+ class << self
57
+ extend Util::InstrumenterMethod
58
+
59
+ def instrumenter
60
+ defined?(@instrumenter) && @instrumenter
61
+ end
62
+
63
+ def probe(*args)
64
+ Probes.probe(*args)
65
+ end
66
+
67
+ def enable_normalizer(*names)
68
+ Normalizers.enable(*names)
69
+ end
70
+
71
+ # Start instrumenting
72
+ def start!(config = nil)
73
+ return instrumenter if instrumenter
74
+
75
+ const_get(:LOCK).synchronize do
76
+ return instrumenter if instrumenter
77
+
78
+ config ||= {}
79
+ config = Config.load(config) unless config.is_a?(Config)
80
+
81
+ Probes.install!
82
+
83
+ @instrumenter = Instrumenter.new(config).start!
84
+ end
85
+ rescue StandardError => e
86
+ level, message =
87
+ if e.is_a?(ConfigError)
88
+ [:warn, format("Unable to start Instrumenter due to a configuration error: %<message>s", message: e.message)]
89
+ else
90
+ [
91
+ :error,
92
+ format("Unable to start Instrumenter; msg=%<message>s; class=%<klass>s", message: e.message, klass: e.class)
93
+ ]
94
+ end
95
+
96
+ if config.respond_to?("log_#{level}") && config.respond_to?(:log_trace)
97
+ config.send("log_#{level}", message)
98
+ config.log_trace e.backtrace.join("\n")
99
+ else
100
+ warn "[#{name.upcase}] #{message}"
101
+ end
102
+ false
103
+ end
104
+
105
+ def started?
106
+ !!instrumenter
107
+ end
108
+
109
+ # Stop instrumenting
110
+ def stop!
111
+ t { "stop!" }
34
112
 
35
- Core::Probes.add_path(File.expand_path("skylight/probes", __dir__))
113
+ const_get(:LOCK).synchronize do
114
+ t { "stop! synchronized" }
115
+ return unless instrumenter
116
+
117
+ # This is only really helpful for getting specs to pass.
118
+ @instrumenter.current_trace = nil
119
+
120
+ @instrumenter.shutdown
121
+ @instrumenter = nil
122
+ end
123
+ end
124
+
125
+ # Check tracing
126
+ def tracing?
127
+ t { "checking tracing?; thread=#{Thread.current.object_id}" }
128
+ instrumenter&.current_trace
129
+ end
130
+
131
+ # Start a trace
132
+ def trace(endpoint = nil, cat = nil, title = nil, meta: nil, segment: nil, component: nil)
133
+ unless instrumenter
134
+ return yield if block_given?
135
+
136
+ return
137
+ end
138
+
139
+ if instrumenter.poisoned?
140
+ spawn_shutdown_thread!
141
+ return yield if block_given?
142
+
143
+ return
144
+ end
145
+
146
+ cat ||= DEFAULT_CATEGORY
147
+
148
+ if block_given?
149
+ instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment, component: component) do |tr|
150
+ yield tr
151
+ end
152
+ else
153
+ instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment, component: component)
154
+ end
155
+ end
156
+
157
+ # @overload instrument(opts)
158
+ # @param [Hash] opts the options for instrumentation.
159
+ # @option opts [String] :category (`DEFAULT_CATEGORY`) The category
160
+ # @option opts [String] :title The title
161
+ # @option opts [String] :description The description
162
+ # @option opts [Hash] :meta The meta
163
+ # @option opts [String] :source_location The source location
164
+ # @option opts [String] :source_file The source file. (Will be sanitized.)
165
+ # @option opts [String] :source_line The source line.
166
+ # @overload instrument(title)
167
+ # Instrument with the specified title and the default category
168
+ # @param [String] title The title
169
+ def instrument(opts = DEFAULT_OPTIONS, &block)
170
+ unless instrumenter
171
+ return yield if block_given?
172
+
173
+ return
174
+ end
175
+
176
+ if opts.is_a?(Hash)
177
+ category = opts[:category] || DEFAULT_CATEGORY
178
+ title = opts[:title]
179
+ desc = opts[:description]
180
+ meta = opts[:meta]
181
+ else
182
+ category = DEFAULT_CATEGORY
183
+ title = opts.to_s
184
+ desc = nil
185
+ meta = nil
186
+ opts = {}
187
+ end
188
+
189
+ # NOTE: unless we have `:internal` (indicating a built-in Skylight instrument block),
190
+ # or we already have a `source_file` or `source_line` (probably set by `instrument_method`),
191
+ # we set the caller location to the second item on the stack
192
+ # (immediate caller of the `instrument` method).
193
+ unless opts[:source_file] || opts[:source_line] || opts[:internal]
194
+ opts = opts.merge(sk_instrument_location: caller_locations(1..1).first)
195
+ end
196
+
197
+ meta ||= {}
198
+
199
+ instrumenter.extensions.process_instrument_options(opts, meta)
200
+ instrumenter.instrument(category, title, desc, meta, &block)
201
+ end
202
+
203
+ instrumenter_method :config
204
+
205
+ instrumenter_method :mute, block: true
206
+ instrumenter_method :unmute, block: true
207
+ instrumenter_method :muted?
208
+
209
+ # End a span
210
+ instrumenter_method :done
211
+
212
+ instrumenter_method :broken!
213
+
214
+ # Temporarily disable
215
+ instrumenter_method :disable, block: true
216
+
217
+ # Runs the shutdown procedure in the background.
218
+ # This should do little more than unsubscribe from all ActiveSupport::Notifications
219
+ def spawn_shutdown_thread!
220
+ @shutdown_thread || const_get(:LOCK).synchronize { @shutdown_thread ||= Thread.new { @instrumenter&.shutdown } }
221
+ end
222
+ end
36
223
  end
data/lib/skylight/api.rb CHANGED
@@ -4,13 +4,19 @@ require "skylight/util/http"
4
4
  module Skylight
5
5
  # @api private
6
6
  class Api
7
- include Core::Util::Logging
7
+ include Util::Logging
8
8
 
9
9
  attr_reader :config
10
10
 
11
- class Error < StandardError; end
12
- class Unauthorized < Error; end
13
- class Conflict < Error; end
11
+ class Error < StandardError
12
+ end
13
+
14
+ class Unauthorized < Error
15
+ end
16
+
17
+ class Conflict < Error
18
+ end
19
+
14
20
  class CreateFailed < Error
15
21
  attr_reader :res
16
22
 
@@ -21,6 +27,7 @@ module Skylight
21
27
 
22
28
  def errors
23
29
  return unless res.respond_to?(:body) && res.body.is_a?(Hash)
30
+
24
31
  res.body["errors"]
25
32
  end
26
33
 
@@ -36,7 +43,7 @@ module Skylight
36
43
  end
37
44
 
38
45
  class ConfigValidationResults
39
- include Core::Util::Logging
46
+ include Util::Logging
40
47
 
41
48
  attr_reader :raw_response
42
49
 
@@ -67,6 +74,7 @@ module Skylight
67
74
  def token_valid?
68
75
  # Don't prevent boot if it's an error response, so assume token is valid
69
76
  return true if error_response?
77
+
70
78
  # A 2xx response means everything is good!
71
79
  return true if raw_response.success?
72
80
  return false if status == 401
@@ -87,11 +95,13 @@ module Skylight
87
95
 
88
96
  def validation_errors
89
97
  return {} if config_valid? || !body
98
+
90
99
  body["errors"]
91
100
  end
92
101
 
93
102
  def corrected_config
94
- return {} if config_valid? || !body
103
+ return nil if config_valid? || !body
104
+
95
105
  body["corrected"]
96
106
  end
97
107
  end
@@ -107,6 +117,7 @@ module Skylight
107
117
  res = http_request(:app_create, :post, params)
108
118
 
109
119
  raise CreateFailed, res unless res.success?
120
+
110
121
  res
111
122
  end
112
123
 
@@ -132,22 +143,22 @@ module Skylight
132
143
 
133
144
  private
134
145
 
135
- # TODO: Improve handling here: https://github.com/tildeio/direwolf-agent/issues/274
136
- def http_request(service, method, *args)
137
- http = Util::HTTP.new(config, service)
138
- uri = URI.parse(config.get("#{service}_url"))
139
- http.send(method, uri.path, *args)
140
- end
146
+ # TODO: Improve handling here: https://github.com/tildeio/direwolf-agent/issues/274
147
+ def http_request(service, method, *args)
148
+ http = Util::HTTP.new(config, service)
149
+ uri = URI.parse(config.get("#{service}_url"))
150
+ http.send(method, uri.path, *args)
151
+ end
141
152
 
142
- def error_for_status(code)
143
- case code
144
- when 401
145
- Unauthorized
146
- when 409
147
- Conflict
148
- else
149
- Error
150
- end
153
+ def error_for_status(code)
154
+ case code
155
+ when 401
156
+ Unauthorized
157
+ when 409
158
+ Conflict
159
+ else
160
+ Error
151
161
  end
162
+ end
152
163
  end
153
164
  end
data/lib/skylight/cli.rb CHANGED
@@ -28,12 +28,13 @@ module Skylight
28
28
  Visit your app at https://www.skylight.io/app or remove config/skylight.yml
29
29
  to set it up as a new app in Skylight.
30
30
  OUT
31
+
31
32
  return
32
33
  end
33
34
 
34
35
  res = api.create_app(app_name, token)
35
36
 
36
- config[:application] = res.get("app.id")
37
+ config[:application] = res.get("app.id")
37
38
  config[:authentication] = res.get("app.token")
38
39
  config.write(config_path)
39
40
 
@@ -60,7 +61,7 @@ module Skylight
60
61
  rescue Api::CreateFailed => e
61
62
  say "Could not create the application. Please run `bundle exec skylight doctor` for diagnostics.", :red
62
63
  say e.to_s, :yellow
63
- rescue Interrupt
64
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
64
65
  end
65
66
 
66
67
  desc "disable_dev_warning", "Disables warning about running Skylight in development mode for all local apps"
@@ -71,7 +72,9 @@ module Skylight
71
72
  say "Development mode warning disabled", :green
72
73
  end
73
74
 
74
- desc "disable_env_warning", "Disables warning about running Skylight in environments not defined in config.skylight.environments"
75
+ desc "disable_env_warning",
76
+ "Disables warning about running Skylight in environments not defined in " \
77
+ "config.skylight.environments"
75
78
  def disable_env_warning
76
79
  user_config.disable_env_warning = true
77
80
  user_config.save
@@ -81,57 +84,56 @@ module Skylight
81
84
 
82
85
  private
83
86
 
84
- def app_name
85
- @app_name ||=
86
- begin
87
- name = nil
88
-
89
- if rails?
90
- # Get the name in a process so that we don't pollute our environment here
91
- # This is especially important since users may have things like WebMock that
92
- # will prevent us from communicating with the Skylight API
93
- begin
94
- namefile = Tempfile.new("skylight-app-name")
95
- # Windows appears to need double quotes for `rails runner`
96
- `rails runner "File.open('#{namefile.path}', 'w') {|f| f.write(Rails.application.class.name) rescue '' }"`
97
- name = namefile.read.split("::").first.underscore.titleize
98
- name = nil if name.empty?
99
- rescue => e
100
- if ENV["DEBUG"]
101
- puts e.class.name
102
- puts e.to_s
103
- puts e.backtrace.join("\n")
104
- end
105
- ensure
106
- namefile.close
107
- namefile.unlink
108
- end
109
-
110
- unless name
111
- warn "Unable to determine Rails application name. Using directory name."
87
+ def app_name
88
+ @app_name ||=
89
+ begin
90
+ name = nil
91
+
92
+ if rails?
93
+ # Get the name in a process so that we don't pollute our environment here
94
+ # This is especially important since users may have things like WebMock that
95
+ # will prevent us from communicating with the Skylight API
96
+ begin
97
+ namefile = Tempfile.new("skylight-app-name")
98
+
99
+ # Windows appears to need double quotes for `rails runner`
100
+ `rails runner "File.open('#{namefile.path}', 'w') {|f| f.write(Rails.application.class.name) rescue '' }"`
101
+ name = namefile.read.split("::").first.underscore.titleize
102
+ name = nil if name.empty?
103
+ rescue StandardError => e
104
+ if ENV["DEBUG"]
105
+ puts e.class.name
106
+ puts e.to_s
107
+ puts e.backtrace.join("\n")
112
108
  end
109
+ ensure
110
+ namefile.close
111
+ namefile.unlink
113
112
  end
114
113
 
115
- name || File.basename(File.expand_path(".")).titleize
114
+ warn "Unable to determine Rails application name. Using directory name." unless name
116
115
  end
117
- end
118
116
 
119
- # Is this duplicated?
120
- def relative_config_path
121
- "config/skylight.yml"
122
- end
117
+ name || File.basename(File.expand_path(".")).titleize
118
+ end
119
+ end
123
120
 
124
- def config_path
125
- File.expand_path(relative_config_path)
126
- end
121
+ # Is this duplicated?
122
+ def relative_config_path
123
+ "config/skylight.yml"
124
+ end
127
125
 
128
- def api
129
- @api ||= Api.new(config)
130
- end
126
+ def config_path
127
+ File.expand_path(relative_config_path)
128
+ end
131
129
 
132
- def user_config
133
- config.user_config
134
- end
130
+ def api
131
+ @api ||= Api.new(config)
132
+ end
133
+
134
+ def user_config
135
+ config.user_config
136
+ end
135
137
  end
136
138
  end
137
139
  end