appsignal 0.6.7 → 0.7.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +3 -2
  3. data/.travis.yml +11 -11
  4. data/CHANGELOG.md +6 -0
  5. data/README.md +26 -0
  6. data/Rakefile +32 -3
  7. data/appsignal.gemspec +25 -16
  8. data/gemfiles/no_dependencies.gemfile +3 -0
  9. data/gemfiles/{3.0.gemfile → rails-3.0.gemfile} +2 -1
  10. data/gemfiles/{3.1.gemfile → rails-3.1.gemfile} +2 -1
  11. data/gemfiles/{3.2.gemfile → rails-3.2.gemfile} +2 -1
  12. data/gemfiles/{4.0.gemfile → rails-4.0.gemfile} +2 -1
  13. data/gemfiles/sinatra.gemfile +5 -0
  14. data/lib/appsignal.rb +30 -25
  15. data/lib/appsignal/agent.rb +37 -17
  16. data/lib/appsignal/aggregator.rb +2 -4
  17. data/lib/appsignal/aggregator/middleware.rb +4 -0
  18. data/lib/appsignal/aggregator/middleware/action_view_sanitizer.rb +23 -0
  19. data/lib/appsignal/aggregator/middleware/active_record_sanitizer.rb +64 -0
  20. data/lib/appsignal/aggregator/middleware/chain.rb +101 -0
  21. data/lib/appsignal/aggregator/middleware/delete_blanks.rb +16 -0
  22. data/lib/appsignal/aggregator/post_processor.rb +21 -18
  23. data/lib/appsignal/auth_check.rb +7 -21
  24. data/lib/appsignal/capistrano.rb +1 -36
  25. data/lib/appsignal/cli.rb +30 -46
  26. data/lib/appsignal/config.rb +46 -61
  27. data/lib/appsignal/integrations/capistrano.rb +44 -0
  28. data/lib/appsignal/{careful_logger.rb → integrations/capistrano/careful_logger.rb} +2 -0
  29. data/lib/appsignal/integrations/passenger.rb +6 -6
  30. data/lib/appsignal/integrations/rails.rb +33 -0
  31. data/lib/appsignal/integrations/sinatra.rb +20 -0
  32. data/lib/appsignal/marker.rb +9 -10
  33. data/lib/appsignal/rack/instrumentation.rb +28 -0
  34. data/lib/appsignal/rack/listener.rb +33 -0
  35. data/lib/appsignal/transaction.rb +18 -12
  36. data/lib/appsignal/transaction/formatter.rb +96 -0
  37. data/lib/appsignal/transaction/params_sanitizer.rb +80 -78
  38. data/lib/appsignal/transmitter.rb +10 -9
  39. data/lib/appsignal/version.rb +1 -1
  40. data/lib/generators/appsignal/appsignal_generator.rb +20 -21
  41. data/lib/generators/appsignal/templates/appsignal.yml +15 -21
  42. data/spec/{appsignal → lib/appsignal}/agent_spec.rb +69 -1
  43. data/spec/lib/appsignal/aggregator/middleware/action_view_sanitizer_spec.rb +32 -0
  44. data/spec/lib/appsignal/aggregator/middleware/active_record_sanitizer_spec.rb +215 -0
  45. data/spec/{appsignal → lib/appsignal/aggregator}/middleware/chain_spec.rb +5 -5
  46. data/spec/{appsignal → lib/appsignal/aggregator}/middleware/delete_blanks_spec.rb +2 -2
  47. data/spec/{appsignal → lib/appsignal}/aggregator/post_processor_spec.rb +15 -6
  48. data/spec/{appsignal → lib/appsignal}/aggregator_spec.rb +4 -1
  49. data/spec/{appsignal → lib/appsignal}/auth_check_spec.rb +8 -23
  50. data/spec/{appsignal → lib/appsignal}/cli_spec.rb +65 -66
  51. data/spec/lib/appsignal/config_spec.rb +132 -0
  52. data/spec/lib/appsignal/integrations/capistrano_spec.rb +123 -0
  53. data/spec/{appsignal → lib/appsignal}/integrations/passenger_spec.rb +0 -1
  54. data/spec/lib/appsignal/integrations/rails_spec.rb +38 -0
  55. data/spec/lib/appsignal/integrations/sinatra_spec.rb +43 -0
  56. data/spec/{appsignal → lib/appsignal}/marker_spec.rb +20 -23
  57. data/spec/lib/appsignal/rack/instrumentation_spec.rb +49 -0
  58. data/spec/{appsignal → lib/appsignal/rack}/listener_spec.rb +39 -6
  59. data/spec/{appsignal/transaction/transaction_formatter_spec.rb → lib/appsignal/transaction/formatter_spec.rb} +29 -6
  60. data/spec/{appsignal → lib/appsignal}/transaction/params_sanitizer_spec.rb +13 -12
  61. data/spec/{appsignal → lib/appsignal}/transaction_spec.rb +52 -7
  62. data/spec/{appsignal → lib/appsignal}/transmitter_spec.rb +27 -20
  63. data/spec/lib/appsignal_spec.rb +230 -0
  64. data/spec/lib/generators/appsignal/appsignal_generator_spec.rb +166 -0
  65. data/spec/lib/tmp/config/appsignal.yml +2 -0
  66. data/spec/spec_helper.rb +29 -20
  67. data/spec/support/delegate_matcher.rb +0 -1
  68. data/spec/support/fixtures/generated_config.yml +20 -0
  69. data/{log/.gitkeep → spec/support/fixtures/uploaded_file.txt} +0 -0
  70. data/spec/support/helpers/config_helpers.rb +24 -0
  71. data/spec/support/helpers/notification_helpers.rb +0 -2
  72. data/spec/support/helpers/transaction_helpers.rb +17 -2
  73. data/spec/support/project_fixture/config/appsignal.yml +18 -0
  74. data/spec/support/project_fixture/log/.gitkeep +0 -0
  75. data/spec/support/rails/my_app.rb +6 -0
  76. metadata +99 -83
  77. data/config/appsignal.yml +0 -10
  78. data/lib/appsignal/listener.rb +0 -21
  79. data/lib/appsignal/middleware.rb +0 -3
  80. data/lib/appsignal/middleware/action_view_sanitizer.rb +0 -21
  81. data/lib/appsignal/middleware/active_record_sanitizer.rb +0 -62
  82. data/lib/appsignal/middleware/chain.rb +0 -99
  83. data/lib/appsignal/middleware/delete_blanks.rb +0 -12
  84. data/lib/appsignal/railtie.rb +0 -37
  85. data/lib/appsignal/to_appsignal_hash.rb +0 -21
  86. data/lib/appsignal/transaction/transaction_formatter.rb +0 -67
  87. data/spec/appsignal/capistrano_spec.rb +0 -81
  88. data/spec/appsignal/config_spec.rb +0 -177
  89. data/spec/appsignal/inactive_railtie_spec.rb +0 -32
  90. data/spec/appsignal/middleware/action_view_sanitizer_spec.rb +0 -27
  91. data/spec/appsignal/middleware/active_record_sanitizer_spec.rb +0 -212
  92. data/spec/appsignal/railtie_spec.rb +0 -74
  93. data/spec/appsignal/to_appsignal_hash_spec.rb +0 -29
  94. data/spec/appsignal_spec.rb +0 -195
  95. data/spec/generators/appsignal/appsignal_generator_spec.rb +0 -181
