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.
- data/.gitignore +6 -0
- data/CHANGELOG.md +4 -38
- data/Rakefile +14 -6
- data/appsignal.gemspec +3 -1
- data/benchmark.rake +12 -16
- data/ext/appsignal_extension.c +183 -0
- data/ext/extconf.rb +39 -0
- data/gemfiles/capistrano2.gemfile +0 -1
- data/gemfiles/capistrano3.gemfile +0 -1
- data/gemfiles/rails-4.2.gemfile +1 -1
- data/lib/appsignal.rb +23 -61
- data/lib/appsignal/capistrano.rb +1 -2
- data/lib/appsignal/config.rb +13 -1
- data/lib/appsignal/event_formatter.rb +67 -0
- data/lib/appsignal/event_formatter/action_view/render_formatter.rb +23 -0
- data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +74 -0
- data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
- data/lib/appsignal/event_formatter/net_http/request_formatter.rb +13 -0
- data/lib/appsignal/instrumentations/net_http.rb +6 -4
- data/lib/appsignal/integrations/resque.rb +2 -10
- data/lib/appsignal/integrations/sidekiq.rb +2 -2
- data/lib/appsignal/integrations/sinatra.rb +1 -0
- data/lib/appsignal/js_exception_transaction.rb +44 -28
- data/lib/appsignal/marker.rb +11 -13
- data/lib/appsignal/params_sanitizer.rb +5 -8
- data/lib/appsignal/rack/instrumentation.rb +2 -0
- data/lib/appsignal/rack/js_exception_catcher.rb +1 -0
- data/lib/appsignal/rack/listener.rb +1 -1
- data/lib/appsignal/rack/sinatra_instrumentation.rb +2 -12
- data/lib/appsignal/subscriber.rb +59 -0
- data/lib/appsignal/transaction.rb +117 -174
- data/lib/appsignal/transmitter.rb +8 -37
- data/lib/appsignal/version.rb +2 -1
- data/spec/lib/appsignal/config_spec.rb +25 -4
- data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +42 -0
- data/spec/lib/appsignal/{aggregator/middleware/active_record_sanitizer_spec.rb → event_formatter/active_record/sql_formatter_spec.rb} +61 -61
- data/spec/lib/appsignal/{event/moped_event_spec.rb → event_formatter/moped/query_formatter_spec.rb} +32 -78
- data/spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb +26 -0
- data/spec/lib/appsignal/event_formatter_spec.rb +102 -0
- data/spec/lib/appsignal/extension_spec.rb +75 -0
- data/spec/lib/appsignal/instrumentations/net_http_spec.rb +20 -4
- data/spec/lib/appsignal/integrations/delayed_job_spec.rb +3 -2
- data/spec/lib/appsignal/integrations/rails_spec.rb +0 -7
- data/spec/lib/appsignal/integrations/resque_spec.rb +51 -55
- data/spec/lib/appsignal/integrations/sequel_spec.rb +8 -3
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -21
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +0 -6
- data/spec/lib/appsignal/js_exception_transaction_spec.rb +57 -60
- data/spec/lib/appsignal/params_sanitizer_spec.rb +11 -27
- data/spec/lib/appsignal/rack/listener_spec.rb +6 -6
- data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +2 -43
- data/spec/lib/appsignal/subscriber_spec.rb +162 -0
- data/spec/lib/appsignal/transaction_spec.rb +283 -615
- data/spec/lib/appsignal/transmitter_spec.rb +3 -32
- data/spec/lib/appsignal_spec.rb +41 -90
- data/spec/lib/generators/appsignal/appsignal_generator_spec.rb +0 -17
- data/spec/spec_helper.rb +18 -22
- data/spec/support/helpers/notification_helpers.rb +1 -1
- data/spec/support/helpers/time_helpers.rb +11 -0
- data/spec/support/helpers/transaction_helpers.rb +6 -18
- data/spec/support/project_fixture/config/appsignal.yml +1 -2
- metadata +68 -78
- checksums.yaml +0 -7
- data/gemfiles/padrino-0.13.gemfile +0 -7
- data/gemfiles/resque.gemfile +0 -5
- data/lib/appsignal/agent.rb +0 -217
- data/lib/appsignal/aggregator.rb +0 -67
- data/lib/appsignal/aggregator/middleware.rb +0 -4
- data/lib/appsignal/aggregator/middleware/action_view_sanitizer.rb +0 -23
- data/lib/appsignal/aggregator/middleware/active_record_sanitizer.rb +0 -65
- data/lib/appsignal/aggregator/middleware/chain.rb +0 -101
- data/lib/appsignal/aggregator/middleware/delete_blanks.rb +0 -16
- data/lib/appsignal/aggregator/post_processor.rb +0 -32
- data/lib/appsignal/event.rb +0 -20
- data/lib/appsignal/event/moped_event.rb +0 -90
- data/lib/appsignal/integrations/padrino.rb +0 -64
- data/lib/appsignal/integrations/passenger.rb +0 -13
- data/lib/appsignal/integrations/rake.rb +0 -29
- data/lib/appsignal/integrations/unicorn.rb +0 -25
- data/lib/appsignal/ipc.rb +0 -68
- data/lib/appsignal/transaction/formatter.rb +0 -85
- data/lib/appsignal/transaction/params_sanitizer.rb +0 -4
- data/lib/appsignal/zipped_payload.rb +0 -37
- data/spec/lib/appsignal/agent_spec.rb +0 -592
- data/spec/lib/appsignal/aggregator/middleware/action_view_sanitizer_spec.rb +0 -44
- data/spec/lib/appsignal/aggregator/middleware/chain_spec.rb +0 -168
- data/spec/lib/appsignal/aggregator/middleware/delete_blanks_spec.rb +0 -37
- data/spec/lib/appsignal/aggregator/post_processor_spec.rb +0 -99
- data/spec/lib/appsignal/aggregator_spec.rb +0 -186
- data/spec/lib/appsignal/event_spec.rb +0 -48
- data/spec/lib/appsignal/integrations/padrino_spec.rb +0 -171
- data/spec/lib/appsignal/integrations/passenger_spec.rb +0 -22
- data/spec/lib/appsignal/integrations/rake_spec.rb +0 -92
- data/spec/lib/appsignal/integrations/unicorn_spec.rb +0 -48
- data/spec/lib/appsignal/ipc_spec.rb +0 -128
- data/spec/lib/appsignal/transaction/formatter_spec.rb +0 -247
- data/spec/lib/appsignal/zipped_payload_spec.rb +0 -42
data/gemfiles/resque.gemfile
DELETED
data/lib/appsignal/agent.rb
DELETED
@@ -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
|
data/lib/appsignal/aggregator.rb
DELETED
@@ -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,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
|