skylight 4.3.2 → 5.0.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -3
  3. data/CONTRIBUTING.md +2 -8
  4. data/ext/extconf.rb +6 -5
  5. data/ext/libskylight.yml +7 -6
  6. data/ext/skylight_native.c +22 -99
  7. data/lib/skylight.rb +211 -14
  8. data/lib/skylight/api.rb +10 -3
  9. data/lib/skylight/cli.rb +4 -3
  10. data/lib/skylight/cli/doctor.rb +13 -14
  11. data/lib/skylight/cli/merger.rb +6 -4
  12. data/lib/skylight/config.rb +597 -127
  13. data/lib/skylight/deprecation.rb +17 -0
  14. data/lib/skylight/errors.rb +21 -6
  15. data/lib/skylight/extensions.rb +107 -0
  16. data/lib/skylight/extensions/source_location.rb +291 -0
  17. data/lib/skylight/formatters/http.rb +20 -0
  18. data/lib/skylight/gc.rb +109 -0
  19. data/lib/skylight/helpers.rb +69 -26
  20. data/lib/skylight/instrumenter.rb +326 -15
  21. data/lib/skylight/middleware.rb +138 -1
  22. data/lib/skylight/native.rb +52 -2
  23. data/lib/skylight/native_ext_fetcher.rb +4 -3
  24. data/lib/skylight/normalizers.rb +153 -0
  25. data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
  26. data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
  27. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  28. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  29. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  30. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  31. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  32. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  33. data/lib/skylight/normalizers/active_job/perform.rb +86 -0
  34. data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
  35. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  36. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  37. data/lib/skylight/normalizers/active_storage.rb +30 -0
  38. data/lib/skylight/normalizers/active_support/cache.rb +22 -0
  39. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  40. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  49. data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
  50. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  51. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  52. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  53. data/lib/skylight/normalizers/default.rb +32 -0
  54. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  55. data/lib/skylight/normalizers/faraday/request.rb +40 -0
  56. data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
  57. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  58. data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
  59. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
  60. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  61. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  62. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  63. data/lib/skylight/normalizers/graphql/base.rb +132 -0
  64. data/lib/skylight/normalizers/render.rb +81 -0
  65. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  66. data/lib/skylight/normalizers/shrine.rb +34 -0
  67. data/lib/skylight/normalizers/sql.rb +45 -0
  68. data/lib/skylight/probes.rb +181 -0
  69. data/lib/skylight/probes/action_controller.rb +48 -0
  70. data/lib/skylight/probes/action_dispatch.rb +2 -0
  71. data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
  72. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
  73. data/lib/skylight/probes/action_view.rb +43 -0
  74. data/lib/skylight/probes/active_job.rb +27 -0
  75. data/lib/skylight/probes/active_job_enqueue.rb +41 -0
  76. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  77. data/lib/skylight/probes/delayed_job.rb +149 -0
  78. data/lib/skylight/probes/elasticsearch.rb +38 -0
  79. data/lib/skylight/probes/excon.rb +25 -0
  80. data/lib/skylight/probes/excon/middleware.rb +66 -0
  81. data/lib/skylight/probes/faraday.rb +23 -0
  82. data/lib/skylight/probes/graphql.rb +43 -0
  83. data/lib/skylight/probes/httpclient.rb +44 -0
  84. data/lib/skylight/probes/middleware.rb +126 -0
  85. data/lib/skylight/probes/mongo.rb +164 -0
  86. data/lib/skylight/probes/mongoid.rb +13 -0
  87. data/lib/skylight/probes/net_http.rb +54 -0
  88. data/lib/skylight/probes/redis.rb +63 -0
  89. data/lib/skylight/probes/sequel.rb +33 -0
  90. data/lib/skylight/probes/sinatra.rb +63 -0
  91. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  92. data/lib/skylight/probes/tilt.rb +27 -0
  93. data/lib/skylight/railtie.rb +162 -18
  94. data/lib/skylight/sidekiq.rb +48 -0
  95. data/lib/skylight/subscriber.rb +110 -0
  96. data/lib/skylight/test.rb +146 -0
  97. data/lib/skylight/trace.rb +307 -10
  98. data/lib/skylight/user_config.rb +61 -0
  99. data/lib/skylight/util.rb +12 -0
  100. data/lib/skylight/util/allocation_free.rb +26 -0
  101. data/lib/skylight/util/clock.rb +56 -0
  102. data/lib/skylight/util/component.rb +5 -2
  103. data/lib/skylight/util/deploy.rb +7 -10
  104. data/lib/skylight/util/gzip.rb +20 -0
  105. data/lib/skylight/util/http.rb +4 -10
  106. data/lib/skylight/util/instrumenter_method.rb +26 -0
  107. data/lib/skylight/util/logging.rb +138 -0
  108. data/lib/skylight/util/lru_cache.rb +40 -0
  109. data/lib/skylight/util/platform.rb +1 -1
  110. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  111. data/lib/skylight/version.rb +5 -1
  112. data/lib/skylight/vm/gc.rb +68 -0
  113. metadata +126 -13
