Skip to content

Commit cec5c4f

Browse files
localai-botmudler
andauthored
fix(http): make handler-error status visible in access log + transcription errors (#9707)
* fix(http): log accurate status code when handler returns error 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 <mudler@localai.io> Assisted-by: Claude:claude-opus-4-7 [Claude Code] * fix(transcription): log underlying error before returning 500 to client 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 <mudler@localai.io> Assisted-by: Claude:claude-opus-4-7 [Claude Code] --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
1 parent c894d9c commit cec5c4f

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

core/http/app.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,21 @@ func API(application *application.Application) (*echo.Echo, error) {
167167
res := c.Response()
168168
err := next(c)
169169

170+
// Echo's central HTTPErrorHandler runs *after* this middleware
171+
// returns, so res.Status still reads the default 200 here when a
172+
// handler returned an error without writing a response. Mirror
173+
// echo.DefaultHTTPErrorHandler's status derivation so the access
174+
// log reflects the status the client actually receives — without
175+
// this, every silent handler error logs as 200.
176+
status := res.Status
177+
if err != nil && !res.Committed {
178+
status = http.StatusInternalServerError
179+
var he *echo.HTTPError
180+
if errors.As(err, &he) {
181+
status = he.Code
182+
}
183+
}
184+
170185
// Fix for #7989: Reduce log verbosity of Web UI polling, resources API, and health checks
171186
// These paths are logged at DEBUG level (hidden by default) instead of INFO.
172187
isQuietPath := false
@@ -177,10 +192,10 @@ func API(application *application.Application) (*echo.Echo, error) {
177192
}
178193
}
179194

180-
if isQuietPath && res.Status == 200 {
181-
xlog.Debug("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status)
195+
if isQuietPath && status == 200 {
196+
xlog.Debug("HTTP request", "method", req.Method, "path", req.URL.Path, "status", status)
182197
} else {
183-
xlog.Info("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status)
198+
xlog.Info("HTTP request", "method", req.Method, "path", req.URL.Path, "status", status)
184199
}
185200
return err
186201
}

core/http/endpoints/openai/transcription.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ func TranscriptEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, app
128128

129129
tr, err := backend.ModelTranscriptionWithOptions(req, ml, *config, appConfig)
130130
if err != nil {
131+
// Log before returning so the underlying error survives. Echo's
132+
// error handler turns this into a 500 with a generic body, which
133+
// otherwise leaves operators chasing a silent failure — see e.g.
134+
// distributed transcription, where the gRPC error from a remote
135+
// node is the only signal of what actually went wrong.
136+
xlog.Error("Transcription failed",
137+
"model", config.Name,
138+
"audio", dst,
139+
"error", err)
131140
return err
132141
}
133142

0 commit comments

Comments
 (0)