Files
signoz/pkg/http/render/render.go
Pandey b811991f9d feat(middleware): add panic recovery middleware (#10666)
* feat(middleware): add panic recovery middleware with TypeFatal error type

Add a global HTTP recovery middleware that catches panics, logs them
with OTel exception semantic conventions via errors.Attr, and returns
a safe user-facing error response. Introduce TypeFatal/CodeFatal for
unrecoverable failures and WithStacktrace to attach pre-formatted
stack traces to errors. Remove redundant per-handler panic recovery
blocks in querier APIs.

* style(errors): keep WithStacktrace call on same line in test

* fix(middleware): replace fmt.Errorf with errors.New in recovery test

* feat(middleware): add request context to panic recovery logs

Capture request body before handler runs and include method, path, and
body in panic recovery logs using OTel semconv attributes. Improve error
message to direct users to GitHub issues or support.
2026-03-23 06:25:26 +00:00

83 lines
2.1 KiB
Go

package render
import (
"net/http"
"github.com/SigNoz/signoz/pkg/errors"
jsoniter "github.com/json-iterator/go"
)
const (
// Non-standard status code (originally introduced by nginx) for the case when a client closes
// the connection while the server is still processing the request.
statusClientClosedConnection = 499
)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type SuccessResponse struct {
Status string `json:"status" required:"true"`
Data interface{} `json:"data,omitempty" required:"true"`
}
type ErrorResponse struct {
Status string `json:"status" required:"true"`
Error *errors.JSON `json:"error" required:"true"`
}
func Success(rw http.ResponseWriter, httpCode int, data interface{}) {
body, err := json.Marshal(&SuccessResponse{Status: StatusSuccess.s, Data: data})
if err != nil {
Error(rw, err)
return
}
if httpCode == 0 {
httpCode = http.StatusOK
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(httpCode)
_, _ = rw.Write(body)
}
func Error(rw http.ResponseWriter, cause error) {
// Derive the http code from the error type
t, _, _, _, _, _ := errors.Unwrapb(cause)
httpCode := http.StatusInternalServerError
switch t {
case errors.TypeInvalidInput:
httpCode = http.StatusBadRequest
case errors.TypeNotFound:
httpCode = http.StatusNotFound
case errors.TypeAlreadyExists:
httpCode = http.StatusConflict
case errors.TypeUnauthenticated:
httpCode = http.StatusUnauthorized
case errors.TypeUnsupported:
httpCode = http.StatusNotImplemented
case errors.TypeForbidden:
httpCode = http.StatusForbidden
case errors.TypeCanceled:
httpCode = statusClientClosedConnection
case errors.TypeTimeout:
httpCode = http.StatusGatewayTimeout
case errors.TypeFatal:
httpCode = http.StatusInternalServerError
case errors.TypeLicenseUnavailable:
httpCode = http.StatusUnavailableForLegalReasons
}
body, err := json.Marshal(&ErrorResponse{Status: StatusError.s, Error: errors.AsJSON(cause)})
if err != nil {
// this should never be the case
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.WriteHeader(httpCode)
_, _ = rw.Write(body)
}