skylight 4.3.2 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +399 -336
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +2 -8
  5. data/LICENSE.md +7 -17
  6. data/README.md +1 -1
  7. data/ext/extconf.rb +45 -56
  8. data/ext/libskylight.yml +10 -6
  9. data/ext/skylight_native.c +22 -99
  10. data/lib/skylight.rb +201 -14
  11. data/lib/skylight/api.rb +32 -21
  12. data/lib/skylight/cli.rb +48 -46
  13. data/lib/skylight/cli/doctor.rb +62 -63
  14. data/lib/skylight/cli/helpers.rb +19 -19
  15. data/lib/skylight/cli/merger.rb +142 -138
  16. data/lib/skylight/config.rb +634 -199
  17. data/lib/skylight/deprecation.rb +17 -0
  18. data/lib/skylight/errors.rb +23 -9
  19. data/lib/skylight/extensions.rb +95 -0
  20. data/lib/skylight/extensions/source_location.rb +291 -0
  21. data/lib/skylight/formatters/http.rb +18 -0
  22. data/lib/skylight/gc.rb +99 -0
  23. data/lib/skylight/helpers.rb +81 -36
  24. data/lib/skylight/instrumenter.rb +336 -18
  25. data/lib/skylight/middleware.rb +134 -1
  26. data/lib/skylight/native.rb +60 -12
  27. data/lib/skylight/native_ext_fetcher.rb +13 -14
  28. data/lib/skylight/normalizers.rb +157 -0
  29. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  30. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  31. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  32. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  33. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  34. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  35. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  36. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  37. data/lib/skylight/normalizers/active_job/perform.rb +90 -0
  38. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  39. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  40. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  41. data/lib/skylight/normalizers/active_storage.rb +28 -0
  42. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  43. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  53. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  54. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  55. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  56. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  57. data/lib/skylight/normalizers/default.rb +24 -0
  58. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  59. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  60. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  61. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  62. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  64. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  65. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  66. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  67. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  68. data/lib/skylight/normalizers/render.rb +79 -0
  69. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  70. data/lib/skylight/normalizers/shrine.rb +32 -0
  71. data/lib/skylight/normalizers/sql.rb +45 -0
  72. data/lib/skylight/probes.rb +173 -0
  73. data/lib/skylight/probes/action_controller.rb +52 -0
  74. data/lib/skylight/probes/action_dispatch.rb +2 -0
  75. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  76. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  77. data/lib/skylight/probes/action_view.rb +42 -0
  78. data/lib/skylight/probes/active_job.rb +27 -0
  79. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  80. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  81. data/lib/skylight/probes/delayed_job.rb +144 -0
  82. data/lib/skylight/probes/elasticsearch.rb +36 -0
  83. data/lib/skylight/probes/excon.rb +25 -0
  84. data/lib/skylight/probes/excon/middleware.rb +65 -0
  85. data/lib/skylight/probes/faraday.rb +23 -0
  86. data/lib/skylight/probes/graphql.rb +38 -0
  87. data/lib/skylight/probes/httpclient.rb +44 -0
  88. data/lib/skylight/probes/middleware.rb +135 -0
  89. data/lib/skylight/probes/mongo.rb +156 -0
  90. data/lib/skylight/probes/mongoid.rb +13 -0
  91. data/lib/skylight/probes/net_http.rb +54 -0
  92. data/lib/skylight/probes/redis.rb +51 -0
  93. data/lib/skylight/probes/sequel.rb +29 -0
  94. data/lib/skylight/probes/sinatra.rb +66 -0
  95. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  96. data/lib/skylight/probes/tilt.rb +25 -0
  97. data/lib/skylight/railtie.rb +157 -27
  98. data/lib/skylight/sidekiq.rb +47 -0
  99. data/lib/skylight/subscriber.rb +108 -0
  100. data/lib/skylight/test.rb +151 -0
  101. data/lib/skylight/trace.rb +325 -22
  102. data/lib/skylight/user_config.rb +58 -0
  103. data/lib/skylight/util.rb +12 -0
  104. data/lib/skylight/util/allocation_free.rb +26 -0
  105. data/lib/skylight/util/clock.rb +57 -0
  106. data/lib/skylight/util/component.rb +22 -22
  107. data/lib/skylight/util/deploy.rb +16 -21
  108. data/lib/skylight/util/gzip.rb +20 -0
  109. data/lib/skylight/util/http.rb +106 -113
  110. data/lib/skylight/util/instrumenter_method.rb +26 -0
  111. data/lib/skylight/util/logging.rb +136 -0
  112. data/lib/skylight/util/lru_cache.rb +36 -0
  113. data/lib/skylight/util/platform.rb +1 -5
  114. data/lib/skylight/util/ssl.rb +1 -25
  115. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  116. data/lib/skylight/version.rb +5 -1
  117. data/lib/skylight/vm/gc.rb +60 -0
  118. metadata +126 -13
