Repository navigation
[BREAKINGCHANGE] echo: upgrade to echo v5 - #373
Merged
Merged
Conversation
4 of 7 tasks
There was a problem hiding this comment.
🟡 Changes recommended
Graceful shutdown timeouts are not propagated to the task runner, falsely reporting success, while recovered panics carrying HTTP statuses can bypass error and stack-trace logging.
2 open issues
What changed in this PR
Upgrades the shared HTTP server package to Echo v5.4.4 as the first step in Perses’ migration. Builder methods and web flags remain unchanged, while their exposed Echo types change.
Changes:
- Replace Echo v4 dependencies and context types with v5 equivalents.
- Adopt
StartConfigfor server lifecycle and TLS configuration. - Centralize error handling, update logging and metrics middleware, and add regression tests.
| File | Description |
|---|---|
| go.sum | Update checksums and remove obsolete dependencies. |
| go.mod | Require Echo v5 and prune unused indirect dependencies. |
| echo/timeout_test.go | Check timeout settings stored on the server. |
| echo/server.go | Migrate lifecycle, TLS, middleware ordering, and error handling. |
| echo/server_test.go | Switch the test import to Echo v5. |
| echo/middleware/metrics.go | Resolve response status and propagate handler errors. |
| echo/middleware/logger.go | Resolve logged status and return errors for central handling. |
| echo/middleware/logger_test.go | Update handlers to use v5 context pointers. |
| echo/metrics.go | Switch metrics route registration to Echo v5. |
| echo/logging_test.go | Test timeout configuration, server logging, and address visibility. |
| echo/error_handler_test.go | Test error responses, panic diagnostics, access logs, and metrics. |
| echo/builder_test.go | Update builder tests to use Echo v5 types. |
Review effort: Max
echo v4 only receives bug fixes until 2026-12-31 and security fixes until 2027-12-31. The public API of the package keeps the same Builder methods and flags, but exposes the echo v5 types. - The server is started with echo.StartConfig, which gracefully shuts it down when the context is canceled. The timeouts are set in BeforeServeFunc, as echo v5 sets a default ReadTimeout of 30s. - The panics are logged with their stack trace by a central error handler and counted as 500 by the logger and the metrics, which now run before Recover. - The logger and the metrics middlewares return the error instead of handling it, and resolve the status with echo.ResolveResponseStatus. - The address listened, the errors of the http.Server and the graceful shutdown timeout are logged with logrus. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
…g an HTTP status Address the review of the upgrade to echo v5: - echo v5 only gives the error of the graceful shutdown (like a timeout) to StartConfig.OnShutdownError. It is now returned by Finalize, like before the upgrade, so the task reports the failure instead of a successful shutdown. - The value of a recovered panic can carry an HTTP status (like panic(echo.NewHTTPError(...))), as PanicStackError unwraps it. The panics are now checked first, so they are always logged with their stack trace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Nexucis
added a commit
to perses/perses
that referenced
this pull request
Nov 5, 2026
It reports the graceful shutdown timeout of the HTTP server as a failure of the task, and logs the recovered panics even when their value carries an HTTP status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Nexucis
marked this pull request as ready for review
November 5, 2026 15:07
The central error handler read the path of the request after the handlers, which can rewrite it (like a reverse proxy). The error log and the access log of the same request could then show different paths. The Logger middleware now saves the path it logs in the echo context, and the error handler uses it (middleware.RequestPath), or the current path when the logger didn't handle the request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Nexucis
added a commit
to perses/perses
that referenced
this pull request
Nov 5, 2026
The errors of the HTTP server are logged with the path logged by the access logger, even when a handler rewrote it (like the datasource proxy). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
…actors Address the review of the upgrade to echo v5: - echo v5 logs with log/slog (JSON on stdout by default). A slog handler writing with logrus is given to echo, so all its logs (like "response already written to client") have the same format, level and output as the other logs. - Builder.IPExtractor and Builder.SchemeExtractor configure how echo.Context.RealIP and echo.Context.Scheme trust the forwarding headers, as echo v5 changed their defaults. - An unexpected error is logged as a warning once the response is sent, and at the debug level when the client went away (the request is canceled). - The TLS handshake errors of the http.Server are caused by the clients, so they are logged as warnings. - Only the stack of the goroutine that panicked is logged: getting the stacks of all the goroutines stops the world. - Document the Logger middleware and the --web.hide-port flag, and harden the new tests (bounded waits, error of Execute checked, t.Context()). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
… the slog caller Address the second review of the upgrade to echo v5: - Most errors logged by the http.Server are caused by the clients (like the HTTP/2 protocol errors), so any client can trigger them. They are now logged as warnings, and only the defects of the server (like a panic not recovered by a middleware) as errors. - With --log.method-trace, the logs of echo report the code calling slog as caller, instead of the slog handler. - The comment of isHTTP2Compatible and its warning explain why HTTP/2 is only offered with one of the cipher suites required by RFC 7540: net/http doesn't check it when the TLS configuration is on the listener. - --web.hide-port is tested with a running server. - The central error handler moves to error_handler.go, and the log of the listen address to logging.go, so each test file matches its source file. - Fix the reason why the levels Fatal and Panic of logrus are not used. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
…nt went away Address the third review of the upgrade to echo v5: - A middleware can replace the context of the request and cancel it (like echo's ContextTimeout), so every unexpected error was logged at the debug level. The context of the request is now saved as received by the server, before any middleware, and an error is logged at the debug level only when it is a cancellation and this context is canceled (the client went away). - The access log, the metrics and the error log use the method of the request as received, like the path, even if a handler rewrites it (like a proxy). middleware.RequestMethod returns it, next to middleware.RequestPath. - logrus has no way to read if it reports the caller without racing with SetReportCaller, so this setting is read once, when the server is built. - With --log.method-trace, the errors of the http.Server report the code of net/http that logged them as caller, instead of the writer. - The slog handler builds the logrus entry directly (12 allocations per log instead of 18), and the error handler builds its entry only when it logs. - The Logger middleware test checks the status and the method logged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Member
Author
|
reviewed by myself, I am moving forward on that topic. |
Nexucis
added a commit
to perses/perses
that referenced
this pull request
Nov 9, 2026
It reports the graceful shutdown timeout of the HTTP server as a failure of the task, and logs the recovered panics even when their value carries an HTTP status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Nexucis
added a commit
to perses/perses
that referenced
this pull request
Nov 9, 2026
The errors of the HTTP server are logged with the path logged by the access logger, even when a handler rewrote it (like the datasource proxy). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Nexucis
added a commit
to perses/perses
that referenced
this pull request
Nov 9, 2026
perses/common v0.33.0 is released with the upgrade to echo v5 (perses/common#373). Compared to the reviewed commit used so far, it: - writes the logs of echo with logrus, and logs the errors of the http.Server caused by the clients (like the TLS handshake errors) as warnings, and only the defects of the server as errors; - logs an unexpected error at the debug level when the client went away, and as a warning once the response is sent; - logs and counts the method of the request as received, like the path; - exposes Builder.IPExtractor and Builder.SchemeExtractor. They are not used: c.RealIP() keeps returning the address of the peer. It also calls Verify on the values of a map and the elements of an array of the configuration (perses/common#378), but no such value of the configuration of Perses implements Verify, and rejects a cron schedule that never matches any date (perses/common#377), not used by Perses. prometheus/common is bumped to v0.73.1, required by perses/common. It upgrades pandatix/go-abnf to v0.5.3, which no longer needs hashicorp/go-uuid. The nested modules go-sdk/test and internal/cli/cmd/dac/build/testdata/go are bumped too. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
This was referenced Nov 10, 2026
Nexucis
added a commit
to perses/metrics-usage
that referenced
this pull request
Nov 10, 2026
perses/common v0.33.0 upgrades echo to v5 (perses/common#373), so Metrics Usage moves to echo v5 too. - The pretty query parameter is no longer supported: echo v5 no longer indents the JSON responses. - A request body with data after the JSON value is rejected with 400, as echo v5 decodes it with json.Unmarshal. echo v4 ignored this data. - The message of the 400 responses to an invalid request has the format "code=400, message=Bad Request, err=<reason>". - The start line of the server, the logs of echo and the errors of the http.Server are logged with logrus. - The handlers take *echo.Context, echo.Map is replaced by map[string]string, and an unknown metric returns echo.ErrNotFound, with the same {"message":"Not Found"} response. - Test the responses of the metric endpoints. The tests pass with echo v4 and echo v5. - Update the help of --web.hide-port in the documentation. echo v4 stays as an indirect dependency, required by perses/perses v0.55.0. Signed-off-by: Augustin Husson <husson.augustin@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gladorme
pushed a commit
to Gladorme/perses
that referenced
this pull request
Nov 10, 2026
* [BREAKINGCHANGE] CORS: reject '*' with allow_credentials and deprecate the wildcard patterns of allow_origins Prepare the upgrade to echo v5, whose CORS middleware has no default allowed origin, panics when '*' is used with credentials, and no longer supports the wildcard patterns. - security.cors.allow_origins defaults explicitly to ["*"] when CORS is enabled. - Perses refuses to start when allow_credentials is true while allow_origins contains '*': the browsers reject the requests with credentials in this case. - The wildcard patterns of allow_origins are deprecated and will be removed in v0.58.0 (perses#4721). A warning is logged at startup for each of them. - The CORS middleware is built by middleware.CORS, tested to pin the matching of the origins before the upgrade. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * [BREAKINGCHANGE] Upgrade echo to v5 echo v4 only receives bug fixes until 2026-12-31 and security fixes until 2027-12-31. This upgrades github.com/labstack/echo to v5.4.4, github.com/labstack/echo-jwt to v5.0.2 and github.com/perses/common to the version using echo v5. Most of the change is mechanical: echo.Context is now the struct *echo.Context. The behavior changes of echo v5 are handled as follows: - HandleError keeps the errors carrying an HTTP status (echo.StatusCode), as the errors like echo.ErrNotFound are no longer *echo.HTTPError. Otherwise, every unknown route would answer 500 instead of 404. - The CORS origins are matched like echo v4 did (UnsafeAllowOriginFunc), as echo v5 doesn't support the deprecated wildcard patterns anymore. - The Decompress middleware removes the gzip headers whatever their case, like echo v5 decompresses the body. - The status of the proxied responses is read with echo.UnwrapResponse. - The native authorization reads the user through a small interface, so the concurrency test can still pause a request in the middle of GetPermissions. - c.RealIP() no longer trusts the X-Forwarded-For header sent by the client: the X-Real-IP sent to the datasources is the address of the peer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Allow every CORS origin by default in the middleware and send real preflight requests in the e2e tests - Like echo v4, the CORS middleware allows every origin when allow_origins is empty, even when the configuration was not verified (like in the e2e tests). - echo v5 only handles an OPTIONS request as a CORS preflight when it carries Access-Control-Request-Method, like the browsers always do (Fetch standard). The e2e tests now send this header. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Bump perses/common to the reviewed version of perses/common#373 It reports the graceful shutdown timeout of the HTTP server as a failure of the task, and logs the recovered panics even when their value carries an HTTP status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Bump perses/common to the reviewed version of perses/common#373 The errors of the HTTP server are logged with the path logged by the access logger, even when a handler rewrote it (like the datasource proxy). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * CORS: only reject '*' with credentials when it is the first allowed origin The allowed origins are checked in order, so a list like ["https://app.example.com", "*"] with allow_credentials worked for the origins listed before '*'. Only the lists starting with '*' (including the default value) answer 'Access-Control-Allow-Origin: *' to every origin, which the browsers reject for the requests with credentials. Reject only these lists at startup, and deprecate '*' placed after other entries with credentials: it will be rejected in v0.58.0, like echo v5 does natively. Document that a '*' label of a wildcard pattern ignores the labels on its left (https://admin.*.example.com allows https://victim.example.com), and that the CORS changes only apply when security.cors.enable is true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Bump perses/common to v0.33.0 perses/common v0.33.0 is released with the upgrade to echo v5 (perses/common#373). Compared to the reviewed commit used so far, it: - writes the logs of echo with logrus, and logs the errors of the http.Server caused by the clients (like the TLS handshake errors) as warnings, and only the defects of the server as errors; - logs an unexpected error at the debug level when the client went away, and as a warning once the response is sent; - logs and counts the method of the request as received, like the path; - exposes Builder.IPExtractor and Builder.SchemeExtractor. They are not used: c.RealIP() keeps returning the address of the peer. It also calls Verify on the values of a map and the elements of an array of the configuration (perses/common#378), but no such value of the configuration of Perses implements Verify, and rejects a cron schedule that never matches any date (perses/common#377), not used by Perses. prometheus/common is bumped to v0.73.1, required by perses/common. It upgrades pandatix/go-abnf to v0.5.3, which no longer needs hashicorp/go-uuid. The nested modules go-sdk/test and internal/cli/cmd/dac/build/testdata/go are bumped too. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Document the logs of the HTTP server written with logrus Since the upgrade to echo v5, the address listened by the server is logged with logrus. CONTRIBUTING.md and the installation docs still showed the line printed by echo v4 ("⇨ http server started on ..."): they now show the log, in the text format used with a terminal, or without one in a container. The upgrade guide tells that this log, the logs of echo and the errors of the http.Server are now written on the standard error with the configured log format and level, and that the errors caused by the clients (like the TLS handshake errors) are warnings, hidden by --log.level=error. The help of --web.hide-port follows perses/common v0.33.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Send the reason why the body of an unsaved datasource request is invalid echo v5's binder wraps the decoding errors of the body in an *echo.HTTPError whose message is only "Bad Request", and the error handler doesn't send the wrapped errors to the client. The /proxy/unsaved/... endpoints returned the error of Bind as is, so they lost the reason sent with echo v4, like the validation errors of unsavedProxyBody ("missing field 'method'", "invalid method ...", "invalid config: ..."). The reason is now the message of the error, with the same status. The other endpoints already put the error of Bind in their message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Answer 400 to the request bodies that are not valid gzip streams When a request declares a gzip body that is not a valid gzip stream, the Decompress middleware of echo returns the error of the gzip reader as is (gzip.ErrHeader, or io.ErrUnexpectedEOF when the header is truncated). Without an HTTP status, this error caused by the client was answered with 500, and the central error handler of perses/common logs it at the error level, before the authentication. It is now answered with 400, and it is no longer logged as an unexpected error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Upgrade guide: echo v4 already printed the panics The Recover middleware of echo v4 printed the panics with the stacks of all the goroutines on the standard output, whatever the log level. What changes is that they are logged with logrus, with the stack of the goroutine that panicked only, and counted in the access logs and in the metric. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Only answer 400 to the errors of the gzip reader in Decompress The Decompress wrapper checked the error returned by the whole chain. As it is a pre-middleware, an io.ErrUnexpectedEOF without HTTP status coming from the rest of the chain was answered with 400 as well. For example, the Recover middleware returns a panic as a *PanicStackError wrapping the panic value, so a panic with io.ErrUnexpectedEOF was answered with 400 instead of 500. The echo middleware returns the errors of the gzip reader without calling the next handler. The wrapper now sets a flag in the echo context when the next handler is called, and only maps the errors returned before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * Upgrade guide: document the pretty query parameter and the OPTIONS requests echo v5 no longer indents the JSON responses when the request has the pretty query parameter. Its CORS middleware only answers the OPTIONS requests carrying Origin and Access-Control-Request-Method as preflights: the other ones are now answered by the router (204 with the Allow header, or 404 when no route matches the path), without the CORS headers of a preflight. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> --------- Signed-off-by: Augustin Husson <husson.augustin@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Upgrade the
echopackage to echo v5 (v5.4.4). echo v4 only receives bug fixes until 2026-12-31 and security fixes until 2027-12-31.Stacked on #372 (the builder tests are fixed there). This is the first step of the upgrade of Perses to echo v5.
Breaking changes / how to migrate
The Builder methods and the
--web.*flags don't change, but the types they use are now the echo v5 ones:Register.RegisterRoute(*echo.Echo);Builder.Middleware/PreMiddleware(echo.MiddlewareFunc);Builder.GzipSkipper(middleware.Skipper), which is now afunc(*echo.Context) bool.In your code: replace
echo.Contextwith*echo.Contextandlabstack/echo/v4withlabstack/echo/v5, then see API_CHANGES_V5.md (echo.Mapis removed,Context.Response()returns anhttp.ResponseWriter, etc.).middleware.Logger/LoggerWithConfigandMetrics.ProcessHTTPRequestreturn the error of the next handler instead of handling it withc.Error(which doesn't exist in v5). The error is handled once, by the central error handler. The status logged and counted is resolved from the error, so it is the same as before.With the default middlewares, the panics are now logged with their stack trace and counted in the access logs and in the metrics, with the status of the response sent:
500, or the status carried by the value of the panic (likepanic(echo.NewHTTPError(...))). Before, they were skipped by both. Like before, the panics withhttp.ErrAbortHandler(used to abort a response, likehttputil.ReverseProxydoes) are not recovered, so they are still skipped.echo v5 changed the defaults of
Context.RealIP()andContext.Scheme(), as the forwarding headers can be spoofed by any client:RealIP()returns the address of the peer instead of trusting theX-Forwarded-For/X-Real-IPheaders;Scheme()only trustsX-Forwarded-Proto(and the similar headers) when the request comes from a loopback, link-local or private address.Behind proxies, use the new
Builder.IPExtractor/Builder.SchemeExtractorto trust them, likebuilder.IPExtractor(echo.ExtractIPFromXFFHeader(echo.TrustIPRange(proxyNetwork))).Changes
echo.StartConfig. It gracefully shuts the server down (30s) when the context is canceled, which replaces the goroutine and channel ofExecute, and the shutdown inFinalize(which now only returns the error of the graceful shutdown).Echo.ServerandEcho.TLSServer, so the timeouts are set inBeforeServeFunc. They are set even when they are zero, because echo v5 sets a defaultReadTimeoutof 30s, which would cut the long requests. The timeouts themselves don't change.StartConfig.TLSConfig.StartConfig.StartTLSis not used, because it loads the certificate only once and reads the paths from the working directory.Logger → Metrics → Recover → Gzip(before:Recover → Logger → Gzip → Metrics).Recoverno longer logs the panic: it returns it as an error, logged with its stack by the new central error handler.{"message": ...}), without exposing the internal error.middleware.ContextTimeout, which cancels its own context).log/slog(JSON on stdout by default), so echo is given a slog handler writing with logrus: all its logs have the same format, level and output as the other logs.http(s) server started on <address>(still hidden with--web.hide-port). echo v4 printed⇨ http(s) server started on <address>on stdout: the Perses docs showing this line (CONTRIBUTING.md,docs/installation/from-source.mdanddocs/installation/in-a-container.md) will need an update when Perses is upgraded.http.Serverare logged with logrus. Most of them are caused by the clients (like the TLS handshake errors and the HTTP/2 protocol errors), so they are logged as warnings, as any client can trigger them. Only the defects of the server (like a panic not recovered by a middleware) are logged as errors.--log.method-trace, the logs of echo and the errors of thehttp.Serverreport the code that logged them as caller (instead of the bridge to logrus). As logrus has no way to read this setting without racing withSetReportCaller, it is read when the server is built: logrus must be configured before, likeapp.Runnerdoes.Finalize(server shutdown not properly: ...), so the task reports the failure.Builder.IPExtractorandBuilder.SchemeExtractorconfigure howContext.RealIP()andContext.Scheme()trust the forwarding headers.middleware.RequestMethodandmiddleware.RequestPathreturn the method and the escaped path of the request as logged by the Logger middleware.labstack/gommon,valyala/fasttemplate,go-colorable,go-isatty,bytebufferpooland the indirectgolang.org/x/crypto,x/net,x/textare removed.Validation
make checkformat: passedgo test -race -count=1 ./...: passedgolangci-lint run ./...: 0 issueshttp.Servererrors and the listen address are logged with logrus;Finalize;http.Server;http.Server, and--web.hide-portwith a running server;