appsignal 0.4.7 → 0.5.0

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