@@ -1,7 +1,6 @@
1
1
  require 'erb'
2
2
  require 'yaml'
3
- require 'appsignal/careful_logger'
4
- require 'erb'
3
+ require 'appsignal/integrations/capistrano/careful_logger'
5
4
 
6
5
  module Appsignal
7
6
  class Config
@@ -13,85 +12,71 @@ module Appsignal
13
12
  :slow_request_threshold => 200
14
13
  }.freeze
15
14
 
16
- attr_reader :configurations, :project_path, :env
15
+ attr_reader :root_path, :env, :config_hash
17
16
 
18
- def initialize(project_path, env, logger=Appsignal.logger)
19
- @project_path = project_path || ENV['PWD']
20
- @env = env.to_sym
17
+ def initialize(root_path, env, logger=Appsignal.logger)
18
+ @root_path = root_path
19
+ @env = env
21
20
  @logger = logger
22
- @configurations = {}
23
- end
24
21
 
25
- def load
26
- return unless load_configurations
27
- return unless current_environment_present
28
- warn_if_duplicate_api_keys
29
-
30
- if Appsignal.respond_to?(:logger) && @logger == Appsignal.logger
31
- @logger.level = Logger::DEBUG if configurations[env][:debug]
22
+ if File.exists?(config_file)
23
+ load_config_from_disk
24
+ elsif ENV['APPSIGNAL_PUSH_API_KEY']
25
+ load_default_config_with_push_api_key(
26
+ ENV['APPSIGNAL_PUSH_API_KEY']
27
+ )
28
+ elsif ENV['APPSIGNAL_API_KEY']
29
+ load_default_config_with_push_api_key(
30
+ ENV['APPSIGNAL_API_KEY']
31
+ )
32
+ else
33
+ carefully_log_error(
34
+ "Not loading: No config file found at '#{config_file}' " \
35
+ "and no APPSIGNAL_PUSH_API_KEY env var present"
36
+ )
32
37
  end
