skylight 0.0.16 → 0.1.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -19
  3. data/bin/skylight +0 -2
  4. data/lib/skylight.rb +52 -42
  5. data/lib/skylight/api.rb +34 -0
  6. data/lib/skylight/cli.rb +68 -57
  7. data/lib/skylight/compat.rb +19 -5
  8. data/lib/skylight/config.rb +219 -98
  9. data/lib/skylight/gc.rb +109 -0
  10. data/lib/skylight/instrumenter.rb +53 -70
  11. data/lib/skylight/messages.rb +19 -0
  12. data/lib/skylight/messages/annotation.rb +13 -0
  13. data/lib/skylight/messages/base.rb +24 -0
  14. data/lib/skylight/messages/batch.rb +11 -0
  15. data/lib/skylight/messages/endpoint.rb +12 -0
  16. data/lib/skylight/messages/event.rb +12 -0
  17. data/lib/skylight/messages/hello.rb +53 -0
  18. data/lib/skylight/messages/span.rb +21 -0
  19. data/lib/skylight/messages/trace.rb +162 -0
  20. data/lib/skylight/middleware.rb +2 -4
  21. data/lib/skylight/normalizers.rb +89 -0
  22. data/lib/skylight/normalizers/default.rb +22 -0
  23. data/lib/skylight/normalizers/process_action.rb +19 -0
  24. data/lib/skylight/normalizers/render_collection.rb +14 -0
  25. data/lib/skylight/normalizers/render_partial.rb +14 -0
  26. data/lib/skylight/normalizers/render_template.rb +14 -0
  27. data/lib/skylight/{normalize → normalizers}/send_file.rb +15 -15
  28. data/lib/skylight/normalizers/sql.rb +25 -0
  29. data/lib/skylight/railtie.rb +21 -41
  30. data/lib/skylight/subscriber.rb +29 -19
  31. data/lib/skylight/util/clock.rb +8 -21
  32. data/lib/skylight/util/http.rb +93 -46
  33. data/lib/skylight/util/logging.rb +66 -0
  34. data/lib/skylight/util/queue.rb +7 -3
  35. data/lib/skylight/util/task.rb +154 -0
  36. data/lib/skylight/{compat → vendor/active_support}/notifications.rb +56 -24
  37. data/lib/skylight/{compat → vendor/active_support}/notifications/fanout.rb +19 -26
  38. data/lib/skylight/{compat → vendor/active_support}/notifications/instrumenter.rb +25 -18
  39. data/lib/skylight/vendor/active_support/per_thread_registry.rb +52 -0
  40. data/lib/skylight/vendor/beefcake.rb +256 -0
  41. data/lib/skylight/vendor/beefcake/buffer.rb +112 -0
  42. data/lib/skylight/vendor/beefcake/decode.rb +107 -0
  43. data/lib/skylight/vendor/beefcake/encode.rb +115 -0
  44. data/lib/skylight/vendor/{highline.rb → cli/highline.rb} +0 -0
  45. data/lib/skylight/vendor/{highline → cli/highline}/color_scheme.rb +0 -0
  46. data/lib/skylight/vendor/{highline → cli/highline}/compatibility.rb +0 -0
  47. data/lib/skylight/vendor/{highline → cli/highline}/import.rb +0 -0
  48. data/lib/skylight/vendor/{highline → cli/highline}/menu.rb +0 -0
  49. data/lib/skylight/vendor/{highline → cli/highline}/question.rb +0 -0
  50. data/lib/skylight/vendor/{highline → cli/highline}/simulate.rb +0 -0
  51. data/lib/skylight/vendor/{highline → cli/highline}/string_extensions.rb +0 -0
  52. data/lib/skylight/vendor/{highline → cli/highline}/style.rb +0 -0
  53. data/lib/skylight/vendor/{highline → cli/highline}/system_extensions.rb +0 -0
  54. data/lib/skylight/vendor/{thor.rb → cli/thor.rb} +0 -0
  55. data/lib/skylight/vendor/{thor → cli/thor}/actions.rb +0 -0
  56. data/lib/skylight/vendor/{thor → cli/thor}/actions/create_file.rb +0 -0
  57. data/lib/skylight/vendor/{thor → cli/thor}/actions/create_link.rb +0 -0
  58. data/lib/skylight/vendor/{thor → cli/thor}/actions/directory.rb +0 -0
  59. data/lib/skylight/vendor/{thor → cli/thor}/actions/empty_directory.rb +0 -0
  60. data/lib/skylight/vendor/{thor → cli/thor}/actions/file_manipulation.rb +0 -0
  61. data/lib/skylight/vendor/{thor → cli/thor}/actions/inject_into_file.rb +0 -0
  62. data/lib/skylight/vendor/{thor → cli/thor}/base.rb +0 -0
  63. data/lib/skylight/vendor/{thor → cli/thor}/command.rb +0 -0
  64. data/lib/skylight/vendor/{thor → cli/thor}/core_ext/hash_with_indifferent_access.rb +0 -0
  65. data/lib/skylight/vendor/{thor → cli/thor}/core_ext/io_binary_read.rb +0 -0
  66. data/lib/skylight/vendor/{thor → cli/thor}/core_ext/ordered_hash.rb +0 -0
  67. data/lib/skylight/vendor/{thor → cli/thor}/error.rb +0 -0
  68. data/lib/skylight/vendor/{thor → cli/thor}/group.rb +0 -0
  69. data/lib/skylight/vendor/{thor → cli/thor}/invocation.rb +0 -0
  70. data/lib/skylight/vendor/{thor → cli/thor}/parser.rb +0 -0
  71. data/lib/skylight/vendor/{thor → cli/thor}/parser/argument.rb +0 -0
  72. data/lib/skylight/vendor/{thor → cli/thor}/parser/arguments.rb +0 -0
  73. data/lib/skylight/vendor/{thor → cli/thor}/parser/option.rb +0 -0
  74. data/lib/skylight/vendor/{thor → cli/thor}/parser/options.rb +0 -0
  75. data/lib/skylight/vendor/{thor → cli/thor}/rake_compat.rb +0 -0
  76. data/lib/skylight/vendor/{thor → cli/thor}/runner.rb +0 -0
  77. data/lib/skylight/vendor/{thor → cli/thor}/shell.rb +0 -0
  78. data/lib/skylight/vendor/{thor → cli/thor}/shell/basic.rb +0 -0
  79. data/lib/skylight/vendor/{thor → cli/thor}/shell/color.rb +0 -0
  80. data/lib/skylight/vendor/{thor → cli/thor}/shell/html.rb +0 -0
  81. data/lib/skylight/vendor/{thor → cli/thor}/util.rb +0 -0
  82. data/lib/skylight/vendor/{thor → cli/thor}/version.rb +0 -0
  83. data/lib/skylight/vendor/thread_safe.rb +126 -0
  84. data/lib/skylight/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  85. data/lib/skylight/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  86. data/lib/skylight/version.rb +2 -1
  87. data/lib/skylight/worker.rb +12 -154
  88. data/lib/skylight/worker/builder.rb +72 -0
  89. data/lib/skylight/worker/collector.rb +124 -0
  90. data/lib/skylight/worker/connection.rb +77 -0
  91. data/lib/skylight/worker/embedded.rb +6 -0
  92. data/lib/skylight/worker/server.rb +307 -0
  93. data/lib/skylight/worker/standalone.rb +356 -0
  94. metadata +89 -77
  95. data/lib/skylight/connection.rb +0 -25
  96. data/lib/skylight/json_proto.rb +0 -88
  97. data/lib/skylight/normalize.rb +0 -63
  98. data/lib/skylight/normalize/default.rb +0 -17
  99. data/lib/skylight/normalize/process_action.rb +0 -17
  100. data/lib/skylight/normalize/render_collection.rb +0 -11
  101. data/lib/skylight/normalize/render_partial.rb +0 -14
  102. data/lib/skylight/normalize/render_template.rb +0 -13
  103. data/lib/skylight/normalize/sql.rb +0 -26
  104. data/lib/skylight/normalize/start_processing.rb +0 -12
  105. data/lib/skylight/sanity_checker.rb +0 -73
  106. data/lib/skylight/trace.rb +0 -160
  107. data/lib/skylight/util/atomic.rb +0 -73
  108. data/lib/skylight/util/bytes.rb +0 -40
  109. data/lib/skylight/util/ewma.rb +0 -32
  110. data/lib/skylight/util/uuid.rb +0 -33
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 82650226a057fc3b8b8595ac4fc760edaf56c656
4
+ data.tar.gz: f50d0c8f8e3d22d915664760f7eba80aece2fbae
5
+ SHA512:
6
+ metadata.gz: 99a7f5225e962df3c946c6440454a1892c1401d19feebee8cb3698a048c4f3a26980d2db77e99092f55023020ec6f85b88146ffd0aa799c03e4b3f1880c969c6
7
+ data.tar.gz: 0b40b0d1f2c08ee5872f6891f7b889900e96ec70beb6c938fe2016c35e07c8271e901f7b6d1e5c1919418a963698027fddbad7e2fe63d78ef2c13953da7b85d6
data/README.md CHANGED
@@ -1,20 +1,4 @@
1
- # Direwolf Agent
1
+ # Skylight Ruby Agent
2
2
 
