fix(prometheus): report subsonic error code (#4282)
* fix(prometheus): report subsonic error code * address feedback
This commit is contained in:
@@ -20,7 +20,7 @@ import (
|
|||||||
type Metrics interface {
|
type Metrics interface {
|
||||||
WriteInitialMetrics(ctx context.Context)
|
WriteInitialMetrics(ctx context.Context)
|
||||||
WriteAfterScanMetrics(ctx context.Context, success bool)
|
WriteAfterScanMetrics(ctx context.Context, success bool)
|
||||||
RecordRequest(ctx context.Context, endpoint, method, client string, status int, elapsed int64)
|
RecordRequest(ctx context.Context, endpoint, method, client string, status int32, elapsed int64)
|
||||||
RecordPluginRequest(ctx context.Context, plugin, method string, ok bool, elapsed int64)
|
RecordPluginRequest(ctx context.Context, plugin, method string, ok bool, elapsed int64)
|
||||||
GetHandler() http.Handler
|
GetHandler() http.Handler
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func (m *metrics) WriteAfterScanMetrics(ctx context.Context, success bool) {
|
|||||||
getPrometheusMetrics().mediaScansCounter.With(scanLabels).Inc()
|
getPrometheusMetrics().mediaScansCounter.With(scanLabels).Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *metrics) RecordRequest(_ context.Context, endpoint, method, client string, status int, elapsed int64) {
|
func (m *metrics) RecordRequest(_ context.Context, endpoint, method, client string, status int32, elapsed int64) {
|
||||||
httpLabel := prometheus.Labels{
|
httpLabel := prometheus.Labels{
|
||||||
"endpoint": endpoint,
|
"endpoint": endpoint,
|
||||||
"method": method,
|
"method": method,
|
||||||
@@ -233,7 +233,7 @@ func (n noopMetrics) WriteInitialMetrics(context.Context) {}
|
|||||||
|
|
||||||
func (n noopMetrics) WriteAfterScanMetrics(context.Context, bool) {}
|
func (n noopMetrics) WriteAfterScanMetrics(context.Context, bool) {}
|
||||||
|
|
||||||
func (n noopMetrics) RecordRequest(context.Context, string, string, string, int, int64) {}
|
func (n noopMetrics) RecordRequest(context.Context, string, string, string, int32, int64) {}
|
||||||
|
|
||||||
func (n noopMetrics) RecordPluginRequest(context.Context, string, string, bool, int64) {}
|
func (n noopMetrics) RecordPluginRequest(context.Context, string, string, bool, int64) {}
|
||||||
|
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub
|
|||||||
sendError(w, r, err)
|
sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Status == responses.StatusOK {
|
if payload.Status == responses.StatusOK {
|
||||||
if log.IsGreaterOrEqualTo(log.LevelTrace) {
|
if log.IsGreaterOrEqualTo(log.LevelTrace) {
|
||||||
log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK", "body", string(response))
|
log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK", "body", string(response))
|
||||||
@@ -342,6 +343,17 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub
|
|||||||
} else {
|
} else {
|
||||||
log.Warn(r.Context(), "API: Failed response", "endpoint", r.URL.Path, "error", payload.Error.Code, "message", payload.Error.Message)
|
log.Warn(r.Context(), "API: Failed response", "endpoint", r.URL.Path, "error", payload.Error.Code, "message", payload.Error.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusPointer, ok := r.Context().Value(subsonicErrorPointer).(*int32)
|
||||||
|
|
||||||
|
if ok && statusPointer != nil {
|
||||||
|
if payload.Status == responses.StatusOK {
|
||||||
|
*statusPointer = 0
|
||||||
|
} else {
|
||||||
|
*statusPointer = payload.Error.Code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := w.Write(response); err != nil {
|
if _, err := w.Write(response); err != nil {
|
||||||
log.Error(r, "Error sending response to client", "endpoint", r.URL.Path, "payload", string(response), err)
|
log.Error(r, "Error sending response to client", "endpoint", r.URL.Path, "payload", string(response), err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/navidrome/navidrome/utils/gg"
|
"github.com/navidrome/navidrome/utils/gg"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("sendResponse", func() {
|
var _ = Describe("sendResponse", func() {
|
||||||
@@ -109,4 +110,18 @@ var _ = Describe("sendResponse", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("updates status pointer when an error occurs", func() {
|
||||||
|
pointer := int32(0)
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), subsonicErrorPointer, &pointer)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
payload.Status = responses.StatusFailed
|
||||||
|
payload.Error = &responses.Error{Code: responses.ErrorDataNotFound}
|
||||||
|
|
||||||
|
sendResponse(w, r, payload)
|
||||||
|
Expect(w.Code).To(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
Expect(pointer).To(Equal(responses.ErrorDataNotFound))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -226,21 +226,35 @@ func playerIDCookieName(userName string) string {
|
|||||||
return cookieName
|
return cookieName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subsonicErrorPointer = "subsonicErrorPointer"
|
||||||
|
|
||||||
func recordStats(metrics metrics.Metrics) func(next http.Handler) http.Handler {
|
func recordStats(metrics metrics.Metrics) func(next http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||||
|
|
||||||
|
status := int32(-1)
|
||||||
|
contextWithStatus := context.WithValue(r.Context(), subsonicErrorPointer, &status)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
elapsed := time.Since(start).Milliseconds()
|
||||||
|
|
||||||
// We want to get the client name (even if not present for certain endpoints)
|
// We want to get the client name (even if not present for certain endpoints)
|
||||||
p := req.Params(r)
|
p := req.Params(r)
|
||||||
client, _ := p.String("c")
|
client, _ := p.String("c")
|
||||||
|
|
||||||
metrics.RecordRequest(r.Context(), strings.Replace(r.URL.Path, ".view", "", 1), r.Method, client, ww.Status(), time.Since(start).Milliseconds())
|
// If there is no Subsonic status (e.g., HTTP 501 not implemented), fallback to HTTP
|
||||||
|
if status == -1 {
|
||||||
|
status = int32(ww.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
shortPath := strings.Replace(r.URL.Path, ".view", "", 1)
|
||||||
|
|
||||||
|
metrics.RecordRequest(r.Context(), shortPath, r.Method, client, status, elapsed)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
next.ServeHTTP(ww, r)
|
next.ServeHTTP(ww, r.WithContext(contextWithStatus))
|
||||||
}
|
}
|
||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user