Skip to content

Commit 9d43df1

Browse files
committed
Add before_complete transaction hook
Add a `before_complete` transaction hook, which is executed right before a transaction is completed. Note that, for transactions containing multiple errors, this hook is called once for each of the duplicate transactions that is used to report each of the errors. This allows the context to be customised for each error.
1 parent 2076f2a commit 9d43df1

File tree

3 files changed

+167
-1
lines changed

3 files changed

+167
-1
lines changed

lib/appsignal/transaction.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ def create(namespace)
4848
# @return [Array<Proc>]
4949
# Add a block, if given, to be executed after a transaction is created.
5050
# The block will be called with the transaction as an argument.
51-
# Returns the array of blocks that will be executed.
51+
# Returns the array of blocks that will be executed after a transaction
52+
# is created.
5253
def after_create(&block)
5354
@after_create ||= Set.new
5455

@@ -57,6 +58,26 @@ def after_create(&block)
5758
@after_create << block
5859
end
5960

61+
# @api private
62+
# @return [Array<Proc>]
63+
# Add a block, if given, to be executed before a transaction is completed.
64+
# This happens after duplicating the transaction for each error that was
65+
# reported in the transaction -- that is, when a transaction with
66+
# several errors is completed, the block will be called once for each
67+
# error, with the transaction (either the original one or a duplicate of it)
68+
# that has each of the errors set.
69+
# The block will be called with the transaction as the first argument,
70+
# and the error reported by the transaction, if any, as the second argument.
71+
# Returns the array of blocks that will be executed before a transaction is
72+
# completed.
73+
def before_complete(&block)
74+
@before_complete ||= Set.new
75+
76+
return @before_complete if block.nil?
77+
78+
@before_complete << block
79+
end
80+
6081
# @api private
6182
def set_current_transaction(transaction)
6283
Thread.current[:appsignal_transaction] = transaction
@@ -208,6 +229,9 @@ def complete
208229
end
209230
end
210231
sample_data if should_sample
232+
233+
run_before_complete_hooks
234+
211235
@ext.complete
212236
end
213237

@@ -617,6 +641,12 @@ def run_after_create_hooks
617641
end
618642
end
619643

644+
def run_before_complete_hooks
645+
self.class.before_complete.each do |block|
646+
block.call(self, @error_set)
647+
end
648+
end
649+
620650
def _set_error(error)
621651
backtrace = cleaned_backtrace(error.backtrace)
622652
@ext.set_error(

spec/lib/appsignal/transaction_spec.rb

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,141 @@ def to_s
22172217
end
22182218
end
22192219

2220+
describe "#before_complete" do
2221+
it "stores the given hook when passed as a block" do
2222+
expect(Appsignal::Transaction.before_complete).to be_empty
2223+
Appsignal::Transaction.before_complete do |transaction, error|
2224+
transaction.set_action(error.message)
2225+
end
2226+
2227+
expect(Appsignal::Transaction.before_complete).to_not be_empty
2228+
2229+
transaction = new_transaction
2230+
error = ExampleStandardError.new("hook_error")
2231+
2232+
expect(transaction).to_not have_action("hook_error")
2233+
Appsignal::Transaction.before_complete.first.call(transaction, error)
2234+
expect(transaction).to have_action("hook_error")
2235+
end
2236+
2237+
it "stores the given hook when using <<" do
2238+
expect(Appsignal::Transaction.before_complete).to be_empty
2239+
proc = proc do |transaction, error|
2240+
transaction.set_action(error.message)
2241+
end
2242+
2243+
Appsignal::Transaction.before_complete << proc
2244+
2245+
expect(Appsignal::Transaction.before_complete).to eq(Set.new([proc]))
2246+
end
2247+
2248+
it "only stores a hook once when added several times" do
2249+
expect(Appsignal::Transaction.before_complete).to be_empty
2250+
proc = proc do |transaction|
2251+
transaction.set_action("hook_action")
2252+
end
2253+
2254+
Appsignal::Transaction.before_complete(&proc)
2255+
Appsignal::Transaction.before_complete << proc
2256+
2257+
expect(Appsignal::Transaction.before_complete).to eq(Set.new([proc]))
2258+
end
2259+
2260+
context "when the transaction has an error" do
2261+
it "calls the given hook with the error when a transaction is completed" do
2262+
block = proc do |transaction, error|
2263+
transaction.set_action(error.message)
2264+
end
2265+
2266+
Appsignal::Transaction.before_complete(&block)
2267+
2268+
transaction = new_transaction
2269+
error = ExampleStandardError.new("hook_error")
2270+
transaction.set_error(error)
2271+
2272+
expect(block).to(
2273+
receive(:call)
2274+
.with(transaction, error)
2275+
.and_call_original
2276+
)
2277+
2278+
transaction.complete
2279+
2280+
expect(transaction).to have_action("hook_error")
2281+
end
2282+
end
2283+
2284+
context "when the transaction has several errors" do
2285+
it "calls the given hook for each of the duplicate error transactions" do
2286+
block = proc do |transaction, error|
2287+
transaction.set_action(error.message)
2288+
end
2289+
2290+
Appsignal::Transaction.before_complete(&block)
2291+
2292+
transaction = new_transaction
2293+
first_error = ExampleStandardError.new("hook_error_first")
2294+
transaction.set_error(first_error)
2295+
2296+
second_error = ExampleStandardError.new("hook_error_second")
2297+
transaction.set_error(second_error)
2298+
2299+
transaction.complete
2300+
2301+
expect(created_transactions.length).to eq(2)
2302+
2303+
expect(created_transactions.find { |t| t == transaction }).to(
2304+
have_action("hook_error_first")
2305+
)
2306+
expect(created_transactions.find { |t| t != transaction }).to(
2307+
have_action("hook_error_second")
2308+
)
2309+
end
2310+
end
2311+
2312+
context "when the transaction does not have an error" do
2313+
it "calls the given hook with nil when a transaction is completed" do
2314+
block = proc do |transaction|
2315+
transaction.set_action("hook_action")
2316+
end
2317+
2318+
Appsignal::Transaction.before_complete(&block)
2319+
2320+
transaction = new_transaction
2321+
2322+
expect(block).to(
2323+
receive(:call)
2324+
.with(transaction, nil)
2325+
.and_call_original
2326+
)
2327+
2328+
transaction.complete
2329+
2330+
expect(transaction).to have_action("hook_action")
2331+
end
2332+
end
2333+
2334+
it "calls all the hooks in order" do
2335+
Appsignal::Transaction.before_complete do |transaction, error|
2336+
transaction.set_namespace(error.message)
2337+
transaction.set_action("hook_action_1")
2338+
end
2339+
2340+
Appsignal::Transaction.before_complete do |transaction, error|
2341+
transaction.set_action(error.message)
2342+
end
2343+
2344+
transaction = new_transaction
2345+
error = ExampleStandardError.new("hook_error")
2346+
transaction.set_error(error)
2347+
2348+
transaction.complete
2349+
2350+
expect(transaction).to have_namespace("hook_error")
2351+
expect(transaction).to have_action("hook_error")
2352+
end
2353+
end
2354+
22202355
describe "#start_event" do
22212356
let(:transaction) { new_transaction }
22222357

spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def spec_system_tmp_dir
101101
Appsignal::Loaders.clear!
102102
Appsignal::CheckIn.clear!
103103
Appsignal::Transaction.after_create.clear
104+
Appsignal::Transaction.before_complete.clear
104105

105106
clear_current_transaction!
106107
stop_minutely_probes

0 commit comments

Comments
 (0)