skylight 4.3.2 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/skylight/api.rb CHANGED
@@ -4,13 +4,16 @@ 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
11
  class Error < StandardError; end
12
+
12
13
  class Unauthorized < Error; end
14
+
13
15
  class Conflict < Error; end
16
+
14
17
  class CreateFailed < Error
15
18
  attr_reader :res
16
19
 
@@ -21,6 +24,7 @@ module Skylight
21
24
 
22
25
  def errors
23
26
  return unless res.respond_to?(:body) && res.body.is_a?(Hash)
27
+
24
28
  res.body["errors"]
25
29
  end
26
30
 
@@ -36,7 +40,7 @@ module Skylight
36
40
  end
37
41
 
38
42
  class ConfigValidationResults
39
- include Core::Util::Logging
43
+ include Util::Logging
40
44
 
41
45
  attr_reader :raw_response
42
46
 
@@ -87,11 +91,13 @@ module Skylight
87
91
 
88
92
  def validation_errors
89
93
  return {} if config_valid? || !body
94
+
90
95
  body["errors"]
91
96
  end
92
97
 
93
98
  def corrected_config
94
- return {} if config_valid? || !body
99
+ return nil if config_valid? || !body
100
+
95
101
  body["corrected"]
96
102
  end
97
103
  end
@@ -107,6 +113,7 @@ module Skylight
107
113
  res = http_request(:app_create, :post, params)
108
114
 
109
115
  raise CreateFailed, res unless res.success?
116
+
110
117
  res
111
118
  end
112
119
 
data/lib/skylight/cli.rb CHANGED
@@ -60,7 +60,7 @@ module Skylight
60
60
  rescue Api::CreateFailed => e
61
61
  say "Could not create the application. Please run `bundle exec skylight doctor` for diagnostics.", :red
62
62
  say e.to_s, :yellow
63
- rescue Interrupt
63
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
64
64
  end
65
65
 
66
66
  desc "disable_dev_warning", "Disables warning about running Skylight in development mode for all local apps"
@@ -71,7 +71,8 @@ module Skylight
71
71
  say "Development mode warning disabled", :green
72
72
  end
73
73
 
74
- desc "disable_env_warning", "Disables warning about running Skylight in environments not defined in config.skylight.environments"
74
+ desc "disable_env_warning", "Disables warning about running Skylight in environments not defined in " \
75
+ "config.skylight.environments"
75
76
  def disable_env_warning
76
77
  user_config.disable_env_warning = true
77
78
  user_config.save
@@ -93,7 +94,7 @@ module Skylight
93
94
  begin
94
95
  namefile = Tempfile.new("skylight-app-name")
95
96
  # 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
+ `rails runner "File.open('#{namefile.path}', 'w') {|f| f.write(Rails.application.class.name) rescue '' }"` # rubocop:disable Layout/LineLength
97
98
  name = namefile.read.split("::").first.underscore.titleize
98
99
  name = nil if name.empty?
99
100
  rescue => e
@@ -24,7 +24,8 @@ module Skylight
24
24
  say "Please update your certificates with RVM by running `rvm osx-ssl-certs update all`.", :yellow
25
25
  say "Alternatively, try setting `SKYLIGHT_FORCE_OWN_CERTS=1` in your environment.", :yellow
26
26
  else
27
- say "Please update your local certificates or try setting `SKYLIGHT_FORCE_OWN_CERTS=1` in your environment.", :yellow
27
+ say "Please update your local certificates or try setting `SKYLIGHT_FORCE_OWN_CERTS=1` in your " \
28
+ "environment.", :yellow
28
29
  end
29
30
  end
30
31
  else
@@ -84,20 +85,18 @@ module Skylight
84
85
  say "Checking for valid configuration"
85
86
 
86
87
  indent do
87
- begin
88
- config.validate!
89
- say "Configuration is valid", :green
90
- rescue Core::ConfigError => e
91
- encountered_error!
92
-
93
- say "Configuration is invalid", :red
94
- indent do
95
- say e.message, :red
96
- say "This may occur if you are configuring with ENV variables and didn't set them in this shell."
97
- end
98
-
99
- done!
88
+ config.validate!
89
+ say "Configuration is valid", :green
90
+ rescue ConfigError => e
91
+ encountered_error!
92
+
93
+ say "Configuration is invalid", :red
94
+ indent do
95
+ say e.message, :red
96
+ say "This may occur if you are configuring with ENV variables and didn't set them in this shell."
100
97
  end
98
+
99
+ done!
101
100
  end
102
101
 
103
102
  puts "\n"
@@ -14,7 +14,7 @@ module Skylight
14
14
 
15
15
  STRINGS = {
16
16
  get_token: "get your merge token from `https://www.skylight.io/merging`",
17
- unlisted: "My app isn't listed here :("
17
+ unlisted: "My app isn't listed here :("
18
18
  }.freeze
19
19
 