@@ -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,178 +13,323 @@ 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
+ # == App settings ==
27
+ -"ROOT" => :root,
28
+ -"HOSTNAME" => :hostname,
29
+ -"SESSION_TOKEN" => :session_token,
30
+ # == Component settings ==
31
+ -"ENV" => :env,
32
+ -"COMPONENT" => :component,
33
+ -"REPORT_RAILS_ENV" => :report_rails_env,
34
+ # == Deploy settings ==
35
+ -"DEPLOY_ID" => :'deploy.id',
36
+ -"DEPLOY_GIT_SHA" => :'deploy.git_sha',
37
+ -"DEPLOY_DESCRIPTION" => :'deploy.description',
38
+ # == Logging ==
39
+ -"LOG_FILE" => :log_file,
40
+ -"LOG_LEVEL" => :log_level,
41
+ -"ALERT_LOG_FILE" => :alert_log_file,
42
+ -"NATIVE_LOG_FILE" => :native_log_file,
43
+ -"NATIVE_LOG_LEVEL" => :native_log_level,
44
+ -"LOG_SQL_PARSE_ERRORS" => :log_sql_parse_errors,
45
+ # == Proxy ==
46
+ -"PROXY_URL" => :proxy_url,
47
+ # == Instrumenter ==
48
+ -"ENABLE_SEGMENTS" => :enable_segments,
49
+ -"ENABLE_SIDEKIQ" => :enable_sidekiq,
50
+ -"IGNORED_ENDPOINT" => :ignored_endpoint,
51
+ -"IGNORED_ENDPOINTS" => :ignored_endpoints,
52
+ -"SINATRA_ROUTE_PREFIXES" => :sinatra_route_prefixes,
53
+ -"ENABLE_SOURCE_LOCATIONS" => :enable_source_locations,
54
+ # == Max Span Handling ==
55
+ -"REPORT_MAX_SPANS_EXCEEDED" => :report_max_spans_exceeded,
56
+ -"PRUNE_LARGE_TRACES" => :prune_large_traces,
57
+ # == Skylight Remote ==
58
+ -"AUTH_URL" => :auth_url,
59
+ -"APP_CREATE_URL" => :app_create_url,
60
+ -"MERGES_URL" => :merges_url,
61
+ -"VALIDATION_URL" => :validation_url,
62
+ -"AUTH_HTTP_DEFLATE" => :auth_http_deflate,
63
+ -"AUTH_HTTP_CONNECT_TIMEOUT" => :auth_http_connect_timeout,
64
+ -"AUTH_HTTP_READ_TIMEOUT" => :auth_http_read_timeout,
65
+ -"REPORT_URL" => :report_url,
66
+ -"REPORT_HTTP_DEFLATE" => :report_http_deflate,
67
+ -"REPORT_HTTP_CONNECT_TIMEOUT" => :report_http_connect_timeout,
68
+ -"REPORT_HTTP_READ_TIMEOUT" => :report_http_read_timeout,
69
+ -"REPORT_HTTP_DISABLED" => :report_http_disabled,
70
+ # == Native agent settings ==
71
+ #
72
+ -"LAZY_START" => :'daemon.lazy_start',
73
+ -"DAEMON_EXEC_PATH" => :'daemon.exec_path',
74
+ -"DAEMON_LIB_PATH" => :'daemon.lib_path',
75
+ -"PIDFILE_PATH" => :'daemon.pidfile_path',
76
+ -"SOCKDIR_PATH" => :'daemon.sockdir_path',
77
+ -"BATCH_QUEUE_DEPTH" => :'daemon.batch_queue_depth',
78
+ -"BATCH_SAMPLE_SIZE" => :'daemon.batch_sample_size',
79
+ -"BATCH_FLUSH_INTERVAL" => :'daemon.batch_flush_interval',
80
+ -"DAEMON_TICK_INTERVAL" => :'daemon.tick_interval',
81
+ -"DAEMON_LOCK_CHECK_INTERVAL" => :'daemon.lock_check_interval',
82
+ -"DAEMON_INACTIVITY_TIMEOUT" => :'daemon.inactivity_timeout',
83
+ -"CLIENT_MAX_TRIES" => :'daemon.max_connect_tries',
84
+ -"CLIENT_CONN_TRY_WIN" => :'daemon.connect_try_window',
85
+ -"MAX_PRESPAWN_JITTER" => :'daemon.max_prespawn_jitter',
86
+ -"DAEMON_WAIT_TIMEOUT" => :'daemon.wait_timeout',
87
+ -"CLIENT_CHECK_INTERVAL" => :'daemon.client_check_interval',
88
+ -"CLIENT_QUEUE_DEPTH" => :'daemon.client_queue_depth',
89
+ -"CLIENT_WRITE_TIMEOUT" => :'daemon.client_write_timeout',
90
+ -"SSL_CERT_PATH" => :'daemon.ssl_cert_path',
91
+ -"ENABLE_TCP" => :'daemon.enable_tcp',
92
+ -"TCP_PORT" => :'daemon.tcp_port',
93
+ # == Legacy env vars ==
94
+ #
95
+ -"AGENT_LOCKFILE" => :'agent.lockfile',
96
+ -"AGENT_SOCKFILE_PATH" => :'agent.sockfile_path',
97
+ # == User config settings ==
98
+ -"USER_CONFIG_PATH" => :user_config_path,
99
+ # == Heroku settings ==
100
+ -"HEROKU_DYNO_INFO_PATH" => :'heroku.dyno_info_path',
101
+ # == Source Location ==
102
+ -"SOURCE_LOCATION_IGNORED_GEMS" => :source_location_ignored_gems,
103
+ -"SOURCE_LOCATION_CACHE_SIZE" => :source_location_cache_size
104
+ }.freeze
105
+
106
+ KEY_TO_NATIVE_ENV = {
107
+ # We use different log files for native and Ruby, but the native code doesn't know this
108
+ native_log_file: "LOG_FILE",
109
+ native_log_level: "LOG_LEVEL"
110
+ }.freeze
111
+
112
+ SERVER_VALIDATE = %i[].freeze
113
+
114
+ DEFAULT_IGNORED_SOURCE_LOCATION_GEMS = [-"skylight", -"activesupport", -"activerecord"].freeze
81
115
 
