skylight 4.2.1 → 5.0.0.beta

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 +27 -0
  3. data/CONTRIBUTING.md +1 -7
  4. data/ext/extconf.rb +4 -3
  5. data/ext/libskylight.yml +5 -6
  6. data/ext/skylight_native.c +24 -100
  7. data/lib/skylight.rb +204 -14
  8. data/lib/skylight/api.rb +7 -3
  9. data/lib/skylight/cli.rb +4 -3
  10. data/lib/skylight/cli/doctor.rb +3 -2
  11. data/lib/skylight/cli/merger.rb +6 -4
  12. data/lib/skylight/config.rb +603 -126
  13. data/lib/skylight/deprecation.rb +15 -0
  14. data/lib/skylight/errors.rb +17 -2
  15. data/lib/skylight/extensions.rb +99 -0
  16. data/lib/skylight/extensions/source_location.rb +249 -0
  17. data/lib/skylight/fanout.rb +0 -0
  18. data/lib/skylight/formatters/http.rb +19 -0
  19. data/lib/skylight/gc.rb +109 -0
  20. data/lib/skylight/helpers.rb +18 -2
  21. data/lib/skylight/instrumenter.rb +325 -15
  22. data/lib/skylight/middleware.rb +138 -1
  23. data/lib/skylight/native.rb +51 -1
  24. data/lib/skylight/native_ext_fetcher.rb +2 -1
  25. data/lib/skylight/normalizers.rb +151 -0
  26. data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
  27. data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
  28. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  29. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  30. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  31. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  32. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  33. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  34. data/lib/skylight/normalizers/active_job/perform.rb +81 -0
  35. data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
  36. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  37. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  38. data/lib/skylight/normalizers/active_storage.rb +30 -0
  39. data/lib/skylight/normalizers/active_support/cache.rb +22 -0
  40. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  50. data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
  51. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  52. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  53. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  54. data/lib/skylight/normalizers/default.rb +32 -0
  55. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  56. data/lib/skylight/normalizers/faraday/request.rb +40 -0
  57. data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
  58. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  59. data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
  60. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
  61. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  62. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  63. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  64. data/lib/skylight/normalizers/graphql/base.rb +131 -0
  65. data/lib/skylight/normalizers/render.rb +81 -0
  66. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  67. data/lib/skylight/normalizers/sql.rb +44 -0
  68. data/lib/skylight/probes.rb +153 -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 +29 -0
  75. data/lib/skylight/probes/active_job_enqueue.rb +37 -0
  76. data/lib/skylight/probes/active_model_serializers.rb +54 -0
  77. data/lib/skylight/probes/delayed_job.rb +62 -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 +125 -0
  85. data/lib/skylight/probes/mongo.rb +163 -0
  86. data/lib/skylight/probes/mongoid.rb +13 -0
  87. data/lib/skylight/probes/net_http.rb +55 -0
  88. data/lib/skylight/probes/redis.rb +60 -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 +43 -0
  95. data/lib/skylight/subscriber.rb +110 -0
  96. data/lib/skylight/test.rb +146 -0
  97. data/lib/skylight/trace.rb +301 -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 +4 -4
  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 +42 -0
  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 +116 -17
@@ -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
@@ -87,7 +88,7 @@ module Skylight
87
88
  begin
88
89
  config.validate!
89
90
  say "Configuration is valid", :green
90
- rescue Core::ConfigError => e
91
+ rescue ConfigError => e
91
92
  encountered_error!
92
93
 
93
94
  say "Configuration is invalid", :red
@@ -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,187 @@ 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
+ }.freeze
115
+
116
+ KEY_TO_NATIVE_ENV = {
117
+ # We use different log files for native and Ruby, but the native code doesn't know this
118
+ native_log_file: "LOG_FILE",
119
+ native_log_level: "LOG_LEVEL"
120
+ }.freeze
121
+
122
+ SERVER_VALIDATE = %i[
123
+ enable_source_locations
124
+ ].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
+
149
+ # Features
150
+ enable_segments: true,
151
+ enable_sidekiq: false,
152
+ sinatra_route_prefixes: false,
153
+ enable_source_locations: false,
154
+
155
+ # Deploys
156
+ 'heroku.dyno_info_path': -"/etc/heroku/dyno",
157
+ report_rails_env: true,
158
+
159
+ # Daemon
160
+ 'daemon.lazy_start': true,
161
+ hostname: Util::Hostname.default_hostname,
162
+ report_max_spans_exceeded: false,
163
+ prune_large_traces: true
164
+ }
165
+
166
+ unless Util::Platform::OS == -"darwin"
167
+ ret[:'daemon.ssl_cert_path'] = Util::SSL.ca_cert_file_or_default
168
+ ret[:'daemon.ssl_cert_dir'] = Util::SSL.ca_cert_dir
169
+ end
96
170
 
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
171
+ if Skylight.native?
172
+ native_path = Skylight.libskylight_path
101
173
 
