Merge pull request #726 from prometheus/beorn7/promhttp

Fix promhttp error handling
This commit is contained in:
Björn Rabenstein 2020-03-13 23:05:57 +01:00 committed by GitHub
commit 4739e9cf8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 20 additions and 17 deletions

View File

@ -53,12 +53,16 @@ func (r *responseWriterDelegator) Written() int64 {
} }
func (r *responseWriterDelegator) WriteHeader(code int) { func (r *responseWriterDelegator) WriteHeader(code int) {
if r.observeWriteHeader != nil && !r.wroteHeader {
// Only call observeWriteHeader for the 1st time. It's a bug if
// WriteHeader is called more than once, but we want to protect
// against it here. Note that we still delegate the WriteHeader
// to the original ResponseWriter to not mask the bug from it.
r.observeWriteHeader(code)
}
r.status = code r.status = code
r.wroteHeader = true r.wroteHeader = true
r.ResponseWriter.WriteHeader(code) r.ResponseWriter.WriteHeader(code)
if r.observeWriteHeader != nil {
r.observeWriteHeader(code)
}
} }
func (r *responseWriterDelegator) Write(b []byte) (int, error) { func (r *responseWriterDelegator) Write(b []byte) (int, error) {

View File

@ -167,15 +167,12 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
enc := expfmt.NewEncoder(w, contentType) enc := expfmt.NewEncoder(w, contentType)
var lastErr error
// handleError handles the error according to opts.ErrorHandling // handleError handles the error according to opts.ErrorHandling
// and returns true if we have to abort after the handling. // and returns true if we have to abort after the handling.
handleError := func(err error) bool { handleError := func(err error) bool {
if err == nil { if err == nil {
return false return false
} }
lastErr = err
if opts.ErrorLog != nil { if opts.ErrorLog != nil {
opts.ErrorLog.Println("error encoding and sending metric family:", err) opts.ErrorLog.Println("error encoding and sending metric family:", err)
} }
@ -184,7 +181,10 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
case PanicOnError: case PanicOnError:
panic(err) panic(err)
case HTTPErrorOnError: case HTTPErrorOnError:
httpError(rsp, err) // We cannot really send an HTTP error at this
// point because we most likely have written
// something to rsp already. But at least we can
// stop sending.
return true return true
} }
// Do nothing in all other cases, including ContinueOnError. // Do nothing in all other cases, including ContinueOnError.
@ -202,10 +202,6 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
return return
} }
} }
if lastErr != nil {
httpError(rsp, lastErr)
}
}) })
if opts.Timeout <= 0 { if opts.Timeout <= 0 {
@ -276,7 +272,12 @@ type HandlerErrorHandling int
// errors are encountered. // errors are encountered.
const ( const (
// Serve an HTTP status code 500 upon the first error // Serve an HTTP status code 500 upon the first error
// encountered. Report the error message in the body. // encountered. Report the error message in the body. Note that HTTP
// errors cannot be served anymore once the beginning of a regular
// payload has been sent. Thus, in the (unlikely) case that encoding the
// payload into the negotiated wire format fails, serving the response
// will simply be aborted. Set an ErrorLog in HandlerOpts to detect
// those errors.
HTTPErrorOnError HandlerErrorHandling = iota HTTPErrorOnError HandlerErrorHandling = iota
// Ignore errors and try to serve as many metrics as possible. However, // Ignore errors and try to serve as many metrics as possible. However,
// if no metrics can be served, serve an HTTP status code 500 and the // if no metrics can be served, serve an HTTP status code 500 and the
@ -365,11 +366,9 @@ func gzipAccepted(header http.Header) bool {
} }
// httpError removes any content-encoding header and then calls http.Error with // httpError removes any content-encoding header and then calls http.Error with
// the provided error and http.StatusInternalServerErrer. Error contents is // the provided error and http.StatusInternalServerError. Error contents is
// supposed to be uncompressed plain text. However, same as with a plain // supposed to be uncompressed plain text. Same as with a plain http.Error, this
// http.Error, any header settings will be void if the header has already been // must not be called if the header or any payload has already been sent.
// sent. The error message will still be written to the writer, but it will
// probably be of limited use.
func httpError(rsp http.ResponseWriter, err error) { func httpError(rsp http.ResponseWriter, err error) {
rsp.Header().Del(contentEncodingHeader) rsp.Header().Del(contentEncodingHeader)
http.Error( http.Error(