skylight 4.3.0 → 5.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -3
  3. data/CONTRIBUTING.md +1 -7
  4. data/ext/extconf.rb +6 -5
  5. data/ext/libskylight.yml +5 -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 +605 -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 +36 -18
  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 +152 -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/sql.rb +45 -0
  67. data/lib/skylight/probes.rb +181 -0
  68. data/lib/skylight/probes/action_controller.rb +48 -0
  69. data/lib/skylight/probes/action_dispatch.rb +2 -0
  70. data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
  71. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
  72. data/lib/skylight/probes/action_view.rb +43 -0
  73. data/lib/skylight/probes/active_job.rb +27 -0
  74. data/lib/skylight/probes/active_job_enqueue.rb +41 -0
  75. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  76. data/lib/skylight/probes/delayed_job.rb +149 -0
  77. data/lib/skylight/probes/elasticsearch.rb +38 -0
  78. data/lib/skylight/probes/excon.rb +25 -0
  79. data/lib/skylight/probes/excon/middleware.rb +66 -0
  80. data/lib/skylight/probes/faraday.rb +23 -0
  81. data/lib/skylight/probes/graphql.rb +43 -0
  82. data/lib/skylight/probes/httpclient.rb +44 -0
  83. data/lib/skylight/probes/middleware.rb +126 -0
  84. data/lib/skylight/probes/mongo.rb +164 -0
  85. data/lib/skylight/probes/mongoid.rb +13 -0
  86. data/lib/skylight/probes/net_http.rb +54 -0
  87. data/lib/skylight/probes/redis.rb +63 -0
  88. data/lib/skylight/probes/sequel.rb +33 -0
  89. data/lib/skylight/probes/sinatra.rb +63 -0
  90. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  91. data/lib/skylight/probes/tilt.rb +27 -0
  92. data/lib/skylight/railtie.rb +162 -18
  93. data/lib/skylight/sidekiq.rb +48 -0
  94. data/lib/skylight/subscriber.rb +110 -0
  95. data/lib/skylight/test.rb +146 -0
  96. data/lib/skylight/trace.rb +307 -10
  97. data/lib/skylight/user_config.rb +61 -0
  98. data/lib/skylight/util.rb +12 -0
  99. data/lib/skylight/util/allocation_free.rb +26 -0
  100. data/lib/skylight/util/clock.rb +56 -0
  101. data/lib/skylight/util/component.rb +5 -2
  102. data/lib/skylight/util/deploy.rb +7 -10
  103. data/lib/skylight/util/gzip.rb +20 -0
  104. data/lib/skylight/util/http.rb +4 -10
  105. data/lib/skylight/util/instrumenter_method.rb +26 -0
  106. data/lib/skylight/util/logging.rb +138 -0
  107. data/lib/skylight/util/lru_cache.rb +40 -0
  108. data/lib/skylight/util/platform.rb +1 -1
  109. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  110. data/lib/skylight/version.rb +5 -1
  111. data/lib/skylight/vm/gc.rb +68 -0
  112. metadata +117 -19
@@ -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