skylight 5.0.0.beta → 5.0.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -6
  3. data/CONTRIBUTING.md +1 -1
  4. data/ext/extconf.rb +2 -2
  5. data/ext/libskylight.yml +7 -5
  6. data/lib/skylight.rb +9 -2
  7. data/lib/skylight/api.rb +3 -0
  8. data/lib/skylight/cli/doctor.rb +11 -13
  9. data/lib/skylight/config.rb +25 -32
  10. data/lib/skylight/deprecation.rb +3 -1
  11. data/lib/skylight/errors.rb +4 -4
  12. data/lib/skylight/extensions.rb +8 -0
  13. data/lib/skylight/extensions/source_location.rb +123 -81
  14. data/lib/skylight/formatters/http.rb +2 -1
  15. data/lib/skylight/helpers.rb +44 -35
  16. data/lib/skylight/instrumenter.rb +3 -2
  17. data/lib/skylight/middleware.rb +4 -4
  18. data/lib/skylight/native.rb +1 -1
  19. data/lib/skylight/native_ext_fetcher.rb +2 -2
  20. data/lib/skylight/normalizers.rb +6 -4
  21. data/lib/skylight/normalizers/action_controller/process_action.rb +1 -1
  22. data/lib/skylight/normalizers/action_dispatch/route_set.rb +1 -1
  23. data/lib/skylight/normalizers/active_job/perform.rb +5 -0
  24. data/lib/skylight/normalizers/graphql/base.rb +1 -0
  25. data/lib/skylight/normalizers/render.rb +1 -1
  26. data/lib/skylight/normalizers/shrine.rb +34 -0
  27. data/lib/skylight/normalizers/sql.rb +3 -2
  28. data/lib/skylight/probes.rb +38 -10
  29. data/lib/skylight/probes/active_job.rb +4 -6
  30. data/lib/skylight/probes/active_job_enqueue.rb +18 -14
  31. data/lib/skylight/probes/active_model_serializers.rb +2 -6
  32. data/lib/skylight/probes/delayed_job.rb +112 -25
  33. data/lib/skylight/probes/elasticsearch.rb +1 -1
  34. data/lib/skylight/probes/excon/middleware.rb +4 -4
  35. data/lib/skylight/probes/middleware.rb +2 -1
  36. data/lib/skylight/probes/mongo.rb +2 -1
  37. data/lib/skylight/probes/net_http.rb +0 -1
  38. data/lib/skylight/probes/redis.rb +6 -3
  39. data/lib/skylight/railtie.rb +1 -1
  40. data/lib/skylight/sidekiq.rb +12 -7
  41. data/lib/skylight/subscriber.rb +1 -1
  42. data/lib/skylight/trace.rb +10 -4
  43. data/lib/skylight/util/deploy.rb +3 -6
  44. data/lib/skylight/util/instrumenter_method.rb +11 -11
  45. data/lib/skylight/util/logging.rb +6 -6
  46. data/lib/skylight/util/lru_cache.rb +1 -3
  47. data/lib/skylight/util/platform.rb +1 -1
  48. data/lib/skylight/version.rb +1 -1
  49. metadata +27 -13
  50. data/lib/skylight/fanout.rb +0 -0
@@ -12,7 +12,8 @@ module Skylight
12
12
  # @return [Hash] a hash containing `:category`, `:title`, and `:annotations`
13
13
  def self.build_opts(method, _scheme, host, _port, _path, _query)
14
14
  { category: "api.http.#{method.downcase}",
15
- title: "#{method.upcase} #{host}" }
15
+ title: "#{method.upcase} #{host}",
16
+ internal: true }
16
17
  end
17
18
  end
18
19
  end
@@ -14,7 +14,7 @@ module Skylight
14
14
 
15
15
  if (opts = @__sk_instrument_next_method)
16
16
  @__sk_instrument_next_method = nil
17
- instrument_method(name, opts)
17
+ instrument_method(name, **opts)
18
18
  end
19
19
  end
20
20
 
@@ -24,7 +24,7 @@ module Skylight
24
24
 
25
25
  if (opts = @__sk_instrument_next_method)
26
26
  @__sk_instrument_next_method = nil
27
- instrument_class_method(name, opts)
27
+ instrument_class_method(name, **opts)
28
28
  end
