skylight 3.1.4 → 5.3.4

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