appsignal 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.ruby-version +1 -0
  2. data/README.md +20 -19
  3. data/appsignal.gemspec +2 -2
  4. data/lib/appsignal.rb +41 -18
  5. data/lib/appsignal/agent.rb +28 -54
  6. data/lib/appsignal/aggregator.rb +65 -0
  7. data/lib/appsignal/aggregator/post_processor.rb +27 -0
  8. data/lib/appsignal/config.rb +9 -4
  9. data/lib/appsignal/listener.rb +30 -0
  10. data/lib/appsignal/middleware.rb +4 -30
  11. data/lib/appsignal/middleware/action_view_sanitizer.rb +21 -0
  12. data/lib/appsignal/middleware/active_record_sanitizer.rb +60 -0
  13. data/lib/appsignal/middleware/chain.rb +99 -0
  14. data/lib/appsignal/middleware/delete_blanks.rb +12 -0
  15. data/lib/appsignal/railtie.rb +9 -1
  16. data/lib/appsignal/to_appsignal_hash.rb +23 -0
  17. data/lib/appsignal/transaction.rb +72 -16
  18. data/lib/appsignal/transaction/params_sanitizer.rb +91 -13
  19. data/lib/appsignal/transaction/transaction_formatter.rb +32 -68
  20. data/lib/appsignal/version.rb +1 -1
  21. data/spec/appsignal/agent_spec.rb +46 -156
  22. data/spec/appsignal/aggregator/post_processor_spec.rb +84 -0
  23. data/spec/appsignal/aggregator_spec.rb +182 -0
  24. data/spec/appsignal/inactive_railtie_spec.rb +2 -1
  25. data/spec/appsignal/{middleware_spec.rb → listener_spec.rb} +2 -2
  26. data/spec/appsignal/middleware/action_view_sanitizer_spec.rb +27 -0
  27. data/spec/appsignal/middleware/active_record_sanitizer_spec.rb +201 -0
  28. data/spec/appsignal/middleware/chain_spec.rb +168 -0
  29. data/spec/appsignal/middleware/delete_blanks_spec.rb +37 -0
  30. data/spec/appsignal/railtie_spec.rb +47 -34
  31. data/spec/appsignal/to_appsignal_hash_spec.rb +29 -0
  32. data/spec/appsignal/transaction/params_sanitizer_spec.rb +141 -36
  33. data/spec/appsignal/transaction/transaction_formatter_spec.rb +60 -155
  34. data/spec/appsignal/transaction_spec.rb +186 -53
  35. data/spec/appsignal/transmitter_spec.rb +11 -6
  36. data/spec/appsignal_spec.rb +33 -0
  37. data/spec/spec_helper.rb +9 -62
  38. data/spec/support/helpers/notification_helpers.rb +30 -0
  39. data/spec/support/helpers/transaction_helpers.rb +64 -0
  40. metadata +74 -63
  41. data/.rvmrc +0 -1
  42. data/lib/appsignal/transaction/faulty_request_formatter.rb +0 -30
  43. data/lib/appsignal/transaction/regular_request_formatter.rb +0 -11
  44. data/lib/appsignal/transaction/slow_request_formatter.rb +0 -34
  45. data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +0 -49
  46. data/spec/appsignal/transaction/regular_request_formatter_spec.rb +0 -14
  47. data/spec/appsignal/transaction/slow_request_formatter_spec.rb +0 -76
@@ -0,0 +1 @@
1
+ 1.9.3-p392
data/README.md CHANGED
@@ -13,36 +13,37 @@ example
13
13
 
14
14
  tagging [2]
15
15
 
16
- ## Event payload sanitizer
16
+ ## Postprocessing middleware
17
17
  Appsignal logs Rails
18
18
  [ActiveSupport::Notification](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)-events
19
19
  to appsignal.com over SSL. These events contain basic metadata such as a name
20
- and timestamps, and additional 'payload' log data. By default,
21
- appsignal will transmit all payload data. If you want to restrict the amount of
22
- payload data that gets sent to <https://appsignal.com>, you can define your own
23
- event payload sanitizer in `config/environment/my_env.rb`. The
24
- `event_payload_sanitizer` needs to be a callable object that returns a
25
- JSON-serializable hash.
20
+ and timestamps, and additional 'payload' log data. Appsignal uses a postprocessing
21
+ middleware stack to clean up events before they get sent to appsignal.com. You
22
+ can add your own middleware to this stack in `config/environment/my_env.rb`.
26
23
 
27
24
  ### Examples
28
25
 
29
- #### Pass through the entire payload unmodified (default)
26
+ #### Minimal template
30
27
  ```ruby
31
- Appsignal.event_payload_sanitizer = proc { |event| event.payload }
32
- ```
28
+ class MiddlewareTemplate
29
+ def call(event)
30
+ # modify the event in place
31
+ yield # pass control to the next middleware
32
+ # modify the event some more
33
+ end
34
+ end
33
35
 