29
29
  end
30
30
 
@@ -77,14 +77,12 @@ module Skylight
77
77
  # do_expensive_stuff
78
78
  # end
79
79
  # end
80
- def instrument_method(*args)
81
- opts = args.pop if args.last.is_a?(Hash)
82
-
80
+ def instrument_method(*args, **opts)
83
81
  if (name = args.pop)
84
82
  title = "#{self}##{name}"
85
- __sk_instrument_method_on(self, name, title, opts || {})
83
+ __sk_instrument_method_on(self, name, title, **opts)
86
84
  else
87
- @__sk_instrument_next_method = opts || {}
85
+ @__sk_instrument_next_method = opts
88
86
  end
89
87
  end
90
88
 
@@ -123,14 +121,18 @@ module Skylight
123
121
  #
124
122
  # instrument_class_method :my_method, title: 'Expensive work'
125
123
  # end
126
- def instrument_class_method(name, opts = {})
124
+ def instrument_class_method(name, **opts)
125
+ # NOTE: If the class is defined anonymously and then assigned to a variable this code
126
+ # will not be aware of the updated name.
127
127
  title = "#{self}.#{name}"
128
- __sk_instrument_method_on(__sk_singleton_class, name, title, opts || {})
128
+ __sk_instrument_method_on(__sk_singleton_class, name, title, **opts)
129
129
  end
130
130
 
131
131
  private
132
132
 
133
- def __sk_instrument_method_on(klass, name, title, opts)
133
+ HAS_KW_ARGUMENT_FORWARDING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
134
+
135
+ def __sk_instrument_method_on(klass, name, title, **opts)
134
136
  category = (opts[:category] || "app.method").to_s
135
137
  title = (opts[:title] || title).to_s
136
138
  desc = opts[:description].to_s if opts[:description]
@@ -143,33 +145,40 @@ module Skylight
143
145
  # source_file and source_line to be removed from the trace span before it is submitted.
144
146
  source_file, source_line = klass.instance_method(name).source_location
145
147
 
