skylight 4.2.2 → 5.0.0.beta2

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  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 +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 +13 -14
  11. data/lib/skylight/cli/merger.rb +6 -4
  12. data/lib/skylight/config.rb +604 -127
  13. data/lib/skylight/deprecation.rb +17 -0
  14. data/lib/skylight/errors.rb +17 -2
  15. data/lib/skylight/extensions.rb +107 -0
  16. data/lib/skylight/extensions/source_location.rb +280 -0
  17. data/lib/skylight/formatters/http.rb +19 -0
  18. data/lib/skylight/gc.rb +109 -0
  19. data/lib/skylight/helpers.rb +18 -2
  20. data/lib/skylight/instrumenter.rb +326 -15
  21. data/lib/skylight/middleware.rb +138 -1
  22. data/lib/skylight/native.rb +51 -1
  23. data/lib/skylight/native_ext_fetcher.rb +4 -3
  24. data/lib/skylight/normalizers.rb +151 -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 +35 -0
  75. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  76. data/lib/skylight/probes/delayed_job.rb +148 -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 +163 -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 +60 -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 +305 -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 +5 -11
  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/vendor/cli/thor/rake_compat.rb +1 -1
  109. data/lib/skylight/version.rb +5 -1
  110. data/lib/skylight/vm/gc.rb +68 -0
  111. 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,185 @@ 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[].freeze
123
+
124
+ DEFAULT_IGNORED_SOURCE_LOCATION_GEMS = [
125
+ -"skylight",
126
+ -"activesupport",
127
+ -"activerecord"
128
+ ].freeze
81
129
 
82
130
  # Default values for Skylight configuration keys
83
131
  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
- )
132
+ @default_values ||=
133
+ begin
134
+ ret = {
135
+ # URLs
136
+ auth_url: -"https://auth.skylight.io/agent",
137
+ app_create_url: -"https://www.skylight.io/apps",
138
+ merges_url: -"https://www.skylight.io/merges",
139
+ validation_url: -"https://auth.skylight.io/agent/config",
140
+
141
+ # Logging
142
+ log_file: -"-",
143
+ log_level: -"INFO",
144
+ alert_log_file: -"-",
145
+ log_sql_parse_errors: true,
146
+
147
+ # Features
148
+ enable_segments: true,
149
+ enable_sidekiq: false,
150
+ sinatra_route_prefixes: false,
151
+ enable_source_locations: true,
152
+
153
+ # Deploys
154
+ 'heroku.dyno_info_path': -"/etc/heroku/dyno",
155
+ report_rails_env: true,
156
+
157
+ # Daemon
158
+ 'daemon.lazy_start': true,
159
+ hostname: Util::Hostname.default_hostname,
160
+ report_max_spans_exceeded: false,
161
+ prune_large_traces: true
162
+ }
163
+
164
+ unless Util::Platform::OS == -"darwin"
165
+ ret[:'daemon.ssl_cert_path'] = Util::SSL.ca_cert_file_or_default
166
+ ret[:'daemon.ssl_cert_dir'] = Util::SSL.ca_cert_dir
167
+ end
96
168
 
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
169
+ if Skylight.native?
170
+ native_path = Skylight.libskylight_path
101
171
 
102
- if Skylight.native?
103
- native_path = Skylight.libskylight_path
172
+ ret[:'daemon.lib_path'] = native_path
173
+ ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
174
+ end
104
175
 
105
- ret[:'daemon.lib_path'] = native_path
106
- ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
176
+ ret
107
177
  end
108
-
109
- ret
110
- end
111
178
  end
112
179
 
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
180
+ REQUIRED_KEYS = {
181
+ authentication: "authentication token",
182
+ hostname: "server hostname",
183
+ auth_url: "authentication url",
184
+ validation_url: "config validation url"
185
+ }.freeze
121
186
 
122
187
  def self.native_env_keys