82
116
  # Default values for Skylight configuration keys
83
117
  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
- )
96
-
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
101
-
102
- if Skylight.native?
103
- native_path = Skylight.libskylight_path
118
+ @default_values ||=
119
+ begin
120
+ ret = {
121
+ # URLs
122
+ auth_url: -"https://auth.skylight.io/agent",
123
+ app_create_url: -"https://www.skylight.io/apps",
124
+ merges_url: -"https://www.skylight.io/merges",
125
+ validation_url: -"https://auth.skylight.io/agent/config",
126
+ # Logging
127
+ log_file: -"-",
128
+ log_level: -"INFO",
129
+ alert_log_file: -"-",
130
+ log_sql_parse_errors: true,
131
+ native_log_level: -"warn",
132
+ # Features
133
+ enable_segments: true,
134
+ enable_sidekiq: false,
135
+ sinatra_route_prefixes: false,
136
+ enable_source_locations: true,
137
+ # Deploys
138
+ 'heroku.dyno_info_path': -"/etc/heroku/dyno",
139
+ report_rails_env: true,
140
+ # Daemon
141
+ 'daemon.lazy_start': true,
142
+ hostname: Util::Hostname.default_hostname,
143
+ report_max_spans_exceeded: false,
144
+ prune_large_traces: true
145
+ }
146
+
147
+ ret[:'daemon.ssl_cert_path'] = Util::SSL.ca_cert_file_or_default unless Util::Platform::OS == -"darwin"
148
+
149
+ if Skylight.native?
150
+ native_path = Skylight.libskylight_path
151
+
152
+ ret[:'daemon.lib_path'] = native_path
153
+ ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
154
+ end
104
155
 
105
- ret[:'daemon.lib_path'] = native_path
106
- ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
156
+ ret
107
157
  end
108
-
109
- ret
110
- end
111
158
  end