@@ -0,0 +1,61 @@
1
+ require "yaml"
2
+ require "skylight/errors"
3
+
4
+ module Skylight
5
+ class UserConfig
6
+ attr_accessor :disable_dev_warning, :disable_env_warning
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @file_path = nil
11
+ reload
12
+ end
13
+
14
+ def file_path
15
+ return @file_path if @file_path
16
+
17
+ config_path = @config[:user_config_path] || begin
18
+ require "etc"
19
+ home_dir = ENV["HOME"] || Etc.getpwuid.dir || (ENV["USER"] && File.expand_path("~#{ENV['USER']}"))
20
+ if home_dir
21
+ File.join(home_dir, ".skylight")
22
+ else
23
+ raise ConfigError,
24
+ "The Skylight `user_config_path` must be defined since the home directory cannot be inferred"
25
+ end
26
+ end
27
+
28
+ @file_path = File.expand_path(config_path)
29
+ end
30
+
31
+ def disable_dev_warning?
32
+ disable_dev_warning || ENV["SKYLIGHT_DISABLE_DEV_WARNING"] =~ /^true$/i
33
+ end
34
+
35
+ def disable_env_warning?
36
+ disable_env_warning
37
+ end
38
+
39
+ def reload
40
+ config = File.exist?(file_path) ? YAML.load_file(file_path) : false
41
+ return unless config
42
+
43
+ self.disable_dev_warning = !!config["disable_dev_warning"]
44
+ self.disable_env_warning = !!config["disable_env_warning"]
45
+ end
46
+
47
+ def save
48
+ FileUtils.mkdir_p(File.dirname(file_path))
49
+ File.open(file_path, "w") do |f|
50
+ f.puts YAML.dump(to_hash)
51
+ end
52
+ end
53
+
54
+ def to_hash
55
+ {
56
+ "disable_dev_warning" => disable_dev_warning,
57
+ "disable_env_warning" => disable_env_warning
58
+ }
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,12 @@
1
+ module Skylight
2
+ # @api private
3
+ module Util
4
+ # Used from the main lib
5
+ require "skylight/util/allocation_free"
6
+ require "skylight/util/clock"
7
+ require "skylight/util/instrumenter_method"
8
+
9
+ # Used from the CLI
10
+ autoload :Gzip, "skylight/util/gzip"
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module Skylight
2
+ module Util
3
+ # Helpers to reduce memory allocation
4
+ module AllocationFree
5
+ # Find an item in an array without allocation.
6
+ #
7
+ # @param array [Array] the array to search
8
+ # @yield a block called against each item until a match is found
9
+ # @yieldparam item an item from the array
10
+ # @yieldreturn [Boolean] whether `item` matches the criteria
11
+ # return the found item or nil, if nothing found
12
+ def array_find(array)
13
+ i = 0
14
+
15
+ while i < array.size
16
+ item = array[i]
17
+ return item if yield item
18
+
19
+ i += 1
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ module Skylight
2
+ module Util
3
+ # A more precise clock
4
+ class Clock
5
+ def self.use_native!
6
+ class_eval do
7
+ def tick
8
+ native_hrtime
9
+ end
10
+ end
11
+ end
12
+
13
+ # rubocop:disable Lint/DuplicateMethods
14
+ def tick
15
+ now = Time.now
16
+ now.to_i * 1_000_000_000 + now.usec * 1_000
17
+ end
18
+ # rubocop:enable Lint/DuplicateMethods
19
+
20
+ # TODO: rename to secs
21
+ def absolute_secs
22
+ Time.now.to_i
23
+ end
24
+
25
+ # TODO: remove
26
+ def nanos
27
+ tick
28
+ end
29
+
30
+ # TODO: remove
31
+ def secs
32
+ nanos / 1_000_000_000
33
+ end
34
+
35
+ class << self
36
+ def absolute_secs
37
+ default.absolute_secs
38
+ end
39
+
40
+ def nanos
41
+ default.nanos
42
+ end
43
+
44
+ def secs
45
+ default.secs
46
+ end
47
+
48
+ def default
49
+ @default ||= Clock.new
50
+ end
51
+
52
+ attr_writer :default
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "uri"
3
4
 
4
5
  module Skylight
@@ -6,7 +7,7 @@ module Skylight
6
7
  class Component
7
8
  attr_accessor :environment, :name
8
9
 
9
- NAME_FORMAT = /\A[a-zA-Z0-9_-]+\z/
10
+ NAME_FORMAT = /\A[a-zA-Z0-9_-]+\z/.freeze
10
11
  DEFAULT_NAME = "web"