3
- Collect instrumentation from a rails application and send it off to the
4
- direwolf servers.
5
-
6
- ## Traces
7
-
8
- An conceptual overview of a trace.
9
-
10
- * Trace ID (UUID)
11
- * Tiers
12
- * Name
13
- * Spans (max 256 unique per tier)
14
- * Category (multi-level, example: cache.redis)
15
- * Description
16
- * Annotations
17
-
18
- ## TODO
19
-
20
- * Cap the max number of spans per trace (2048k?)
3
+ Instrument your ruby application and send the data to the Skylight
4
+ servers.
data/bin/skylight CHANGED
@@ -1,5 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require "skylight/cli"
4
-
5
3
  Skylight::CLI.start(ARGV)
data/lib/skylight.rb CHANGED
@@ -1,11 +1,49 @@
1
- require 'thread'
1
+ require 'rbconfig'
2
2
  require 'socket'
3
- require 'openssl'
4
- require 'net/http'
5
- require 'active_support/notifications'
3
+ require 'skylight/version'
6
4
 
7
5
  module Skylight
8
- class Error < RuntimeError; end
6
+ TRACE_ENV_KEY = 'SK_ENABLE_TRACE_LOGS'.freeze
7
+ STANDALONE_ENV_KEY = 'SK_STANDALONE'.freeze
8
+ STANDALONE_ENV_VAL = 'server'.freeze
9
+
10
+ def self.daemon?
11
+ ENV[STANDALONE_ENV_KEY] == STANDALONE_ENV_VAL
12
+ end
13
+
14
+ unless daemon?
15
+ require 'active_support/notifications'
16
+ require 'skylight/compat' # Require after AS::N
17
+ end
18
+
19
+ autoload :Api, 'skylight/api'
20
+ autoload :CLI, 'skylight/cli'
21
+ autoload :Config, 'skylight/config'
22
+ autoload :GC, 'skylight/gc'
23
+ autoload :Instrumenter, 'skylight/instrumenter'
24
+ autoload :Messages, 'skylight/messages'
25
+ autoload :Normalizers, 'skylight/normalizers'
26
+ autoload :Subscriber, 'skylight/subscriber'
27
+ autoload :Worker, 'skylight/worker'
28
+
29
+ module Util
30
+ autoload :Clock, 'skylight/util/clock'
31
+ autoload :Gzip, 'skylight/util/gzip'
32
+ autoload :HTTP, 'skylight/util/http'
33
+ autoload :Logging, 'skylight/util/logging'
34
+ autoload :Queue, 'skylight/util/queue'
35
+ autoload :Task, 'skylight/util/task'
36
+ autoload :UniformSample, 'skylight/util/uniform_sample'
37
+ end
38
+
39
+ # ==== Vendor ====
40
+ autoload :Beefcake, 'skylight/vendor/beefcake'
41
+
42
+ # ==== Exceptions ====
43
+ class IpcProtoError < RuntimeError; end
44
+ class WorkerStateError < RuntimeError; end
45
+ class ConfigError < RuntimeError; end
46
+ class TraceError < RuntimeError; end
9
47
 