112
159
 
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
160
+ REQUIRED_KEYS = {
161
+ authentication: "authentication token",
162
+ hostname: "server hostname",
163
+ auth_url: "authentication url",
164
+ validation_url: "config validation url"
165
+ }.freeze
121
166
 
122
167
  def self.native_env_keys
123
- @native_env_keys ||= super + %i[
124
- version
125
- root
126
- hostname
127
- session_token
128
- auth_url
129
- auth_http_deflate
130
- auth_http_connect_timeout
131
- auth_http_read_timeout
132
- report_url
133
- report_http_deflate
134
- report_http_connect_timeout
135
- report_http_read_timeout
136
- report_http_disabled
137
- daemon.lazy_start
138
- daemon.exec_path
139
- daemon.lib_path
140
- daemon.pidfile_path
141
- daemon.sockdir_path
142
- daemon.batch_queue_depth
143
- daemon.batch_sample_size
144
- daemon.batch_flush_interval
145
- daemon.tick_interval
146
- daemon.sanity_check_interval
147
- daemon.inactivity_timeout
148
- daemon.max_connect_tries
149
- daemon.connect_try_window
150
- daemon.max_prespawn_jitter
151
- daemon.wait_timeout
152
- daemon.client_check_interval
153
- daemon.client_queue_depth
154
- daemon.client_write_timeout
155
- daemon.ssl_cert_path
156
- daemon.ssl_cert_dir
157
- ]
168
+ @native_env_keys ||=
169
+ %i[
170
+ native_log_level
171
+ native_log_file
172
+ log_sql_parse_errors
173
+ version
174
+ root
175
+ proxy_url
176
+ hostname
177
+ session_token
178
+ auth_url
179
+ auth_http_deflate
180
+ auth_http_connect_timeout
181
+ auth_http_read_timeout
182
+ report_url
183
+ report_http_deflate
184
+ report_http_connect_timeout
185
+ report_http_read_timeout
186
+ report_http_disabled
187
+ daemon.lazy_start
188
+ daemon.exec_path
189
+ daemon.lib_path
190
+ daemon.pidfile_path
191
+ daemon.sockdir_path
192
+ daemon.batch_queue_depth
193
+ daemon.batch_sample_size
194
+ daemon.batch_flush_interval
195
+ daemon.tick_interval
196
+ daemon.lock_check_interval
197
+ daemon.inactivity_timeout
198
+ daemon.max_connect_tries
199
+ daemon.connect_try_window
200
+ daemon.max_prespawn_jitter
201
+ daemon.wait_timeout
202
+ daemon.client_check_interval
203
+ daemon.client_queue_depth
204
+ daemon.client_write_timeout
205
+ daemon.ssl_cert_path
206
+ daemon.ssl_cert_dir
207
+ daemon.enable_tcp
208
+ daemon.tcp_port
209
+ ]
158
210
  end
159
211
 
212
+ # Maps legacy config keys to new config keys
160
213
  def self.legacy_keys
161
- @legacy_keys ||= super.merge(
162
- 'agent.sockfile_path': :'daemon.sockdir_path',
163
- 'agent.lockfile': :'daemon.pidfile_path'
164
- )
214
+ @legacy_keys ||= { 'agent.sockfile_path': :'daemon.sockdir_path', 'agent.lockfile': :'daemon.pidfile_path' }
165
215
  end
166
216
 
167
217
  def self.validators
168
- @validators ||= super.merge(
169
- 'agent.interval': [->(v, _c) { v.is_a?(Integer) && v > 0 }, "must be an integer greater than 0"]
170
- )
218
+ @validators ||=
219
+ { 'agent.interval': [->(v, _c) { v.is_a?(Integer) && v > 0 }, "must be an integer greater than 0"] }
171
220
  end
172
221
 
173
222
  # @api private
