From e81c1cd9586a8e36f6cd3cdebbd7d2375411a1d4 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 7 May 2026 13:02:29 +0000 Subject: [PATCH 1/2] fix(http): log accurate status code when handler returns error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The custom xlog access-log middleware in API() reads res.Status *before* Echo's central HTTPErrorHandler runs, so when a handler returns an error without writing a response (e.g. TranscriptEndpoint's `return err` on backend failure) the status field stays at its default 200. The logged line then claims status=200 while the client receives 500 — silently hiding every 500/503/etc. that bubbles up through Echo's error handler. Mirror echo.DefaultHTTPErrorHandler's status derivation when err != nil and the response hasn't been committed: default to 500, upgrade to *echo.HTTPError.Code if applicable. The logged status now matches what the client actually sees, so failed transcription requests stop appearing as 200 in the access log. Signed-off-by: Ettore Di Giacinto Assisted-by: Claude:claude-opus-4-7 [Claude Code] --- core/http/app.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/core/http/app.go b/core/http/app.go index e18690def986..bfa47c584009 100644 --- a/core/http/app.go +++ b/core/http/app.go @@ -167,6 +167,21 @@ func API(application *application.Application) (*echo.Echo, error) { res := c.Response() err := next(c) + // Echo's central HTTPErrorHandler runs *after* this middleware + // returns, so res.Status still reads the default 200 here when a + // handler returned an error without writing a response. Mirror + // echo.DefaultHTTPErrorHandler's status derivation so the access + // log reflects the status the client actually receives — without + // this, every silent handler error logs as 200. + status := res.Status + if err != nil && !res.Committed { + status = http.StatusInternalServerError + var he *echo.HTTPError + if errors.As(err, &he) { + status = he.Code + } + } + // Fix for #7989: Reduce log verbosity of Web UI polling, resources API, and health checks // These paths are logged at DEBUG level (hidden by default) instead of INFO. isQuietPath := false @@ -177,10 +192,10 @@ func API(application *application.Application) (*echo.Echo, error) { } } - if isQuietPath && res.Status == 200 { - xlog.Debug("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status) + if isQuietPath && status == 200 { + xlog.Debug("HTTP request", "method", req.Method, "path", req.URL.Path, "status", status) } else { - xlog.Info("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status) + xlog.Info("HTTP request", "method", req.Method, "path", req.URL.Path, "status", status) } return err } From e56723bb274aa954f9a80d1c5adbf383803c68ae Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 7 May 2026 13:02:41 +0000 Subject: [PATCH 2/2] fix(transcription): log underlying error before returning 500 to client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ModelTranscriptionWithOptions surfaces real failures — gRPC errors from a remote node, model load problems, ffmpeg conversion crashes — but TranscriptEndpoint just did `return err`, so Echo turned it into a 500 with a generic body and the original error was lost. Operators chasing transcription failures across distributed mode were left with "upstream returned 500" on the client and zero context anywhere in the frontend's logs. Add an xlog.Error before returning, recording model name, the staged audio path, and the underlying error. Combined with the access-log status fix, a failing transcription now leaves an audit trail (real status code in the access line, real cause in an Error line) instead of vanishing. Signed-off-by: Ettore Di Giacinto Assisted-by: Claude:claude-opus-4-7 [Claude Code] --- core/http/endpoints/openai/transcription.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/http/endpoints/openai/transcription.go b/core/http/endpoints/openai/transcription.go index 2979ecd59809..6312e3cb91e2 100644 --- a/core/http/endpoints/openai/transcription.go +++ b/core/http/endpoints/openai/transcription.go @@ -128,6 +128,15 @@ func TranscriptEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, app tr, err := backend.ModelTranscriptionWithOptions(req, ml, *config, appConfig) if err != nil { + // Log before returning so the underlying error survives. Echo's + // error handler turns this into a 500 with a generic body, which + // otherwise leaves operators chasing a silent failure — see e.g. + // distributed transcription, where the gRPC error from a remote + // node is the only signal of what actually went wrong. + xlog.Error("Transcription failed", + "model", config.Name, + "audio", dst, + "error", err) return err }