skylight 1.0.0.beta4 → 1.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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