174
- def api
175
- @api ||= Api.new(self)
223
+ attr_reader :priority_key
224
+
225
+ # @api private
226
+ def initialize(*args)
227
+ attrs = {}
228
+
229
+ attrs = args.pop.dup if args.last.is_a?(Hash)
230
+
231
+ @values = {}
232
+ @priority = {}
233
+ @priority_regexp = nil
234
+ @alert_logger = nil
235
+ @logger = nil
236
+
237
+ p = attrs.delete(:priority)
238
+
239
+ if (@priority_key = args[0])
240
+ @priority_regexp = /^#{Regexp.escape(priority_key)}\.(.+)$/
241
+ end
242
+
243
+ attrs.each { |k, v| self[k] = v }
244
+
245
+ p&.each { |k, v| @priority[self.class.remap_key(k)] = v }
246
+ end
247
+
248
+ def self.load(opts = {}, env = ENV)
249
+ attrs = {}
250
+ path = opts.delete(:file)
251
+ priority_key = opts.delete(:priority_key)
252
+ priority_key ||= opts[:env] # if a priority_key is not given, use env if available
253
+
254
+ if path
255
+ error = nil
256
+ begin
257
+ attrs =
258
+ YAML.safe_load(ERB.new(File.read(path)).result, permitted_classes: [], permitted_symbols: [], aliases: true)
259
+ error = "empty file" unless attrs
260
+ error = "invalid format" if attrs && !attrs.is_a?(Hash)
261
+ rescue Exception => e
262
+ error = e.message
263
+ end
264
+
265
+ raise ConfigError, "could not load config file; msg=#{error}" if error
266
+ end
267
+
268
+ # The key-value pairs in this `priority` option are inserted into the
269
+ # config's @priority hash *after* anything listed under priority_key;
270
+ # i.e., ENV takes precendence over priority_key
271
+ attrs[:priority] = remap_env(env) if env
272
+
273
+ config = new(priority_key, attrs)
274
+
275
+ opts.each { |k, v| config[k] = v }
276
+
277
+ config
278
+ end
279
+
280
+ def self.remap_key(key)
281
+ key = key.to_sym
282
+ legacy_keys[key] || key
283
+ end
284
+
285
+ # @api private
286
+ def self.remap_env(env)
287
+ ret = {}
288
+
289
+ return ret unless env
290
+
291
+ # Only set if it exists, we don't want to set to a nil value
292
+ if (proxy_url = Util::Proxy.detect_url(env))
293
+ ret[:proxy_url] = proxy_url
294
+ end
295
+
296
+ env.each do |k, val|
297
+ next unless k =~ /^(?:SK|SKYLIGHT)_(.+)$/
298
+ next unless (key = ENV_TO_KEY[$1])
299
+
300
+ ret[key] =
301
+ case val
302
+ when /^false$/i
303
+ false
304
+ when /^true$/i
305
+ true
306
+ when /^(nil|null)$/i
307
+ nil
308
+ when /^\d+$/
309
+ val.to_i
310
+ when /^\d+\.\d+$/
311
+ val.to_f
312
+ else
313
+ val
314
+ end
315
+ end
316
+
317
+ ret
176
318
  end
177
319
 
178
320
  # @api private
179
321
  def validate!
180
- super
322
+ REQUIRED_KEYS.each { |k, v| raise ConfigError, "#{v} required" unless get(k) }
323
+
324
+ log_file = self[:log_file]
325
+ alert_log_file = self[:alert_log_file]
326
+ native_log_file = self.native_log_file
327
+
328
+ check_logfile_permissions(log_file, "log_file")
329
+ check_logfile_permissions(alert_log_file, "alert_log_file")
330
+
331
+ # TODO: Support rotation interpolation in this check
332
+ check_logfile_permissions(native_log_file, "native_log_file")
181
333
 
182
334
  # TODO: Move this out of the validate! method: https://github.com/tildeio/direwolf-agent/issues/273
183
335
  # FIXME: Why not set the sockdir_path and pidfile_path explicitly?
@@ -191,6 +343,299 @@ module Skylight
191
343
  true
192
344
  end
193
345
 
