skylight 4.3.2 → 5.0.0.beta

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 +14 -5
  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 +22 -99
  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 +110 -11
data/lib/skylight/api.rb CHANGED
@@ -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
 
data/lib/skylight/cli.rb CHANGED
@@ -60,7 +60,7 @@ module Skylight
60
60
  rescue Api::CreateFailed => e
61
61
  say "Could not create the application. Please run `bundle exec skylight doctor` for diagnostics.", :red
62
62
  say e.to_s, :yellow
63
- rescue Interrupt
63
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
64
64
  end
65
65
 
66
66
  desc "disable_dev_warning", "Disables warning about running Skylight in development mode for all local apps"
@@ -71,7 +71,8 @@ module Skylight
71
71
  say "Development mode warning disabled", :green
72
72
  end
73
73
 
74
- desc "disable_env_warning", "Disables warning about running Skylight in environments not defined in config.skylight.environments"
74
+ desc "disable_env_warning", "Disables warning about running Skylight in environments not defined in " \
75
+ "config.skylight.environments"
75
76
  def disable_env_warning
76
77
  user_config.disable_env_warning = true
77
78
  user_config.save
@@ -93,7 +94,7 @@ module Skylight
93
94
  begin
94
95
  namefile = Tempfile.new("skylight-app-name")
95
96
  # Windows appears to need double quotes for `rails runner`
96
- `rails runner "File.open('#{namefile.path}', 'w') {|f| f.write(Rails.application.class.name) rescue '' }"`
97
+ `rails runner "File.open('#{namefile.path}', 'w') {|f| f.write(Rails.application.class.name) rescue '' }"` # rubocop:disable Layout/LineLength
97
98
  name = namefile.read.split("::").first.underscore.titleize
98
99
  name = nil if name.empty?
99
100
  rescue => e
@@ -24,7 +24,8 @@ module Skylight
24
24
  say "Please update your certificates with RVM by running `rvm osx-ssl-certs update all`.", :yellow
25
25
  say "Alternatively, try setting `SKYLIGHT_FORCE_OWN_CERTS=1` in your environment.", :yellow
26
26
  else
27
- say "Please update your local certificates or try setting `SKYLIGHT_FORCE_OWN_CERTS=1` in your environment.", :yellow
27
+ say "Please update your local certificates or try setting `SKYLIGHT_FORCE_OWN_CERTS=1` in your " \
28
+ "environment.", :yellow
28
29
  end
29
30
  end
30
31
  else
@@ -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