10
48
  TIERS = %w(
11
49
  app
@@ -14,42 +52,14 @@ module Skylight
14
52
  noise
15
53
  other)
16
54
 
17
- # First require all util files
18
- require 'skylight/util/atomic'
19
- require 'skylight/util/bytes'
20
- require 'skylight/util/clock'
21
- require 'skylight/util/ewma'
22
- require 'skylight/util/gzip'
23
- require 'skylight/util/http'
24
- require 'skylight/util/queue'
25
- require 'skylight/util/uniform_sample'
26
- require 'skylight/util/uuid'
27
-
28
- # Then require the rest
29
- require 'skylight/sanity_checker'
30
- require 'skylight/compat'
31
- require 'skylight/config'
32
- require 'skylight/instrumenter'
33
- require 'skylight/middleware'
34
- require 'skylight/normalize'
35
- require 'skylight/json_proto'
36
- require 'skylight/subscriber'
37
- require 'skylight/trace'
38
- require 'skylight/worker'
39
-
40
- # Some helper functions
41
- def self.current_endpoint=(name)
42
- if t = Trace.current
43
- t.endpoint = name
44
- end
45
-
46
- name
47
- rescue
48
- nil
49
- ensure
50
- end
51
- end
55
+ RUBYBIN = File.join(
56
+ RbConfig::CONFIG['bindir'],
57
+ "#{RbConfig::CONFIG['ruby_install_name']}#{RbConfig::CONFIG['EXEEXT']}")
58
+
59
+ # Called by the standalone agent
60
+ Worker::Server.boot if daemon?
52
61
 
