skylight 1.0.0.beta4 → 1.0.0.beta5

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.
@@ -0,0 +1,32 @@
1
+ module Skylight
2
+ module CLI
3
+ module Helpers
4
+
5
+ private
6
+
7
+ # Duplicated below
8
+ def rails_rb
9
+ File.expand_path("config/application.rb")
10
+ end
11
+
12
+ def is_rails?
13
+ File.exist?(rails_rb)
14
+ end
15
+
16
+ def config
17
+ # Calling .load checks ENV variables
18
+ @config ||= Config.load
19
+ end
20
+
21
+ # Sets the output padding while executing a block and resets it.
22
+ #
23
+ def indent(count = 1, &block)
24
+ orig_padding = shell.padding
25
+ shell.padding += count
26
+ yield
27
+ shell.padding = orig_padding
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -3,26 +3,30 @@ require 'fileutils'
3
3
  require 'thread'
4
4
  require 'openssl'
5
5
  require 'erb'
6
+ require 'json'
6
7
  require 'skylight/util/deploy'
7
8
  require 'skylight/util/hostname'
8
9
  require 'skylight/util/logging'
9
10
  require 'skylight/util/platform'
10
11
  require 'skylight/util/ssl'
12
+ require 'skylight/util/proxy'
11
13
 
12
14
  module Skylight
13
15
  class Config
16
+ include Util::Logging
17
+
14
18
  # @api private
15
19
  MUTEX = Mutex.new
16
20
 
17
21
  # Map environment variable keys with Skylight configuration keys
18
22
  ENV_TO_KEY = {
19
23
  # == Authentication ==
20
- 'AUTHENTICATION' => :'authentication',
24
+ 'AUTHENTICATION' => :authentication,
21
25
 
22
26
  # == App settings ==
23
- 'ROOT' => :'root',
24
- 'HOSTNAME' => :'hostname',
25
- 'SESSION_TOKEN' => :'session_token',
27
+ 'ROOT' => :root,
28
+ 'HOSTNAME' => :hostname,
29
+ 'SESSION_TOKEN' => :session_token,
26
30
 
27
31
  # == Deploy settings ==
28
32
  'DEPLOY_ID' => :'deploy.id',
@@ -30,29 +34,30 @@ module Skylight
30
34
  'DEPLOY_DESCRIPTION' => :'deploy.description',
31
35
 
32
36
  # == Logging ==
33
- 'LOG_FILE' => :'log_file',
34
- 'LOG_LEVEL' => :'log_level',
35
- 'ALERT_LOG_FILE' => :'alert_log_file',
36
- 'LOG_SQL_PARSE_ERRORS' => :'log_sql_parse_errors',
37
+ 'LOG_FILE' => :log_file,
38
+ 'LOG_LEVEL' => :log_level,
39
+ 'ALERT_LOG_FILE' => :alert_log_file,
40
+ 'LOG_SQL_PARSE_ERRORS' => :log_sql_parse_errors,
37
41
 
38
42
  # == Proxy ==
39
- 'PROXY_URL' => :'proxy_url',
43
+ 'PROXY_URL' => :proxy_url,
40
44
 
41
45
  # == Instrumenter ==
42
- "IGNORED_ENDPOINT" => :'ignored_endpoint',
43
- "IGNORED_ENDPOINTS" => :'ignored_endpoints',
44
- "SQL_MODE" => :'sql_mode',
45
- "SEPARATE_FORMATS" => :'separate_formats',
46
+ "IGNORED_ENDPOINT" => :ignored_endpoint,
47
+ "IGNORED_ENDPOINTS" => :ignored_endpoints,
48
+ "ENABLE_SEGMENTS" => :enable_segments,
46
49
 
47
50
  # == Skylight Remote ==
48
- "AUTH_URL" => :'auth_url',
49
- "AUTH_HTTP_DEFLATE" => :'auth_http_deflate',
50
- "AUTH_HTTP_CONNECT_TIMEOUT" => :'auth_http_connect_timeout',
51
- "AUTH_HTTP_READ_TIMEOUT" => :'auth_http_read_timeout',
52
- "REPORT_URL" => :'report_url',
53
- "REPORT_HTTP_DEFLATE" => :'report_http_deflate',
54
- "REPORT_HTTP_CONNECT_TIMEOUT" => :'report_http_connect_timeout',
55
- "REPORT_HTTP_READ_TIMEOUT" => :'report_http_read_timeout',
51
+ "AUTH_URL" => :auth_url,
52
+ "APP_CREATE_URL" => :app_create_url,
53
+ "VALIDATION_URL" => :validation_url,
54
+ "AUTH_HTTP_DEFLATE" => :auth_http_deflate,
55
+ "AUTH_HTTP_CONNECT_TIMEOUT" => :auth_http_connect_timeout,
56
+ "AUTH_HTTP_READ_TIMEOUT" => :auth_http_read_timeout,
57
+ "REPORT_URL" => :report_url,
58
+ "REPORT_HTTP_DEFLATE" => :report_http_deflate,
59
+ "REPORT_HTTP_CONNECT_TIMEOUT" => :report_http_connect_timeout,
60
+ "REPORT_HTTP_READ_TIMEOUT" => :report_http_read_timeout,
56
61
 
57
62
  # == Native agent settings ==
58
63
  #
@@ -89,15 +94,16 @@ module Skylight
89
94
 
90
95
  # Default values for Skylight configuration keys
91
96
  DEFAULTS = {
92
- :'auth_url' => 'https://auth.skylight.io/agent',
93
- :'sql_mode' => 'rust',
94
- :'daemon.lazy_start' => true,
95
- :'log_file' => '-'.freeze,
96
- :'log_level' => 'INFO'.freeze,
97
- :'alert_log_file' => '-'.freeze,
98
- :'log_sql_parse_errors' => false,
99
- :'separate_formats' => false,
100
- :'hostname' => Util::Hostname.default_hostname,
97
+ :auth_url => 'https://auth.skylight.io/agent',
98
+ :app_create_url => 'https://www.skylight.io/apps',
99
+ :validation_url => 'https://auth.skylight.io/agent/config',
100
+ :'daemon.lazy_start' => true,
101
+ :log_file => '-'.freeze,
102
+ :log_level => 'INFO'.freeze,
103
+ :alert_log_file => '-'.freeze,
104
+ :log_sql_parse_errors => false,
105
+ :enable_segments => false,
106
+ :hostname => Util::Hostname.default_hostname,
101
107
  :'heroku.dyno_info_path' => '/etc/heroku/dyno'
102
108
  }
103
109
 
@@ -116,25 +122,30 @@ module Skylight
116
122
  DEFAULTS.freeze
117
123
 
118
124
  REQUIRED = {
119
- :'authentication' => "authentication token",
120
- :'hostname' => "server hostname",
121
- :'auth_url' => "authentication url" }
125
+ authentication: "authentication token",
126
+ hostname: "server hostname",
127
+ auth_url: "authentication url",
128
+ validation_url: "config validation url" }
129
+
130
+ SERVER_VALIDATE = [
131
+ :enable_segments
132
+ ]
122
133
 