11
12
  WORKER_NAME = "worker"
12
13
  DEFAULT_ENVIRONMENT = "production"
@@ -16,6 +17,7 @@ module Skylight
16
17
  @name = resolve_name(name, force_worker)
17
18
 
18
19
  raise ArgumentError, "environment can't be blank" if @environment.empty?
20
+
19
21
  validate_string!(@environment, "environment")
20
22
  validate_string!(@name, "name")
21
23
  end
@@ -40,7 +42,7 @@ module Skylight
40
42
  def as_json(*)
41
43
  {
42
44
  component: name,
43
- env: environment
45
+ env: environment
44
46
  }
45
47
  end
46
48
 
@@ -64,6 +66,7 @@ module Skylight
64
66
 
65
67
  def validate_string!(string, kind)
66
68
  return true if string =~ NAME_FORMAT
69
+
67
70
  raise ArgumentError, "#{kind} can only contain lowercase letters, numbers, and dashes"
68
71
  end
69
72
  end
@@ -1,5 +1,5 @@
1
1
  require "json"
2
- require "skylight/core/util/logging"
2
+ require "skylight/util/logging"
3
3
 
4
4
  module Skylight
5
5
  module Util
@@ -13,8 +13,7 @@ module Skylight
13
13
  end
14
14
 
15
15
  class EmptyDeploy
16
- attr_reader :config
17
- attr_reader :timestamp
16
+ attr_reader :config, :timestamp
18
17
 
19
18
  def initialize(config)
20
19
  @config = config
@@ -35,8 +34,8 @@ module Skylight
35
34
 
36
35
  def to_query_hash
37
36
  hash = {
38
- timestamp: timestamp,
39
- deploy_id: id.to_s[0..100] # Keep this sane
37
+ timestamp: timestamp,
38
+ deploy_id: id.to_s[0..100] # Keep this sane
40
39
  }
41
40
  hash[:git_sha] = git_sha.to_s[0..40] if git_sha # A valid SHA will never exceed 40
42
41
  hash[:description] = description[0..255] if description # Avoid massive descriptions
@@ -45,7 +44,7 @@ module Skylight
45
44
  end
46
45
 
47
46
  class DefaultDeploy < EmptyDeploy
48
- include Core::Util::Logging
47
+ include Util::Logging
49
48
 
50
49
  def initialize(*)
51
50
  super
@@ -90,10 +89,8 @@ module Skylight
90
89
  def get_info
91
90
  info_path = config[:'heroku.dyno_info_path']
92
91
 
93
- if File.exist?(info_path)
94
- if (info = JSON.parse(File.read(info_path)))
95
- info["release"]
96
- end
92
+ if File.exist?(info_path) && (info = JSON.parse(File.read(info_path)))
93
+ info["release"]
97
94
  end
98
95
  end
99
96
  end
@@ -0,0 +1,20 @@
1
+ require "zlib"
2
+
3
+ module Skylight
4
+ module Util
5
+ # Provides Gzip compressing support
6
+ module Gzip
7
+ # Compress a string with Gzip
8
+ #
9
+ # @param str [String] uncompressed string
10
+ # @return [String] compressed string
11
+ def self.compress(str)
12
+ output = StringIO.new
13
+ gz = Zlib::GzipWriter.new(output)
14
+ gz.write(str)
15
+ gz.close
16
+ output.string
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,7 +3,7 @@ require "json"
3
3
  require "openssl"
4
4
  require "net/http"
5
5
  require "net/https"
6
- require "skylight/core/util/gzip"
6
+ require "skylight/util/gzip"
7
7
  require "skylight/util/ssl"
8
8
 
9
9
  module Skylight
@@ -19,7 +19,7 @@ module Skylight
19
19
  DEFLATE = "deflate".freeze
20
20
  GZIP = "gzip".freeze
21
21
 
22
- include Core::Util::Logging
22
+ include Logging
23
23
 
24
24
  attr_accessor :authentication
25
25
  attr_reader :host, :port, :config
@@ -118,7 +118,7 @@ module Skylight
118
118
 
119
119
  yield res
120
120
  ensure
121
- client.finish if client
121
+ client&.finish
122
122
  end
123
123
 
124
124
  def execute(req, body = nil)
@@ -189,13 +189,7 @@ module Skylight
189
189
  end
190
190
 
191
191
  def get(key)
192
- return nil unless body.is_a?(Hash)
193
-
194
- res = body
195
- key.split(".").each do |part|
196
- return unless (res = res[part])
197
- end
198
- res
192
+ body.dig(*key.split(".")) if body.is_a?(Hash)
199
193
  end
200
194
 
201
195
  def respond_to_missing?(name, include_all = false)
