skylight 4.3.2 → 5.1.1

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