skylight 4.2.3 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +420 -331
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +2 -8
  5. data/ERRORS.md +3 -0
  6. data/LICENSE.md +7 -17
  7. data/README.md +1 -1
  8. data/ext/extconf.rb +61 -56
  9. data/ext/libskylight.yml +8 -6
  10. data/ext/skylight_native.c +26 -100
  11. data/lib/skylight/api.rb +32 -21
  12. data/lib/skylight/cli/doctor.rb +64 -65
  13. data/lib/skylight/cli/helpers.rb +19 -19
  14. data/lib/skylight/cli/merger.rb +142 -138
  15. data/lib/skylight/cli.rb +48 -46
  16. data/lib/skylight/config.rb +640 -201
  17. data/lib/skylight/data/cacert.pem +730 -1023
  18. data/lib/skylight/deprecation.rb +17 -0
  19. data/lib/skylight/errors.rb +26 -9
  20. data/lib/skylight/extensions/source_location.rb +291 -0
  21. data/lib/skylight/extensions.rb +95 -0
  22. data/lib/skylight/formatters/http.rb +18 -0
  23. data/lib/skylight/gc.rb +99 -0
  24. data/lib/skylight/helpers.rb +81 -36
  25. data/lib/skylight/instrumenter.rb +336 -18
  26. data/lib/skylight/middleware.rb +147 -1
  27. data/lib/skylight/native.rb +60 -12
  28. data/lib/skylight/native_ext_fetcher.rb +13 -14
  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 +87 -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 +20 -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 +41 -0
  72. data/lib/skylight/normalizers.rb +157 -0
  73. data/lib/skylight/probes/action_controller.rb +52 -0
  74. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  75. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  76. data/lib/skylight/probes/action_dispatch.rb +2 -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/active_record_async.rb +96 -0
  82. data/lib/skylight/probes/delayed_job.rb +144 -0
  83. data/lib/skylight/probes/elasticsearch.rb +36 -0
  84. data/lib/skylight/probes/excon/middleware.rb +65 -0
  85. data/lib/skylight/probes/excon.rb +25 -0
  86. data/lib/skylight/probes/faraday.rb +23 -0
  87. data/lib/skylight/probes/graphql.rb +38 -0
  88. data/lib/skylight/probes/httpclient.rb +44 -0
  89. data/lib/skylight/probes/middleware.rb +135 -0
  90. data/lib/skylight/probes/mongo.rb +156 -0
  91. data/lib/skylight/probes/mongoid.rb +13 -0
  92. data/lib/skylight/probes/net_http.rb +54 -0
  93. data/lib/skylight/probes/rack_builder.rb +37 -0
  94. data/lib/skylight/probes/redis.rb +51 -0
  95. data/lib/skylight/probes/sequel.rb +29 -0
  96. data/lib/skylight/probes/sinatra.rb +66 -0
  97. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  98. data/lib/skylight/probes/tilt.rb +25 -0
  99. data/lib/skylight/probes.rb +173 -0
  100. data/lib/skylight/railtie.rb +166 -28
  101. data/lib/skylight/sidekiq.rb +47 -0
  102. data/lib/skylight/sinatra.rb +1 -1
  103. data/lib/skylight/subscriber.rb +130 -0
  104. data/lib/skylight/test.rb +147 -0
  105. data/lib/skylight/trace.rb +325 -22
  106. data/lib/skylight/user_config.rb +58 -0
  107. data/lib/skylight/util/allocation_free.rb +26 -0
  108. data/lib/skylight/util/clock.rb +57 -0
  109. data/lib/skylight/util/component.rb +22 -22
  110. data/lib/skylight/util/deploy.rb +19 -24
  111. data/lib/skylight/util/gzip.rb +20 -0
  112. data/lib/skylight/util/http.rb +106 -113
  113. data/lib/skylight/util/instrumenter_method.rb +26 -0
  114. data/lib/skylight/util/logging.rb +136 -0
  115. data/lib/skylight/util/lru_cache.rb +36 -0
  116. data/lib/skylight/util/platform.rb +3 -7
  117. data/lib/skylight/util/ssl.rb +1 -25
  118. data/lib/skylight/util.rb +12 -0
  119. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  120. data/lib/skylight/version.rb +5 -1
  121. data/lib/skylight/vm/gc.rb +60 -0
  122. data/lib/skylight.rb +201 -14
  123. metadata +134 -18
@@ -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,184 +13,333 @@ 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
+ -"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
81
117
 
82
118
  # Default values for Skylight configuration keys
83
119
  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
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
104
157
 
105
- ret[:'daemon.lib_path'] = native_path
106
- ret[:'daemon.exec_path'] = File.join(native_path, "skylightd")
158
+ ret
107
159
  end
108
-
109
- ret
110
- end
111
160
  end
112
161
 
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
162
+ REQUIRED_KEYS = {
163
+ authentication: "authentication token",
164
+ hostname: "server hostname",
165
+ auth_url: "authentication url",
166
+ validation_url: "config validation url"
167
+ }.freeze
121
168
 
122
169
  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
- ]
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
+ ]
158
214
  end
159
215
 
216
+ # Maps legacy config keys to new config keys
160
217
  def self.legacy_keys