@@ -0,0 +1,26 @@
1
+ module Skylight
2
+ module Util
3
+ module InstrumenterMethod
4
+ def instrumenter_method(name, block: false)
5
+ if block
6
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
7
+ def #{name}(*args) # def mute(*args)
8
+ unless instrumenter # unless instrumenter
9
+ return yield if block_given? # return yield if block_given?
10
+ return # return
11
+ end # end
12
+ #
13
+ instrumenter.#{name}(*args) { yield } # instrumenter.mute(*args) { yield }
14
+ end # end
15
+ RUBY
16
+ else
17
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
18
+ def #{name}(*args) # def config(*args)
19
+ instrumenter&.#{name}(*args) # instrumenter&.config(*args)
20
+ end # end
21
+ RUBY
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,138 @@
1
+ require "logger"
2
+
3
+ module Skylight
4
+ module Util
5
+ # Log both to the specified logger and STDOUT
6
+ class AlertLogger
7
+ def initialize(logger)
8
+ @logger = logger
9
+ end
10
+
11
+ def write(*args)
12
+ $stderr.write(*args)
13
+
14
+ # Try to avoid writing to STDOUT/STDERR twice
15
+ logger_logdev = @logger.instance_variable_get(:@logdev)
16
+ logger_out = logger_logdev.respond_to?(:dev) ? logger_logdev.dev : nil
17
+ if logger_out != $stdout && logger_out != $stderr
18
+ @logger.<<(*args)
19
+ end
20
+ end
21
+
22
+ def close; end
23
+ end
24
+
25
+ module Logging
26
+ def log_context
27
+ {}
28
+ end
29
+
30
+ def trace?
31
+ !!ENV[-"SKYLIGHT_ENABLE_TRACE_LOGS"]
32
+ end
33
+
34
+ def raise_on_error?
35
+ !!ENV[-"SKYLIGHT_RAISE_ON_ERROR"]
36
+ end
37
+
38
+ # Logs if tracing
39
+ #
40
+ # @param (see #debug)
41
+ #
42
+ # See {trace?}.
43
+ def trace(msg, *args)
44
+ return unless trace?
45
+
46
+ log :debug, msg, *args
47
+ end
48
+
49
+ # Evaluates and logs the result of the block if tracing
50
+ #
51
+ # @yield block to be evaluted
52
+ # @yieldreturn arguments for {#debug}
53
+ #
54
+ # See {trace?}.
55
+ def t
56
+ return unless trace?
57
+
58
+ log :debug, yield
59
+ end
60
+
61
+ # @param msg (see #log)
62
+ # @param args (see #log)
63
+ def debug(msg, *args)
64
+ log :debug, msg, *args
65
+ end
66
+
67
+ # @param msg (see #log)
68
+ # @param args (see #log)
69
+ def info(msg, *args)
70
+ log :info, msg, *args
71
+ end
72
+
73
+ # @param msg (see #log)
74
+ # @param args (see #log)
75
+ def warn(msg, *args)
76
+ log :warn, msg, *args
77
+ end
78
+
79
+ # @param msg (see #log)
80
+ # @param args (see #log)
81
+ def error(msg, *args)
82
+ log :error, msg, *args
83
+ raise format(msg, *args) if raise_on_error?
84
+ end
85
+
86
+ alias log_trace trace
87
+ alias log_debug debug
88
+ alias log_info info
89
+ alias log_warn warn
90
+ alias log_error error
91
+
92
+ # Alias for `Kernel#sprintf`
93
+ # @return [String]
94
+ def fmt(*args)
95
+ sprintf(*args)
96
+ end
97
+
98
+ def config_for_logging
99
+ if respond_to?(:config)
100
+ config
101
+ elsif is_a?(Skylight::Config)
102
+ self
103
+ end
104
+ end
105
+
106
+ # @param level [String,Symbol] the method on `logger` to use for logging
107
+ # @param msg [String] the message to log
108
+ # @param args [Array] values for `Kernel#sprintf` on `msg`
109
+ def log(level, msg, *args)
110
+ c = config_for_logging
111
+ logger = c ? c.logger : nil
112
+
113
+ msg = log_context.map { |(k, v)| "#{k}=#{v}; " }.join << msg
114
+
115
+ if logger
116
+ if logger.respond_to?(level)
117
+ if args.empty?
118
+ logger.send level, msg
119
+ else
120
+ logger.send level, format(msg, *args)
121
+ end
122
+ return # rubocop:disable Style/RedundantReturn
123
+ else
124
+ Kernel.warn "Invalid logger"
125
+ end
126
+ # Fallback to stderr for warn and error levels
127
+ elsif %i[warn error].include?(level)
128
+ $stderr.puts format("[SKYLIGHT] #{msg}", *args)
129
+ end
130
+ rescue Exception => e
131
+ if trace?
132
+ puts "[ERROR] #{e.message}"
133
+ puts e.backtrace
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end