53
- if defined?(Rails)
54
- require 'skylight/railtie'
62
+ if defined?(Rails)
63
+ require 'skylight/railtie'
64
+ end
55
65
  end
@@ -0,0 +1,34 @@
1
+ module Skylight
2
+ class Api
3
+ include Util::Logging
4
+
5
+ attr_reader :config, :http
6
+
7
+ def initialize(config, service = :accounts)
8
+ @config = config
9
+ @http = Util::HTTP.new(config, service)
10
+ end
11
+
12
+ def authentication
13
+ @http.authentication
14
+ end
15
+
16
+ def authentication=(token)
17
+ @http.authentication = token
18
+ end
19
+
20
+ def login(email, password)
21
+ res = http.get('/me', 'X-Email' => email, 'X-Password' => password)
22
+
23
+ if res && res.success?
24
+ res.get('me.authentication_token')
25
+ end
26
+ end
27
+
28
+ def create_app(name)
29
+ res = @http.post('/apps', { app: { name: name }})
30
+ res if res.success?
31
+ end
32
+
33
+ end
34
+ end
data/lib/skylight/cli.rb CHANGED
@@ -1,92 +1,103 @@
1
- # Use vendored thor / highline
2
- $:.unshift File.expand_path('../vendor', __FILE__)
1
+ $:.unshift File.expand_path('../vendor/cli', __FILE__)
3
2
 
4
- require "skylight"
5
- require "thor"
6
- require "highline"
7
- require "active_support/inflector"
3
+ require 'skylight'
4
+ require 'thor'
5
+ require 'yaml'
6
+ require 'highline'
7
+ require 'active_support/inflector'
8
8
 
9
9
  module Skylight
10
10
  class CLI < Thor
11
+
11
12
  desc "setup", "Sets up a new app"
12
13
  def setup
13
- if !SanityChecker.new.smoke_test(File.expand_path("config/skylight.yml"))
14
+ if File.exist?(config_path)
14
15
  say "Your app is already on Skylight. http://www.skylight.io", :green
15
16
  return
16
17
  end
17
18
 
18
- token = load_credentials
19
- config = http_config
20
-
21
- response = Util::HTTP.new(config).create_app(token, app_name)["app"]
22
-
23
- config.app_id = response["id"]
24
- config.authentication_token = response["token"]
19
+ api.authentication = load_credentials
25
20
 
26
- config.yaml_file = File.expand_path("config/skylight.yml")
21
+ unless res = api.create_app(app_name)
22
+ say "Could not create the application", :red
23
+ return
24
+ end
27
25
 