123
- @native_env_keys ||= super + %i[
188
+ @native_env_keys ||= %i[
189
+ native_log_level
190
+ native_log_file
191
+ log_sql_parse_errors
124
192
  version
125
193
  root
194
+ proxy_url
126
195
  hostname
127
196
  session_token
128
197
  auth_url
@@ -143,7 +212,7 @@ module Skylight
143
212
  daemon.batch_sample_size
144
213
  daemon.batch_flush_interval
145
214
  daemon.tick_interval
146
- daemon.sanity_check_interval
215
+ daemon.lock_check_interval
147
216
  daemon.inactivity_timeout
148
217
  daemon.max_connect_tries
149
218
  daemon.connect_try_window
@@ -157,27 +226,140 @@ module Skylight
157
226
  ]
158
227
  end
159
228
 
229
+ # Maps legacy config keys to new config keys
160
230
  def self.legacy_keys
161
- @legacy_keys ||= super.merge(
231
+ @legacy_keys ||= {
162
232
  'agent.sockfile_path': :'daemon.sockdir_path',
163
233
  'agent.lockfile': :'daemon.pidfile_path'
164
- )
234
+ }
165
235
  end
166
236
 
167
237
  def self.validators
168
- @validators ||= super.merge(
238
+ @validators ||= {
169
239
  'agent.interval': [->(v, _c) { v.is_a?(Integer) && v > 0 }, "must be an integer greater than 0"]
170
- )
240
+ }
171
241
  end
172
242
 
173
243
  # @api private
174
- def api
175
- @api ||= Api.new(self)
244
+ attr_reader :priority_key
245
+
246
+ # @api private
247
+ def initialize(*args)
248
+ attrs = {}
249
+
250
+ if args.last.is_a?(Hash)
251
+ attrs = args.pop.dup
252
+ end
253
+
254
+ @values = {}
255
+ @priority = {}
256
+ @priority_regexp = nil
257
+ @alert_logger = nil
258
+ @logger = nil
259
+
260
+ p = attrs.delete(:priority)
261
+
262
+ if (@priority_key = args[0])
263
+ @priority_regexp = /^#{Regexp.escape(priority_key)}\.(.+)$/
264
+ end
265
+
266
+ attrs.each do |k, v|
267
+ self[k] = v
268
+ end
269
+
270
+ p&.each do |k, v|
271
+ @priority[self.class.remap_key(k)] = v
272
+ end
273
+ end
274
+
275
+ def self.load(opts = {}, env = ENV)
276
+ attrs = {}
277
+ path = opts.delete(:file)
278
+ priority_key = opts.delete(:priority_key)
279
+ priority_key ||= opts[:env] # if a priority_key is not given, use env if available
280
+
281
+ if path
282
+ error = nil
283
+ begin
284
+ attrs = YAML.safe_load(ERB.new(File.read(path)).result,
285
+ [], # permitted_classes
286
+ [], # permitted_symbols
287
+ true) # aliases enabled
288
+ error = "empty file" unless attrs
289
+ error = "invalid format" if attrs && !attrs.is_a?(Hash)
290
+ rescue Exception => e
291
+ error = e.message
292
+ end
293
+
294
+ raise ConfigError, "could not load config file; msg=#{error}" if error
295
+ end
296
+
297
+ # The key-value pairs in this `priority` option are inserted into the
298
+ # config's @priority hash *after* anything listed under priority_key;
299
+ # i.e., ENV takes precendence over priority_key
300
+ if env
301
+ attrs[:priority] = remap_env(env)
302
+ end
303
+
304
+ config = new(priority_key, attrs)
305
+
306
+ opts.each do |k, v|
307
+ config[k] = v
308
+ end
309
+
310
+ config
311
+ end
312
+
313
+ def self.remap_key(key)
314
+ key = key.to_sym
315
+ legacy_keys[key] || key
316
+ end
317
+
318
+ # @api private
319
+ def self.remap_env(env)
320
+ ret = {}
321
+
322
+ return ret unless env
323
+
324
+ # Only set if it exists, we don't want to set to a nil value
325
+ if (proxy_url = Util::Proxy.detect_url(env))
326
+ ret[:proxy_url] = proxy_url
327
+ end
328
+
329
+ env.each do |k, val|
330
+ next unless k =~ /^(?:SK|SKYLIGHT)_(.+)$/
331
+ next unless (key = ENV_TO_KEY[$1])
332
+
333
+ ret[key] =
334
+ case val
335
+ when /^false$/i then false
336
+ when /^true$/i then true
337
+ when /^(nil|null)$/i then nil
338
+ when /^\d+$/ then val.to_i
339
+ when /^\d+\.\d+$/ then val.to_f
340
+ else val
341
+ end
342
+ end
343
+
344
+ ret
176
345
  end
177
346
 
178
347
  # @api private
179
348
  def validate!
180
- super
349
+ REQUIRED_KEYS.each do |k, v|
350
+ unless get(k)
351
+ raise ConfigError, "#{v} required"
352
+ end
353
+ end
354
+
355
+ log_file = self[:log_file]
356
+ alert_log_file = self[:alert_log_file]
357
+ native_log_file = self.native_log_file
358
+
359
+ check_logfile_permissions(log_file, "log_file")
360
+ check_logfile_permissions(alert_log_file, "alert_log_file")
361
+ # TODO: Support rotation interpolation in this check
362
+ check_logfile_permissions(native_log_file, "native_log_file")
181
363
 
182
364
  # TODO: Move this out of the validate! method: https://github.com/tildeio/direwolf-agent/issues/273
183
365
  # FIXME: Why not set the sockdir_path and pidfile_path explicitly?
@@ -191,6 +373,300 @@ module Skylight
191
373
  true
192
374
  end
193
375
 
376
+ def check_file_permissions(file, key)
377
+ file_root = File.dirname(file)
378
+
379
+ # Try to make the directory, don't blow up if we can't. Our writable? check will fail later.
380
+ FileUtils.mkdir_p file_root rescue nil
381
+
382
+ if File.exist?(file) && !FileTest.writable?(file)
383
+ raise ConfigError, "File `#{file}` is not writable. Please set #{key} in your config to a writable path"
384
+ end
385
+
386
+ unless FileTest.writable?(file_root)
387
+ raise ConfigError, "Directory `#{file_root}` is not writable. Please set #{key} in your config to a " \
388
+ "writable path"
389
+ end
390
+ end
391
+
392
+ def check_logfile_permissions(log_file, key)
393
+ return if log_file == "-" # STDOUT
394
+
395
+ log_file = File.expand_path(log_file, root)
396
+ check_file_permissions(log_file, key)
397
+ end
398
+
399
+ def key?(key)
400
+ key = self.class.remap_key(key)
401
+ @priority.key?(key) || @values.key?(key)
402
+ end
403
+
404
+ def get(key, default = nil)
405
+ key = self.class.remap_key(key)
406
+
407
+ return @priority[key] if @priority.key?(key)
408
+ return @values[key] if @values.key?(key)
409
+ return self.class.default_values[key] if self.class.default_values.key?(key)
410
+
411
+ if default
412
+ return default
413
+ elsif block_given?
414
+ return yield key
415
+ end
416
+
417
+ nil
418
+ end
419
+
420
+ alias [] get
421
+
422
+ def set(key, val, scope = nil)
423
+ if scope
424
+ key = [scope, key].join(".")
425
+ end
426
+
427
+ if val.is_a?(Hash)
428
+ val.each do |k, v|
429
+ set(k, v, key)
430
+ end
431
+ else
432
+ k = self.class.remap_key(key)
433
+
434
+ if (validator = self.class.validators[k])
435
+ blk, msg = validator
436
+
437
+ unless blk.call(val, self)
438
+ error_msg = "invalid value for #{k} (#{val})"
439
+ error_msg << ", #{msg}" if msg
440
+ raise ConfigError, error_msg
441
+ end
442
+ end
443
+
444
+ if @priority_regexp && k =~ @priority_regexp
445
+ @priority[$1.to_sym] = val
446
+ end
447
+
448
+ @values[k] = val
449
+ end
450
+ end
451
+
452
+ alias []= set
453
+
454
+ def send_or_get(val)
455
+ respond_to?(val) ? send(val) : get(val)
456
+ end
457
+
458
+ def duration_ms(key, default = nil)
459
+ if (v = self[key]) && v.to_s =~ /^\s*(\d+)(s|sec|ms|micros|nanos)?\s*$/
460
+ v = $1.to_i
461
+ case $2
462
+ when "ms"
463
+ v
464
+ when "micros"
465
+ v / 1_000
466
+ when "nanos"
467
+ v / 1_000_000
468
+ else # "s", "sec", nil
469
+ v * 1000
470
+ end
471
+ else
472
+ default
473
+ end
474
+ end
475
+
476
+ def to_native_env
477
+ ret = []
478
+
479
+ self.class.native_env_keys.each do |key|
480
+ value = send_or_get(key)
481
+ unless value.nil?
482
+ env_key = KEY_TO_NATIVE_ENV[key] || ENV_TO_KEY.key(key) || key.upcase
483
+ ret << "SKYLIGHT_#{env_key}" << cast_for_env(value)
484
+ end
485
+ end
486
+
487
+ ret << "SKYLIGHT_AUTHENTICATION" << authentication_with_meta
488
+ ret << "SKYLIGHT_VALIDATE_AUTHENTICATION" << "false"
489
+
490
+ ret
491
+ end
492
+
493
+ #
494
+ #
495
+ # ===== Helpers =====
496
+ #
497
+ #
498
+
499
+ def version
500
+ VERSION
501
+ end
502
+
503
+ # @api private
504
+ def gc
505
+ @gc ||= GC.new(self, get("gc.profiler", VM::GC.new))
506
+ end
507
+
508
+ # @api private
509
+ def ignored_endpoints
510
+ @ignored_endpoints ||=
511
+ begin
512
+ ignored_endpoints = get(:ignored_endpoints)
513
+
514
+ # If, for some odd reason you have a comma in your endpoint name, use the
515
+ # YML config instead.
516
+ if ignored_endpoints.is_a?(String)
517
+ ignored_endpoints = ignored_endpoints.split(/\s*,\s*/)
518
+ end
519
+
520
+ val = Array(get(:ignored_endpoint))
521
+ val.concat(Array(ignored_endpoints))
522
+ val
523
+ end
524
+ end
525
+
526
+ # @api private
527
+ def source_location_ignored_gems
528
+ @source_location_ignored_gems ||=
529
+ begin
530
+ ignored_gems = get(:source_location_ignored_gems)
531
+ if ignored_gems.is_a?(String)
532
+ ignored_gems = ignored_gems.split(/\s*,\s*/)
533
+ end
534
+
535
+ Array(ignored_gems) | DEFAULT_IGNORED_SOURCE_LOCATION_GEMS
536
+ end
537
+ end
538
+
539
+ def root
540
+ @root ||= Pathname.new(self[:root] || Dir.pwd).realpath
541
+ end
542
+
543
+ def log_level
544
+ @log_level ||=
545
+ if trace?
546
+ Logger::DEBUG
547
+ else
548
+ case get(:log_level)
549
+ when /^debug$/i then Logger::DEBUG
550
+ when /^info$/i then Logger::INFO
551
+ when /^warn$/i then Logger::WARN
552
+ when /^error$/i then Logger::ERROR
553
+ when /^fatal$/i then Logger::FATAL
554
+ else Logger::ERROR
555
+ end
556
+ end
557
+ end
558
+
559
+ def native_log_level
560
+ @native_log_level ||=
561
+ if trace?
562
+ "trace"
563
+ else
564
+ case log_level
565
+ when Logger::DEBUG then "debug"
566
+ when Logger::INFO then "info"
567
+ when Logger::WARN then "warn"
568
+ else "error"
569
+ end
570
+ end
571
+ end
572
+
573
+ def logger
574
+ @logger ||=
575
+ MUTEX.synchronize do
576
+ load_logger
577
+ end
578
+ end
579
+
580
+ def native_log_file
581
+ @native_log_file ||= get("native_log_file") do
582
+ log_file = self["log_file"]
583
+ return "-" if log_file == "-"
584
+
585
+ parts = log_file.to_s.split(".")
586
+ parts.insert(-2, "native")
587
+ parts.join(".")
588
+ end
589
+ end
590
+
591
+ attr_writer :logger, :alert_logger
592
+
593
+ def alert_logger
594
+ @alert_logger ||= MUTEX.synchronize do
595
+ unless (l = @alert_logger)
596
+ out = get(:alert_log_file)
597
+ out = Util::AlertLogger.new(load_logger) if out == "-"
598
+
599
+ l = create_logger(out, level: Logger::DEBUG)
600
+ end
601
+
602
+ l
603
+ end
604
+ end
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:")
@@ -220,7 +700,7 @@ module Skylight
220
700
 
221
701
  # This is a weird way to handle priorities
222
702
  # See https://github.com/tildeio/direwolf-agent/issues/275
223
- k = "#{environment}.#{k}" if environment
703
+ k = "#{priority_key}.#{k}" if priority_key
224
704
 
225
705
  set(k, v)
226
706
  end
@@ -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