Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditionally set url label for 404 responses #111

Merged
merged 9 commits into from
Mar 21, 2024
6 changes: 5 additions & 1 deletion echoprometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ type MiddlewareConfig struct {
AfterNext func(c echo.Context, err error)

timeNow func() time.Time

// If DoNotUseRequestPathFor404 is true, all 404 responses (due to non-matching route) will have the same `url` label and
// thus won't generate new metrics.
DoNotUseRequestPathFor404 bool
}

type LabelValueFunc func(c echo.Context, err error) string
Expand Down Expand Up @@ -246,7 +250,7 @@ func (conf MiddlewareConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
}

url := c.Path() // contains route path ala `/users/:id`
if url == "" {
if url == "" && !conf.DoNotUseRequestPathFor404 {
// as of Echo v4.10.1 path is empty for 404 cases (when router did not find any matching routes)
// in this case we use actual path from request to have some distinction in Prometheus
url = c.Request().URL.Path
Expand Down
39 changes: 39 additions & 0 deletions echoprometheus/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,45 @@ func TestRunPushGatewayGatherer(t *testing.T) {

assert.EqualError(t, err, "code=400, message=post metrics request did not succeed")
assert.True(t, receivedMetrics)
unregisterDefaults("myapp")
}

// TestSetPathFor404NoMatchingRoute tests that the url is not included in the metric when
// the 404 response is due to no matching route
func TestSetPathFor404NoMatchingRoute(t *testing.T) {
e := echo.New()

e.Use(NewMiddlewareWithConfig(MiddlewareConfig{DoNotUseRequestPathFor404: true, Subsystem: defaultSubsystem}))
e.GET("/metrics", NewHandler())

assert.Equal(t, http.StatusNotFound, request(e, "/nonExistentPath"))

s, code := requestBody(e, "/metrics")
assert.Equal(t, http.StatusOK, code)
assert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url=""} 1`, defaultSubsystem))
assert.NotContains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/nonExistentPath"} 1`, defaultSubsystem))

unregisterDefaults(defaultSubsystem)
}

// TestSetPathFor404Logic tests that the url is included in the metric when the 404 response is due to logic
func TestSetPathFor404Logic(t *testing.T) {
unregisterDefaults("myapp")
e := echo.New()

e.Use(NewMiddlewareWithConfig(MiddlewareConfig{DoNotUseRequestPathFor404: true, Subsystem: defaultSubsystem}))
e.GET("/metrics", NewHandler())

e.GET("/sample", echo.NotFoundHandler)

assert.Equal(t, http.StatusNotFound, request(e, "/sample"))

s, code := requestBody(e, "/metrics")
assert.Equal(t, http.StatusOK, code)
assert.NotContains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url=""} 1`, defaultSubsystem))
assert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/sample"} 1`, defaultSubsystem))

unregisterDefaults(defaultSubsystem)
}

func requestBody(e *echo.Echo, path string) (string, int) {
Expand Down
Loading