-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve error handling and logging in HTTP::Server #9115
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
5316c5f
Write errors handled at HTTP::Server::RequestProcessor with Log
waj 653ebeb
HTTP::Server::RequestProcessor silently closes when it cannot write o…
waj f599b52
HTTP::Server::RequestProcessor doesn't write error when headers were …
waj a617416
HTTP::Server::RequestProcessor flushes data written so far when an er…
waj f135628
HTTP::ErrorHandler doesn't try to write output when some content was …
waj fc5a93c
HTTP::LogHandler write logs to Log with source "http.server"
waj 8e07ed4
HTTP::LogHandler doesn't log errors when the client connection has be…
waj b0788db
Add missing spec support files
waj ed88050
Fix typo in spec/std/http/server/response_spec.cr
waj 023c9d5
Add deprecation notice to HTTP::LogHandler.new(IO)
waj 78b99df
Require 'log' on each file that uses it
waj 479b6cf
Use a wrapping exception HTTP::Server::ClientError to differentiate c…
waj 8e83b13
Allow custom loggers for LogHandler and ErrorHandler
waj 8b73789
Log client errors as debug in RequestProcessor
waj 1a86862
Improve assertions in request_processor_spec.cr
waj 5c9e79c
Fix deprecated `LogHandler.new(IO)`
waj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,71 @@ | ||
require "spec" | ||
require "http/server/handler" | ||
require "../../../../support/log" | ||
require "../../../../support/io" | ||
|
||
describe HTTP::LogHandler do | ||
it "logs" do | ||
io = IO::Memory.new | ||
request = HTTP::Request.new("GET", "/") | ||
request.remote_address = "192.168.0.1" | ||
response = HTTP::Server::Response.new(io) | ||
context = HTTP::Server::Context.new(request, response) | ||
|
||
called = false | ||
log_io = IO::Memory.new | ||
handler = HTTP::LogHandler.new(log_io) | ||
handler = HTTP::LogHandler.new | ||
handler.next = ->(ctx : HTTP::Server::Context) { called = true } | ||
handler.call(context) | ||
log_io.to_s.should match %r(GET / - 200 \(\d+(\.\d+)?[mµn]s\)) | ||
logs = capture_logs("http.server") { handler.call(context) } | ||
match_logs(logs, | ||
{:info, %r(^192.168.0.1 - GET / HTTP/1.1 - 200 \(\d+(\.\d+)?[mµn]s\)$)} | ||
) | ||
called.should be_true | ||
end | ||
|
||
it "does log errors" do | ||
it "logs to custom logger" do | ||
request = HTTP::Request.new("GET", "/") | ||
response = HTTP::Server::Response.new(IO::Memory.new) | ||
context = HTTP::Server::Context.new(request, response) | ||
|
||
backend = Log::MemoryBackend.new | ||
log = Log.new("custom", backend, :info) | ||
handler = HTTP::LogHandler.new(log) | ||
handler.next = ->(ctx : HTTP::Server::Context) {} | ||
handler.call(context) | ||
|
||
match_logs(backend.entries, | ||
{:info, %r(^- - GET / HTTP/1.1 - 200 \(\d+(\.\d+)?[mµn]s\)$)} | ||
) | ||
end | ||
|
||
it "logs to io" do | ||
request = HTTP::Request.new("GET", "/") | ||
response = HTTP::Server::Response.new(IO::Memory.new) | ||
context = HTTP::Server::Context.new(request, response) | ||
|
||
backend = Log::MemoryBackend.new | ||
io = IO::Memory.new | ||
handler = HTTP::LogHandler.new(io) | ||
handler.next = ->(ctx : HTTP::Server::Context) {} | ||
handler.call(context) | ||
|
||
io.to_s.should match(%r(- - GET / HTTP/1.1 - 200 \(\d+(\.\d+)?[mµn]s\)$)) | ||
end | ||
|
||
it "log failed request" do | ||
io = IO::Memory.new | ||
request = HTTP::Request.new("GET", "/") | ||
response = HTTP::Server::Response.new(io) | ||
context = HTTP::Server::Context.new(request, response) | ||
|
||
called = false | ||
log_io = IO::Memory.new | ||
handler = HTTP::LogHandler.new(log_io) | ||
handler = HTTP::LogHandler.new | ||
handler.next = ->(ctx : HTTP::Server::Context) { raise "foo" } | ||
expect_raises(Exception, "foo") do | ||
handler.call(context) | ||
logs = capture_logs("http.server") do | ||
expect_raises(Exception, "foo") do | ||
handler.call(context) | ||
end | ||
end | ||
(log_io.to_s =~ %r(GET / - Unhandled exception:)).should be_truthy | ||
match_logs(logs, | ||
{:info, %r(^- - GET / HTTP/1.1 - 200 \(\d+(\.\d+)?[mµn]s\)$)} | ||
) | ||
end | ||
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
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 |
---|---|---|
@@ -0,0 +1,19 @@ | ||
class RaiseIOError < IO | ||
getter writes = 0 | ||
|
||
def initialize(@raise_on_write = false) | ||
end | ||
|
||
def read(slice : Bytes) | ||
raise IO::Error.new("...") | ||
end | ||
|
||
def write(slice : Bytes) : Nil | ||
@writes += 1 | ||
raise IO::Error.new("...") if @raise_on_write | ||
end | ||
|
||
def flush | ||
raise IO::Error.new("...") | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logged status code is plainly wrong here. The server actually responds with 500, not 200 and the log must reflect that.
I'm not sure what's the best way to make this accurate. The
LogHandler
probably needs to differentiate whether the handler chain was successfully completed or an exception was raised. The exception part would then need to duplicate some of the logic fromRequestProcessor
to tell the expected response status.Another option would be to integrate
LogHandler
directly intoRequestProcessor
. This would avoid duplication of the error handling and centralize logging to a single place. It would obviously need to be configurable.The purpose of
ErrorHandler
should also be considered in this regard. It seems quite useless when its only effect (with defaultverbose: false
) is to change this:To this:
This continues the thread from #9115 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also realized the problem with the status codes. That happens when only
LogHandler
is used. In Rack the log handler writes logs without handling errors so when this happens the request is not even logged. I think it's better (not ideal though) to log the wrong status code than not logging anything at all.The purpose of
ErrorHandler
is handling errors, showing them to the client and writing the logs. I think the issue is the placement of theLogHandler
and the fact that both handlers must be used at the same time to work properly. Rack also has the same "problem" but it setups both handlers automatically by default. Maybe we could do that but probably this could be better if the design is changed a little bit. MaybeLogHandler
cannot be a regularHTTP::Handler
and it should be a strategy instead, plugged in theRequestProcessor
.