skylight 4.3.2 → 5.1.1

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 (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