33
- DEFAULT_CONFIG.merge(configurations[env])
34
38
  end
35
39
 
36
- def load_all
37
- return unless load_configurations
38
- warn_if_duplicate_api_keys
40
+ def loaded?
41
+ !! config_hash
42
+ end
39
43
 
40
- {}.tap do |result|
41
- configurations.each do |env, config|
42
- result[env] = DEFAULT_CONFIG.merge(config)
43
- end
44
- end
44
+ def [](key)
45
+ return unless loaded?
46
+ config_hash[key]
45
47
  end
46
48
 
47
49
  protected
48
50
 
49
51
  def config_file
50
- File.join(project_path, 'config', 'appsignal.yml')
52
+ @config_file ||= File.join(root_path, 'config', 'appsignal.yml')
51
53
  end
52
54
 
53
- def load_configurations
54
- unless load_configurations_from_disk || load_configurations_from_env
55
- carefully_log_error "no config file found at '#{config_file}'
56
- and no APPSIGNAL_API_KEY found in ENV"
57
- return false
58
- end
59
- true
60
- end
55
+ def load_config_from_disk
56
+ configurations = YAML.load(ERB.new(IO.read(config_file)).result)
57
+ config_for_this_env = configurations[env]
58
+ if config_for_this_env
59
+ config_for_this_env = Hash[config_for_this_env.map do |key, value|
60
+ [key.to_sym, value]
61
+ end] # convert keys to symbols
61
62
 
62
- def load_configurations_from_disk
63
- file = config_file
64
- unless File.exists?(file)
65
- return false
66
- end
67
- @configurations = YAML.load(ERB.new(IO.read(file)).result)
68
- configurations.each { |k,v| v.symbolize_keys! }
69
- configurations.symbolize_keys!
70
- true
71
- end
63
+ # Backwards compatibility with config files generated by earlier
64
+ # versions of the gem
65
+ if !config_for_this_env[:push_api_key] && config_for_this_env[:api_key]
66
+ config_for_this_env[:push_api_key] = config_for_this_env[:api_key]
67
+ end
72
68
 
73
- def load_configurations_from_env
74
- api_key = ENV['APPSIGNAL_API_KEY']
75
- env = ENV['RAILS_ENV'] || :production
76
- if api_key.present?
77
- @configurations = {env.to_sym => {:api_key => api_key, :active => true}}
78
- true
69
+ @config_hash = DEFAULT_CONFIG.merge(config_for_this_env)
79
70
  else
