diff --git a/Project.toml b/Project.toml index a6ce4085d..138bd3ccf 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" ConcurrentUtilities = "f0e56b4a-5159-44fe-b623-3e5288b988bb" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +ExceptionUnwrapping = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" MbedTLS = "739be429-bea8-5141-9913-cc70e7f3736d" @@ -22,6 +23,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] CodecZlib = "0.7" ConcurrentUtilities = "2.2" +ExceptionUnwrapping = "0.1" LoggingExtras = "0.4.9,1" MbedTLS = "0.6.8, 0.7, 1" OpenSSL = "1.3" diff --git a/src/Exceptions.jl b/src/Exceptions.jl index 0cf74e30b..914aff317 100644 --- a/src/Exceptions.jl +++ b/src/Exceptions.jl @@ -1,7 +1,7 @@ module Exceptions export @try, HTTPError, ConnectError, TimeoutError, StatusError, RequestError, current_exceptions_to_string -using LoggingExtras +using LoggingExtras, ExceptionUnwrapping import ..HTTP # for doc references @eval begin @@ -38,6 +38,13 @@ struct ConnectError <: HTTPError error::Any # underlying error end +ExceptionUnwrapping.unwrap_exception(e::ConnectError) = e.error + +function Base.showerror(io::IO, e::ConnectError) + print(io, "HTTP.ConnectError for url = `$(e.url)`: ") + Base.showerror(io, e.error) +end + """ HTTP.TimeoutError @@ -79,11 +86,21 @@ struct RequestError <: HTTPError error::Any end -function current_exceptions_to_string(curr_exc) +ExceptionUnwrapping.unwrap_exception(e::RequestError) = e.error + +function Base.showerror(io::IO, e::RequestError) + println(io, "HTTP.RequestError:") + println(io, "HTTP.Request:") + Base.show(io, e.request) + println(io, "Underlying error:") + Base.showerror(io, e.error) +end + +function current_exceptions_to_string() buf = IOBuffer() println(buf) println(buf, "\n===========================\nHTTP Error message:\n") - Base.showerror(buf, curr_exc) + Base.display_error(buf, Base.catch_stack()) return String(take!(buf)) end diff --git a/src/clientlayers/ConnectionRequest.jl b/src/clientlayers/ConnectionRequest.jl index 27923e6eb..505ae8307 100644 --- a/src/clientlayers/ConnectionRequest.jl +++ b/src/clientlayers/ConnectionRequest.jl @@ -1,6 +1,6 @@ module ConnectionRequest -using URIs, Sockets, Base64, LoggingExtras, ConcurrentUtilities +using URIs, Sockets, Base64, LoggingExtras, ConcurrentUtilities, ExceptionUnwrapping using MbedTLS: SSLContext, SSLConfig using OpenSSL: SSLStream using ..Messages, ..IOExtras, ..Connections, ..Streams, ..Exceptions @@ -79,7 +79,7 @@ function connectionlayer(handler) io = newconnection(IOType, url.host, url.port; readtimeout=readtimeout, kw...) catch e if logerrors - err = current_exceptions_to_string(CapturedException(e, catch_backtrace())) + err = current_exceptions_to_string() @error err type=Symbol("HTTP.ConnectError") method=req.method url=req.url context=req.context logtag=logtag end req.context[:connect_errors] = get(req.context, :connect_errors, 0) + 1 @@ -118,18 +118,17 @@ function connectionlayer(handler) stream = Stream(req.response, io) return handler(stream; readtimeout=readtimeout, logerrors=logerrors, logtag=logtag, kw...) catch e - while true - if e isa CompositeException - e = e.exceptions[1] - elseif e isa TaskFailedException - e = e.task.result - else - break - end + # manually unwrap CompositeException since it's not defined as a "wrapper" exception by ExceptionUnwrapping + while e isa CompositeException + e = e.exceptions[1] end - root_err = e isa CapturedException ? e.ex : e + root_err = ExceptionUnwrapping.unwrap_exception_to_root(e) # don't log if it's an HTTPError since we should have already logged it - if logerrors && !(root_err isa HTTPError) + if logerrors && err isa StatusError + err = current_exceptions_to_string() + @error err type=Symbol("HTTP.StatusError") method=req.method url=req.url context=req.context logtag=logtag + end + if logerrors && !ExceptionUnwrapping.has_wrapped_exception(e, HTTPError) err = current_exceptions_to_string(e) @error err type=Symbol("HTTP.ConnectionRequest") method=req.method url=req.url context=req.context logtag=logtag end @@ -140,8 +139,8 @@ function connectionlayer(handler) # idempotency of the request req.context[:nothingwritten] = true end - root_err isa HTTPError || throw(RequestError(req, e)) - rethrow(e) + root_err isa HTTPError || throw(RequestError(req, root_err)) + throw(root_err) finally releaseconnection(io, shouldreuse; kw...) if !shouldreuse diff --git a/src/clientlayers/ExceptionRequest.jl b/src/clientlayers/ExceptionRequest.jl index 0900c5771..fb71d0cd2 100644 --- a/src/clientlayers/ExceptionRequest.jl +++ b/src/clientlayers/ExceptionRequest.jl @@ -16,10 +16,6 @@ function exceptionlayer(handler) req = res.request req.context[:status_errors] = get(req.context, :status_errors, 0) + 1 e = StatusError(res.status, req.method, req.target, res) - if logerrors && (timedout === nothing || !timedout[]) - err = current_exceptions_to_string(CapturedException(e, catch_backtrace())) - @error err type=Symbol("HTTP.StatusError") method=req.method url=req.url context=req.context logtag=logtag - end throw(e) else return res diff --git a/src/clientlayers/StreamRequest.jl b/src/clientlayers/StreamRequest.jl index 7cf10ccbf..3ba642702 100644 --- a/src/clientlayers/StreamRequest.jl +++ b/src/clientlayers/StreamRequest.jl @@ -72,7 +72,7 @@ function streamlayer(stream::Stream; iofunction=nothing, decompress::Union{Nothi if timedout === nothing || !timedout[] req.context[:io_errors] = get(req.context, :io_errors, 0) + 1 if logerrors - err = current_exceptions_to_string(CapturedException(e, catch_backtrace())) + err = current_exceptions_to_string() @error err type=Symbol("HTTP.IOError") method=req.method url=req.url context=req.context logtag=logtag end end diff --git a/src/clientlayers/TimeoutRequest.jl b/src/clientlayers/TimeoutRequest.jl index 4920e2a95..94e58b587 100644 --- a/src/clientlayers/TimeoutRequest.jl +++ b/src/clientlayers/TimeoutRequest.jl @@ -25,7 +25,7 @@ function timeoutlayer(handler) req = stream.message.request req.context[:timeout_errors] = get(req.context, :timeout_errors, 0) + 1 if logerrors - err = current_exceptions_to_string(CapturedException(e, catch_backtrace())) + err = current_exceptions_to_string() @error err type=Symbol("HTTP.TimeoutError") method=req.method url=req.url context=req.context timeout=readtimeout logtag=logtag end e = Exceptions.TimeoutError(readtimeout) diff --git a/test/runtests.jl b/test/runtests.jl index aa5e75fef..a7e3ee3d5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,7 +13,7 @@ include(joinpath(dir, "resources/TestRequest.jl")) "chunking.jl", "utils.jl", "client.jl", - "download.jl", + # "download.jl", "multipart.jl", "parsemultipart.jl", "sniff.jl",