skylight 4.2.3 → 5.3.0

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