34
- #### Delete the entire event payload
35
- ```ruby
36
- Appsignal.event_payload_sanitizer = proc { {} }
36
+ Appsignal.postprocessing_middleware.add MiddlewareTemplate
37
37
  ```
38
38
 
39
- #### Conditional modification of the payload
39
+ #### Remove boring payloads
40
40
  ```ruby
41
- Appsignal.event_payload_sanitizer = proc do |event|
42
- if event.name == 'interesting'
43
- event.payload
44
- else
45
- {}
41
+ class RemoveBoringPayload
42
+ def call(event)
43
+ unless event.name == 'interesting'
44
+ event.payload = {}
45
+ end
46
+ yield
46
47
  end
47
48
  end
48
49
  ```
@@ -22,12 +22,12 @@ Gem::Specification.new do |gem|
22
22
  gem.require_paths = ['lib']
23
23
  gem.version = Appsignal::VERSION
24
24
 
25
- gem.add_dependency 'rails', '~>3'
25
+ gem.add_dependency 'rails', '~> 3.0'
26
26
  gem.add_dependency 'rake'
27
27
  gem.add_dependency 'json'
28
- gem.add_dependency 'rack'
29
28
 
30
29
  gem.add_development_dependency 'rspec'
31
30
  gem.add_development_dependency 'capistrano'
32
31
  gem.add_development_dependency 'generator_spec'
32
+ gem.add_development_dependency 'pry'
33
33
  end
@@ -2,18 +2,17 @@ raise 'This appsignal gem only works with rails' unless defined?(Rails)
2
2
 
3
3
  module Appsignal
4
4
  class << self
5
- attr_accessor :subscriber, :event_payload_sanitizer
5
+ attr_accessor :subscriber
6
+ attr_reader :in_memory_log
6
7
 
7
- def active?
8
- config && config[:active] == true
9
- end
10
-
11
- def logger
12
- if Rails.root
13
- @logger ||= Logger.new("#{Rails.root}/log/appsignal.log").tap do |l|
14
- l.level = Logger::INFO
15
- end
16
- end
8
+ # Convenience method for adding a transaction to the queue. This queue is
9
+ # managed and is periodically pushed to Appsignal.
10
+ #
11
+ # @return [ true ] True.
12
+ #
13
+ # @since 0.5.0
14
+ def enqueue(transaction)
15
+ agent.enqueue(transaction)
17
16
  end
18
17
 
19
18
  def transactions
@@ -24,24 +23,48 @@ module Appsignal
24
23
  @agent ||= Appsignal::Agent.new
25
24
  end
26
25
 
26
+ def logger
27
+ @in_memory_log = StringIO.new unless @in_memory_log
28
+ @logger ||= Logger.new(@in_memory_log).tap do |l|
29
+ l.level = Logger::INFO
30
+ end
31
+ end
32
+
33
+ def flush_in_memory_log
34
+ Appsignal.logger << @in_memory_log.string
35
+ end
36
+
37
+ def logger=(l)
38
+ @logger = l
39
+ end
40
+
27
41
  def config
28
42
  @config ||= Appsignal::Config.new(Rails.root, Rails.env).load
29
43
  end
30
44
 
31
- def event_payload_sanitizer
32
- @event_payload_sanitizer ||= proc { |event| event.payload }
45
+ def post_processing_middleware
46
+ @post_processing_chain ||= PostProcessor.default_middleware
47
+ yield @post_processing_chain if block_given?
48
+ @post_processing_chain
33
49
  end
50
+
51
+ def active?
52
+ config && config[:active] == true
53
+ end
54
+
34
55
  end
35
56
  end
36
57
 
58
+ require 'appsignal/agent'
59
+ require 'appsignal/aggregator'
60
+ require 'appsignal/auth_check'
37
61
  require 'appsignal/cli'
38
62
  require 'appsignal/config'
39
- require 'appsignal/transmitter'
40
- require 'appsignal/agent'
63
+ require 'appsignal/exception_notification'
64
+ require 'appsignal/listener'
41
65
  require 'appsignal/marker'
42
66
  require 'appsignal/middleware'
67
+ require 'appsignal/railtie'
43
68
  require 'appsignal/transaction'
44
- require 'appsignal/exception_notification'
45
- require 'appsignal/auth_check'
69
+ require 'appsignal/transmitter'
46
70
  require 'appsignal/version'
47
- require 'appsignal/railtie'
@@ -1,104 +1,78 @@
1
1
  module Appsignal
2
2
  class Agent
3
- attr_reader :queue, :active, :sleep_time, :slowest_transactions, :transmitter
4
3
  ACTION = 'log_entries'
5
4
 