102
- if Skylight.native?
103
- native_path = Skylight.libskylight_path
174
+ ret[:'daemon.lib_path'] = native_path
175
+ ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
176
+ end
104
177
 
105
- ret[:'daemon.lib_path'] = native_path
106
- ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
178
+ ret
107
179
  end
108
-
109
- ret
110
- end
111
180
  end
112
181
 
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
182
+ REQUIRED_KEYS = {
183
+ authentication: "authentication token",
184
+ hostname: "server hostname",
185
+ auth_url: "authentication url",
186
+ validation_url: "config validation url"
187
+ }.freeze
121
188
 
122
189
  def self.native_env_keys
123
- @native_env_keys ||= super + %i[
190
+ @native_env_keys ||= %i[
191
+ native_log_level
192
+ native_log_file
193
+ log_sql_parse_errors
124
194
  version
125
195
  root
196
+ proxy_url
126
197
  hostname
127
198
  session_token
128
199
  auth_url
@@ -143,7 +214,7 @@ module Skylight
143
214
  daemon.batch_sample_size
144
215
  daemon.batch_flush_interval
145
216
  daemon.tick_interval
146
- daemon.sanity_check_interval
217
+ daemon.lock_check_interval
147
218
  daemon.inactivity_timeout
148
219
  daemon.max_connect_tries
149
220
  daemon.connect_try_window
@@ -157,27 +228,136 @@ module Skylight
157
228
  ]
158
229
  end
159
230
 
231
+ # Maps legacy config keys to new config keys
160
232
  def self.legacy_keys
161
- @legacy_keys ||= super.merge(
233
+ @legacy_keys ||= {
162
234
  'agent.sockfile_path': :'daemon.sockdir_path',
163
235
  'agent.lockfile': :'daemon.pidfile_path'
164
- )
236
+ }
165
237
  end
166
238
 
167
239
  def self.validators
168
- @validators ||= super.merge(
240
+ @validators ||= {
169
241
  'agent.interval': [->(v, _c) { v.is_a?(Integer) && v > 0 }, "must be an integer greater than 0"]
170
- )
242
+ }
171
243
  end
172
244
 
173
245
  # @api private
174
- def api
175
- @api ||= Api.new(self)
246
+ attr_reader :environment
247
+
248
+ # @api private
249
+ def initialize(*args)
250
+ attrs = {}
251
+
252
+ if args.last.is_a?(Hash)
253
+ attrs = args.pop.dup
254
+ end
255
+
256
+ @values = {}
257
+ @priority = {}
258
+ @regexp = nil
259
+ @alert_logger = nil
260
+ @logger = nil
261
+
262
+ p = attrs.delete(:priority)
263
+
264
+ if (@environment = args[0])
265
+ @regexp = /^#{Regexp.escape(@environment)}\.(.+)$/
266
+ end
267
+
268
+ attrs.each do |k, v|
269
+ self[k] = v
270
+ end
271
+
272
+ p&.each do |k, v|
273
+ @priority[self.class.remap_key(k)] = v
274
+ end
275
+ end
276
+
277
+ def self.load(opts = {}, env = ENV)
278
+ attrs = {}
279
+ path = opts.delete(:file)
280
+ environment = opts.delete(:environment)
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
+ if env
299
+ attrs[:priority] = remap_env(env)
300
+ end
301
+
302
+ config = new(environment, attrs)
303
+
304
+ opts.each do |k, v|
305
+ config[k] = v
306
+ end
307
+
308
+ config
309
+ end
310
+
311
+ def self.remap_key(key)
312
+ key = key.to_sym
313
+ legacy_keys[key] || key
314
+ end
315
+
316
+ # @api private
317
+ def self.remap_env(env)
318
+ ret = {}
319
+
320
+ return ret unless env
321
+
322
+ # Only set if it exists, we don't want to set to a nil value
323
+ if (proxy_url = Util::Proxy.detect_url(env))
324
+ ret[:proxy_url] = proxy_url
325
+ end
326
+
327
+ env.each do |k, val|
328
+ next unless k =~ /^(?:SK|SKYLIGHT)_(.+)$/
329
+ next unless (key = ENV_TO_KEY[$1])
330
+
331
+ ret[key] =
332
+ case val
333
+ when /^false$/i then false
334
+ when /^true$/i then true
335
+ when /^(nil|null)$/i then nil
336
+ when /^\d+$/ then val.to_i
337
+ when /^\d+\.\d+$/ then val.to_f
338
+ else val
339
+ end
340
+ end
341
+
342
+ ret
176
343
  end
177
344
 
178
345
  # @api private
179
346
  def validate!
180
- super
347
+ REQUIRED_KEYS.each do |k, v|
348
+ unless get(k)
349
+ raise ConfigError, "#{v} required"
350
+ end
351
+ end
352
+
353
+ log_file = self[:log_file]
354
+ alert_log_file = self[:alert_log_file]
355
+ native_log_file = self.native_log_file
356
+
357
+ check_logfile_permissions(log_file, "log_file")
358
+ check_logfile_permissions(alert_log_file, "alert_log_file")
359
+ # TODO: Support rotation interpolation in this check
360
+ check_logfile_permissions(native_log_file, "native_log_file")
181
361
 
182
362
  # TODO: Move this out of the validate! method: https://github.com/tildeio/direwolf-agent/issues/273
183
363
  # FIXME: Why not set the sockdir_path and pidfile_path explicitly?
@@ -191,6 +371,302 @@ module Skylight
191
371
  true
192
372
  end
193
373
 
374
+ def check_file_permissions(file, key)
375
+ file_root = File.dirname(file)
376
+
377
+ # Try to make the directory, don't blow up if we can't. Our writable? check will fail later.
378
+ FileUtils.mkdir_p file_root rescue nil
379
+
380
+ if File.exist?(file) && !FileTest.writable?(file)
381
+ raise ConfigError, "File `#{file}` is not writable. Please set #{key} in your config to a writable path"
382
+ end
383
+
384
+ unless FileTest.writable?(file_root)
385
+ raise ConfigError, "Directory `#{file_root}` is not writable. Please set #{key} in your config to a " \
386
+ "writable path"
387
+ end
388
+ end
389
+
390
+ def check_logfile_permissions(log_file, key)
391
+ return if log_file == "-" # STDOUT
392
+
393
+ log_file = File.expand_path(log_file, root)
394
+ check_file_permissions(log_file, key)
395
+ end
396
+
397
+ def key?(key)
398
+ key = self.class.remap_key(key)
399
+ @priority.key?(key) || @values.key?(key)
400
+ end
401
+
402
+ def get(key, default = nil)
403
+ key = self.class.remap_key(key)
404
+
405
+ return @priority[key] if @priority.key?(key)
406
+ return @values[key] if @values.key?(key)
407
+ return self.class.default_values[key] if self.class.default_values.key?(key)
408
+
409
+ if default
410
+ return default
411
+ elsif block_given?
412
+ return yield key
413
+ end
414
+
415
+ nil
416
+ end
417
+
418
+ alias [] get
419
+
420
+ def set(key, val, scope = nil)
421
+ if scope
422
+ key = [scope, key].join(".")
423
+ end
424
+
425
+ if val.is_a?(Hash)
426
+ val.each do |k, v|
427
+ set(k, v, key)
428
+ end
429
+ else
430
+ k = self.class.remap_key(key)
431
+
432
+ if (validator = self.class.validators[k])
433
+ blk, msg = validator
434
+
435
+ unless blk.call(val, self)
436
+ error_msg = "invalid value for #{k} (#{val})"
437
+ error_msg << ", #{msg}" if msg
438
+ raise ConfigError, error_msg
439
+ end
440
+ end
441
+
442
+ if @regexp && k =~ @regexp
443
+ @priority[$1.to_sym] = val
444
+ end
445
+
446
+ @values[k] = val
447
+ end
448
+ end
449
+
450
+ alias []= set
451
+
452
+ def send_or_get(val)
453
+ respond_to?(val) ? send(val) : get(val)
454
+ end
455
+
456
+ def duration_ms(key, default = nil)
457
+ if (v = self[key]) && v.to_s =~ /^\s*(\d+)(s|sec|ms|micros|nanos)?\s*$/
458
+ v = $1.to_i
459
+ case $2
460
+ when "ms"
461
+ v
462
+ when "micros"
463
+ v / 1_000
464
+ when "nanos"
465
+ v / 1_000_000
466
+ else # "s", "sec", nil
467
+ v * 1000
468
+ end
469
+ else
470
+ default
471
+ end
472
+ end
473
+
474
+ def to_native_env
475
+ ret = []
476
+
477
+ self.class.native_env_keys.each do |key|
478
+ value = send_or_get(key)
479
+ unless value.nil?
480
+ env_key = KEY_TO_NATIVE_ENV[key] || ENV_TO_KEY.key(key) || key.upcase
481
+ ret << "SKYLIGHT_#{env_key}" << cast_for_env(value)
482
+ end
483
+ end
484
+
485
+ ret << "SKYLIGHT_AUTHENTICATION" << authentication_with_meta
486
+ ret << "SKYLIGHT_VALIDATE_AUTHENTICATION" << "false"
487
+
488
+ ret
489
+ end
490
+
491
+ #
492
+ #
493
+ # ===== Helpers =====
494
+ #
495
+ #
496
+
497
+ def version
498
+ VERSION
499
+ end
500
+
501
+ # @api private
502
+ def gc
503
+ @gc ||= GC.new(self, get("gc.profiler", VM::GC.new))
504
+ end
505
+
506
+ # @api private
507
+ def ignored_endpoints
508
+ @ignored_endpoints ||=
509
+ begin
510
+ ignored_endpoints = get(:ignored_endpoints)
511
+
512
+ # If, for some odd reason you have a comma in your endpoint name, use the
513
+ # YML config instead.
514
+ if ignored_endpoints.is_a?(String)
515
+ ignored_endpoints = ignored_endpoints.split(/\s*,\s*/)
516
+ end
517
+
518
+ val = Array(get(:ignored_endpoint))
519
+ val.concat(Array(ignored_endpoints))
520
+ val
521
+ end
522
+ end
523
+
524
+ # @api private
525
+ def source_location_ignored_gems
526
+ @source_location_ignored_gems ||=
527
+ begin
528
+ ignored_gems = get(:source_location_ignored_gems)
529
+ if ignored_gems.is_a?(String)
530
+ ignored_gems = ignored_gems.split(/\s*,\s*/)
531
+ end
532
+
533
+ Array(ignored_gems) | DEFAULT_IGNORED_SOURCE_LOCATION_GEMS
534
+ end
535
+ end
536
+
537
+ def root
538
+ @root ||= Pathname.new(self[:root] || Dir.pwd).realpath
539
+ end
540
+
541
+ def log_level
542
+ @log_level ||=
543
+ if trace?
544
+ Logger::DEBUG
545
+ else
546
+ case get(:log_level)
547
+ when /^debug$/i then Logger::DEBUG
548
+ when /^info$/i then Logger::INFO
549
+ when /^warn$/i then Logger::WARN
550
+ when /^error$/i then Logger::ERROR
551
+ when /^fatal$/i then Logger::FATAL
552
+ else Logger::ERROR
553
+ end
554
+ end
555
+ end
556
+
557
+ def native_log_level
558
+ @native_log_level ||=
559
+ if trace?
560
+ "trace"
561
+ else
562
+ case log_level
563
+ when Logger::DEBUG then "debug"
564
+ when Logger::INFO then "info"
565
+ when Logger::WARN then "warn"
566
+ else "error"
567
+ end
568
+ end
569
+ end
570
+
571
+ def logger
572
+ @logger ||=
573
+ MUTEX.synchronize do
574
+ load_logger
575
+ end
576
+ end
577
+
578
+ def native_log_file
579
+ @native_log_file ||= get("native_log_file") do
580
+ log_file = self["log_file"]
581
+ return "-" if log_file == "-"
582
+
583
+ parts = log_file.to_s.split(".")
584
+ parts.insert(-2, "native")
585
+ parts.join(".")
586
+ end
587
+ end
588
+
589
+ attr_writer :logger
590
+
591
+ def alert_logger
592
+ @alert_logger ||= MUTEX.synchronize do
593
+ unless (l = @alert_logger)
594
+ out = get(:alert_log_file)
595
+ out = Util::AlertLogger.new(load_logger) if out == "-"
596
+
597
+ l = create_logger(out, level: Logger::DEBUG)
598
+ end
599
+
600
+ l
601
+ end
602
+ end
603
+
604
+ attr_writer :alert_logger
605
+
606
+ def enable_segments?
607
+ !!get(:enable_segments)
608
+ end
609
+
610
+ def enable_sidekiq?
611
+ !!get(:enable_sidekiq)
612
+ end
613
+
614
+ def sinatra_route_prefixes?
615
+ !!get(:sinatra_route_prefixes)
616
+ end
617
+
618
+ def enable_source_locations?
619
+ !!get(:enable_source_locations)
620
+ end
621
+
622
+ def user_config
623
+ @user_config ||= UserConfig.new(self)
624
+ end
625
+
626
+ def on_heroku?
627
+ File.exist?(get(:'heroku.dyno_info_path'))
628
+ end
629
+
630
+ private
631
+
632
+ def create_logger(out, level: :info)
633
+ if out.is_a?(String)
634
+ out = File.expand_path(out, root)
635
+ # May be redundant since we also do this in the permissions check
636
+ FileUtils.mkdir_p(File.dirname(out))
637
+ end
638
+
639
+ Logger.new(out, progname: "Skylight", level: level)
640
+ rescue
641
+ Logger.new(STDOUT, progname: "Skylight", level: level)
642
+ end
643
+
644
+ def load_logger
645
+ unless (l = @logger)
646
+ out = get(:log_file)
647
+ out = STDOUT if out == "-"
648
+ l = create_logger(out, level: log_level)
649
+ end
650
+
651
+ l
652
+ end
653
+
654
+ def cast_for_env(val)
655
+ case val
656
+ when true then "true"
657
+ when false then "false"
658
+ when nil then "nil"
659
+ else val.to_s
660
+ end
661
+ end
662
+
663
+ public
664
+
665
+ # @api private
666
+ def api
667
+ @api ||= Api.new(self)
668
+ end
669
+
194
670
  def validate_with_server
195
671
  res = api.validate_config
196
672
 
@@ -212,6 +688,10 @@ module Skylight
212
688
  return false if res.forbidden?
213
689
 
214
690
  corrected_config = res.corrected_config
691
+
692
+ # Use defaults if no corrected config is available. This will happen if the request failed.
693
+ corrected_config ||= Hash[SERVER_VALIDATE.map { |k| [k, self.class.default_values.fetch(k)] }]
694
+
215
695
  config_to_update = corrected_config.reject { |k, v| get(k) == v }
216
696
  unless config_to_update.empty?
217
697
  info("Updating config values:")
@@ -235,23 +715,16 @@ module Skylight
235
715
  FileUtils.mkdir_p sockdir_path rescue nil
236
716
 
237
717
  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"
718
+ raise ConfigError, "Directory `#{sockdir_path}` is not writable. Please set daemon.sockdir_path in " \
719
+ "your config to a writable path"
239
720
  end
240
721
 
241
722
  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."
723
+ raise ConfigError, "Directory `#{sockdir_path}` is an NFS mount and will not allow sockets. Please set " \
724
+ "daemon.sockdir_path in your config to a non-NFS path."
243
725
  end
244
726
  end
245
727
 
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
728
  def write(path)
256
729
  FileUtils.mkdir_p(File.dirname(path))
257
730
 
@@ -292,7 +765,7 @@ module Skylight
292
765
 
293
766
  def components
294
767
  @components ||= {
295
- web: Util::Component.new(
768
+ web: Util::Component.new(
296
769
  get(:env),
297
770
  Util::Component::DEFAULT_NAME
298
771
  ),
@@ -303,18 +776,22 @@ module Skylight
303
776
  )
304
777
  }
305
778
  rescue ArgumentError => e
306
- raise Core::ConfigError, e.message
779
+ raise ConfigError, e.message
307
780
  end
308
781
 
309
782
  def component
310
783
  components[:web]
311
784
  end
312
785
 
786
+ def to_json(*)
787
+ JSON.generate(as_json)
788
+ end
789
+
313
790
  def as_json(*)
314
791
  {
315
792
  config: {
316
793
  priority: @priority.merge(component.as_json),
317
- values: @values
794
+ values: @values
318
795
  }
319
796
  }
320
797
  end