skylight 4.2.3 → 5.0.0.beta3

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  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 +24 -100
  7. data/lib/skylight.rb +211 -14
  8. data/lib/skylight/api.rb +7 -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,7 +4,7 @@ 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
 
@@ -21,6 +21,7 @@ module Skylight
21
21
 
22
22
  def errors
23
23
  return unless res.respond_to?(:body) && res.body.is_a?(Hash)
24
+
24
25
  res.body["errors"]
25
26
  end
26
27
 
@@ -36,7 +37,7 @@ module Skylight
36
37
  end
37
38
 
38
39
  class ConfigValidationResults
39
- include Core::Util::Logging
40
+ include Util::Logging
40
41
 
41
42
  attr_reader :raw_response
42
43
 
@@ -87,11 +88,13 @@ module Skylight
87
88
 
88
89
  def validation_errors
89
90
  return {} if config_valid? || !body
91
+
90
92
  body["errors"]
91
93
  end
92
94
 
93
95
  def corrected_config
94
- return {} if config_valid? || !body
96
+ return nil if config_valid? || !body
97
+
95
98
  body["corrected"]
96
99
  end
97
100
  end
@@ -107,6 +110,7 @@ module Skylight
107
110
  res = http_request(:app_create, :post, params)
108
111
 
109
112
  raise CreateFailed, res unless res.success?
113
+
110
114
  res
111
115
  end
112
116
 
@@ -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