28
- config.save
26
+ config[:application] = res.get('app.id')
27
+ config[:authentication] = res.get('app.token')
28
+ config.write(config_path)
29
29
 
30
30
  say "Congratulations. Your application is on Skylight! http://www.skylight.io", :green
31
+ rescue Interrupt
31
32
  end
32
33
 
33
34
  private
34
- def user_settings
35
- File.expand_path("~/.skylight")
36
- end
37
35
 
38
- def http_config
39
- @http_config ||= Config.new do |c|
40
- c.host = "www.skylight.io"
41
- c.port = 443
42
- c.ssl = true
43
- c.deflate = false
44
- end
36
+ def app_name
37
+ @app_name ||=
38
+ begin
39
+ if File.exist?("config/application.rb")
40
+ require "./config/application"
41
+ Rails.application.class.name.split("::").first.underscore.humanize
42
+ else
43
+ File.basename(File.expand_path('.')).humanize
44
+ end
45
+ end
45
46
  end
46
47
 
47
48
  def load_credentials
48
- if credentials?
49
- return YAML.load_file(user_settings)["token"]
50
- end
49
+ load_credentials_from_file || login
50
+ end
51
51
 
52
- token = nil
52
+ def login
53
+ 10.times do
54
+ email = highline.ask("Email: ")
55
+ password = highline.ask("Password: ") { |q| q.echo = "*" }
53
56
 
54
- loop do
55
- h = HighLine.new
56
- email = h.ask("Email: ")
57
- password = h.ask("Password: ") { |q| q.echo = "*" }
57
+ if token = api.login(email, password)
58
+ # Write the token
59
+ FileUtils.mkdir_p(File.dirname(credentials_path))
60
+ File.open(credentials_path, 'w') do |f|
61
+ f.puts YAML.dump('token' => token)
62
+ end
58
63
 
59
- response = Util::HTTP.new(http_config).auth(email, password)
60
- if response["authenticated"] == false
61
- say "Sorry. That email and password was invalid. Please try again", :red
62
- puts
63
- else
64
- token = response["token"]
65
- break
64
+ return token
66
65
  end
67
- end
68
66
 
69
- File.open(user_settings, "w") do |file|
70
- file.puts YAML.dump("token" => token)
67
+ say "Sorry. That email and password was invalid. Please try again", :red
68
+ puts
71
69
  end
72
70
 
73
- token
71
+ say "Could not login", :red
72
+ return
74
73
  end
75
74
 
76
- def credentials?
77
- !SanityChecker.new.user_credentials(user_settings)
75
+ def load_credentials_from_file
76
+ return unless File.exist?(credentials_path)
77
+ return unless yaml = YAML.load_file(credentials_path)
78
+ yaml['token']
78
79
  end
79
80
 
80
- def app_name
81
- @app_name ||=
82
- begin
83
- if File.exist?("config/application.rb")
84
- require "./config/application"
85
- Rails.application.class.name.split("::").first.underscore.humanize
86
- else
87
- File.basename(File.expand_path('.')).humanize
88
- end
89
- end
81
+ def config_path
82
+ File.expand_path('config/skylight.yml')
83
+ end
84
+
85
+ def credentials_path
86
+ return nil unless ENV['HOME']
87
+ File.expand_path(".skylight", ENV['HOME'])
88
+ end
89
+
90
+ def api
91
+ @api ||= Api.new(config)
90
92
  end
93
+
94
+ def highline
95
+ @highline ||= HighLine.new
96
+ end
97
+
98
+ def config
99
+ @config ||= Config.new
100
+ end
101
+
91
102
  end
92
103
  end
@@ -1,9 +1,23 @@
1
1
  module Skylight
2
- # Catch-up out of date software
3
- #
4
- # TODO: Add smarter detection
5
- if true
6
- require 'skylight/compat/notifications'
2
+ # Ensure the version of AS:N being used is recent enough
3
+ begin
4
+ # Attempt to reference an internal class
5
+ ActiveSupport::Notifications::Fanout::Subscribers
6
+ rescue NameError
7
+ # If the class is missing, require our vendored AS::N
8
+ require 'skylight/vendor/active_support/notifications'
7
9
  end
