From 38bffc971131ff1522d01af23345f7b0ed45b034 Mon Sep 17 00:00:00 2001 From: Pawan Kumar Regoti Date: Mon, 13 Apr 2026 09:16:56 +0200 Subject: [PATCH 01/10] Use path.Join for constructing Loki query URL https://github.com/kedacore/keda/issues/7646 Signed-off-by: Pawan Kumar Regoti --- pkg/scalers/loki_scaler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index c565eb9b11b..22558c8eded 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "path" "net/http" "net/url" "strconv" @@ -119,7 +120,7 @@ func (s *lokiScaler) ExecuteLokiQuery(ctx context.Context) (float64, error) { if err != nil { return -1, err } - u.Path = "/loki/api/v1/query" + u.Path = path.Join(u.Path, "/loki/api/v1/query") u.RawQuery = url.Values{"query": []string{s.metadata.Query}}.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) From 14f2ebb6779e5c452505054bb3140de2f42b6879 Mon Sep 17 00:00:00 2001 From: Pawan Kumar Regoti Date: Mon, 13 Apr 2026 09:44:52 +0200 Subject: [PATCH 02/10] Update Loki Scaler serverAddress path handling Updated Loki Scaler to append '/loki/api/v1/query' to existing serverAddress path. Signed-off-by: Pawan Kumar Regoti --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49206cb4a7d..a89cd6f0c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio - **Github Runner Scaler**: Improve URL construction and error handling ([#7495](https://github.com/kedacore/keda/pull/7495)) - **Github Runner Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469)) - **Loki Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469)) +- **Loki Scaler**: `serverAddress` now appends `/loki/api/v1/query` to the end of existing path instead of overriding - **Metrics API Scaler**: Fix `aggregateFromKubeServiceEndpoints` using empty label selector that matched all EndpointSlices in the namespace instead of only the target service's ([#7641](https://github.com/kedacore/keda/issues/7641)) - **NATS JetStream Scaler**: URL-encode user input in monitoring URL construction ([#7483](https://github.com/kedacore/keda/pull/7483)) - **Prometheus Scaler**: Handle NaN results in the same manner as Inf ([#7475](https://github.com/kedacore/keda/issues/7475)) From 0f5d937ee7194fc3fe7977d196e26bf10b04458c Mon Sep 17 00:00:00 2001 From: Pawan Kumar Regoti Date: Mon, 13 Apr 2026 09:49:21 +0200 Subject: [PATCH 03/10] Update CHANGELOG for Loki Scaler changes Updated Loki Scaler entry to include pull request reference. Signed-off-by: Pawan Kumar Regoti --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a89cd6f0c98..e7ad8ff1c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,7 +96,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio - **Github Runner Scaler**: Improve URL construction and error handling ([#7495](https://github.com/kedacore/keda/pull/7495)) - **Github Runner Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469)) - **Loki Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469)) -- **Loki Scaler**: `serverAddress` now appends `/loki/api/v1/query` to the end of existing path instead of overriding +- **Loki Scaler**: `serverAddress` now appends `/loki/api/v1/query` to the end of existing path instead of overriding ([#7648](https://github.com/kedacore/keda/pull/7648)) - **Metrics API Scaler**: Fix `aggregateFromKubeServiceEndpoints` using empty label selector that matched all EndpointSlices in the namespace instead of only the target service's ([#7641](https://github.com/kedacore/keda/issues/7641)) - **NATS JetStream Scaler**: URL-encode user input in monitoring URL construction ([#7483](https://github.com/kedacore/keda/pull/7483)) - **Prometheus Scaler**: Handle NaN results in the same manner as Inf ([#7475](https://github.com/kedacore/keda/issues/7475)) From 78e76013f0deaecc3413f3d4614fa2c2904f602e Mon Sep 17 00:00:00 2001 From: Pawan Kumar Regoti Date: Mon, 13 Apr 2026 11:00:12 +0200 Subject: [PATCH 04/10] Reorder import statements in loki_scaler.go Signed-off-by: Pawan Kumar Regoti --- pkg/scalers/loki_scaler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index 22558c8eded..e88a587dc6b 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -5,9 +5,9 @@ import ( "encoding/json" "fmt" "io" - "path" "net/http" "net/url" + "path" "strconv" "github.com/go-logr/logr" From 601e7596ec4a7fc327a2f3f874c3fc81d32346fd Mon Sep 17 00:00:00 2001 From: Pawan Regoti Date: Mon, 13 Apr 2026 21:38:50 +0200 Subject: [PATCH 05/10] refactor into separate function for testability Signed-off-by: Pawan Regoti --- pkg/scalers/loki_scaler.go | 21 ++++++++++- pkg/scalers/loki_scaler_test.go | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index e88a587dc6b..828f1aae4e5 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -9,6 +9,7 @@ import ( "net/url" "path" "strconv" + "strings" "github.com/go-logr/logr" v2 "k8s.io/api/autoscaling/v2" @@ -116,11 +117,10 @@ func (s *lokiScaler) GetMetricSpecForScaling(context.Context) []v2.MetricSpec { } func (s *lokiScaler) ExecuteLokiQuery(ctx context.Context) (float64, error) { - u, err := url.ParseRequestURI(s.metadata.ServerAddress) + u, err := getServerAddress(&s.metadata) if err != nil { return -1, err } - u.Path = path.Join(u.Path, "/loki/api/v1/query") u.RawQuery = url.Values{"query": []string{s.metadata.Query}}.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) @@ -219,3 +219,20 @@ func (s *lokiScaler) GetMetricsAndActivity(ctx context.Context, metricName strin metric := GenerateMetricInMili(metricName, val) return []external_metrics.ExternalMetricValue{metric}, val > s.metadata.ActivationThreshold, nil } + +func getServerAddress(metadata *lokiMetadata) (url.URL, error) { + loki_path := "/loki/api/v1/query" + u, err := url.ParseRequestURI(metadata.ServerAddress) + if err != nil { + return url.URL{}, err + } + + if strings.HasPrefix(loki_path, u.Path) { + remaining := strings.TrimPrefix(loki_path, u.Path) + u.Path = path.Join(u.Path, remaining) + } else { + u.Path = path.Join(u.Path, loki_path) + } + + return *u, nil +} diff --git a/pkg/scalers/loki_scaler_test.go b/pkg/scalers/loki_scaler_test.go index a089f186dc0..ebc1f6fd7a6 100644 --- a/pkg/scalers/loki_scaler_test.go +++ b/pkg/scalers/loki_scaler_test.go @@ -250,3 +250,69 @@ func TestLokiScalerTenantHeader(t *testing.T) { _, err := scaler.ExecuteLokiQuery(context.TODO()) assert.NoError(t, err) } + +type getServerAddressTestData struct { + name string + serverAddress string + expectedPath string + isError bool +} + +var testGetServerAddressData = []getServerAddressTestData{ + { + name: "no path in URL", + serverAddress: "http://localhost:3100", + expectedPath: "/loki/api/v1/query", + isError: false, + }, + { + name: "URL with trailing slash", + serverAddress: "http://localhost:3100/", + expectedPath: "/loki/api/v1/query", + isError: false, + }, + { + name: "URL with partial loki path", + serverAddress: "http://localhost:3100/loki", + expectedPath: "/loki/api/v1/query", + isError: false, + }, + { + name: "URL with longer partial loki path", + serverAddress: "http://localhost:3100/loki/api/v1", + expectedPath: "/loki/api/v1/query", + isError: false, + }, + { + name: "URL with full loki path already present", + serverAddress: "http://localhost:3100/loki/api/v1/query", + expectedPath: "/loki/api/v1/query", + isError: false, + }, + { + name: "URL with custom prefix", + serverAddress: "http://localhost:3100/custom", + expectedPath: "/custom/loki/api/v1/query", + isError: false, + }, + { + name: "invalid URL", + serverAddress: "not-a-url", + isError: true, + }, +} + +func TestGetServerAddress(t *testing.T) { + for _, testData := range testGetServerAddressData { + t.Run(testData.name, func(t *testing.T) { + meta := &lokiMetadata{ServerAddress: testData.serverAddress} + u, err := getServerAddress(meta) + if testData.isError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, testData.expectedPath, u.Path) + }) + } +} From 087b0930adce3c7620d6573d0e9f3afcb2475462 Mon Sep 17 00:00:00 2001 From: Pawan Regoti Date: Mon, 13 Apr 2026 21:51:42 +0200 Subject: [PATCH 06/10] propery rename Signed-off-by: Pawan Regoti --- pkg/scalers/loki_scaler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index 828f1aae4e5..cc0b9f40a00 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -221,17 +221,17 @@ func (s *lokiScaler) GetMetricsAndActivity(ctx context.Context, metricName strin } func getServerAddress(metadata *lokiMetadata) (url.URL, error) { - loki_path := "/loki/api/v1/query" + const lokiPath = "/loki/api/v1/query" u, err := url.ParseRequestURI(metadata.ServerAddress) if err != nil { return url.URL{}, err } - if strings.HasPrefix(loki_path, u.Path) { - remaining := strings.TrimPrefix(loki_path, u.Path) + if strings.HasPrefix(lokiPath, u.Path) { + remaining := strings.TrimPrefix(lokiPath, u.Path) u.Path = path.Join(u.Path, remaining) } else { - u.Path = path.Join(u.Path, loki_path) + u.Path = path.Join(u.Path, lokiPath) } return *u, nil From 3d293643f3f913b466832bbf6ab642dbed1c6cb7 Mon Sep 17 00:00:00 2001 From: Pawan Regoti Date: Mon, 13 Apr 2026 22:25:18 +0200 Subject: [PATCH 07/10] added edge case of partial path Signed-off-by: Pawan Regoti --- pkg/scalers/loki_scaler.go | 6 +++++- pkg/scalers/loki_scaler_test.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index cc0b9f40a00..2ff31399d95 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -227,7 +227,11 @@ func getServerAddress(metadata *lokiMetadata) (url.URL, error) { return url.URL{}, err } - if strings.HasPrefix(lokiPath, u.Path) { + if u.Path == lokiPath { + return *u, nil + } + + if strings.HasPrefix(lokiPath, u.Path+"/") { remaining := strings.TrimPrefix(lokiPath, u.Path) u.Path = path.Join(u.Path, remaining) } else { diff --git a/pkg/scalers/loki_scaler_test.go b/pkg/scalers/loki_scaler_test.go index ebc1f6fd7a6..5bf9876f253 100644 --- a/pkg/scalers/loki_scaler_test.go +++ b/pkg/scalers/loki_scaler_test.go @@ -295,6 +295,12 @@ var testGetServerAddressData = []getServerAddressTestData{ expectedPath: "/custom/loki/api/v1/query", isError: false, }, + { + name: "URL with partial loki path not on segment boundary", + serverAddress: "http://localhost:3100/lo", + expectedPath: "/lo/loki/api/v1/query", + isError: false, + }, { name: "invalid URL", serverAddress: "not-a-url", From 5e4c594d472cd2d91e28edf30288dad09677e67a Mon Sep 17 00:00:00 2001 From: Pawan Regoti Date: Mon, 13 Apr 2026 22:40:05 +0200 Subject: [PATCH 08/10] another edge case with trailing slash Signed-off-by: Pawan Regoti --- pkg/scalers/loki_scaler.go | 2 ++ pkg/scalers/loki_scaler_test.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index 2ff31399d95..071b572ec5f 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -227,6 +227,8 @@ func getServerAddress(metadata *lokiMetadata) (url.URL, error) { return url.URL{}, err } + u.Path = strings.TrimRight(u.Path, "/") + if u.Path == lokiPath { return *u, nil } diff --git a/pkg/scalers/loki_scaler_test.go b/pkg/scalers/loki_scaler_test.go index 5bf9876f253..f3077e286db 100644 --- a/pkg/scalers/loki_scaler_test.go +++ b/pkg/scalers/loki_scaler_test.go @@ -289,6 +289,12 @@ var testGetServerAddressData = []getServerAddressTestData{ expectedPath: "/loki/api/v1/query", isError: false, }, + { + name: "URL with full loki path and trailing slash", + serverAddress: "http://localhost:3100/loki/api/v1/query/", + expectedPath: "/loki/api/v1/query", + isError: false, + }, { name: "URL with custom prefix", serverAddress: "http://localhost:3100/custom", From 23b2c14734ae6c04497a60e926fc97cae198cb1b Mon Sep 17 00:00:00 2001 From: Pawan Regoti Date: Wed, 15 Apr 2026 13:10:35 +0200 Subject: [PATCH 09/10] added suffix check Signed-off-by: Pawan Regoti --- pkg/scalers/loki_scaler.go | 2 +- pkg/scalers/loki_scaler_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/scalers/loki_scaler.go b/pkg/scalers/loki_scaler.go index 071b572ec5f..a77641f1d45 100644 --- a/pkg/scalers/loki_scaler.go +++ b/pkg/scalers/loki_scaler.go @@ -229,7 +229,7 @@ func getServerAddress(metadata *lokiMetadata) (url.URL, error) { u.Path = strings.TrimRight(u.Path, "/") - if u.Path == lokiPath { + if strings.HasSuffix(u.Path, lokiPath) { return *u, nil } diff --git a/pkg/scalers/loki_scaler_test.go b/pkg/scalers/loki_scaler_test.go index f3077e286db..5cdefb98b32 100644 --- a/pkg/scalers/loki_scaler_test.go +++ b/pkg/scalers/loki_scaler_test.go @@ -301,6 +301,12 @@ var testGetServerAddressData = []getServerAddressTestData{ expectedPath: "/custom/loki/api/v1/query", isError: false, }, + { + name: "URL with custom prefix", + serverAddress: "http://localhost:3100/custom/loki/api/v1/query", + expectedPath: "/custom/loki/api/v1/query", + isError: false, + }, { name: "URL with partial loki path not on segment boundary", serverAddress: "http://localhost:3100/lo", From c504eea8736e73b10db19fc5d772036de74f6ce7 Mon Sep 17 00:00:00 2001 From: Pawan Regoti Date: Thu, 16 Apr 2026 16:46:50 +0200 Subject: [PATCH 10/10] updated tests Signed-off-by: Pawan Regoti --- pkg/scalers/loki_scaler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/scalers/loki_scaler_test.go b/pkg/scalers/loki_scaler_test.go index 5cdefb98b32..2852aee4a07 100644 --- a/pkg/scalers/loki_scaler_test.go +++ b/pkg/scalers/loki_scaler_test.go @@ -302,7 +302,7 @@ var testGetServerAddressData = []getServerAddressTestData{ isError: false, }, { - name: "URL with custom prefix", + name: "URL with custom prefix and full loki path already present", serverAddress: "http://localhost:3100/custom/loki/api/v1/query", expectedPath: "/custom/loki/api/v1/query", isError: false,