161
- @legacy_keys ||= super.merge(
162
- 'agent.sockfile_path': :'daemon.sockdir_path',
163
- 'agent.lockfile': :'daemon.pidfile_path'
164
- )
218
+ @legacy_keys ||= { "agent.sockfile_path": :"daemon.sockdir_path", "agent.lockfile": :"daemon.pidfile_path" }
165
219
  end
166
220
 
167
221
  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
- )
222
+ @validators ||=
223
+ { "agent.interval": [->(v, _c) { v.is_a?(Integer) && v > 0 }, "must be an integer greater than 0"] }
171
224
  end
172
225
 
173
226
  # @api private
174
- def api
175
- @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
176
322
  end
177
323
 
178
324
  # @api private
179
325
  def validate!
180
- 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")
181
337
 
182
338
  # TODO: Move this out of the validate! method: https://github.com/tildeio/direwolf-agent/issues/273
183
339
  # FIXME: Why not set the sockdir_path and pidfile_path explicitly?
184
340
  # That way we don't have to keep this in sync with the Rust repo.
185
- sockdir_path = File.expand_path(self[:'daemon.sockdir_path'] || ".", root)
186
- 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)
187
343
 
188
344
  check_file_permissions(pidfile_path, "daemon.pidfile_path or daemon.sockdir_path")
189
345
  check_sockdir_permissions(sockdir_path)
@@ -191,6 +347,299 @@ module Skylight
191
347
  true
192
348
  end
193
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
+
194
643
  def validate_with_server
195
644
  res = api.validate_config
196
645
 
@@ -199,19 +648,19 @@ module Skylight
199
648
  return false
200
649
  end
201
650
 
202
- if res.error_response?
203
- warn("Unable to reach server for config validation")
204
- end
651
+ warn("Unable to reach server for config validation") if res.error_response?
205
652
 
206
653
  unless res.config_valid?
207
654
  warn("Invalid configuration") unless res.error_response?
208
- res.validation_errors.each do |k, v|
209
- warn(" #{k}: #{v}")
210
- end
655
+ res.validation_errors.each { |k, v| warn(" #{k}: #{v}") }
211
656
 
212
657
  return false if res.forbidden?
213
658
 
214
659
  corrected_config = res.corrected_config
660
+
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
+
215
664
  config_to_update = corrected_config.reject { |k, v| get(k) == v }
216
665
  unless config_to_update.empty?
217
666
  info("Updating config values:")
@@ -220,7 +669,7 @@ module Skylight
220
669
 
221
670
  # This is a weird way to handle priorities
222
671
  # See https://github.com/tildeio/direwolf-agent/issues/275
223
- k = "#{environment}.#{k}" if environment
672
+ k = "#{priority_key}.#{k}" if priority_key
224
673
 
225
674
  set(k, v)
226
675
  end
@@ -232,36 +681,33 @@ module Skylight
232
681
 
233
682
  def check_sockdir_permissions(sockdir_path)
234
683
  # 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
684
+ begin
685
+ FileUtils.mkdir_p sockdir_path
686
+ rescue StandardError
687
+ nil
688
+ end
236
689
 
237
690
  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"
691
+ raise ConfigError,
692
+ "Directory `#{sockdir_path}` is not writable. Please set daemon.sockdir_path in " \
693
+ "your config to a writable path"
239
694
  end
240
695
 
241
696
  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."
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."
243
700
  end
244
701
  end
245
702
 
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
703
  def write(path)
256
704
  FileUtils.mkdir_p(File.dirname(path))
257
705
 
258
- File.open(path, "w") do |f|
259
- f.puts <<~YAML
706
+ File.open(path, "w") { |f| f.puts <<~YAML }
260
707
  ---
261
708
  # The authentication token for the application.
262
709
  authentication: #{self[:authentication]}
263
710
  YAML
264
- end
265
711
  end
266
712
 
267
713
  #
@@ -291,45 +737,38 @@ module Skylight
291
737
  end
292
738
 
293
739
  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
- }
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
+ }
305
745
  rescue ArgumentError => e
306
- raise Core::ConfigError, e.message
746
+ raise ConfigError, e.message
307
747
  end
308
748
 
309
749
  def component
310
750
  components[:web]
311
751
  end
312
752
 
753
+ def to_json(*)
754
+ JSON.generate(as_json)
755
+ end
756
+
313
757
  def as_json(*)
314
- {
315
- config: {
316
- priority: @priority.merge(component.as_json),
317
- values: @values
318
- }
319
- }
758
+ { config: { priority: @priority.merge(component.as_json), values: @values } }
320
759
  end
321
760
 
322
761
  private
323
762
 
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
763
+ def check_nfs(path)
764
+ # Should work on most *nix, though not on OS X
765
+ `stat -f -L -c %T #{path} 2>&1`.strip == "nfs"
766
+ end
328
767
 
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
768
+ def reporting_env?
769
+ # true if env was explicitly set,
770
+ # or if we are auto-detecting via the opt-in SKYLIGHT_REPORT_RAILS_ENV=true
771
+ !!(get(:report_rails_env) || get(:env))
772
+ end
334
773
  end
335
774
  end