20
20
  argument :merge_token, type: :string, desc: STRINGS[:get_token]
@@ -171,9 +171,9 @@ module Skylight
171
171
  say "Merging..."
172
172
 
173
173
  api.merge_apps!(@merge_token,
174
- app_guid: @parent_app.guid,
174
+ app_guid: @parent_app.guid,
175
175
  component_guid: @child_app.guid,
176
- environment: @child_env)
176
+ environment: @child_env)
177
177
  rescue => e
178
178
  say("Something went wrong. Please contact support@skylight.io for more information.", :red)
179
179
  done!(message: e.message, success: false)
@@ -252,7 +252,8 @@ module Skylight
252
252
 
253
253
  def valid_component?(component_name, env)
254
254
  return false unless env
255
- Skylight::Util::Component.new(env, component_name) && true
255
+
256
+ Util::Component.new(env, component_name) && true
256
257
  rescue ArgumentError
257
258
  false
258
259
  end
@@ -265,6 +266,7 @@ module Skylight
265
266
  ret = Enumerator.new do |yielder|
266
267
  @parents.each do |_, app|
267
268
  next if app == @parent_app
269
+
268
270
  app.components.each do |component|
269
271
  yielder << OpenStruct.new({ app_name: app.name }.merge(component))
270
272
  end
@@ -1,4 +1,11 @@
1
1
  require "openssl"
2
+ require "yaml"
3
+ require "fileutils"
4
+ require "erb"
5
+ require "json"
6
+ require "skylight/util/logging"
7
+ require "skylight/util/proxy"
8
+ require "skylight/errors"
2
9
  require "skylight/util/component"
3
10
  require "skylight/util/deploy"
4
11
  require "skylight/util/platform"
@@ -6,123 +13,188 @@ require "skylight/util/hostname"
6
13
  require "skylight/util/ssl"
7
14
 
8
15
  module Skylight