346
+ def check_file_permissions(file, key)
347
+ file_root = File.dirname(file)
348
+
349
+ # Try to make the directory, don't blow up if we can't. Our writable? check will fail later.
350
+ begin
351
+ FileUtils.mkdir_p file_root
352
+ rescue StandardError
353
+ nil
354
+ end
355
+
356
+ if File.exist?(file) && !FileTest.writable?(file)
357
+ raise ConfigError, "File `#{file}` is not writable. Please set #{key} in your config to a writable path"
358
+ end
359
+
360
+ unless FileTest.writable?(file_root)
361
+ raise ConfigError,
362
+ "Directory `#{file_root}` is not writable. Please set #{key} in your config to a " \
363
+ "writable path"
364
+ end
365
+ end
366
+
367
+ def check_logfile_permissions(log_file, key)
368
+ return if log_file == "-" # STDOUT
369
+
370
+ log_file = File.expand_path(log_file, root)
371
+ check_file_permissions(log_file, key)
372
+ end
373
+
374
+ def key?(key)
375
+ key = self.class.remap_key(key)
376
+ @priority.key?(key) || @values.key?(key)
377
+ end
378
+
379
+ def get(key, default = nil)
380
+ key = self.class.remap_key(key)
381
+
382
+ return @priority[key] if @priority.key?(key)
383
+ return @values[key] if @values.key?(key)
384
+ return self.class.default_values[key] if self.class.default_values.key?(key)
385
+
386
+ if default
387
+ return default
388
+ elsif block_given?
389
+ return yield key
390
+ end
391
+
392
+ nil
393
+ end
394
+
395
+ alias [] get
396
+
397
+ def set(key, val, scope = nil)
398
+ key = [scope, key].join(".") if scope
399
+
400
+ if val.is_a?(Hash)
401
+ val.each { |k, v| set(k, v, key) }
402
+ else
403
+ k = self.class.remap_key(key)
404
+
405
+ if (validator = self.class.validators[k])
406
+ blk, msg = validator
407
+
408
+ unless blk.call(val, self)
409
+ error_msg = "invalid value for #{k} (#{val})"
410
+ error_msg << ", #{msg}" if msg
411
+ raise ConfigError, error_msg
412
+ end
413
+ end
414
+
415
+ @priority[$1.to_sym] = val if @priority_regexp && k =~ @priority_regexp
416
+
417
+ @values[k] = val
418
+ end
419
+ end
420
+
421
+ alias []= set
422
+
423
+ def send_or_get(val)
424
+ respond_to?(val) ? send(val) : get(val)
425
+ end
426
+
427
+ def duration_ms(key, default = nil)
428
+ if (v = self[key]) && v.to_s =~ /^\s*(\d+)(s|sec|ms|micros|nanos)?\s*$/
429
+ v = $1.to_i
430
+ case $2
431
+ when "ms"
432
+ v
433
+ when "micros"
434
+ v / 1_000
435
+ when "nanos"
436
+ v / 1_000_000
437
+ else
438
+ # "s", "sec", nil
439
+ v * 1000
440
+ end
441
+ else
442
+ default
443
+ end
444
+ end
445
+
446
+ def to_native_env
447
+ ret = []
448
+
449
+ self
450
+ .class
451
+ .native_env_keys
452
+ .each do |key|
453
+ value = send_or_get(key)
454
+ unless value.nil?
455
+ env_key = KEY_TO_NATIVE_ENV[key] || ENV_TO_KEY.key(key) || key.upcase
456
+ ret << "SKYLIGHT_#{env_key}" << cast_for_env(value)
457
+ end
458
+ end
459
+
460
+ ret << "SKYLIGHT_AUTHENTICATION" << authentication_with_meta
461
+ ret << "SKYLIGHT_VALIDATE_AUTHENTICATION" << "false"
462
+
463
+ ret
464
+ end
465
+
466
+ #
467
+ #
468
+ # ===== Helpers =====
469
+ #
470
+ #
471
+
472
+ def version
473
+ VERSION
474
+ end
475
+
476
+ # @api private
477
+ def gc
478
+ @gc ||= GC.new(self, get("gc.profiler", VM::GC.new))
479
+ end
480
+
481
+ # @api private
482
+ def ignored_endpoints
483
+ @ignored_endpoints ||=
484
+ begin
485
+ ignored_endpoints = get(:ignored_endpoints)
486
+
487
+ # If, for some odd reason you have a comma in your endpoint name, use the
488
+ # YML config instead.
489
+ ignored_endpoints = ignored_endpoints.split(/\s*,\s*/) if ignored_endpoints.is_a?(String)
490
+
491
+ val = Array(get(:ignored_endpoint))
492
+ val.concat(Array(ignored_endpoints))
493
+ val
494
+ end
495
+ end
496
+
497
+ # @api private
498
+ def source_location_ignored_gems
499
+ @source_location_ignored_gems ||=
500
+ begin
501
+ ignored_gems = get(:source_location_ignored_gems)
502
+ ignored_gems = ignored_gems.split(/\s*,\s*/) if ignored_gems.is_a?(String)
503
+
504
+ Array(ignored_gems) | DEFAULT_IGNORED_SOURCE_LOCATION_GEMS
505
+ end
506
+ end
507
+
508
+ def root
509
+ @root ||= Pathname.new(self[:root] || Dir.pwd).realpath
510
+ end
511
+
512
+ def log_level
513
+ @log_level ||=
514
+ if trace?
515
+ Logger::DEBUG
516
+ else
517
+ case get(:log_level)
518
+ when /^debug$/i
519
+ Logger::DEBUG
520
+ when /^info$/i
521
+ Logger::INFO
522
+ when /^warn$/i
523
+ Logger::WARN
524
+ when /^error$/i
525
+ Logger::ERROR
526
+ when /^fatal$/i
527
+ Logger::FATAL
528
+ else
529
+ Logger::ERROR
530
+ end
531
+ end
532
+ end
533
+
534
+ def native_log_level
535
+ get(:native_log_level).to_s.downcase
536
+ end
537
+
538
+ def logger
539
+ @logger ||= MUTEX.synchronize { load_logger }
540
+ end
541
+
542
+ def native_log_file
543
+ @native_log_file ||=
544
+ get("native_log_file") do
545
+ log_file = self["log_file"]
546
+ return "-" if log_file == "-"
547
+
548
+ parts = log_file.to_s.split(".")
549
+ parts.insert(-2, "native")
550
+ parts.join(".")
551
+ end
552
+ end
553
+
554
+ attr_writer :logger, :alert_logger
555
+
556
+ def alert_logger
557
+ @alert_logger ||=
558
+ MUTEX.synchronize do
559
+ unless (l = @alert_logger)
560
+ out = get(:alert_log_file)
561
+ out = Util::AlertLogger.new(load_logger) if out == "-"
562
+
563
+ l = create_logger(out, level: Logger::DEBUG)
564
+ end
565
+
566
+ l
567
+ end
568
+ end
569
+
570
+ def enable_segments?
571
+ !!get(:enable_segments)
572
+ end
573
+
574
+ def enable_sidekiq?
575
+ !!get(:enable_sidekiq)
576
+ end
577
+
578
+ def sinatra_route_prefixes?
579
+ !!get(:sinatra_route_prefixes)
580
+ end
581
+
582
+ def enable_source_locations?
583
+ !!get(:enable_source_locations)
584
+ end
585
+
586
+ def user_config
587
+ @user_config ||= UserConfig.new(self)
588
+ end
589
+
590
+ def on_heroku?
591
+ File.exist?(get(:'heroku.dyno_info_path'))
592
+ end
593
+
594
+ private
595
+
596
+ def create_logger(out, level: :info)
597
+ if out.is_a?(String)
598
+ out = File.expand_path(out, root)
599
+
600
+ # May be redundant since we also do this in the permissions check
601
+ FileUtils.mkdir_p(File.dirname(out))
602
+ end
603
+
604
+ Logger.new(out, progname: "Skylight", level: level)
605
+ rescue StandardError
606
+ Logger.new($stdout, progname: "Skylight", level: level)
607
+ end
608
+
609
+ def load_logger
610
+ unless (l = @logger)
611
+ out = get(:log_file)
612
+ out = $stdout if out == "-"
613
+ l = create_logger(out, level: log_level)
614
+ end
615
+
616
+ l
617
+ end
618
+
619
+ def cast_for_env(val)
620
+ case val
621
+ when true
622
+ "true"
623
+ when false
624
+ "false"
625
+ when nil
626
+ "nil"
627
+ else
628
+ val.to_s
629
+ end
630
+ end
631
+
632
+ public
633
+
634
+ # @api private
635
+ def api
636
+ @api ||= Api.new(self)
637
+ end
638
+
194
639
  def validate_with_server