10
+ end
8
11
 
12
+ if defined?(ActiveSupport::Notifications::Fanout::Subscribers::Evented)
13
+ # Handle early RCs of rails 4.0
14
+ class ActiveSupport::Notifications::Fanout::Subscribers::Evented
15
+ unless method_defined?(:publish)
16
+ def publish(name, *args)
17
+ if @delegate.respond_to?(:publish)
18
+ @delegate.publish name, *args
19
+ end
20
+ end
21
+ end
22
+ end
9
23
  end
@@ -1,147 +1,268 @@
1
1
  require 'yaml'
2
- require 'logger'
3
2
  require 'fileutils'
3
+ require 'logger'
4
4
 
5
5
  module Skylight
6
6
  class Config
7
- class Normalizer < Struct.new(:view_paths)
8
- end
9
7
 
10
- # Stub out the GC Profiler in cases when it is not requested
11
- GC_PROFILER_STUB = Struct.new(
12
- :enable,
13
- :disable,
14
- :clear,
15
- :total_time).
16
- new(nil, nil, nil, 0)
17
-
18
- class << self
19
- def load_from_yaml(path, env=ENV)
20
- new do |config|
21
- data = YAML.load_file(path)
22
-
23
- data.each do |key, value|
24
- apply_config(config, key, value)
25
- end
8
+ # Map environment variable keys with Skylight configuration keys
9
+ ENV_TO_KEY = {
10
+ 'SK_LOG' => :'log',
11
+ 'SK_LOG_LEVEL' => :'log_level',
12
+ 'SK_APPLICATION' => :'application',
13
+ 'SK_AUTHENTICATION' => :'authentication',
14
+ 'SK_AGENT_INTERVAL' => :'agent.interval',
15
+ 'SK_AGENT_KEEPALIVE' => :'agent.keepalive',
16
+ 'SK_AGENT_SAMPLE_SIZE' => :'agent.sample',
17
+ 'SK_AGENT_SOCKFILE_PATH' => :'agent.sockfile_path',
18
+ 'SK_REPORT_HOST' => :'report.host',
19
+ 'SK_REPORT_PORT' => :'report.port',
20
+ 'SK_REPORT_SSL' => :'report.ssl',
21
+ 'SK_REPORT_DEFLATE' => :'report.deflate',
22
+ 'SK_ACCOUNTS_HOST' => :'accounts.host',
23
+ 'SK_ACCOUNTS_PORT' => :'accounts.port',
24
+ 'SK_ACCOUNTS_SSL' => :'accounts.ssl',
25
+ 'SK_ACCOUNTS_DEFLATE' => :'accounts.deflate' }
26
+
27
+ # Default values for Skylight configuration keys
28
+ DEFAULTS = {
29
+ :'log' => '-'.freeze,
30
+ :'log_level' => 'INFO'.freeze,
31
+ :'agent.keepalive' => 60,
32
+ :'agent.interval' => 5,
33
+ :'agent.sample' => 200,
34
+ :'report.host' => 'agent.skylight.io'.freeze,
35
+ :'report.port' => 443,
36
+ :'report.ssl' => true,
37
+ :'report.deflate' => true,
38
+ :'accounts.host' => 'www.skylight.io'.freeze,
39
+ :'accounts.port' => 443,
40
+ :'accounts.ssl' => true,
41
+ :'accounts.deflate' => false }.freeze
42
+
43
+ REQUIRED = {
44
+ :'authentication' => "authentication token",
45
+ :'report.host' => "skylight remote host",
46
+ :'report.port' => "skylight remote port" }
47
+
48
+ VALIDATORS = {
49
+ :'agent.interval' => lambda { |v, c| Integer === v && v > 0 }
50
+ }
26
51
 
27
- config.yaml_file = path
52
+ def self.load(path = nil, environment = nil, env = ENV)
53
+ attrs = {}
54
+ version = nil
28
55
 
29
- apply_env(config, env)
56
+ if path
57
+ begin
58
+ attrs = YAML.load_file(path)
59
+ rescue Exception => e
60
+ raise ConfigError, "could not load config file; msg=#{e.message}"
30
61
  end