9
- class Config < Core::Config
10
- def self.env_to_key
11
- @env_to_key ||= super.merge(
12
- # == Authentication ==
13
- "AUTHENTICATION" => :authentication,
14
-
15
- # == App settings ==
16
- "ROOT" => :root,
17
- "HOSTNAME" => :hostname,
18
- "SESSION_TOKEN" => :session_token,
19
-
20
- # == Component settings ==
21
- "ENV" => :env,
22
- "COMPONENT" => :component,
23
- "REPORT_RAILS_ENV" => :report_rails_env,
24
-
25
- # == Deploy settings ==
26
- "DEPLOY_ID" => :'deploy.id',
27
- "DEPLOY_GIT_SHA" => :'deploy.git_sha',
28
- "DEPLOY_DESCRIPTION" => :'deploy.description',
29
-
30
- # == Max Span Handling ==
31
- "REPORT_MAX_SPANS_EXCEEDED" => :report_max_spans_exceeded,
32
- "PRUNE_LARGE_TRACES" => :prune_large_traces,
33
-
34
- # == Instrumenter ==
35
- "IGNORED_ENDPOINT" => :ignored_endpoint,
36
- "IGNORED_ENDPOINTS" => :ignored_endpoints,
37
-
38
- # == Skylight Remote ==
39
- "AUTH_URL" => :auth_url,
40
- "APP_CREATE_URL" => :app_create_url,
41
- "MERGES_URL" => :merges_url,
42
- "VALIDATION_URL" => :validation_url,
43
- "AUTH_HTTP_DEFLATE" => :auth_http_deflate,
44
- "AUTH_HTTP_CONNECT_TIMEOUT" => :auth_http_connect_timeout,
45
- "AUTH_HTTP_READ_TIMEOUT" => :auth_http_read_timeout,
46
- "REPORT_URL" => :report_url,
47
- "REPORT_HTTP_DEFLATE" => :report_http_deflate,
48
- "REPORT_HTTP_CONNECT_TIMEOUT" => :report_http_connect_timeout,
49
- "REPORT_HTTP_READ_TIMEOUT" => :report_http_read_timeout,
50
- "REPORT_HTTP_DISABLED" => :report_http_disabled,
51
-
52
- # == Native agent settings ==
53
- #
54
- "LAZY_START" => :'daemon.lazy_start',
55
- "DAEMON_EXEC_PATH" => :'daemon.exec_path',
56
- "DAEMON_LIB_PATH" => :'daemon.lib_path',
57
- "PIDFILE_PATH" => :'daemon.pidfile_path',
58
- "SOCKDIR_PATH" => :'daemon.sockdir_path',
59
- "BATCH_QUEUE_DEPTH" => :'daemon.batch_queue_depth',
60
- "BATCH_SAMPLE_SIZE" => :'daemon.batch_sample_size',
61
- "BATCH_FLUSH_INTERVAL" => :'daemon.batch_flush_interval',
62
- "DAEMON_TICK_INTERVAL" => :'daemon.tick_interval',
63
- "DAEMON_SANITY_CHECK_INTERVAL" => :'daemon.sanity_check_interval',
64
- "DAEMON_INACTIVITY_TIMEOUT" => :'daemon.inactivity_timeout',
65
- "CLIENT_MAX_TRIES" => :'daemon.max_connect_tries',
66
- "CLIENT_CONN_TRY_WIN" => :'daemon.connect_try_window',
67
- "MAX_PRESPAWN_JITTER" => :'daemon.max_prespawn_jitter',
68
- "DAEMON_WAIT_TIMEOUT" => :'daemon.wait_timeout',
69
- "CLIENT_CHECK_INTERVAL" => :'daemon.client_check_interval',
70
- "CLIENT_QUEUE_DEPTH" => :'daemon.client_queue_depth',
71
- "CLIENT_WRITE_TIMEOUT" => :'daemon.client_write_timeout',
72
- "SSL_CERT_PATH" => :'daemon.ssl_cert_path',
73
- "SSL_CERT_DIR" => :'daemon.ssl_cert_dir',
74
-
75
- # == Legacy env vars ==
76
- #
77
- "AGENT_LOCKFILE" => :'agent.lockfile',
78
- "AGENT_SOCKFILE_PATH" => :'agent.sockfile_path'
79
- )
80
- end
16
+ class Config
17
+ include Util::Logging
18
+
19
+ # @api private
20
+ MUTEX = Mutex.new
21
+
22
+ # Map environment variable keys with Skylight configuration keys
23
+ ENV_TO_KEY = {
24
+ # == Authentication ==
25
+ -"AUTHENTICATION" => :authentication,
26
+
27
+ # == App settings ==
28
+ -"ROOT" => :root,
29
+ -"HOSTNAME" => :hostname,
30
+ -"SESSION_TOKEN" => :session_token,
31
+
32
+ # == Component settings ==
33
+ -"ENV" => :env,
34
+ -"COMPONENT" => :component,
35
+ -"REPORT_RAILS_ENV" => :report_rails_env,
36
+
37
+ # == Deploy settings ==
38
+ -"DEPLOY_ID" => :'deploy.id',
39
+ -"DEPLOY_GIT_SHA" => :'deploy.git_sha',
40
+ -"DEPLOY_DESCRIPTION" => :'deploy.description',
41
+
42
+ # == Logging ==
43
+ -"LOG_FILE" => :log_file,
44
+ -"LOG_LEVEL" => :log_level,
45
+ -"ALERT_LOG_FILE" => :alert_log_file,
46
+ -"NATIVE_LOG_FILE" => :native_log_file,
47
+ -"NATIVE_LOG_LEVEL" => :native_log_level,
48
+ -"LOG_SQL_PARSE_ERRORS" => :log_sql_parse_errors,
49
+
50
+ # == Proxy ==
51
+ -"PROXY_URL" => :proxy_url,
52
+
53
+ # == Instrumenter ==
54
+ -"ENABLE_SEGMENTS" => :enable_segments,
55
+ -"ENABLE_SIDEKIQ" => :enable_sidekiq,
56
+ -"IGNORED_ENDPOINT" => :ignored_endpoint,
57
+ -"IGNORED_ENDPOINTS" => :ignored_endpoints,
58
+ -"SINATRA_ROUTE_PREFIXES" => :sinatra_route_prefixes,
59
+ -"ENABLE_SOURCE_LOCATIONS" => :enable_source_locations,
60
+
61
+ # == Max Span Handling ==
62
+ -"REPORT_MAX_SPANS_EXCEEDED" => :report_max_spans_exceeded,
63
+ -"PRUNE_LARGE_TRACES" => :prune_large_traces,
64
+
65
+ # == Skylight Remote ==
66
+ -"AUTH_URL" => :auth_url,
67
+ -"APP_CREATE_URL" => :app_create_url,
68
+ -"MERGES_URL" => :merges_url,
69
+ -"VALIDATION_URL" => :validation_url,
70
+ -"AUTH_HTTP_DEFLATE" => :auth_http_deflate,
71
+ -"AUTH_HTTP_CONNECT_TIMEOUT" => :auth_http_connect_timeout,
72
+ -"AUTH_HTTP_READ_TIMEOUT" => :auth_http_read_timeout,
73
+ -"REPORT_URL" => :report_url,
74
+ -"REPORT_HTTP_DEFLATE" => :report_http_deflate,
75
+ -"REPORT_HTTP_CONNECT_TIMEOUT" => :report_http_connect_timeout,
76
+ -"REPORT_HTTP_READ_TIMEOUT" => :report_http_read_timeout,
77
+ -"REPORT_HTTP_DISABLED" => :report_http_disabled,
78
+
79
+ # == Native agent settings ==
80
+ #
81
+ -"LAZY_START" => :'daemon.lazy_start',
82
+ -"DAEMON_EXEC_PATH" => :'daemon.exec_path',
83
+ -"DAEMON_LIB_PATH" => :'daemon.lib_path',
84
+ -"PIDFILE_PATH" => :'daemon.pidfile_path',
85
+ -"SOCKDIR_PATH" => :'daemon.sockdir_path',
86
+ -"BATCH_QUEUE_DEPTH" => :'daemon.batch_queue_depth',
87
+ -"BATCH_SAMPLE_SIZE" => :'daemon.batch_sample_size',
88
+ -"BATCH_FLUSH_INTERVAL" => :'daemon.batch_flush_interval',
89
+ -"DAEMON_TICK_INTERVAL" => :'daemon.tick_interval',
90
+ -"DAEMON_LOCK_CHECK_INTERVAL" => :'daemon.lock_check_interval',
91
+ -"DAEMON_INACTIVITY_TIMEOUT" => :'daemon.inactivity_timeout',
92
+ -"CLIENT_MAX_TRIES" => :'daemon.max_connect_tries',
93
+ -"CLIENT_CONN_TRY_WIN" => :'daemon.connect_try_window',
94
+ -"MAX_PRESPAWN_JITTER" => :'daemon.max_prespawn_jitter',
95
+ -"DAEMON_WAIT_TIMEOUT" => :'daemon.wait_timeout',
96
+ -"CLIENT_CHECK_INTERVAL" => :'daemon.client_check_interval',
97
+ -"CLIENT_QUEUE_DEPTH" => :'daemon.client_queue_depth',
98
+ -"CLIENT_WRITE_TIMEOUT" => :'daemon.client_write_timeout',
99
+ -"SSL_CERT_PATH" => :'daemon.ssl_cert_path',
100
+ -"SSL_CERT_DIR" => :'daemon.ssl_cert_dir',
101
+
102
+ # == Legacy env vars ==
103
+ #
104
+ -"AGENT_LOCKFILE" => :'agent.lockfile',
105
+ -"AGENT_SOCKFILE_PATH" => :'agent.sockfile_path',
106
+
107
+ # == User config settings ==
108
+ -"USER_CONFIG_PATH" => :user_config_path,
109
+
110
+ # == Heroku settings ==
111
+ -"HEROKU_DYNO_INFO_PATH" => :'heroku.dyno_info_path',
112
+
113
+ # == Source Location ==
114
+ -"SOURCE_LOCATION_IGNORED_GEMS" => :source_location_ignored_gems,
115
+ -"SOURCE_LOCATION_CACHE_SIZE" => :source_location_cache_size
116
+ }.freeze
117
+
118
+ KEY_TO_NATIVE_ENV = {
119
+ # We use different log files for native and Ruby, but the native code doesn't know this
120
+ native_log_file: "LOG_FILE",
121
+ native_log_level: "LOG_LEVEL"
122
+ }.freeze
123
+
124
+ SERVER_VALIDATE = %i[].freeze
125
+
126
+ DEFAULT_IGNORED_SOURCE_LOCATION_GEMS = [
127
+ -"skylight",
128
+ -"activesupport",
129
+ -"activerecord"
130
+ ].freeze
81
131
 
