appsignal 0.11.18 → 0.12.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG.md +4 -38
  3. data/Rakefile +14 -6
  4. data/appsignal.gemspec +3 -1
  5. data/benchmark.rake +12 -16
  6. data/ext/appsignal_extension.c +183 -0
  7. data/ext/extconf.rb +39 -0
  8. data/gemfiles/capistrano2.gemfile +0 -1
  9. data/gemfiles/capistrano3.gemfile +0 -1
  10. data/gemfiles/rails-4.2.gemfile +1 -1
  11. data/lib/appsignal.rb +23 -61
  12. data/lib/appsignal/capistrano.rb +1 -2
  13. data/lib/appsignal/config.rb +13 -1
  14. data/lib/appsignal/event_formatter.rb +67 -0
  15. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +23 -0
  16. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +74 -0
  17. data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
  18. data/lib/appsignal/event_formatter/net_http/request_formatter.rb +13 -0
  19. data/lib/appsignal/instrumentations/net_http.rb +6 -4
  20. data/lib/appsignal/integrations/resque.rb +2 -10
  21. data/lib/appsignal/integrations/sidekiq.rb +2 -2
  22. data/lib/appsignal/integrations/sinatra.rb +1 -0
  23. data/lib/appsignal/js_exception_transaction.rb +44 -28
  24. data/lib/appsignal/marker.rb +11 -13
  25. data/lib/appsignal/params_sanitizer.rb +5 -8
  26. data/lib/appsignal/rack/instrumentation.rb +2 -0
  27. data/lib/appsignal/rack/js_exception_catcher.rb +1 -0
  28. data/lib/appsignal/rack/listener.rb +1 -1
  29. data/lib/appsignal/rack/sinatra_instrumentation.rb +2 -12
  30. data/lib/appsignal/subscriber.rb +59 -0
  31. data/lib/appsignal/transaction.rb +117 -174
  32. data/lib/appsignal/transmitter.rb +8 -37
  33. data/lib/appsignal/version.rb +2 -1
  34. data/spec/lib/appsignal/config_spec.rb +25 -4
  35. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +42 -0
  36. data/spec/lib/appsignal/{aggregator/middleware/active_record_sanitizer_spec.rb → event_formatter/active_record/sql_formatter_spec.rb} +61 -61
  37. data/spec/lib/appsignal/{event/moped_event_spec.rb → event_formatter/moped/query_formatter_spec.rb} +32 -78
  38. data/spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb +26 -0
  39. data/spec/lib/appsignal/event_formatter_spec.rb +102 -0
  40. data/spec/lib/appsignal/extension_spec.rb +75 -0
  41. data/spec/lib/appsignal/instrumentations/net_http_spec.rb +20 -4
  42. data/spec/lib/appsignal/integrations/delayed_job_spec.rb +3 -2
  43. data/spec/lib/appsignal/integrations/rails_spec.rb +0 -7
  44. data/spec/lib/appsignal/integrations/resque_spec.rb +51 -55
  45. data/spec/lib/appsignal/integrations/sequel_spec.rb +8 -3
  46. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -21
  47. data/spec/lib/appsignal/integrations/sinatra_spec.rb +0 -6
  48. data/spec/lib/appsignal/js_exception_transaction_spec.rb +57 -60
  49. data/spec/lib/appsignal/params_sanitizer_spec.rb +11 -27
  50. data/spec/lib/appsignal/rack/listener_spec.rb +6 -6
  51. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +2 -43
  52. data/spec/lib/appsignal/subscriber_spec.rb +162 -0
  53. data/spec/lib/appsignal/transaction_spec.rb +283 -615
  54. data/spec/lib/appsignal/transmitter_spec.rb +3 -32
  55. data/spec/lib/appsignal_spec.rb +41 -90
  56. data/spec/lib/generators/appsignal/appsignal_generator_spec.rb +0 -17
  57. data/spec/spec_helper.rb +18 -22
  58. data/spec/support/helpers/notification_helpers.rb +1 -1
  59. data/spec/support/helpers/time_helpers.rb +11 -0
  60. data/spec/support/helpers/transaction_helpers.rb +6 -18
  61. data/spec/support/project_fixture/config/appsignal.yml +1 -2
  62. metadata +68 -78
  63. checksums.yaml +0 -7
  64. data/gemfiles/padrino-0.13.gemfile +0 -7
  65. data/gemfiles/resque.gemfile +0 -5
  66. data/lib/appsignal/agent.rb +0 -217
  67. data/lib/appsignal/aggregator.rb +0 -67
  68. data/lib/appsignal/aggregator/middleware.rb +0 -4
  69. data/lib/appsignal/aggregator/middleware/action_view_sanitizer.rb +0 -23
  70. data/lib/appsignal/aggregator/middleware/active_record_sanitizer.rb +0 -65
  71. data/lib/appsignal/aggregator/middleware/chain.rb +0 -101
  72. data/lib/appsignal/aggregator/middleware/delete_blanks.rb +0 -16
  73. data/lib/appsignal/aggregator/post_processor.rb +0 -32
  74. data/lib/appsignal/event.rb +0 -20
  75. data/lib/appsignal/event/moped_event.rb +0 -90
  76. data/lib/appsignal/integrations/padrino.rb +0 -64
  77. data/lib/appsignal/integrations/passenger.rb +0 -13
  78. data/lib/appsignal/integrations/rake.rb +0 -29
  79. data/lib/appsignal/integrations/unicorn.rb +0 -25
  80. data/lib/appsignal/ipc.rb +0 -68
  81. data/lib/appsignal/transaction/formatter.rb +0 -85
  82. data/lib/appsignal/transaction/params_sanitizer.rb +0 -4
  83. data/lib/appsignal/zipped_payload.rb +0 -37
  84. data/spec/lib/appsignal/agent_spec.rb +0 -592
  85. data/spec/lib/appsignal/aggregator/middleware/action_view_sanitizer_spec.rb +0 -44
  86. data/spec/lib/appsignal/aggregator/middleware/chain_spec.rb +0 -168
  87. data/spec/lib/appsignal/aggregator/middleware/delete_blanks_spec.rb +0 -37
  88. data/spec/lib/appsignal/aggregator/post_processor_spec.rb +0 -99
  89. data/spec/lib/appsignal/aggregator_spec.rb +0 -186
  90. data/spec/lib/appsignal/event_spec.rb +0 -48
  91. data/spec/lib/appsignal/integrations/padrino_spec.rb +0 -171
  92. data/spec/lib/appsignal/integrations/passenger_spec.rb +0 -22
  93. data/spec/lib/appsignal/integrations/rake_spec.rb +0 -92
  94. data/spec/lib/appsignal/integrations/unicorn_spec.rb +0 -48
  95. data/spec/lib/appsignal/ipc_spec.rb +0 -128
  96. data/spec/lib/appsignal/transaction/formatter_spec.rb +0 -247
  97. data/spec/lib/appsignal/zipped_payload_spec.rb +0 -42
