-
-
Notifications
You must be signed in to change notification settings - Fork 494
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR implements Puma's error reporting by patching `Puma::Server#lowlevel_error`. I thought about using the `lowlevel_error_handler` API to add a Sentry error handler, but it's not possible to do that after `puma.rb` is loaded (which is extremely early in a normal app setup, way before the SDK is loaded). There is also no good ways to fetch and update the server's `lowlevel_error_handler` option. So the only option left is to patch the method.
- Loading branch information
Showing
5 changed files
with
127 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -519,3 +519,4 @@ def utc_now | |
# patches | ||
require "sentry/net/http" | ||
require "sentry/redis" | ||
require "sentry/puma" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
module Sentry | ||
module Puma | ||
module Server | ||
def lowlevel_error(e, env, status=500) | ||
result = super | ||
|
||
begin | ||
Sentry.capture_exception(e) do |scope| | ||
scope.set_rack_env(env) | ||
end | ||
rescue | ||
# if anything happens, we don't want to break the app | ||
end | ||
|
||
result | ||
end | ||
end | ||
end | ||
end | ||
|
||
if defined?(Puma::Server) | ||
Sentry.register_patch(Sentry::Puma::Server, Puma::Server) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
require "puma" | ||
require_relative "../spec_helper" | ||
|
||
# Because puma doesn't have any dependency, if Rack is not installed the entire test won't work | ||
return if ENV["RACK_VERSION"] == "0" | ||
|
||
RSpec.describe Puma::Server do | ||
class TestServer | ||
def initialize(app, options) | ||
@host = "127.0.0.1" | ||
@ios = [] | ||
@server = Puma::Server.new(app, nil, options) | ||
@port = (@server.add_tcp_listener @host, 0).addr[1] | ||
@server.run | ||
end | ||
|
||
def send_http_and_read(req) | ||
(new_connection << req).read | ||
end | ||
|
||
def new_connection | ||
TCPSocket.new(@host, @port).tap {|sock| @ios << sock} | ||
end | ||
|
||
def close | ||
@ios.each { |io| io.close } | ||
end | ||
end | ||
|
||
let(:app) do | ||
proc { raise "foo" } | ||
end | ||
|
||
def server_run(app, lowlevel_error_handler: nil, &block) | ||
server = TestServer.new(app, lowlevel_error_handler: lowlevel_error_handler) | ||
yield server | ||
ensure | ||
server.close | ||
end | ||
|
||
before do | ||
perform_basic_setup | ||
end | ||
|
||
it "captures low-level errors" do | ||
res = server_run(app) do |server| | ||
server.send_http_and_read("GET / HTTP/1.0\r\n\r\n") | ||
end | ||
expect(res).to match(/500 Internal Server Error/) | ||
events = sentry_events | ||
expect(events.count).to eq(1) | ||
event = events.first | ||
expect(event.exception.values.first.value).to match("foo") | ||
end | ||
|
||
context "when user defines lowlevel_error_handler" do | ||
it "captures low-level errors" do | ||
handler_executed = false | ||
|
||
lowlevel_error_handler = ->(e, env) do | ||
handler_executed = true | ||
# Due to the way we test Puma::Server, we won't be verify this response | ||
[500, {}, ["Error is handled"]] | ||
end | ||
|
||
res = server_run(app, lowlevel_error_handler: lowlevel_error_handler) do |server| | ||
server.send_http_and_read("GET / HTTP/1.0\r\n\r\n") | ||
end | ||
|
||
expect(res).to match(/500 Internal Server Error/) | ||
expect(handler_executed).to eq(true) | ||
events = sentry_events | ||
expect(events.count).to eq(1) | ||
event = events.first | ||
expect(event.exception.values.first.value).to match("foo") | ||
end | ||
end | ||
|
||
context "when Sentry.capture_exception causes error" do | ||
it "doesn't affect the response" do | ||
expect(Sentry).to receive(:capture_exception).and_raise("bar") | ||
|
||
res = server_run(app) do |server| | ||
server.send_http_and_read("GET / HTTP/1.0\r\n\r\n") | ||
end | ||
|
||
expect(res).to match(/500 Internal Server Error/) | ||
expect(sentry_events).to be_empty | ||
end | ||
end | ||
end |