80
- false
81
- end
82
- end
83
-
84
- def warn_if_duplicate_api_keys
85
- keys = configurations.each_value.map { |config| config[:api_key] }.compact
86
- if keys.uniq.count < keys.count
87
- carefully_log_error('Warning: Duplicate API keys found in appsignal.yml')
71
+ carefully_log_error("Not loading: config for '#{env}' not found")
88
72
  end
89
73
  end
90
74
 
91
- def current_environment_present
92
- return true if configurations[env].present?
93
- carefully_log_error "config for '#{env}' not found"
94
- false
75
+ def load_default_config_with_push_api_key(key)
76
+ @config_hash = DEFAULT_CONFIG.merge(
77
+ :push_api_key => key,
78
+ :active => true
79
+ )
95
80
  end
96
81
  end
97
82
  end
@@ -0,0 +1,44 @@
1
+ require 'capistrano'
2
+
3
+ module Appsignal
4
+ module Integrations
5
+ class Capistrano
6
+ def self.tasks(config)
7
+ config.load do
8
+ after 'deploy', 'appsignal:deploy'
9
+ after 'deploy:migrations', 'appsignal:deploy'
10
+
11
+ namespace :appsignal do
12
+ task :deploy do
13
+ env = fetch(:rails_env, fetch(:rack_env, 'production'))
14
+ user = ENV['USER'] || ENV['USERNAME']
15
+
16
+ appsignal_config = Appsignal::Config.new(
17
+ ENV['PWD'],
18
+ env,
19
+ logger
20
+ )
21
+
22
+ marker_data = {
23
+ :revision => current_revision,
24
+ :repository => repository,
25
+ :user => user
26
+ }
27
+
28
+ marker = Marker.new(marker_data, appsignal_config, logger)
29
+ if config.dry_run
30
+ logger.info('Dry run: Deploy marker not actually sent.')
31
+ else
32
+ marker.transmit
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ if ::Capistrano::Configuration.instance
43
+ Appsignal::Integrations::Capistrano.tasks(::Capistrano::Configuration.instance)
44
+ end
@@ -1,5 +1,7 @@
1
1
  module Appsignal
2
2
  module CarefulLogger
3
+ # Because Capistrano's logger uses the term important
4
+ # instead of error.
3
5
  def carefully_log_error(message)
4
6
  if @logger.respond_to?(:important)
5
7
  @logger.important(message)
@@ -1,13 +1,13 @@
1
- if defined?(PhusionPassenger)
2
- Appsignal.logger.info("Using Passenger")
1
+ if defined?(::PhusionPassenger)
2
+ Appsignal.logger.info('Using Passenger')
3
3
 
4
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
5
- Appsignal.logger.debug("starting worker process")
4
+ ::PhusionPassenger.on_event(:starting_worker_process) do |forked|
5
+ Appsignal.logger.debug('starting worker process')
6
6
  Appsignal.agent.forked!
7
7
  end
8
8
 
9
- PhusionPassenger.on_event(:stopping_worker_process) do
10
- Appsignal.logger.debug("stopping worker process")
9
+ ::PhusionPassenger.on_event(:stopping_worker_process) do
10
+ Appsignal.logger.debug('stopping worker process')
11
11
  Appsignal.agent.shutdown(true)
12
12
  end
13
13
  end
