Skip to content

Commit b756dad

Browse files
committed
Harden Server-Timing fallback logging
1 parent acf6ad3 commit b756dad

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,12 @@ def emit_rsc_stream_server_timing_header
174174
response.headers["Server-Timing"] = existing.present? ? "#{existing}, #{entry}" : entry
175175
rescue StandardError => e
176176
# Observability must never break a real response. Swallow and log.
177-
Rails.logger.warn do
178-
"[React on Rails Pro] Failed to emit RSC stream Server-Timing header: #{e.class}: #{e.message}"
177+
begin
178+
Rails.logger.warn(
179+
"[React on Rails Pro] Failed to emit RSC stream Server-Timing header: #{e.class}: #{e.message}"
180+
)
181+
rescue StandardError
182+
# Logging itself can fail if the logger is unavailable; keep the response alive.
179183
end
180184
end
181185

react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,22 @@ def build_mocked_response
225225
expect(server_timing).to start_with("action_total;dur=5, ror_stream_shell;dur=")
226226
end
227227

228+
it "swallows Server-Timing header emission errors even when logging fails" do
229+
_queues, controller, _stream = setup_stream_test(component_count: 0)
230+
failing_headers = instance_double(Hash)
231+
allow(failing_headers).to receive(:[]).and_raise(StandardError, "headers unavailable")
232+
allow(controller.response).to receive(:headers).and_return(failing_headers)
233+
234+
failing_logger = instance_double(Logger)
235+
allow(failing_logger).to receive(:warn).and_raise(StandardError, "logger unavailable")
236+
allow(Rails).to receive(:logger).and_return(failing_logger)
237+
238+
controller.instance_variable_set(:@react_on_rails_rsc_stream_observability, true)
239+
controller.instance_variable_set(:@react_on_rails_rsc_stream_initial_render_duration_ms, 12.3)
240+
241+
expect { controller.send(:emit_rsc_stream_server_timing_header) }.not_to raise_error
242+
end
243+
228244
it "escapes the final observability mark name inside the generated script" do
229245
_queues, controller, _stream = setup_stream_test(component_count: 0)
230246

0 commit comments

Comments
 (0)