82
132
  # Default values for Skylight configuration keys
83
133
  def self.default_values
84
- @default_values ||= begin
85
- ret = super.merge(
86
- auth_url: "https://auth.skylight.io/agent",
87
- app_create_url: "https://www.skylight.io/apps",
88
- merges_url: "https://www.skylight.io/merges",
89
- validation_url: "https://auth.skylight.io/agent/config",
90
- 'daemon.lazy_start': true,
91
- hostname: Util::Hostname.default_hostname,
92
- report_max_spans_exceeded: false,
93
- prune_large_traces: true,
94
- report_rails_env: true
95
- )
134
+ @default_values ||=
135
+ begin
136
+ ret = {
137
+ # URLs
138
+ auth_url: -"https://auth.skylight.io/agent",
139
+ app_create_url: -"https://www.skylight.io/apps",
140
+ merges_url: -"https://www.skylight.io/merges",
141
+ validation_url: -"https://auth.skylight.io/agent/config",
142
+
143
+ # Logging
144
+ log_file: -"-",
145
+ log_level: -"INFO",
146
+ alert_log_file: -"-",
147
+ log_sql_parse_errors: true,
148
+ native_log_level: -"warn",
149
+
150
+ # Features
151
+ enable_segments: true,
152
+ enable_sidekiq: false,
153
+ sinatra_route_prefixes: false,
154
+ enable_source_locations: true,
155
+
156
+ # Deploys
157
+ 'heroku.dyno_info_path': -"/etc/heroku/dyno",
158
+ report_rails_env: true,
159
+
160
+ # Daemon
161
+ 'daemon.lazy_start': true,
162
+ hostname: Util::Hostname.default_hostname,
163
+ report_max_spans_exceeded: false,
164
+ prune_large_traces: true
165
+ }
166
+
167
+ unless Util::Platform::OS == -"darwin"
168
+ ret[:'daemon.ssl_cert_path'] = Util::SSL.ca_cert_file_or_default
169
+ ret[:'daemon.ssl_cert_dir'] = Util::SSL.ca_cert_dir
170
+ end
96
171
 