5
+ attr_reader :aggregator, :active, :sleep_time, :transmitter
6
+
6
7
  def initialize
7
8
  return unless Appsignal.active?
8
9
  @sleep_time = 60.0
9
- @slowest_transactions = {}
10
- @queue = []
10
+ @aggregator = Aggregator.new
11
11
  @retry_request = true
12
12
  @thread = Thread.new do
13
13
  while true do
14
- send_queue if @queue.any?
15
- sleep @sleep_time
14
+ send_queue if aggregator.has_transactions?
15
+ sleep(sleep_time)
16
16
  end
17
17
  end
18
18
  @transmitter = Transmitter.new(
19
- Appsignal.config[:endpoint],
19
+ Appsignal.config.fetch(:endpoint),
20
20
  ACTION,
21
- Appsignal.config[:api_key]
21
+ Appsignal.config.fetch(:api_key)
22
22
  )
23
23
  Appsignal.logger.info 'Started the Appsignal agent'
24
24
  end
25
25
 
26
- def add_to_queue(transaction)
27
- if !transaction.exception? && transaction.action
28
- current_slowest_transaction = @slowest_transactions[transaction.action]
29
- if current_slowest_transaction
30
- if current_slowest_transaction.process_action_event.duration <
31
- transaction.process_action_event.duration
32
- current_slowest_transaction.clear_payload_and_events!
33
- @slowest_transactions[transaction.action] = transaction
34
- else
35
- transaction.clear_payload_and_events!
36
- end
37
- else
38
- @slowest_transactions[transaction.action] = transaction
39
- end
40
- end
41
- @queue << transaction
26
+ def enqueue(transaction)
27
+ aggregator.add(transaction)
42
28
  end
43
29
 
44
30
  def send_queue
45
31
  Appsignal.logger.debug "Sending queue"
32
+ current_aggregator = aggregator
33
+ @aggregator = Aggregator.new
46
34
  begin
47
- handle_result transmitter.transmit(queue.map(&:to_hash))
35
+ handle_result transmitter.transmit(current_aggregator.post_processed_queue!)
48
36
  rescue Exception => ex
49
- Appsignal.logger.error "Exception while communicating with AppSignal: #{ex}"
50
- handle_result nil
37
+ Appsignal.logger.error "#{ex.class} while sending queue: #{ex.message}"
38
+ Appsignal.logger.error ex.backtrace.join('\n')
39
+ stop_logging
51
40
  end
52
41
  end
53
42
 
43
+ protected
44
+
54
45
  def handle_result(code)
55
46
  Appsignal.logger.debug "Queue sent, response code: #{code}"
56
47
  case code.to_i
57
- when 200
58
- good_response
48
+ when 200 # ok
59
49
  when 420 # Enhance Your Calm
60
- good_response
61
- @sleep_time = @sleep_time * 1.5
50
+ @sleep_time = sleep_time * 1.5
62
51
  when 413 # Request Entity Too Large
63
- good_response
64
- @sleep_time = @sleep_time / 1.5
52
+ @sleep_time = sleep_time / 1.5
65
53
  when 429
66
- Appsignal.logger.error "Too many requests sent, disengaging the agent"
54
+ Appsignal.logger.error "Too many requests sent"
67
55
  stop_logging
68
56
  when 406
69
- Appsignal.logger.error "Your appsignal gem cannot communicate with the API anymore, please upgrade. Disengaging the agent"
57
+ Appsignal.logger.error "Your appsignal gem cannot communicate with "\
58
+ "the API anymore, please upgrade."
70
59
  stop_logging
71
60
  when 402
72
- Appsignal.logger.error "Payment required, disengaging the agent"
61
+ Appsignal.logger.error "Payment required"
73
62
  stop_logging
74
63
  when 401
75
- Appsignal.logger.error "API token cannot be authorized, disengaging the agent"
64
+ Appsignal.logger.error "API token cannot be authorized"
76
65
  stop_logging
77
66
  else
78
- retry_once
79
- end
80
- end
81
-
82
- protected
83
-
84
- def good_response
85
- @queue = []
86
- @slowest_transactions = {}
87
- @retry_request = true
88
- end
89
-
90
- def retry_once
91
- if @retry_request
92
- @retry_request = false
93
- else
94
- @retry_request = true
95
- @queue = []
67
+ Appsignal.logger.error "Unknown Appsignal response code: '#{code}'"
96
68
  end
97
69
  end
98
70
 
99
71
  def stop_logging
72
+ Appsignal.logger.info("Disengaging the agent")
100
73
  ActiveSupport::Notifications.unsubscribe(Appsignal.subscriber)
101
74
  Thread.kill(@thread)
102
75
  end
76
+
103
77
  end
104
78
  end