@@ -1,7 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'padrino', '~> 0.12.0'
4
-
5
- gem 'generator_spec'
6
-
7
- gemspec :path => '../'
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'resque'
4
-
5
- gemspec :path => '../'
@@ -1,217 +0,0 @@
1
- module Appsignal
2
- class Agent
3
- ACTION = 'log_entries'.freeze
4
- AGGREGATOR_LIMIT = 3 # Three minutes with a sleep time of 60 seconds
5
-
6
- attr_accessor :aggregator, :thread, :master_pid, :pid, :active, :sleep_time,
7
- :transmitter, :subscriber, :paused, :aggregator_queue, :revision,
8
- :transmission_successful
9
-
10
- def initialize
11
- return unless Appsignal.config.active?
12
- if Appsignal.config.env == 'development'
13
- @sleep_time = 10.0
14
- else
15
- @sleep_time = 60.0
16
- end
17
- @master_pid = Process.pid
18
- @pid = @master_pid
19
- @aggregator = Aggregator.new
20
- @transmitter = Transmitter.new(ACTION)
21
- @aggregator_queue = []
22
- @transmission_successful = true
23
-
24
- subscribe
25
- start_thread
26
- @active = true
27
- Appsignal.logger.info('Started Appsignal agent')
28
- end
29
-
30
- def active?
31
- !! @active
32
- end
33
-
34
- def start_thread
35
- Appsignal.logger.debug('Starting agent thread')
36
- @revision = ENV['APP_REVISION']
37
- @thread = Thread.new do
38
- begin
39
- sleep(rand(sleep_time))
40
- loop do
41
- if aggregator.has_transactions? || aggregator_queue.any?
42
- send_queue
43
- end
44
- truncate_aggregator_queue
45
- Appsignal.logger.debug("Sleeping #{sleep_time}")
46
- sleep(sleep_time)
47
- end
48
- rescue Exception=>ex
49
- Appsignal.logger.error "#{ex.class} in agent thread: '#{ex.message}'\n#{ex.backtrace.join("\n")}"
50
- end
51
- end
52
- end
53
-
54
- def restart_thread
55
- Appsignal.logger.debug 'Restarting agent thread'
56
- stop_thread
57
- start_thread
58
- end
59
-
60
- def stop_thread
61
- if @thread && @thread.alive?
62
- Appsignal.logger.debug 'Stopping agent thread'
63
- Thread.kill(@thread)
64
- end
65
- end
66
-
67
- def subscribe
68
- Appsignal.logger.debug('Subscribing to notifications')
69
- # Subscribe to notifications that don't start with a !
70
- @subscriber = ActiveSupport::Notifications.subscribe(/^[^!]/) do |*args|
71
- # Some people abuse the notification system and send their own data over it
72
- # (looking at you, active_admin), make sure we only process valid events.
73
- if Appsignal::Transaction.current && args.length == 5
74
- event = Appsignal::Event.event_for_instrumentation(*args)
75
- if event.name.start_with?('process_action')
76
- Appsignal::Transaction.current.set_process_action_event(event)
77
- elsif event.name.start_with?('perform_job')
78
- Appsignal::Transaction.current.set_perform_job_event(event)
79
- end
80
- Appsignal::Transaction.current.add_event(event)
81
- end
82
- end
83
- end
84
-
85
- def resubscribe
86
- Appsignal.logger.debug('Resubscribing to notifications')
87
- unsubscribe
88
- subscribe
89
- end
90
-
91
- def unsubscribe
92
- Appsignal.logger.debug('Unsubscribing from notifications')
93
- ActiveSupport::Notifications.unsubscribe(@subscriber)
94
- @subscriber = nil
95
- end
96
-
97
- def enqueue(transaction)
98
- forked! if @pid != Process.pid
99
- if Appsignal.is_ignored_action?(transaction.action)
100
- Appsignal.logger.debug("Ignoring transaction: #{transaction.request_id} (#{transaction.action})")
101
- return
102
- end
103
- aggregator.add(transaction)
104
- end
105
-
106
- def send_queue
107
- Appsignal.logger.debug('Sending queue')
108
- unless aggregator.has_transactions? || aggregator_queue.any?
109
- return
110
- end
111
- # Replace aggregator while making sure no thread
112
- # is adding to it's queue
113
- aggregator_to_be_sent = nil
114
- Thread.exclusive do
115
- aggregator_to_be_sent = aggregator
116
- @aggregator = Aggregator.new
117
- end
118
-
119
- begin
120
- payload = Appsignal::ZippedPayload.new(aggregator_to_be_sent.post_processed_queue!)
121
- add_to_aggregator_queue(payload)
122
- send_aggregators
123
- rescue Exception => ex
124
- Appsignal.logger.error "#{ex.class} while sending queue: #{ex.message}\n#{ex.backtrace.join("\n")}"
125
- end
126
- end
127
-
128
- def add_to_aggregator_queue(aggregator)
129
- @aggregator_queue.unshift(aggregator)
130
- end
131
-
132
- def send_aggregators
133
- @aggregator_queue.map! do |payload|
134
- begin
135
- if handle_result(transmitter.transmit(payload))
136
- nil
137
- else
138
- payload
139
- end
140
- rescue *Transmitter::HTTP_ERRORS => ex
141
- Appsignal.logger.error "#{ex} while sending aggregators"
142
- payload
143
- end
144
- end.compact!
145
- @transmission_successful = @aggregator_queue.empty?
146
- end
147
-
148
- def truncate_aggregator_queue(limit = AGGREGATOR_LIMIT)
149
- return unless @aggregator_queue.length > limit
150
- Appsignal.logger.error "Aggregator queue to large, removing items"
151
- @aggregator_queue = @aggregator_queue.first(limit)
152
- end
153
-
154
- def forked!
155
- Appsignal.logger.info('Forked worker process')
156
- @active = true
157
- @pid = Process.pid
158
- Thread.exclusive do
159
- @aggregator = Aggregator.new
160
- end
161
- resubscribe
162
- restart_thread
163
- end
164
-
165
- def shutdown(send_current_queue=false, reason=nil)
166
- Appsignal.logger.info("Shutting down agent (#{reason})")
167
- @active = false
168
- unsubscribe
169
- stop_thread
170
-
171
- # Only attempt to send the queue on shutdown when there are no API issues
172
- if send_current_queue && @transmission_successful
173
- send_queue
174
- end
175
- end
176
-
177
- protected
178
-
179
- def handle_result(code)
180
- Appsignal.logger.debug "Queue sent, response code: #{code}"
181
- case code.to_i
182
- when 200 # ok
183
- true
184
- when 420 # Enhance Your Calm
185
- Appsignal.logger.info 'Increasing sleep time since the server told us to'
186
- @sleep_time = sleep_time * 1.5
187
- true
188
- when 413 # Request Entity Too Large
189
- Appsignal.logger.info 'Decreasing sleep time since our last push was too large'
190
- @sleep_time = sleep_time / 1.5
191
- true
192
- when 429
193
- Appsignal.logger.error 'Too many requests sent'
194
- shutdown(false, 429)
195
- true
196
- when 406
197
- Appsignal.logger.error 'Your appsignal gem cannot communicate with the API anymore, please upgrade.'
198
- shutdown(false, 406)
199
- true
200
- when 402
201
- Appsignal.logger.error 'Payment required'
202
- shutdown(false, 402)
203
- true
204
- when 401
205
- Appsignal.logger.error 'API token cannot be authorized'
206
- shutdown(false, 401)
207
- true
208
- when 400
209
- Appsignal.logger.error 'Empty body sent'
210
- true
211
- else
212
- Appsignal.logger.error "Unknown Appsignal response code: '#{code}'"
213
- false
214
- end
215
- end
216
- end
217
- end
@@ -1,67 +0,0 @@
1
- module Appsignal
2
- class Aggregator
3
- attr_reader :queue, :slowness_index, :counts
4
-
5
- def initialize(queue=::ThreadSafe::Array.new, slowness_index={})
6
- @queue = queue
7
- @slowness_index = slowness_index
8
- @counts = {:regular_request => 0, :slow_request => 0, :exception => 0}
9
- end
10
-
11
- # truncates or reduces the size of event values of the transaction, and
12
- # adds it to the queue.
13
- #
14
- # @returns [ Array ] Array with transactions
15
- def add(transaction)
16
- case transaction.type
17
- when :regular_request
18
- transaction.truncate!
19
- when :slow_request
20
- pre_process_slowness!(transaction)
21
- when :exception
22
- transaction.clear_events!
23
- transaction.convert_values_to_primitives!
24
- end
25
- counts[transaction.type] += 1
26
- queue << transaction
27
- return true
28
- end
29
-
30
- # Informs whether the queue has any transactions in it or not
31
- #
32
- # @returns [ Boolean ]
33
- def has_transactions?
34
- queue.any?
35
- end
36
-
37
- # Post process the queue and return it
38
- #
39
- # @returns [ Array ] Array of post processed Appsignal::Transaction objects
40
- def post_processed_queue!
41
- Appsignal.logger.debug("Post processing queue: #{counts.inspect}")
42
- Appsignal::Aggregator::PostProcessor.new(queue).post_processed_queue!
43
- end
44
-
45
- protected
46
-
47
- def similar_slowest(transaction)
48
- slowness_index[transaction.action]
49
- end
50
-
51
- def pre_process_slowness!(transaction)
52
- similar_slowest = similar_slowest(transaction)
53
- if similar_slowest
54
- if transaction.slower?(similar_slowest)
55
- slowness_index[transaction.action] = transaction
56
- transaction.convert_values_to_primitives!
57
- similar_slowest.truncate!
58
- else
59
- transaction.truncate!
60
- end
61
- else
62
- slowness_index[transaction.action] = transaction
63
- transaction.convert_values_to_primitives!
64
- end
65
- end
66
- end
67
- end
@@ -1,4 +0,0 @@
1
- require 'appsignal/aggregator/middleware/chain'
2
- require 'appsignal/aggregator/middleware/delete_blanks'
3
- require 'appsignal/aggregator/middleware/action_view_sanitizer'
4
- require 'appsignal/aggregator/middleware/active_record_sanitizer'
@@ -1,23 +0,0 @@
1
- module Appsignal
2
- class Aggregator
3
- module Middleware
4
- class ActionViewSanitizer
5
- TARGET_EVENT_CATEGORY = 'action_view'.freeze
6
-
7
- def call(event)
8
- if event.name.end_with?(TARGET_EVENT_CATEGORY)
9
- identifier = event.payload[:identifier]
10
- if identifier
11
- event.payload[:identifier] = identifier.gsub(root_path, '')
12
- end
13
- end
14
- yield
15
- end
16
-
17
- def root_path
18
- @root_path ||= "#{Rails.root.to_s}/"
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,65 +0,0 @@
1
- module Appsignal
2
- class Aggregator
3
- module Middleware
4
- class ActiveRecordSanitizer
5
- TARGET_EVENT_NAME = 'sql.active_record'.freeze
6
-
7
- SINGLE_QUOTE = /\\'/.freeze
8
- DOUBLE_QUOTE = /\\"/.freeze
9
- QUOTED_DATA = /(?:"[^"]+"|'[^']+')/.freeze
10
- SINGLE_QUOTED_DATA = /(?:'[^']+')/.freeze
11
- IN_ARRAY = /(IN \()[^\)]+(\))/.freeze
12
- NUMERIC_DATA = /\b\d+\b/.freeze
13
-
14
- SANITIZED_VALUE = '\1?\2'.freeze
15
-
16
- def call(event)
17
- if event.name == TARGET_EVENT_NAME
18
- unless schema_query?(event) || adapter_uses_prepared_statements?
19
- query_string = event.payload[:sql].dup
20
- if query_string
21
- if adapter_uses_double_quoted_table_names?
22
- query_string.gsub!(SINGLE_QUOTE, SANITIZED_VALUE)
23
- query_string.gsub!(SINGLE_QUOTED_DATA, SANITIZED_VALUE)
24
- else
25
- query_string.gsub!(SINGLE_QUOTE, SANITIZED_VALUE)
26
- query_string.gsub!(DOUBLE_QUOTE, SANITIZED_VALUE)
27
- query_string.gsub!(QUOTED_DATA, SANITIZED_VALUE)
28
- end
29
- query_string.gsub!(IN_ARRAY, SANITIZED_VALUE)
30
- query_string.gsub!(NUMERIC_DATA, SANITIZED_VALUE)
31
- event.payload[:sql] = query_string
32
- end
33
- end
34
- event.payload.delete(:connection_id)
35
- event.payload.delete(:binds)
36
- end
37
- yield
38
- end
39
-
40
- def schema_query?(event)
41
- event.payload[:name] == 'SCHEMA'
42
- end
43
-
44
- def connection_config
45
- @connection_config ||= if ActiveRecord::Base.respond_to?(:connection_config)
46
- ActiveRecord::Base.connection_config
47
- else
48
- ActiveRecord::Base.connection_pool.spec.config
49
- end
50
- end
51
-
52
- def adapter_uses_double_quoted_table_names?
53
- adapter = connection_config[:adapter]
54
- adapter =~ /postgres/ || adapter =~ /sqlite/
55
- end
56
-
57
- def adapter_uses_prepared_statements?
58
- return false unless adapter_uses_double_quoted_table_names?
59
- return true if connection_config[:prepared_statements].nil?
60
- connection_config[:prepared_statements]
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,101 +0,0 @@
1
- module Appsignal
2
- class Aggregator
3
- # Middleware is code configured to run before/after a message is processed.
4
- # It is patterned after Rack middleware.
5
- #
6
- # @example To add middleware:
7
- #
8
- # Appsignal.post_processing_middleware do |chain|
9
- # chain.add MyPostProcessingHook
10
- # end
11
- #
12
- # @example To insert immediately preceding another entry:
13
- #
14
- # Appsignal.post_process_middleware do |chain|
15
- # chain.insert_before ActiveRecord, MyPostProcessingHook
16
- # end
17
- #
18
- # @example To insert immediately after another entry:
19
- #
20
- # Appsignal.post_process_middleware do |chain|
21
- # chain.insert_after ActiveRecord, MyPostProcessingHook
22
- # end
23
- #
24
- # @example This is an example of a minimal middleware class:
25
- #
26
- # class MySHook
27
- # def call(transaction)
28
- # puts "Before post processing"
29
- # yield
30
- # puts "After post processing"
31
- # end
32
- # end
33
- #
34
- module Middleware
35
- class Chain
36
- attr_reader :entries
37
-
38
- def initialize
39
- @entries = []
40
- yield self if block_given?
41
- end
42
-
43
- def remove(klass)
44
- entries.delete_if { |entry| entry.klass == klass }
45
- end
46
-
47
- def add(klass, *args)
48
- entries << Entry.new(klass, *args) unless exists?(klass)
49
- end
50
-
51
- def insert_before(oldklass, newklass, *args)
52
- i = entries.index { |entry| entry.klass == newklass }
53
- new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
54
- i = entries.find_index { |entry| entry.klass == oldklass } || 0
55
- entries.insert(i, new_entry)
56
- end
57
-
58
- def insert_after(oldklass, newklass, *args)
59
- i = entries.index { |entry| entry.klass == newklass }
60
- new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
61
- i = entries.find_index { |entry| entry.klass == oldklass } || entries.count - 1
62
- entries.insert(i+1, new_entry)
63
- end
64
-
65
- def exists?(klass)
66
- entries.any? { |entry| entry.klass == klass }
67
- end
68
-
69
- def retrieve
70
- @retrieve ||= entries.map(&:make_new)
71
- end
72
-
73
- def clear
74
- entries.clear
75
- end
76
-
77
- def invoke(*args)
78
- chain = retrieve.dup
79
- traverse_chain = lambda do
80
- unless chain.empty?
81
- chain.shift.call(*args, &traverse_chain)
82
- end
83
- end
84
- traverse_chain.call
85
- end
86
- end
87
-
88
- class Entry
89
- attr_reader :klass
90
- def initialize(klass, *args)
91
- @klass = klass
92
- @args = args
93
- end
94
-
95
- def make_new
96
- @klass.new(*@args)
97
- end
98
- end
99
- end
100
- end
101
- end