97
- unless Util::Platform::OS == "darwin"
98
- ret[:'daemon.ssl_cert_path'] = Util::SSL.ca_cert_file_or_default
99
- ret[:'daemon.ssl_cert_dir'] = Util::SSL.ca_cert_dir
100
- end
172
+ if Skylight.native?
173
+ native_path = Skylight.libskylight_path
101
174
 
102
- if Skylight.native?
103
- native_path = Skylight.libskylight_path
175
+ ret[:'daemon.lib_path'] = native_path
176
+ ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
177
+ end
104
178
 
105
- ret[:'daemon.lib_path'] = native_path
106
- ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
179
+ ret
107
180
  end
108
-
109
- ret
110
- end
111
181
  end
112
182
 
113
- def self.required_keys
114
- @required_keys ||= super.merge(
115
- authentication: "authentication token",
116
- hostname: "server hostname",
117
- auth_url: "authentication url",
118
- validation_url: "config validation url"
119
- )
120
- end
183
+ REQUIRED_KEYS = {
184
+ authentication: "authentication token",
185
+ hostname: "server hostname",
186
+ auth_url: "authentication url",
187
+ validation_url: "config validation url"
188
+ }.freeze
121
189
 
122
190
  def self.native_env_keys
123
- @native_env_keys ||= super + %i[
191
+ @native_env_keys ||= %i[
192
+ native_log_level
193
+ native_log_file
194
+ log_sql_parse_errors
124
195
  version
125
196
  root
197
+ proxy_url
126
198
  hostname
127
199
  session_token
128
200
  auth_url
@@ -143,7 +215,7 @@ module Skylight
143
215
  daemon.batch_sample_size
144
216
  daemon.batch_flush_interval
145
217
  daemon.tick_interval
146
- daemon.sanity_check_interval
218
+ daemon.lock_check_interval
147
219
  daemon.inactivity_timeout
148
220
  daemon.max_connect_tries
149
221
  daemon.connect_try_window
@@ -157,27 +229,140 @@ module Skylight
157
229
  ]
158
230
  end
159
231
 
232
+ # Maps legacy config keys to new config keys
160
233
  def self.legacy_keys
161
- @legacy_keys ||= super.merge(
234
+ @legacy_keys ||= {
162
235
  'agent.sockfile_path': :'daemon.sockdir_path',
163
236
  'agent.lockfile': :'daemon.pidfile_path'
164
- )
237
+ }
165
238
  end
166
239
 
167
240
  def self.validators
168
- @validators ||= super.merge(
241
+ @validators ||= {
169
242
  'agent.interval': [->(v, _c) { v.is_a?(Integer) && v > 0 }, "must be an integer greater than 0"]
170
- )
243
+ }
171
244
  end
172
245
 
173
246
  # @api private
174
- def api
175
- @api ||= Api.new(self)
247
+ attr_reader :priority_key
248
+
249
+ # @api private
250
+ def initialize(*args)
251
+ attrs = {}
252
+
253
+ if args.last.is_a?(Hash)
254
+ attrs = args.pop.dup
255
+ end
256
+
257
+ @values = {}
258
+ @priority = {}
259
+ @priority_regexp = nil
260
+ @alert_logger = nil
261
+ @logger = nil
262
+
263
+ p = attrs.delete(:priority)
264
+
265
+ if (@priority_key = args[0])
266
+ @priority_regexp = /^#{Regexp.escape(priority_key)}\.(.+)$/
267
+ end
268
+
269
+ attrs.each do |k, v|
270
+ self[k] = v
271
+ end
272
+
273
+ p&.each do |k, v|
274
+ @priority[self.class.remap_key(k)] = v
275
+ end
276
+ end
277
+
278
+ def self.load(opts = {}, env = ENV)
279
+ attrs = {}
280
+ path = opts.delete(:file)
281
+ priority_key = opts.delete(:priority_key)
282
+ priority_key ||= opts[:env] # if a priority_key is not given, use env if available
283
+
284
+ if path
285
+ error = nil
286
+ begin
287
+ attrs = YAML.safe_load(ERB.new(File.read(path)).result,
288
+ [], # permitted_classes
289
+ [], # permitted_symbols
290
+ true) # aliases enabled
291
+ error = "empty file" unless attrs
292
+ error = "invalid format" if attrs && !attrs.is_a?(Hash)
293
+ rescue Exception => e
294
+ error = e.message
295
+ end
296
+
297
+ raise ConfigError, "could not load config file; msg=#{error}" if error
298
+ end
299
+
300
+ # The key-value pairs in this `priority` option are inserted into the
301
+ # config's @priority hash *after* anything listed under priority_key;
302
+ # i.e., ENV takes precendence over priority_key
303
+ if env
304
+ attrs[:priority] = remap_env(env)
305
+ end
306
+
307
+ config = new(priority_key, attrs)
308
+
309
+ opts.each do |k, v|
310
+ config[k] = v
311
+ end
312
+
313
+ config
314
+ end
315
+
316
+ def self.remap_key(key)
317
+ key = key.to_sym
318
+ legacy_keys[key] || key
319
+ end
320
+
321
+ # @api private
322
+ def self.remap_env(env)
323
+ ret = {}
324
+
325
+ return ret unless env
326
+
327
+ # Only set if it exists, we don't want to set to a nil value
328
+ if (proxy_url = Util::Proxy.detect_url(env))
329
+ ret[:proxy_url] = proxy_url
330
+ end
331
+
332
+ env.each do |k, val|
333
+ next unless k =~ /^(?:SK|SKYLIGHT)_(.+)$/
334
+ next unless (key = ENV_TO_KEY[$1])
335
+
336
+ ret[key] =
337
+ case val
338
+ when /^false$/i then false
339
+ when /^true$/i then true
340
+ when /^(nil|null)$/i then nil
341
+ when /^\d+$/ then val.to_i
342
+ when /^\d+\.\d+$/ then val.to_f
343
+ else val
344
+ end
345
+ end
346
+
347
+ ret
176
348
  end
177
349
 
178
350
  # @api private
179
351
  def validate!
180
- super
352
+ REQUIRED_KEYS.each do |k, v|
353
+ unless get(k)
354
+ raise ConfigError, "#{v} required"
355
+ end
356
+ end
357
+
358
+ log_file = self[:log_file]
359
+ alert_log_file = self[:alert_log_file]
360
+ native_log_file = self.native_log_file
361
+
362
+ check_logfile_permissions(log_file, "log_file")
363
+ check_logfile_permissions(alert_log_file, "alert_log_file")
364
+ # TODO: Support rotation interpolation in this check
365
+ check_logfile_permissions(native_log_file, "native_log_file")
181
366
 
182
367
  # TODO: Move this out of the validate! method: https://github.com/tildeio/direwolf-agent/issues/273
183
368
  # FIXME: Why not set the sockdir_path and pidfile_path explicitly?
@@ -191,6 +376,290 @@ module Skylight
191
376
  true
192
377
  end
193
378
 
379
+ def check_file_permissions(file, key)
380
+ file_root = File.dirname(file)
381
+
382
+ # Try to make the directory, don't blow up if we can't. Our writable? check will fail later.
383
+ FileUtils.mkdir_p file_root rescue nil
384
+
385
+ if File.exist?(file) && !FileTest.writable?(file)
386
+ raise ConfigError, "File `#{file}` is not writable. Please set #{key} in your config to a writable path"
387
+ end
388
+
389
+ unless FileTest.writable?(file_root)
390
+ raise ConfigError, "Directory `#{file_root}` is not writable. Please set #{key} in your config to a " \
391
+ "writable path"
392
+ end
393
+ end
394
+
395
+ def check_logfile_permissions(log_file, key)
396
+ return if log_file == "-" # STDOUT
397
+
398
+ log_file = File.expand_path(log_file, root)
399
+ check_file_permissions(log_file, key)
400
+ end
401
+
402
+ def key?(key)
403
+ key = self.class.remap_key(key)
404
+ @priority.key?(key) || @values.key?(key)
405
+ end
406
+
407
+ def get(key, default = nil)
408
+ key = self.class.remap_key(key)
409
+
410
+ return @priority[key] if @priority.key?(key)
411
+ return @values[key] if @values.key?(key)
412
+ return self.class.default_values[key] if self.class.default_values.key?(key)
413
+
414
+ if default
415
+ return default
416
+ elsif block_given?
417
+ return yield key
418
+ end
419
+
420
+ nil
421
+ end
422
+
423
+ alias [] get
424
+
425
+ def set(key, val, scope = nil)
426
+ if scope
427
+ key = [scope, key].join(".")
428
+ end
429
+
430
+ if val.is_a?(Hash)
431
+ val.each do |k, v|
432
+ set(k, v, key)
433
+ end
434
+ else
435
+ k = self.class.remap_key(key)
436
+
437
+ if (validator = self.class.validators[k])
438
+ blk, msg = validator
439
+
440
+ unless blk.call(val, self)
441
+ error_msg = "invalid value for #{k} (#{val})"
442
+ error_msg << ", #{msg}" if msg
443
+ raise ConfigError, error_msg
444
+ end
445
+ end
446
+
447
+ if @priority_regexp && k =~ @priority_regexp
448
+ @priority[$1.to_sym] = val
449
+ end
450
+
451
+ @values[k] = val
452
+ end
453
+ end
454
+
455
+ alias []= set
456
+
457
+ def send_or_get(val)
458
+ respond_to?(val) ? send(val) : get(val)
459
+ end
460
+
461
+ def duration_ms(key, default = nil)
462
+ if (v = self[key]) && v.to_s =~ /^\s*(\d+)(s|sec|ms|micros|nanos)?\s*$/
463
+ v = $1.to_i
464
+ case $2
465
+ when "ms"
466
+ v
467
+ when "micros"
468
+ v / 1_000
469
+ when "nanos"
470
+ v / 1_000_000
471
+ else # "s", "sec", nil
472
+ v * 1000
473
+ end
474
+ else
475
+ default
476
+ end
477
+ end
478
+
479
+ def to_native_env
480
+ ret = []
481
+
482
+ self.class.native_env_keys.each do |key|
483
+ value = send_or_get(key)
484
+ unless value.nil?
485
+ env_key = KEY_TO_NATIVE_ENV[key] || ENV_TO_KEY.key(key) || key.upcase
486
+ ret << "SKYLIGHT_#{env_key}" << cast_for_env(value)
487
+ end
488
+ end
489
+
490
+ ret << "SKYLIGHT_AUTHENTICATION" << authentication_with_meta
491
+ ret << "SKYLIGHT_VALIDATE_AUTHENTICATION" << "false"
492
+
493
+ ret
494
+ end
495
+
496
+ #
497
+ #
498
+ # ===== Helpers =====
499
+ #
500
+ #
501
+
502
+ def version
503
+ VERSION
504
+ end
505
+
506
+ # @api private
507
+ def gc
508
+ @gc ||= GC.new(self, get("gc.profiler", VM::GC.new))
509
+ end
510
+
511
+ # @api private
512
+ def ignored_endpoints
513
+ @ignored_endpoints ||=
514
+ begin
515
+ ignored_endpoints = get(:ignored_endpoints)
516
+
517
+ # If, for some odd reason you have a comma in your endpoint name, use the
518
+ # YML config instead.
519
+ if ignored_endpoints.is_a?(String)
520
+ ignored_endpoints = ignored_endpoints.split(/\s*,\s*/)
521
+ end
522
+
523
+ val = Array(get(:ignored_endpoint))
524
+ val.concat(Array(ignored_endpoints))
525
+ val
526
+ end
527
+ end
528
+
529
+ # @api private
530
+ def source_location_ignored_gems
531
+ @source_location_ignored_gems ||=
532
+ begin
533
+ ignored_gems = get(:source_location_ignored_gems)
534
+ if ignored_gems.is_a?(String)
535
+ ignored_gems = ignored_gems.split(/\s*,\s*/)
536
+ end
537
+
538
+ Array(ignored_gems) | DEFAULT_IGNORED_SOURCE_LOCATION_GEMS
539
+ end
540
+ end
541
+
542
+ def root
543
+ @root ||= Pathname.new(self[:root] || Dir.pwd).realpath
544
+ end
545
+
546
+ def log_level
547
+ @log_level ||=
548
+ if trace?
549
+ Logger::DEBUG
550
+ else
551
+ case get(:log_level)
552
+ when /^debug$/i then Logger::DEBUG
553
+ when /^info$/i then Logger::INFO
554
+ when /^warn$/i then Logger::WARN
555
+ when /^error$/i then Logger::ERROR
556
+ when /^fatal$/i then Logger::FATAL
557
+ else Logger::ERROR # rubocop:disable Lint/DuplicateBranch
558
+ end
559
+ end
560
+ end
561
+
562
+ def native_log_level
563
+ get(:native_log_level).to_s.downcase
564
+ end
565
+
566
+ def logger
567
+ @logger ||=
568
+ MUTEX.synchronize do
569
+ load_logger
570
+ end
571
+ end
572
+
573
+ def native_log_file
574
+ @native_log_file ||= get("native_log_file") do
575
+ log_file = self["log_file"]
576
+ return "-" if log_file == "-"
577
+
578
+ parts = log_file.to_s.split(".")
579
+ parts.insert(-2, "native")
580
+ parts.join(".")
581
+ end
582
+ end
583
+
584
+ attr_writer :logger, :alert_logger
585
+
586
+ def alert_logger
587
+ @alert_logger ||= MUTEX.synchronize do
588
+ unless (l = @alert_logger)
589
+ out = get(:alert_log_file)
590
+ out = Util::AlertLogger.new(load_logger) if out == "-"
591
+
592
+ l = create_logger(out, level: Logger::DEBUG)
593
+ end
594
+
595
+ l
596
+ end
597
+ end
598
+
599
+ def enable_segments?
600
+ !!get(:enable_segments)
601
+ end
602
+
603
+ def enable_sidekiq?
604
+ !!get(:enable_sidekiq)
605
+ end
606
+
607
+ def sinatra_route_prefixes?
608
+ !!get(:sinatra_route_prefixes)
609
+ end
610
+
611
+ def enable_source_locations?
612
+ !!get(:enable_source_locations)
613
+ end
614
+
615
+ def user_config
616
+ @user_config ||= UserConfig.new(self)
617
+ end
618
+
619
+ def on_heroku?
620
+ File.exist?(get(:'heroku.dyno_info_path'))
621
+ end
622
+
623
+ private
624
+
625
+ def create_logger(out, level: :info)
626
+ if out.is_a?(String)
627
+ out = File.expand_path(out, root)
628
+ # May be redundant since we also do this in the permissions check
629
+ FileUtils.mkdir_p(File.dirname(out))
630
+ end
631
+
632
+ Logger.new(out, progname: "Skylight", level: level)
633
+ rescue
634
+ Logger.new($stdout, progname: "Skylight", level: level)
635
+ end
636
+
637
+ def load_logger
638
+ unless (l = @logger)
639
+ out = get(:log_file)
640
+ out = $stdout if out == "-"
641
+ l = create_logger(out, level: log_level)
642
+ end
643
+
644
+ l
645
+ end
646
+
647
+ def cast_for_env(val)
648
+ case val
649
+ when true then "true"
650
+ when false then "false"
651
+ when nil then "nil"
652
+ else val.to_s
653
+ end
654
+ end
655
+
656
+ public
657
+
658
+ # @api private
659
+ def api
660
+ @api ||= Api.new(self)
661
+ end
662
+
194
663
  def validate_with_server
195
664
  res = api.validate_config
196
665
 
@@ -212,6 +681,10 @@ module Skylight
212
681
  return false if res.forbidden?
213
682
 
214
683
  corrected_config = res.corrected_config
684
+
685
+ # Use defaults if no corrected config is available. This will happen if the request failed.
686
+ corrected_config ||= SERVER_VALIDATE.map { |k| [k, self.class.default_values.fetch(k)] }.to_h
687
+
215
688
  config_to_update = corrected_config.reject { |k, v| get(k) == v }
216
689
  unless config_to_update.empty?
217
690
  info("Updating config values:")
@@ -220,7 +693,7 @@ module Skylight
220
693
 
221
694
  # This is a weird way to handle priorities
222
695
  # See https://github.com/tildeio/direwolf-agent/issues/275
223
- k = "#{environment}.#{k}" if environment
696
+ k = "#{priority_key}.#{k}" if priority_key
224
697
 
225
698
  set(k, v)
226
699
  end
@@ -235,23 +708,16 @@ module Skylight
235
708
  FileUtils.mkdir_p sockdir_path rescue nil
236
709
 
237
710
  unless FileTest.writable?(sockdir_path)
238
- raise Core::ConfigError, "Directory `#{sockdir_path}` is not writable. Please set daemon.sockdir_path in your config to a writable path"
711
+ raise ConfigError, "Directory `#{sockdir_path}` is not writable. Please set daemon.sockdir_path in " \
712
+ "your config to a writable path"
239
713
  end
240
714
 
241
715
  if check_nfs(sockdir_path)
242
- raise Core::ConfigError, "Directory `#{sockdir_path}` is an NFS mount and will not allow sockets. Please set daemon.sockdir_path in your config to a non-NFS path."
716
+ raise ConfigError, "Directory `#{sockdir_path}` is an NFS mount and will not allow sockets. Please set " \
717
+ "daemon.sockdir_path in your config to a non-NFS path."
243
718
  end
244
719
  end
245
720
 
246
- def to_native_env
247
- ret = super
248
-
249
- ret << "SKYLIGHT_AUTHENTICATION" << authentication_with_meta
250
- ret << "SKYLIGHT_VALIDATE_AUTHENTICATION" << "false"
251
-
252
- ret
253
- end
254
-
255
721
  def write(path)
256
722
  FileUtils.mkdir_p(File.dirname(path))
257
723
 
@@ -292,7 +758,7 @@ module Skylight
292
758
 
293
759
  def components
294
760
  @components ||= {
295
- web: Util::Component.new(
761
+ web: Util::Component.new(
296
762
  get(:env),
297
763
  Util::Component::DEFAULT_NAME
298
764
  ),
@@ -303,18 +769,22 @@ module Skylight
303
769
  )
304
770
  }
305
771
  rescue ArgumentError => e
306
- raise Core::ConfigError, e.message
772
+ raise ConfigError, e.message
307
773
  end
308
774
 
309
775
  def component
310
776
  components[:web]
311
777
  end
312
778
 
779
+ def to_json(*)
780
+ JSON.generate(as_json)
781
+ end
782
+
313
783
  def as_json(*)
314
784
  {
315
785
  config: {
316
786
  priority: @priority.merge(component.as_json),
317
- values: @values
787
+ values: @values
318
788
  }
319
789
  }
320
790
  end