skylight 3.1.4 → 5.3.4

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