62
+
63
+ version = File.mtime(path).to_i
64
+ end
65
+
66
+ if env
67
+ attrs[:priority] = remap_env(env)
31
68
  end
32
69
 
33
- def load_from_env(opts={}, env=ENV)
34
- new(opts) do |config|
35
- apply_env(config, env)
70
+ new(environment, attrs)
71
+ end
72
+
73
+ def self.load_from_env(env = ENV)
74
+ self.load(nil, nil, env)
75
+ end
76
+
77
+ def self.remap_env(env)
78
+ ret = {}
79
+
80
+ env.each do |k, val|
81
+ if key = ENV_TO_KEY[k]
82
+ ret[key] =
83
+ case val
84
+ when /^false$/i then false
85
+ when /^true$/i then true
86
+ when /^(nil|null)$/i then nil
87
+ when /^\d+$/ then val.to_i
88
+ when /^\d+\.\d+$/ then val.to_f
89
+ else val
90
+ end
36
91
  end
92
+ end if env
93
+
94
+ ret
95
+ end
96
+
97
+ attr_reader :environment
98
+
99
+ def initialize(*args)
100
+ attrs = {}
101
+
102
+ if Hash === args.last
103
+ attrs = args.pop.dup
37
104
  end
38
105
 
39
- private
106
+ @values = {}
107
+ @priority = {}
108
+ @regexp = nil
109
+
110
+ p = attrs.delete(:priority)
40
111
 
41
- def apply_env(config, env)
42
- env.each do |key, value|
43
- name = normalize_env(key)
44
- apply_config(config, name, value) if name
112
+ if @environment = args[0]
113
+ @regexp = /^#{Regexp.escape(@environment)}\.(.+)$/
114
+ end
115
+
116
+ attrs.each do |k, v|
117
+ self[k] = v
118
+ end
119
+
120
+ if p
121
+ p.each do |k, v|
122
+ @priority[k.to_sym] = v
45
123
  end
46
124
  end
125
+ end
47
126
 
48
- def normalize_env(key)
49
- match = key.match(/^SKYLIGHT_(\w+)$/)
50
- match && match[1].downcase
127
+ def validate!
128
+ REQUIRED.each do |k, v|
129
+ unless get(k)
130
+ raise ConfigError, "#{v} required"
131
+ end
51
132
  end
52
133
 
53
- def apply_config(config, key, value)
54
- config.send("#{key}=", value) if config.respond_to?("#{key}=")
134
+ true
135
+ end
136
+
137
+ def get(key, default = nil, &blk)
138
+ key = key.to_sym
139
+
140
+ return @priority[key] if @priority.key?(key)
141
+ return @values[key] if @values.key?(key)
142
+ return DEFAULTS[key] if DEFAULTS.key?(key)
143
+
144
+ if default
145
+ return default
146
+ elsif blk
147
+ return blk.call(key)
55
148
  end
149
+
150
+ nil
56
151
  end
57
152
 
58
- def initialize(attrs = {})
59
- # Set defaults
60
- @ssl = true
61
- @deflate = true
62
- @host = "agent.skylight.io"
63
- @port = 443
64
- @interval = 5
65
- @protocol = JsonProto.new(self)
66
- @enable_gc_profiler = true
67
- @max_pending_traces = 500
68
- @samples_per_interval = 100
153
+ alias [] get
154
+
155
+ def set(key, val, scope = nil)
156
+ if scope
157
+ key = [scope, key].join('.')
158
+ end
69
159
 
70
- @logger = Logger.new(STDOUT)
71
- @logger.level = Logger::INFO
160
+ if Hash === val
161
+ val.each do |k, v|
162
+ set(k, v, key)
163
+ end
164
+ else
165
+ k = key.to_sym
72
166
 
73
- @normalizer = Normalizer.new
167
+ if validator = VALIDATORS[k]
168
+ unless validator.call(val, self)
169
+ raise ConfigError, "invalid value for #{k} (#{val})"
170
+ end
171
+ end
74
172
 
75
- attrs.each do |k, v|
76
- if respond_to?("#{k}=")
77
- send("#{k}=", v)
173
+ if @regexp && key =~ @regexp
174
+ @priority[$1.to_sym] = val
78
175
  end