195
640
  res = api.validate_config
196
641
 
@@ -199,19 +644,19 @@ module Skylight
199
644
  return false
200
645
  end
201
646
 
202
- if res.error_response?
203
- warn("Unable to reach server for config validation")
204
- end
647
+ warn("Unable to reach server for config validation") if res.error_response?
205
648
 
206
649
  unless res.config_valid?
207
650
  warn("Invalid configuration") unless res.error_response?
208
- res.validation_errors.each do |k, v|
209
- warn(" #{k}: #{v}")
210
- end
651
+ res.validation_errors.each { |k, v| warn(" #{k}: #{v}") }
211
652
 
212
653
  return false if res.forbidden?
213
654
 
214
655
  corrected_config = res.corrected_config
656
+
657
+ # Use defaults if no corrected config is available. This will happen if the request failed.
658
+ corrected_config ||= SERVER_VALIDATE.map { |k| [k, self.class.default_values.fetch(k)] }.to_h
659
+
215
660
  config_to_update = corrected_config.reject { |k, v| get(k) == v }
216
661
  unless config_to_update.empty?
217
662
  info("Updating config values:")
@@ -220,7 +665,7 @@ module Skylight
220
665
 
221
666
  # This is a weird way to handle priorities
222
667
  # See https://github.com/tildeio/direwolf-agent/issues/275