@@ -0,0 +1,33 @@
1
+ if defined?(::Rails)
2
+ module Appsignal
3
+ module Integrations
4
+ class Railtie < ::Rails::Railtie
5
+ initializer 'appsignal.configure_rails_initialization' do |app|
6
+ app.middleware.insert_before(
7
+ ActionDispatch::RemoteIp,
8
+ Appsignal::Rack::Listener
9
+ )
10
+ end
11
+
12
+ config.after_initialize do
13
+ # Setup logging
14
+ if File.writable?(Rails.root.join('log'))
15
+ output = Rails.root.join('log/appsignal.log')
16
+ else
17
+ output = STDOUT
18
+ end
19
+ Appsignal.logger = Logger.new(output).tap do |l|
20
+ l.level = Logger::INFO
21
+ end
22
+ Appsignal.flush_in_memory_log
23
+
24
+ # Load config
25
+ Appsignal.config = Appsignal::Config.new(Rails.root, Rails.env)
26
+
27
+ # Start agent if config for this env is present
28
+ Appsignal.start if Appsignal.active?
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ require 'appsignal'
2
+
3
+ Appsignal.logger.info('Loading Sinatra integration')
4
+
5
+ app_settings = ::Sinatra::Application.settings
6
+ Appsignal.config = Appsignal::Config.new(
7
+ app_settings.root,
8
+ app_settings.environment.to_s
9
+ )
10
+
11
+ Appsignal.logger = Logger.new(File.join(app_settings.root, 'appsignal.log')).tap do |l|
12
+ l.level = Logger::DEBUG
13
+ end
14
+ Appsignal.flush_in_memory_log
15
+
16
+ if Appsignal.active?
17
+ Appsignal.start
18
+ ::Sinatra::Application.use(Appsignal::Rack::Listener)
19
+ ::Sinatra::Application.use(Appsignal::Rack::Instrumentation)
20
+ end
@@ -1,4 +1,4 @@
1
- require 'appsignal/careful_logger'
1
+ require 'appsignal/integrations/capistrano/careful_logger'
2
2
 
3
3
  module Appsignal
4
4
  class Marker
@@ -7,27 +7,26 @@ module Appsignal
7
7
  attr_reader :marker_data, :config, :logger
8
8
  ACTION = 'markers'
9
9
 
10
- def initialize(marker_data, root_path, rails_env, logger)
10
+ def initialize(marker_data, config, logger)
11
11
  @marker_data = marker_data
12
- @config = Appsignal::Config.new(root_path, rails_env, logger).load
12
+ @config = config
13
13
  @logger = logger
14
14
  end
15
15
 
16
16
  def transmit
17
17
  begin
18
- transmitter = Transmitter.new(
19
- @config[:endpoint], ACTION, @config[:api_key]
20
- )
21
- @logger.info("Notifying Appsignal of deploy...")
18
+ transmitter = Transmitter.new(ACTION, config)
19
+ logger.info('Notifying Appsignal of deploy...')
22
20
  result = transmitter.transmit(marker_data)
23
21
  if result == '200'
24
- @logger.info("Appsignal has been notified of this deploy!")
22
+ logger.info('Appsignal has been notified of this deploy!')
25
23
  else
26
24
  raise "#{result} at #{transmitter.uri}"
27
25
  end
28
26
  rescue Exception => e
29
- message = "Something went wrong while trying to notify Appsignal: #{e}"
30
- carefully_log_error message
27
+ carefully_log_error(
28
+ "Something went wrong while trying to notify Appsignal: #{e}"
29
+ )
31
30
  end
32
31
  end
33
32
  end
