Skip to content

Commit 1f59f0b

Browse files
authored
Add tracing support to ActionCable integration (#1640)
* Avoid using :all callback to setup Rails apps * Adding tracing support to ActionCable integration * Use context instead of extra to pass ActionCable data * Improve ActionCable tests * Update changelog
1 parent 5d6460e commit 1f59f0b

3 files changed

Lines changed: 194 additions & 51 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
## Unreleased
22

3+
### Features
4+
35
- Add Action Cable exception capturing (Rails 6+) [#1638](https://github.com/getsentry/sentry-ruby/pull/1638)
6+
- Add tracing support to `ActionCable` integration [#1640](https://github.com/getsentry/sentry-ruby/pull/1640)
47

58
### Bug Fixes
69

sentry-rails/lib/sentry/rails/action_cable.rb

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,40 @@ module Sentry
22
module Rails
33
module ActionCableExtensions
44
class ErrorHandler
5-
def self.capture(env, transaction_name:, extra_context: nil, &block)
6-
Sentry.with_scope do |scope|
7-
scope.set_rack_env(env)
8-
scope.set_extras(action_cable: extra_context) if extra_context
9-
scope.set_transaction_name(transaction_name)
5+
class << self
6+
def capture(env, transaction_name:, extra_context: nil, &block)
7+
Sentry.with_scope do |scope|
8+
scope.set_rack_env(env)
9+
scope.set_context("action_cable", extra_context) if extra_context
10+
scope.set_transaction_name(transaction_name)
11+
transaction = start_transaction(env, scope.transaction_name)
12+
scope.set_span(transaction) if transaction
1013

11-
begin
12-
block.call
13-
rescue Exception => e # rubocop:disable Lint/RescueException
14-
Sentry::Rails.capture_exception(e)
14+
begin
15+
block.call
16+
finish_transaction(transaction, 200)
17+
rescue Exception => e # rubocop:disable Lint/RescueException
18+
Sentry::Rails.capture_exception(e)
19+
finish_transaction(transaction, 500)
1520

16-
raise
21+
raise
22+
end
1723
end
1824
end
25+
26+
def start_transaction(env, transaction_name)
27+
sentry_trace = env["HTTP_SENTRY_TRACE"]
28+
options = { name: transaction_name, op: "rails.action_cable".freeze }
29+
transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
30+
Sentry.start_transaction(transaction: transaction, **options)
31+
end
32+
33+
def finish_transaction(transaction, status_code)
34+
return unless transaction
35+
36+
transaction.set_http_status(status_code)
37+
transaction.finish
38+
end
1939
end
2040
end
2141

sentry-rails/spec/sentry/rails/action_cable_spec.rb

Lines changed: 161 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
require "spec_helper"
33
require "action_cable/engine"
44

5+
::ActionCable.server.config.cable = { "adapter" => "test" }
6+
57
# ensure we can access `connection.env` in tests like we can in production
68
ActiveSupport.on_load :action_cable_channel_test_case do
79
class ::ActionCable::Channel::ConnectionStub
@@ -30,73 +32,191 @@ def unsubscribed
3032
RSpec.describe "Sentry::Rails::ActionCableExtensions", type: :channel do
3133
let(:transport) { Sentry.get_current_client.transport }
3234

33-
before(:all) do
34-
make_basic_app
35-
::ActionCable.server.config.cable = { "adapter" => "test" }
36-
end
37-
3835
after do
3936
transport.events = []
4037
end
4138

42-
describe ChatChannel do
43-
it "captures errors during the subscribe" do
44-
expect { subscribe room_id: 42 }.to raise_error('foo')
45-
expect(transport.events.count).to eq(1)
39+
context "without tracing" do
40+
before do
41+
make_basic_app
42+
end
43+
44+
describe ChatChannel do
45+
it "captures errors during the subscribe" do
46+
expect { subscribe room_id: 42 }.to raise_error('foo')
47+
expect(transport.events.count).to eq(1)
4648

47-
event = transport.events.last.to_json_compatible
49+
event = transport.events.last.to_json_compatible
50+
expect(event["transaction"]).to eq("ChatChannel#subscribed")
51+
expect(event["contexts"]).to include("action_cable" => { "params" => { "room_id" => 42 } })
52+
end
53+
end
4854

49-
expect(event).to include(
50-
"transaction" => "ChatChannel#subscribed",
51-
"extra" => {
55+
describe AppearanceChannel do
56+
before { subscribe room_id: 42 }
57+
58+
it "captures errors during the action" do
59+
expect { perform :appear, foo: 'bar' }.to raise_error('foo')
60+
expect(transport.events.count).to eq(1)
61+
62+
event = transport.events.last.to_json_compatible
63+
64+
expect(event["transaction"]).to eq("AppearanceChannel#appear")
65+
expect(event["contexts"]).to include(
5266
"action_cable" => {
53-
"params" => { "room_id" => 42 }
67+
"params" => { "room_id" => 42 },
68+
"data" => { "action" => "appear", "foo" => "bar" }
5469
}
55-
}
56-
)
70+
)
71+
end
72+
73+
it "captures errors during unsubscribe" do
74+
expect { unsubscribe }.to raise_error('foo')
75+
expect(transport.events.count).to eq(1)
76+
77+
event = transport.events.last.to_json_compatible
5778

58-
expect(Sentry.get_current_scope.extra).to eq({})
79+
expect(event["transaction"]).to eq("AppearanceChannel#unsubscribed")
80+
expect(event["contexts"]).to include(
81+
"action_cable" => {
82+
"params" => { "room_id" => 42 }
83+
}
84+
)
85+
end
5986
end
6087
end
6188

62-
describe AppearanceChannel do
63-
before { subscribe room_id: 42 }
89+
context "with tracing enabled" do
90+
before do
91+
make_basic_app do |config|
92+
config.traces_sample_rate = 1.0
93+
end
94+
end
95+
96+
describe ChatChannel do
97+
it "captures errors and transactions during the subscribe" do
98+
expect { subscribe room_id: 42 }.to raise_error('foo')
99+
expect(transport.events.count).to eq(2)
100+
101+
event = transport.events.first.to_json_compatible
64102

65-
it "captures errors during the action" do
66-
expect { perform :appear, foo: 'bar' }.to raise_error('foo')
67-
expect(transport.events.count).to eq(1)
103+
expect(event["transaction"]).to eq("ChatChannel#subscribed")
104+
expect(event["contexts"]).to include("action_cable" => { "params" => { "room_id" => 42 } })
68105

69-
event = transport.events.last.to_json_compatible
106+
transaction = transport.events.last.to_json_compatible
70107

71-
expect(event).to include(
72-
"transaction" => "AppearanceChannel#appear",
73-
"extra" => {
108+
expect(transaction["type"]).to eq("transaction")
109+
expect(transaction["transaction"]).to eq("ChatChannel#subscribed")
110+
expect(transaction["contexts"]).to include(
111+
"action_cable" => {
112+
"params" => { "room_id" => 42 }
113+
}
114+
)
115+
expect(transaction["contexts"]).to include(
116+
"trace" => hash_including(
117+
"op" => "rails.action_cable",
118+
"status" => "internal_error"
119+
)
120+
)
121+
end
122+
end
123+
124+
describe AppearanceChannel do
125+
before { subscribe room_id: 42 }
126+
127+
it "captures errors and transactions during the action" do
128+
expect { perform :appear, foo: 'bar' }.to raise_error('foo')
129+
expect(transport.events.count).to eq(3)
130+
131+
subscription_transaction = transport.events[0].to_json_compatible
132+
133+
expect(subscription_transaction["type"]).to eq("transaction")
134+
expect(subscription_transaction["transaction"]).to eq("AppearanceChannel#subscribed")
135+
expect(subscription_transaction["contexts"]).to include(
136+
"action_cable" => {
137+
"params" => { "room_id" => 42 }
138+
}
139+
)
140+
expect(subscription_transaction["contexts"]).to include(
141+
"trace" => hash_including(
142+
"op" => "rails.action_cable",
143+
"status" => "ok"
144+
)
145+
)
146+
147+
event = transport.events[1].to_json_compatible
148+
149+
expect(event["transaction"]).to eq("AppearanceChannel#appear")
150+
expect(event["contexts"]).to include(
74151
"action_cable" => {
75152
"params" => { "room_id" => 42 },
76153
"data" => { "action" => "appear", "foo" => "bar" }
77154
}
78-
}
79-
)
155+
)
80156

81-
expect(Sentry.get_current_scope.extra).to eq({})
82-
end
157+
action_transaction = transport.events[2].to_json_compatible
83158

84-
it "captures errors during unsubscribe" do
85-
expect { unsubscribe }.to raise_error('foo')
86-
expect(transport.events.count).to eq(1)
159+
expect(action_transaction["type"]).to eq("transaction")
160+
expect(action_transaction["transaction"]).to eq("AppearanceChannel#appear")
161+
expect(action_transaction["contexts"]).to include(
162+
"action_cable" => {
163+
"params" => { "room_id" => 42 },
164+
"data" => { "action" => "appear", "foo" => "bar" }
165+
}
166+
)
167+
expect(action_transaction["contexts"]).to include(
168+
"trace" => hash_including(
169+
"op" => "rails.action_cable",
170+
"status" => "internal_error"
171+
)
172+
)
173+
end
174+
175+
it "captures errors during unsubscribe" do
176+
expect { unsubscribe }.to raise_error('foo')
177+
expect(transport.events.count).to eq(3)
178+
179+
subscription_transaction = transport.events[0].to_json_compatible
180+
181+
expect(subscription_transaction["type"]).to eq("transaction")
182+
expect(subscription_transaction["transaction"]).to eq("AppearanceChannel#subscribed")
183+
expect(subscription_transaction["contexts"]).to include(
184+
"action_cable" => {
185+
"params" => { "room_id" => 42 }
186+
}
187+
)
188+
expect(subscription_transaction["contexts"]).to include(
189+
"trace" => hash_including(
190+
"op" => "rails.action_cable",
191+
"status" => "ok"
192+
)
193+
)
194+
195+
event = transport.events[1].to_json_compatible
196+
197+
expect(event["transaction"]).to eq("AppearanceChannel#unsubscribed")
198+
expect(event["contexts"]).to include(
199+
"action_cable" => {
200+
"params" => { "room_id" => 42 }
201+
}
202+
)
87203

88-
event = transport.events.last.to_json_compatible
204+
transaction = transport.events[2].to_json_compatible
89205

90-
expect(event).to include(
91-
"transaction" => "AppearanceChannel#unsubscribed",
92-
"extra" => {
206+
expect(transaction["type"]).to eq("transaction")
207+
expect(transaction["transaction"]).to eq("AppearanceChannel#unsubscribed")
208+
expect(transaction["contexts"]).to include(
93209
"action_cable" => {
94210
"params" => { "room_id" => 42 }
95211
}
96-
}
97-
)
98-
99-
expect(Sentry.get_current_scope.extra).to eq({})
212+
)
213+
expect(transaction["contexts"]).to include(
214+
"trace" => hash_including(
215+
"op" => "rails.action_cable",
216+
"status" => "internal_error"
217+
)
218+
)
219+
end
100220
end
101221
end
102222
end

0 commit comments

Comments
 (0)