176
+
177
+ @values[k] = val
79
178
  end
179
+ end
80
180
 
81
- yield self if block_given?
181
+ alias []= set
82
182
 
83
- @http ||= Util::HTTP.new(self)
183
+ def to_env
184
+ ret = {}
84
185
 
85
- unless @gc_profiler
86
- if enable_gc_profiler && GC::Profiler.respond_to?(:enable)
87
- @gc_profiler = GC::Profiler
88
- else
89
- @gc_profiler = GC_PROFILER_STUB
186
+ ENV_TO_KEY.each do |k, v|
187
+ if (c = get(v)) != DEFAULTS[v]
188
+ ret[k] = cast_for_env(c)
90
189
  end
91
190
  end
191
+
192
+ ret
92
193
  end
93
194
 
94
- attr_accessor :yaml_file,
95
- :authentication_token,
96
- :app_id,
97
- :ssl,
98
- :deflate,
99
- :host,
100
- :port,
101
- :http,
102
- :samples_per_interval,
103
- :interval,
104
- :max_pending_traces,
105
- :normalizer,
106
- :protocol,
107
- :logger,
108
- :enable_gc_profiler,
109
- :gc_profiler
110
-
111
- alias_method :ssl?, :ssl
112
- alias_method :deflate?, :deflate
113
-
114
- def protocol=(val)
115
- if val.is_a?(String) || val.is_a?(Symbol)
116
- class_name = val.to_s.capitalize+"Proto"
117
- val = Skylight.const_get(class_name).new(self)
195
+ #
196
+ #
197
+ # ===== Writing =====
198
+ #
199
+ #
200
+
201
+ def write(path)
202
+ FileUtils.mkdir_p(File.dirname(path))
203
+
204
+ File.open(path, 'w') do |f|
205
+ f.puts <<-YAML
206
+ ---
207
+ # The Skylight ID for the application.
208
+ application: #{self[:application]}
209
+
210
+ # The authentication token for the application.
211
+ authentication: #{self[:authentication]}
212
+ YAML
118
213
  end
214
+ end
215
+
216
+ #
217
+ #
218
+ # ===== Helpers =====
219
+ #
220
+ #
119
221
 
120
- @protocol = val
222
+ def worker
223
+ Worker::Builder.new(self)
121
224
  end
122
225
 
123
- def log_level
124
- logger && logger.level
226
+ def gc
227
+ GC.new(get('gc.profiler', ::GC::Profiler))
125
228
  end
126
229
 
127
- def log_level=(level)
128
- if logger
129
- if level.is_a?(String) || level.is_a?(Symbol)
130
- level = Logger.const_get(level.to_s.upcase)
230
+ def logger
231
+ @logger ||=
232
+ begin
233
+ out = get(:'log')
234
+ out = STDOUT if out == '-'
235
+
236
+ unless IO === out
237
+ FileUtils.mkdir_p(File.dirname(out))
238
+ end
239
+
240
+ l = Logger.new(out)
241
+ l.level =
242
+ case get(:'log_level')
243
+ when /^debug$/i then Logger::DEBUG
244
+ when /^info$/i then Logger::INFO
245
+ when /^warn$/i then Logger::WARN
246
+ when /^error$/i then Logger::ERROR
247
+ end
248
+ l
131
249
  end
132
- logger.level = level
133
- end
134
250
  end
135
251
 
136
- def save(filename=yaml_file)
137
- FileUtils.mkdir_p File.dirname(filename)
252
+ def logger=(logger)
253
+ @logger = logger
254
+ end
255
+
256
+ private
138
257
 
139
- File.open(filename, "w") do |file|
140
- config = {}
141
- config["authentication_token"] = authentication_token if authentication_token
142
- config["app_id"] = app_id if app_id
143
- file.puts YAML.dump(config)
258
+ def cast_for_env(v)
259
+ case v
260
+ when true then 'true'
261
+ when false then 'false'
262
+ when nil then 'nil'
263
+ else v.to_s
144
264
  end
145
265
  end
266
+
146
267
  end
147
268
  end