skylight 0.0.16 → 0.1.0.alpha1

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