146
- klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
147
- alias_method :"before_instrument_#{name}", :"#{name}"
148
-
149
- def #{name}(*args, &blk)
150
- span = Skylight.instrument(
151
- category: :"#{category}",
152
- title: #{title.inspect},
153
- description: #{desc.inspect},
154
- source_file: #{source_file.inspect},
155
- source_line: #{source_line.inspect})
156
-
157
- meta = {}
158
- begin
159
- send(:before_instrument_#{name}, *args, &blk)
160
- rescue Exception => e
161
- meta[:exception_object] = e
162
- raise e
163
- ensure
164
- Skylight.done(span, meta) if span
165
- end
148
+ arg_string =
149
+ if HAS_KW_ARGUMENT_FORWARDING
150
+ "*args, **kwargs, &blk"
151
+ else
152
+ "*args, &blk"
166
153
  end
167
154
 
168
- if protected_method_defined?(:"before_instrument_#{name}")
169
- protected :"#{name}"
170
- elsif private_method_defined?(:"before_instrument_#{name}")
171
- private :"#{name}"
172
- end
155
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
156
+ alias_method :"before_instrument_#{name}", :"#{name}" # alias_method :"before_instrument_process", :"process"
157
+ def #{name}(#{arg_string}) # def process(*args, **kwargs, &blk)
158
+ span = Skylight.instrument( # span = Skylight.instrument(
159
+ category: :"#{category}", # category: :"app.method",
160
+ title: #{title.inspect}, # title: "process",
161
+ description: #{desc.inspect}, # description: "Process data",
162
+ source_file: #{source_file.inspect}, # source_file: "myapp/lib/processor.rb",
163
+ source_line: #{source_line.inspect}) # source_line: 123)
164
+ #
165
+ meta = {} # meta = {}
166
+ #
167
+ begin # begin
168
+ send(:before_instrument_#{name}, #{arg_string}) # send(:before_instrument_process, *args, **kwargs, &blk)
169
+ rescue Exception => e # rescue Exception => e
170
+ meta[:exception_object] = e # meta[:exception_object] = e
171
+ raise e # raise e
172
+ ensure # ensure
173
+ Skylight.done(span, meta) if span # Skylight.done(span, meta) if span
174
+ end # end
175
+ end # end
176
+ #
177
+ if protected_method_defined?(:"before_instrument_#{name}") # if protected_method_defined?(:"before_instrument_process")
178
+ protected :"#{name}" # protected :"process"
179
+ elsif private_method_defined?(:"before_instrument_#{name}") # elsif private_method_defined?(:"before_instrument_process")
180
+ private :"#{name}" # private :"process"
181
+ end # end
173
182
  RUBY
174
183
  end
175
184
 
@@ -60,8 +60,6 @@ module Skylight
60
60
  @trace_info = @config[:trace_info] || TraceInfo.new(KEY)
61
61
  @mutex = Mutex.new
62
62
  @extensions = Skylight::Extensions::Collection.new(@config)
63
-
64
- enable_extension!(:source_location) if @config.enable_source_locations?
65
63
  end
66
64
 
67
65
  def enable_extension!(name)
@@ -176,6 +174,7 @@ module Skylight
176
174
  return
177
175
  end
178
176
 
177
+ enable_extension!(:source_location) if @config.enable_source_locations?
179
178
  config.gc.enable
180
179
  @subscriber.register!
181
180
 
@@ -202,6 +201,8 @@ module Skylight
202
201
  end
203
202
 
204
203
  begin
204
+ meta ||= {}
205
+ extensions.process_trace_meta(meta)
205
206
  trace = Trace.new(self, endpoint, Skylight::Util::Clock.nanos, cat, title, desc,
206
207
  meta: meta, segment: segment, component: component)
207
208
  rescue Exception => e
@@ -10,10 +10,10 @@ module Skylight
10
10
  @closed = false
11
11
  end
12
12
 
13
- def respond_to_missing?(*args)
14
- return false if args.first.to_s !~ /^to_ary$/
13
+ def respond_to_missing?(name, include_all = false)
14
+ return false if name.to_s !~ /^to_ary$/
15
15
 
16
- @body.respond_to?(*args)
16
+ @body.respond_to?(name, include_all)
17
17
  end
18
18
 
19
19
  def close
@@ -117,7 +117,7 @@ module Skylight
117
117
  end
118
118
 
119
119
  def endpoint_meta(_env)
120
- nil
120
+ { source_location: Trace::SYNTHETIC }
121
121
  end
122
122
 
123
123
  # Request ID code based on ActionDispatch::RequestId
@@ -101,7 +101,7 @@ module Skylight
101
101
 
102
102
  # @api private
103
103
  def self.check_install_errors(config)
104
- # Note: An unsupported arch doesn't count as an error.
104
+ # NOTE: An unsupported arch doesn't count as an error.
105
105
  install_log = File.expand_path("../../ext/install.log", __dir__)
106
106
 
107
107
  if File.exist?(install_log) && File.read(install_log) =~ /ERROR/
@@ -23,7 +23,7 @@ module Skylight
23
23
  # @param opts [Hash]
24
24
  def self.fetch(**args)
25
25
  args[:source] ||= BASE_URL
26
- args[:logger] ||= Logger.new(STDOUT)
26
+ args[:logger] ||= Logger.new($stdout)
27
27
  new(**args).fetch
28
28
  end
29
29
 
@@ -35,7 +35,7 @@ module Skylight
35
35
  # @param required [Boolean] whether the download is required to be successful
36
36
  # @param platform
37
37
  # @param log [Logger]
38
- def initialize(source:, target:, version:, checksum:, arch:, required: false, platform: nil, logger:)
38
+ def initialize(source:, target:, version:, checksum:, arch:, logger:, required: false, platform: nil)
39
39
  raise "source required" unless source
40
40
  raise "target required" unless target
41
41
  raise "checksum required" unless checksum
@@ -20,7 +20,7 @@ module Skylight
20
20
  matches = registry.select { |n, _| n =~ /(^|\.)#{name}$/ }
21
21
  raise ArgumentError, "no normalizers match #{name}" if matches.empty?
22
22
 
23
- matches.values.each { |v| v[1] = enabled }
23
+ matches.each_value { |v| v[1] = enabled }
24
24
  end
25
25
  end
26
26
 
@@ -69,7 +69,8 @@ module Skylight
69
69
  return cat if cat == :skip
70
70
 
71
71
  meta ||= {}
72
- process_meta(trace, name, payload, meta, cache_key: ret.hash)
72
+ cache_key = ret.hash
73
+ process_meta(trace, name, payload, meta, cache_key: cache_key)
73
74
 
74
75
  [cat, title, desc, meta]
75
76
  end
@@ -78,7 +79,7 @@ module Skylight
78
79
 
79
80
  private
80
81
 
81
- def process_meta(trace, name, payload, meta, cache_key: nil)
82
+ def process_meta(trace, _name, payload, meta, cache_key: nil)
82
83
  trace.instrumenter.extensions.process_normalizer_meta(
83
84
  payload,
84
85
  meta,
@@ -144,7 +145,8 @@ module Skylight
144
145
  graphiti/resolve
145
146
  graphiti/render
146
147
  graphql/base
147
- sequel/sql].each do |file|
148
+ sequel/sql
149
+ shrine].each do |file|
148
150
  require "skylight/normalizers/#{file}"
149
151
  end
150
152
  end
@@ -38,7 +38,7 @@ module Skylight
38
38
 
39
39
  def process_meta_options(payload)
40
40
  # provide hints to override default source_location behavior
41
- super.merge(source_location: [:instance_method, payload[:controller], payload[:action]])
41
+ super.merge(source_location_hint: [:instance_method, payload[:controller], payload[:action]])
42
42
  end
43
43
 
44
44
  def segment_from_payload(payload)
@@ -19,7 +19,7 @@ module Skylight
19
19
 
20
20
  def process_meta_options(_payload)
21
21
  # provide hints to override default source_location behavior
22
- super.merge(source_location: [:own_instance_method, router_class_name, "call"])
22
+ super.merge(source_location_hint: [:own_instance_method, router_class_name, "call"])
23
23
  end
24
24
  end
25
25
  end
@@ -36,6 +36,11 @@ module Skylight
36
36
 
37
37
  private
38
38
 
39
+ def process_meta_options(payload)
40
+ # provide hints to override default source_location behavior
41
+ super.merge(source_location_hint: [:instance_method, payload[:job].class.to_s, "perform"])
42
+ end
43
+
39
44
  def normalize_adapter_name(adapter)
40
45
  adapter_string = adapter.is_a?(Class) ? adapter.to_s : adapter.class.to_s
41
46
  adapter_string[/ActiveJob::QueueAdapters::(\w+)Adapter/, 1].underscore
@@ -24,6 +24,7 @@ module Skylight::Normalizers::GraphQL
24
24
  end
25
25
 
26
26
  def self.inherited(klass)
27
+ super
27
28
  klass.const_set(
28
29
  :KEY,
29
30
  ActiveSupport::Inflector.underscore(
@@ -40,7 +40,7 @@ module Skylight
40
40
  # Matches a Gem Version or 12-digit hex (sha)
41
41
  # that is preceeded by a `-` and followed by `/`
42
42
  # Also matches 'app/views/' if it exists
43
- %r{-(?:#{Gem::Version::VERSION_PATTERN}|[0-9a-f]{12})\/(?:app\/views\/)*},
43
+ %r{-(?:#{Gem::Version::VERSION_PATTERN}|[0-9a-f]{12})/(?:app/views/)*},
44
44
  ": ".freeze
45
45
  )
46
46
  else
@@ -0,0 +1,34 @@
1
+ module Skylight
2
+ module Normalizers
3
+ class Shrine < Normalizer
4
+ TITLES = {
5
+ "upload.shrine" => "Upload",
6
+ "download.shrine" => "Download",
7
+ "open.shrine" => "Open",
8
+ "exists.shrine" => "Exists",
9
+ "delete.shrine" => "Delete",
10
+ "metadata.shrine" => "Metadata",
11
+ "mime_type.shrine" => "MIME Type",
12
+ "image_dimensions.shrine" => "Image Dimensions",
13
+ "signature.shrine" => "Signature",
14
+ "extension.shrine" => "Extension",
15
+ "derivation.shrine" => "Derivation",
16
+ "derivatives.shrine" => "Derivatives",
17
+ "data_uri.shrine" => "Data URI",
18
+ "remote_url.shrine" => "Remote URL"
19
+ }.freeze
20
+
21
+ TITLES.each_key do |key|
22
+ register key
23
+ end
24
+
25
+ def normalize(_trace, name, _payload)
26
+ title = ["Shrine", TITLES[name]].join(" ")
27
+
28
+ cat = "app.#{name.split('.').reverse.join('.')}"
29
+
30
+ [cat, title, nil]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -14,7 +14,7 @@ module Skylight
14
14
  # @option payload [String] [:name] The SQL operation
15
15
  # @option payload [Hash] [:binds] The bound parameters
16
16
  # @return [Array]
17
- def normalize(trace, name, payload)
17
+ def normalize(_trace, name, payload)
18
18
  case payload[:name]
19
19
  when "SCHEMA", "CACHE"
20
20
  return :skip
@@ -29,7 +29,8 @@ module Skylight
29
29
 
30
30
  unless sql.valid_encoding?
31
31
  if config[:log_sql_parse_errors]
32
- config.logger.error "[#{Skylight::SqlLexError.formatted_code}] Unable to extract binds from non-UTF-8 query. " \
32
+ config.logger.error "[#{Skylight::SqlLexError.formatted_code}] Unable to extract binds from non-UTF-8 " \
33
+ "query. " \
33
34
  "encoding=#{payload[:sql].encoding.name} " \
34
35
  "sql=#{payload[:sql].inspect} "
35
36
  end
@@ -16,11 +16,41 @@ module Skylight
16
16
 
17
17
  def install
18
18
  probe.install
19
+ rescue StandardError, LoadError => e
20
+ log_install_exception(e)
19
21
  end
20
22
 
21
23
  def constant_available?
22
24
  Skylight::Probes.constant_available?(const_name)
23
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
+ gems =
36
+ begin
37
+ Bundler.locked_gems.dependencies.map { |d| [d.name, d.requirement.to_s] }
38
+ rescue # rubocop:disable Lint/SuppressedException
39
+ end
40
+
41
+ error = "[SKYLIGHT] [#{Skylight::VERSION}] Encountered an error while installing the " \
42
+ "probe for #{const_name}. Please notify support@skylight.io with the debugging " \
43
+ "information below. It's recommended that you disable this probe until the " \
44
+ "issue is resolved." \
45
+ "\n\nERROR: #{description}\n\n#{backtrace}\n\n"
46
+
47
+ if gems
48
+ gems_string = gems.map { |g| " #{g[0]} #{g[1]}" }.join("\n")
49
+ error << "GEMS:\n\n#{gems_string}\n\n"
50
+ end
51
+
52
+ $stderr.puts(error)
53
+ end
24
54
  end
25
55
 
26
56
  class << self
@@ -99,12 +129,12 @@ module Skylight
99
129
  def require_hook(require_path)
100
130
  each_by_require_path(require_path) do |registration|
101
131
  # Double check constant is available
102
- if registration.constant_available?
103
- install_probe(registration)
132
+ next unless registration.constant_available?
104
133
 
105
- # Don't need this to be called again
106
- unregister_require_hook(registration)
107
- end
134
+ install_probe(registration)
135
+
136
+ # Don't need this to be called again
137
+ unregister_require_hook(registration)
108
138
  end
109
139
  end
110
140
 
@@ -143,11 +173,9 @@ module Kernel
143
173
 
144
174
  def require(name)
145
175
  require_without_sk(name).tap do
146
- begin
147
- Skylight::Probes.require_hook(name)
148
- rescue Exception => e # rubocop:disable Lint/SuppressedException
149
- warn("[SKYLIGHT] Rescued exception in require hook", e)
150
- end
176
+ Skylight::Probes.require_hook(name)
177
+ rescue Exception => e
178
+ warn("[SKYLIGHT] Rescued exception in require hook", e)
151
179
  end
152
180
  end
153
181
  end
@@ -7,12 +7,10 @@ module Skylight
7
7
  def execute(*)
8
8
  Skylight.trace(TITLE, "app.job.execute", component: :worker) do |trace|
9
9
  # See normalizers/active_job/perform for endpoint/segment assignment
10
- begin
11
- super
12
- rescue Exception
13
- trace.segment = "error" if trace
14
- raise
15
- end
10
+ super
11
+ rescue Exception
12
+ trace.segment = "error" if trace
13
+ raise
16
14
  end
17
15
  end
18
16
  end