123
134
  NATIVE_ENV = [
124
- :'version',
125
- :'root',
126
- :'hostname',
127
- :'deploy_id',
128
- :'session_token',
129
- :'proxy_url',
130
- :'auth_url',
131
- :'auth_http_deflate',
132
- :'auth_http_connect_timeout',
133
- :'auth_http_read_timeout',
134
- :'report_url',
135
- :'report_http_deflate',
136
- :'report_http_connect_timeout',
137
- :'report_http_read_timeout',
135
+ :version,
136
+ :root,
137
+ :hostname,
138
+ :deploy_id,
139
+ :session_token,
140
+ :proxy_url,
141
+ :auth_url,
142
+ :auth_http_deflate,
143
+ :auth_http_connect_timeout,
144
+ :auth_http_read_timeout,
145
+ :report_url,
146
+ :report_http_deflate,
147
+ :report_http_connect_timeout,
148
+ :report_http_read_timeout,
138
149
  :'daemon.lazy_start',
139
150
  :'daemon.exec_path',
140
151
  :'daemon.lib_path',
@@ -245,7 +256,10 @@ module Skylight
245
256
 
246
257
  return ret unless env
247
258
 
248
- ret[:proxy_url] = detect_proxy_url(env)
259
+ # Only set if it exists, we don't want to set to a nil value
260
+ if proxy_url = Util::Proxy.detect_url(env)
261
+ ret[:proxy_url] = proxy_url
262
+ end
249
263
 
250
264
  env.each do |k, val|
251
265
  # Support deprecated SK_ key prefix
@@ -267,28 +281,19 @@ module Skylight
267
281
  ret
268
282
  end
269
283
 
270
- def self.detect_proxy_url(env)
271
- if u = env['HTTP_PROXY'] || env['http_proxy']
272
- u = "http://#{u}" unless u =~ %r[://]
273
- u
274
- end
275
- end
276
-
277
- # @api private
278
- def skip_validation?
279
- !!get(:skip_validation)
284
+ def api
285
+ @api ||= Api.new(self)
280
286
  end
281
287
 
282
288
  # @api private
283
289
  def validate!
284
- return true if skip_validation?
285
-
286
290
  REQUIRED.each do |k, v|
287
291
  unless get(k)
288
292
  raise ConfigError, "#{v} required"
289
293
  end
290
294
  end
291
295
 
296
+ # TODO: Move this out of the validate! method: https://github.com/tildeio/direwolf-agent/issues/273
292
297
  # FIXME: Why not set the sockdir_path and pidfile_path explicitly?
293
298
  # That way we don't have to keep this in sync with the Rust repo.
294
299
  sockdir_path = self[:'daemon.sockdir_path'] || File.expand_path('.')
@@ -299,6 +304,50 @@ module Skylight
299
304
  true
300
305
  end
301
306
 
307
+ def validate_with_server
308
+ res = api.validate_config
309
+
310
+ unless res.token_valid?
311
+ warn("Invalid authentication token")
312
+ return false
313
+ end
314
+
315
+ if res.is_error_response?
316
+ warn("Unable to reach server for config validation")
317
+ end
318
+
319
+ unless res.config_valid?
320
+ warn("Invalid configuration") unless res.is_error_response?
321
+ if errors = res.validation_errors
322
+ errors.each do |k,v|
323
+ warn(" #{k} #{v}")
324
+ end
325
+ end
326
+
327
+ corrected_config = res.corrected_config
328
+ unless corrected_config
329
+ # Use defaults if no corrected config is available. This will happen if the request failed.
330
+ corrected_config = Hash[SERVER_VALIDATE.map{|k| [k, DEFAULTS[k]] }]
331
+ end
332
+
333
+ config_to_update = corrected_config.select{|k,v| get(k) != v }
334
+ unless config_to_update.empty?
335
+ info("Updating config values:")
336
+ config_to_update.each do |k,v|
337
+ info(" setting #{k} to #{v}")
338
+
339
+ # This is a weird way to handle priorities
340
+ # See https://github.com/tildeio/direwolf-agent/issues/275
341
+ k = "#{environment}.#{k}" if environment
342
+
343
+ set(k, v)
344
+ end
345
+ end
346
+ end
347
+
348
+ return true
349
+ end
350
+
302
351
  def check_permissions(pidfile, sockdir_path)
303
352
  pidfile_root = File.dirname(pidfile)
304
353
 
@@ -401,13 +450,23 @@ module Skylight
401
450
  end
402
451
  end
403
452
 
453
+ def to_json
454
+ JSON.generate(
455
+ config: {
456
+ priority: @priority,
457
+ values: @values
458
+ }
459
+ )
460
+ end
461
+
404
462
  def to_native_env
405
463
  ret = []
406
464
 
407
465
  ret << "SKYLIGHT_AUTHENTICATION" << authentication_with_deploy
408
466
 
409
467
  NATIVE_ENV.each do |key|
410
- if value = send_or_get(key)
468
+ value = send_or_get(key)
469
+ unless value.nil?
411
470
  env_key = ENV_TO_KEY.key(key) || key.upcase
412
471
  ret << "SKYLIGHT_#{env_key}" << cast_for_env(value)
413
472
  end
@@ -454,7 +513,7 @@ authentication: #{self[:authentication]}
454
513
  def ignored_endpoints
455
514
  @ignored_endpoints ||=
456
515
  begin
457
- ignored_endpoints = get(:'ignored_endpoints')
516
+ ignored_endpoints = get(:ignored_endpoints)
458
517
 
459
518
  # If, for some odd reason you have a comma in your endpoint name, use the
460
519
  # YML config instead.
@@ -462,7 +521,7 @@ authentication: #{self[:authentication]}
462
521
  ignored_endpoints = ignored_endpoints.split(/\s*,\s*/)
463
522
  end
464
523
 
465
- val = Array(get(:'ignored_endpoint'))
524
+ val = Array(get(:ignored_endpoint))
466
525
  val.concat(Array(ignored_endpoints))
467
526
  val
468
527
  end
@@ -501,7 +560,7 @@ authentication: #{self[:authentication]}
501
560
  begin
502
561
  MUTEX.synchronize do
503
562
  unless l = @alert_logger
504
- out = get(:'alert_log_file')
563
+ out = get(:alert_log_file)
505
564
 
506
565
  if out == '-'
507
566
  out = Util::AlertLogger.new(load_logger)
@@ -523,12 +582,16 @@ authentication: #{self[:authentication]}
523
582
  @alert_logger = logger
524
583
  end
525
584
 
585
+ def on_heroku?
586
+ File.exist?(get(:'heroku.dyno_info_path'))
587
+ end
588
+
526
589
  def deploy
527
590
  @deploy ||= Util::Deploy.build(self)
528
591
  end
529
592
 
530
- def separate_formats?
531
- !!get(:separate_formats)
593
+ def enable_segments?
594
+ !!get(:enable_segments)
532
595
  end
533
596
 
534
597
  private
@@ -540,7 +603,7 @@ authentication: #{self[:authentication]}
540
603
 
541
604
  def load_logger
542
605
  unless l = @logger
543
- out = get(:'log_file')
606
+ out = get(:log_file)
544
607
  out = STDOUT if out == '-'
545
608
 
546
609
  unless IO === out
@@ -550,7 +613,7 @@ authentication: #{self[:authentication]}
550
613
 
551
614
  l = Logger.new(out)
552
615
  l.level =
553
- case get(:'log_level')
616
+ case get(:log_level)
554
617
  when /^debug$/i then Logger::DEBUG
555
618
  when /^info$/i then Logger::INFO
556
619
  when /^warn$/i then Logger::WARN
@@ -7,7 +7,9 @@ module Skylight
7
7
  # Load the native agent
8
8
  require 'skylight/native'
9
9
 
10
- if defined?(Rails)
10
+ # Specifically check for Railtie since we've had at least one case of a
11
+ # customer having Rails defined without having all of Rails loaded.
12
+ if defined?(Rails::Railtie)
11
13
  require 'skylight/railtie'
12
14
  end
13
15
 
@@ -107,10 +107,9 @@ module Skylight
107
107
  end
108
108
 
109
109
  t { "starting instrumenter" }
110
- @config.validate!
111
110
 
112
- unless validate_authentication
113
- warn "invalid authentication token"
111
+ unless config.validate_with_server
112
+ log_error "invalid config"
114
113
  return
115
114
  end
116
115
 
@@ -120,13 +119,13 @@ module Skylight
120
119
  return
121
120
  end
122
121
 
123
- @config.gc.enable
122
+ config.gc.enable
124
123
  @subscriber.register!
125
124
 
126
125
  self
127
126
 
128
127
  rescue Exception => e
129
- log_error "failed to start instrumenter; msg=%s; config=%s", e.message, @config.inspect
128
+ log_error "failed to start instrumenter; msg=%s; config=%s", e.message, config.inspect
130
129
  t { e.backtrace.join("\n") }
131
130
  nil
132
131
  end
@@ -252,33 +251,7 @@ module Skylight
252
251
  end
253
252
 
254
253
  def ignore?(trace)
255
- @config.ignored_endpoints.include?(trace.endpoint)
256
- end
257
-
258
- # Validates that the provided authentication token is valid. This is done
259
- # by issuing a request for a session token and checking the response
260
- def validate_authentication
261
- # If a session token is specified, don't bother attempting to validate
262
- if config[:session_token]
263
- debug "using pre-generated session token"
264
- true
265
- else
266
- api = Api.new(config)
267
- api.authentication = config[:authentication]
268
-
269
- case res = api.validate_authentication
270
- when :ok
271
- true
272
- when :invalid
273
- false
274
- when :unknown
275
- warn "unable to validate authentication token"
276
- true
277
- else
278
- error "[BUG] unexpected validate_token result; res=%s", res
279
- true
280
- end
281
- end
254
+ config.ignored_endpoints.include?(trace.endpoint)
282
255
  end
283
256
 
284
257
  end
@@ -16,11 +16,19 @@ module Skylight
16
16
  end
17
17
 
18
18
  def normalize_after(trace, span, name, payload)
19
- return unless config.separate_formats?
19
+ return unless config.enable_segments?
20
20
 
21
- format = [payload[:format], payload[:variant]].compact.flatten.join('+')
22
- unless format.empty?
23
- trace.endpoint += "<sk-format>#{format}</sk-format>"
21
+ # Show 'error' if there's an unhandled exception or if the status is 4xx or 5xx
22
+ if payload[:exception] || payload[:status].to_s =~ /^[45]/
23
+ segment = "error"
24
+ # We won't have a rendered_format if it's a `head` outside of a `respond_to` block.
25
+ elsif payload[:rendered_format]
26
+ # We only show the variant if we actually have a format
27
+ segment = [payload[:rendered_format], payload[:variant]].compact.flatten.join('+')
28
+ end
29
+
30
+ if segment
31
+ trace.endpoint += "<sk-segment>#{segment}</sk-segment>"
24
32
  end
25
33
  end
26
34