@@ -0,0 +1,28 @@
1
+ module Appsignal
2
+ module Rack
3
+ class Instrumentation
4
+ def initialize(app, options = {})
5
+ @app, @options = app, options
6
+ end
7
+
8
+ def call(env)
9
+ ActiveSupport::Notifications.instrument(
10
+ 'process_action.rack',
11
+ raw_payload(env)
12
+ ) do |payload|
13
+ @app.call(env)
14
+ end
15
+ end
16
+
17
+ def raw_payload(env)
18
+ request = ::Rack::Request.new(env)
19
+ {
20
+ :action => "#{request.request_method}:#{request.path}",
21
+ :params => request.params,
22
+ :method => request.request_method,
23
+ :path => request.path
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module Appsignal
2
+ module Rack
3
+ class Listener
4
+ def initialize(app, options = {})
5
+ @app, @options = app, options
6
+ end
7
+
8
+ def call(env)
9
+ if Appsignal.active?
10
+ call_with_appsignal_monitoring(env)
11
+ else
12
+ @app.call(env)
13
+ end
14
+ end
15
+
16
+ def call_with_appsignal_monitoring(env)
17
+ Appsignal::Transaction.create(request_id(env), env)
18
+ @app.call(env)
19
+ rescue Exception => exception
20
+ unless Appsignal.is_ignored_exception?(exception)
21
+ Appsignal::Transaction.current.add_exception(exception)
22
+ end
23
+ raise exception
24
+ ensure
25
+ Appsignal::Transaction.current.complete!
26
+ end
27
+
28
+ def request_id(env)
29
+ env['action_dispatch.request_id'] || SecureRandom.uuid
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,7 +1,3 @@
1
- require 'socket'
2
- require 'appsignal/transaction/transaction_formatter'
3
- require 'appsignal/transaction/params_sanitizer'
4
-
5
1
  module Appsignal
6
2
  class Transaction
7
3
  # Based on what Rails uses + some variables we'd like to show
@@ -26,7 +22,7 @@ module Appsignal
26
22
  end
27
23
 
28
24
  attr_reader :request_id, :events, :process_action_event, :action, :exception,
29
- :env, :fullpath, :time, :tags
25
+ :env, :fullpath, :time, :tags
30
26
 
31
27
  def initialize(request_id, env)
32
28
  @request_id = request_id
@@ -46,7 +42,7 @@ module Appsignal
46
42
  end
47
43
 
48
44
  def request
49
- ActionDispatch::Request.new(@env)
45
+ ::Rack::Request.new(@env)
50
46
  end
51
47
 
52
48
  def set_tags(given_tags={})
@@ -75,8 +71,7 @@ module Appsignal
75
71
 
76
72
  def slow_request?
77
73
  return false unless process_action_event && process_action_event.payload
78
- Appsignal.config[:slow_request_threshold] <=
79
- process_action_event.duration
74
+ Appsignal.config[:slow_request_threshold] <= process_action_event.duration
80
75
  end
81
76
 
82
77
  def slower?(transaction)
@@ -93,8 +88,8 @@ module Appsignal
93
88
  end
94
89
 
95
90
  def convert_values_to_primitives!
96
- Appsignal::ParamsSanitizer.sanitize!(@process_action_event.payload) if @process_action_event
97
- @events.each { |o| Appsignal::ParamsSanitizer.sanitize!(o.payload) }
91
+ Appsignal::Transaction::ParamsSanitizer.sanitize!(@process_action_event.payload) if @process_action_event
92
+ @events.each { |o| Appsignal::Transaction::ParamsSanitizer.sanitize!(o.payload) }
98
93
  add_sanitized_context!
99
94
  end
100
95
 
@@ -105,7 +100,7 @@ module Appsignal
105
100
  end
106
101
 
107
102
  def to_hash
108
- TransactionFormatter.new(self).to_hash
103
+ Formatter.new(self).to_hash
109
104
  end
110
105
 
111
106
  def complete!
@@ -121,6 +116,17 @@ module Appsignal
121
116
 
122
117
  protected
123
118
 
119
+ def http_queue_start
120
+ return unless env
121
+ env_var = env['HTTP_X_QUEUE_START'] || env['HTTP_X_REQUEST_START']
122
+ if env_var
123
+ value = env_var.tr('^0-9', '')
124
+ unless value.empty?
125
+ value.to_f / 1_000_000
126
+ end
127
+ end
128
+ end
129
+
124
130
  def add_sanitized_context!
125
131
  sanitize_environment!
126
132
  sanitize_session_data!
@@ -147,7 +153,7 @@ module Appsignal
147
153
 
148
154
  def sanitize_session_data!
149
155
  @sanitized_session_data =
150
- Appsignal::ParamsSanitizer.sanitize(request.session.to_hash)
156
+ Appsignal::Transaction::ParamsSanitizer.sanitize(request.session.to_hash)
151
157
  @fullpath = request.fullpath
152
158
  end
153
159
  end