223
- k = "#{environment}.#{k}" if environment
668
+ k = "#{priority_key}.#{k}" if priority_key
224
669
 
225
670
  set(k, v)
226
671
  end
@@ -232,36 +677,33 @@ module Skylight
232
677
 
233
678
  def check_sockdir_permissions(sockdir_path)
234
679
  # Try to make the directory, don't blow up if we can't. Our writable? check will fail later.
235
- FileUtils.mkdir_p sockdir_path rescue nil
680
+ begin
681
+ FileUtils.mkdir_p sockdir_path
682
+ rescue StandardError
683
+ nil
684
+ end
236
685
 
237
686
  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"
687
+ raise ConfigError,
688
+ "Directory `#{sockdir_path}` is not writable. Please set daemon.sockdir_path in " \
689
+ "your config to a writable path"
239
690
  end
240
691
 
241
692
  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."
693
+ raise ConfigError,
694
+ "Directory `#{sockdir_path}` is an NFS mount and will not allow sockets. Please set " \
695
+ "daemon.sockdir_path in your config to a non-NFS path."
243
696
  end
244
697
  end
245
698
 
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
699
  def write(path)
256
700
  FileUtils.mkdir_p(File.dirname(path))
257
701
 
258
- File.open(path, "w") do |f|
259
- f.puts <<~YAML
702
+ File.open(path, "w") { |f| f.puts <<~YAML }
260
703
  ---
261
704
  # The authentication token for the application.
262
705
  authentication: #{self[:authentication]}
263
706
  YAML
264
- end
265
707
  end
266
708
 
267
709
  #
@@ -291,45 +733,38 @@ module Skylight
291
733
  end
292
734
 
293
735
  def components
294
- @components ||= {
295
- web: Util::Component.new(
296
- get(:env),
297
- Util::Component::DEFAULT_NAME
298
- ),
299
- worker: Util::Component.new(
300
- get(:env),
301
- get(:component) || get(:worker_component),
302
- force_worker: true
303
- )
304
- }
736
+ @components ||=
737
+ {
738
+ web: Util::Component.new(get(:env), Util::Component::DEFAULT_NAME),
739
+ worker: Util::Component.new(get(:env), get(:component) || get(:worker_component), force_worker: true)
740
+ }
305
741
  rescue ArgumentError => e
306
- raise Core::ConfigError, e.message
742
+ raise ConfigError, e.message
307
743
  end
308
744
 
309
745
  def component
310
746
  components[:web]
311
747
  end
312
748
 
749
+ def to_json(*)
750
+ JSON.generate(as_json)
751
+ end
752
+
313
753
  def as_json(*)
314
- {
315
- config: {
316
- priority: @priority.merge(component.as_json),
317
- values: @values
318
- }
319
- }
754
+ { config: { priority: @priority.merge(component.as_json), values: @values } }
320
755
  end
321
756
 
322
757
  private
323
758
 
324
- def check_nfs(path)
325
- # Should work on most *nix, though not on OS X
326
- `stat -f -L -c %T #{path} 2>&1`.strip == "nfs"
327
- end
759
+ def check_nfs(path)
760
+ # Should work on most *nix, though not on OS X
761
+ `stat -f -L -c %T #{path} 2>&1`.strip == "nfs"
762
+ end
328
763
 
329
- def reporting_env?
330
- # true if env was explicitly set,
331
- # or if we are auto-detecting via the opt-in SKYLIGHT_REPORT_RAILS_ENV=true
332
- !!(get(:report_rails_env) || get(:env))
333
- end
764
+ def reporting_env?
765
+ # true if env was explicitly set,
766
+ # or if we are auto-detecting via the opt-in SKYLIGHT_REPORT_RAILS_ENV=true
767
+ !!(get(:report_rails_env) || get(:env))
768
+ end
334
769
  end
335
770
  end