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