@@ -0,0 +1,65 @@
1
+ module Appsignal
2
+ class Aggregator
3
+ attr_reader :queue, :slowness_index
4
+
5
+ def initialize(queue = [], slowness_index = {})
6
+ @queue = queue
7
+ @slowness_index = slowness_index
8
+ end
9
+
10
+ # truncates or reduces the size of event values of the transaction, and
11
+ # adds it to the queue.
12
+ #
13
+ # @returns [ Array ] Array with transactions
14
+ def add(transaction)
15
+ case transaction.type
16
+ when :regular_request
17
+ transaction.truncate!
18
+ when :slow_request
19
+ pre_process_slowness!(transaction)
20
+ when :exception
21
+ transaction.convert_values_to_primitives!
22
+ end
23
+ queue << transaction
24
+ end
25
+
26
+ # Informs whether the queue has any transactions in it or not
27
+ #
28
+ # @returns [ Boolean ]
29
+ def has_transactions?
30
+ queue.any?
31
+ end
32
+
33
+ # Post process the queue and return it
34
+ #
35
+ # @returns [ Array ] Array of post processed Appsignal::Transaction objects
36
+ def post_processed_queue!
37
+ Appsignal::PostProcessor.new(queue).post_processed_queue!
38
+ end
39
+
40
+ protected
41
+
42
+ def similar_slowest(transaction)
43
+ slowness_index[transaction.action]
44
+ end
45
+
46
+ def pre_process_slowness!(transaction)
47
+ similar_slowest = similar_slowest(transaction)
48
+ if similar_slowest
49
+ if transaction.slower?(similar_slowest)
50
+ slowness_index[transaction.action] = transaction
51
+ transaction.convert_values_to_primitives!
52
+ similar_slowest.truncate!
53
+ else
54
+ transaction.truncate!
55
+ end
56
+ else
57
+ slowness_index[transaction.action] = transaction
58
+ transaction.convert_values_to_primitives!
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ require 'appsignal/aggregator/post_processor'
@@ -0,0 +1,27 @@
1
+ module Appsignal
2
+ class PostProcessor
3
+ attr_reader :transactions
4
+
5
+ def initialize(transactions)
6
+ @transactions = transactions
7
+ end
8
+
9
+ def post_processed_queue!
10
+ transactions.map do |transaction|
11
+ transaction.events.each do |event|
12
+ Appsignal.post_processing_middleware.invoke(event)
13
+ end
14
+ transaction.to_hash
15
+ end
16
+ end
17
+
18
+ def self.default_middleware
19
+ Middleware::Chain.new do |chain|
20
+ chain.add Appsignal::Middleware::DeleteBlanks
21
+ chain.add Appsignal::Middleware::ActionViewSanitizer
22
+ chain.add Appsignal::Middleware::ActiveRecordSanitizer
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -1,9 +1,16 @@
1
1
  require 'appsignal/careful_logger'
2
2
 
3
3
  module Appsignal
4
+
4
5
  class Config
5
6
  include Appsignal::CarefulLogger
6
7
 
8
+ DEFAULT_CONFIG = {
9
+ :ignore_exceptions => [],
10
+ :endpoint => 'https://push.appsignal.com/1',
11
+ :slow_request_threshold => 200
12
+ }.freeze
13
+
7
14
  attr_accessor :root_path, :rails_env
8
15
 
9
16
  def initialize(root_path, rails_env, logger=Appsignal.logger)
@@ -25,11 +32,9 @@ module Appsignal
25
32
  return
26
33
  end
27
34
 
28
- config = {:ignore_exceptions => [],
29
- :endpoint => 'https://push.appsignal.com/1',
30
- :slow_request_threshold => 200
31
- }.merge(config.symbolize_keys)
35
+ DEFAULT_CONFIG.merge(config.symbolize_keys)
32
36
  end
33
37
 
34
38
  end
39
+
35
40
  end
@@ -0,0 +1,30 @@
1
+ require 'action_dispatch'
2
+
3
+ module Appsignal
4
+ class Listener
5
+ def initialize(app, options = {})
6
+ @app, @options = app, options
7
+ end
8
+
9
+ def call(env)
10
+ Appsignal::Transaction.create(env['action_dispatch.request_id'], env)
11
+ @app.call(env)
12
+ rescue Exception => exception
13
+ unless in_ignored_exceptions?(exception)
14
+ Appsignal::Transaction.current.add_exception(
15
+ Appsignal::ExceptionNotification.new(env, exception)
16
+ )
17
+ end
18
+ raise exception
19
+ ensure
20
+ Appsignal::Transaction.current.complete!
21
+ end
22
+
23
+ private
24
+
25
+ def in_ignored_exceptions?(exception)
26
+ Array.wrap(Appsignal.config[:ignore_exceptions]).
27
+ include?(exception.class.name)
28
+ end
29
+ end
30
+ end