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