Skip to content

Commit 7d2af04

Browse files
committed
chore(logs): switch k8s route discovery and main to contextual logging
Thread context through GetMetricsBackendURL, GetAlertmanagerURL, and all determine*URL helpers in main. Store the configured logger in the root context at startup so all downstream code uses contextual logging. This is the final step in switching obs-mcp from global slog to contextual logging, aligning with upstream kubernetes-mcp-server to prepare for OTel log export. Signed-off-by: Jayapriya Pai <janantha@redhat.com>
1 parent 53f3011 commit 7d2af04

4 files changed

Lines changed: 63 additions & 50 deletions

File tree

cmd/obs-mcp/main.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/rhobs/obs-mcp/pkg/auth"
1717
"github.com/rhobs/obs-mcp/pkg/k8s"
18+
"github.com/rhobs/obs-mcp/pkg/logging"
1819
mcpserver "github.com/rhobs/obs-mcp/pkg/mcp"
1920
"github.com/rhobs/obs-mcp/pkg/otelcol"
2021
"github.com/rhobs/obs-mcp/pkg/prometheus"
@@ -68,6 +69,8 @@ func main() {
6869

6970
// Configure slog with specified log level
7071
configureLogging(*logLevel)
72+
ctx := logging.NewContext(context.Background(), slog.Default())
73+
logger := logging.FromContext(ctx)
7174

7275
// Parse and validate auth mode
7376
parsedAuthMode, err := auth.ParseAuthMode(*authMode)
@@ -92,7 +95,7 @@ func main() {
9295
metricsBackendURL := ""
9396
metricsURLSource := ""
9497
if slices.Contains(parsedToolsets, mcpserver.ToolsetMetrics) {
95-
metricsBackendURL, metricsURLSource, err = determineMetricsBackendURL(parsedAuthMode, parsedMetricsBackend)
98+
metricsBackendURL, metricsURLSource, err = determineMetricsBackendURL(ctx, parsedAuthMode, parsedMetricsBackend)
9699
if err != nil {
97100
log.Fatalf("%v", err)
98101
}
@@ -101,7 +104,7 @@ func main() {
101104
alertmanagerURL := ""
102105
alertmanagerURLSource := ""
103106
if slices.Contains(parsedToolsets, mcpserver.ToolsetMetrics) {
104-
alertmanagerURL, alertmanagerURLSource, err = determineAlertmanagerURL(parsedAuthMode)
107+
alertmanagerURL, alertmanagerURLSource, err = determineAlertmanagerURL(ctx, parsedAuthMode)
105108
if err != nil {
106109
log.Fatalf("%v", err)
107110
}
@@ -111,7 +114,7 @@ func main() {
111114
lokiResolvedURL := ""
112115
lokiURLSource := ""
113116
if slices.Contains(parsedToolsets, mcpserver.ToolsetLogs) {
114-
lokiResolvedURL, lokiURLSource, err = determineLokiURL(parsedAuthMode, *lokiURL, *lokiUseRoute)
117+
lokiResolvedURL, lokiURLSource, err = determineLokiURL(ctx, parsedAuthMode, *lokiURL, *lokiUseRoute)
115118
if err != nil {
116119
log.Fatalf("%v", err)
117120
}
@@ -155,7 +158,7 @@ func main() {
155158
tempoResolvedURL := ""
156159
tempoURLSource := ""
157160
if slices.Contains(parsedToolsets, mcpserver.ToolsetTraces) {
158-
tempoResolvedURL, tempoURLSource = determineTempoURL(*tempoURL)
161+
tempoResolvedURL, tempoURLSource = determineTempoURL(ctx, *tempoURL)
159162
}
160163

161164
// Create MCP options
@@ -184,7 +187,7 @@ func main() {
184187
log.Fatalf("Failed to create MCP server: %v", err)
185188
}
186189

187-
slog.Info("Starting server",
190+
logger.Info("Starting server",
188191
"toolsets", opts.Toolsets,
189192
"auth_mode", opts.AuthMode,
190193
"metrics_backend_url", opts.MetricsBackendURL,
@@ -201,14 +204,13 @@ func main() {
201204
// Choose server mode based on flags
202205
if *listen != "" {
203206
// HTTP mode
204-
ctx := context.Background()
205207
if err := mcpserver.Serve(ctx, mcpServer, *listen, opts.AuthMode); err != nil {
206208
log.Fatalf("HTTP server failed: %v", err)
207209
}
208210
} else {
209211
// Start server on stdio (default mode)
210212
transport := &mcp.StdioTransport{}
211-
if _, err := mcpServer.Connect(context.Background(), transport, nil); err != nil {
213+
if _, err := mcpServer.Connect(ctx, transport, nil); err != nil {
212214
log.Fatalf("Server failed: %v", err)
213215
}
214216
}
@@ -241,16 +243,17 @@ func parseMetricsBackend(backend string) (k8s.MetricsBackend, error) {
241243

242244
// determineMetricsBackendURL determines the metrics backend URL based on auth mode and environment.
243245
// Returns the resolved URL, a source description for logging, and an error if the configuration is invalid.
244-
func determineMetricsBackendURL(authMode auth.AuthMode, backend k8s.MetricsBackend) (url, source string, err error) {
246+
func determineMetricsBackendURL(ctx context.Context, authMode auth.AuthMode, backend k8s.MetricsBackend) (url, source string, err error) {
247+
logger := logging.FromContext(ctx)
245248
if prometheusURL := os.Getenv("PROMETHEUS_URL"); prometheusURL != "" {
246249
return prometheusURL, "PROMETHEUS_URL env var", nil
247250
}
248251

249252
if authMode == auth.AuthModeKubeConfig {
250-
slog.Info("No PROMETHEUS_URL set, attempting route discovery", "backend", backend)
251-
url, err := k8s.GetMetricsBackendURL(backend)
253+
logger.Info("No PROMETHEUS_URL set, attempting route discovery", "backend", string(backend))
254+
url, err := k8s.GetMetricsBackendURL(ctx, backend)
252255
if err != nil {
253-
slog.Warn("Route discovery failed, falling back to default", "err", err, "default", defaultPrometheusURL)
256+
logger.Warn("Route discovery failed, falling back to default", "err", err, "default", defaultPrometheusURL)
254257
return defaultPrometheusURL, "default (route discovery failed)", nil
255258
}
256259
return url, "route discovery", nil
@@ -267,16 +270,17 @@ func determineMetricsBackendURL(authMode auth.AuthMode, backend k8s.MetricsBacke
267270

268271
// determineAlertmanagerURL determines the Alertmanager URL based on auth mode and environment.
269272
// Returns the resolved URL, a source description for logging, and an error if the configuration is invalid.
270-
func determineAlertmanagerURL(authMode auth.AuthMode) (url, source string, err error) {
273+
func determineAlertmanagerURL(ctx context.Context, authMode auth.AuthMode) (url, source string, err error) {
274+
logger := logging.FromContext(ctx)
271275
if alertmanagerURL := os.Getenv("ALERTMANAGER_URL"); alertmanagerURL != "" {
272276
return alertmanagerURL, "ALERTMANAGER_URL env var", nil
273277
}
274278

275279
if authMode == auth.AuthModeKubeConfig {
276-
slog.Info("No ALERTMANAGER_URL set, attempting route discovery")
277-
url, err := k8s.GetAlertmanagerURL()
280+
logger.Info("No ALERTMANAGER_URL set, attempting route discovery")
281+
url, err := k8s.GetAlertmanagerURL(ctx)
278282
if err != nil {
279-
slog.Warn("Route discovery failed, falling back to default", "err", err, "default", defaultAlertmanagerURL)
283+
logger.Warn("Route discovery failed, falling back to default", "err", err, "default", defaultAlertmanagerURL)
280284
return defaultAlertmanagerURL, "default (route discovery failed)", nil
281285
}
282286
return url, "route discovery", nil
@@ -289,29 +293,31 @@ func determineAlertmanagerURL(authMode auth.AuthMode) (url, source string, err e
289293
)
290294
}
291295

292-
func determineTempoURL(flagURL string) (url, source string) {
296+
func determineTempoURL(ctx context.Context, flagURL string) (url, source string) {
297+
logger := logging.FromContext(ctx)
293298
if flagURL != "" {
294299
return flagURL, "--traces.tempo-url flag"
295300
}
296301
if tempoURL := os.Getenv("TEMPO_URL"); tempoURL != "" {
297302
return tempoURL, "TEMPO_URL env var"
298303
}
299-
slog.Info("No Tempo URL configured; Tempo tools require tempoNamespace+tempoName discovery parameters or explicit Tempo URL")
304+
logger.Info("No Tempo URL configured; Tempo tools require tempoNamespace+tempoName discovery parameters or explicit Tempo URL")
300305
return "", "unset"
301306
}
302307

303-
func determineLokiURL(authMode auth.AuthMode, flagURL string, useRoute bool) (url, source string, err error) {
308+
func determineLokiURL(ctx context.Context, authMode auth.AuthMode, flagURL string, useRoute bool) (url, source string, err error) {
309+
logger := logging.FromContext(ctx)
304310
if flagURL != "" {
305311
return flagURL, "--loki-url flag", nil
306312
}
307313
if lokiURL := os.Getenv("LOKI_URL"); lokiURL != "" {
308314
return lokiURL, "LOKI_URL env var", nil
309315
}
310316
if authMode == auth.AuthModeKubeConfig && !useRoute {
311-
slog.Warn("No Loki URL configured, falling back to default", "default", defaultLokiURL)
317+
logger.Warn("No Loki URL configured, falling back to default", "default", defaultLokiURL)
312318
return defaultLokiURL, "default", nil
313319
}
314-
slog.Warn("No Loki URL configured; Loki tools require lokiNamespace+lokiName discovery parameters or explicit Loki URL")
320+
logger.Warn("No Loki URL configured; Loki tools require lokiNamespace+lokiName discovery parameters or explicit Loki URL")
315321
return "", "unset", nil
316322
}
317323

cmd/obs-mcp/main_test.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/rhobs/obs-mcp/pkg/auth"
@@ -108,7 +109,7 @@ func TestDetermineMetricsBackendURL_RequiresURLForNonKubeconfigModes(t *testing.
108109

109110
for _, tt := range tests {
110111
t.Run(tt.name, func(t *testing.T) {
111-
_, _, err := determineMetricsBackendURL(tt.authMode, tt.backend)
112+
_, _, err := determineMetricsBackendURL(context.Background(), tt.authMode, tt.backend)
112113
if err == nil {
113114
t.Errorf("expected error for auth mode %q without PROMETHEUS_URL, got nil", tt.authMode)
114115
}
@@ -131,7 +132,7 @@ func TestDetermineMetricsBackendURL_EnvVarOverridesAll(t *testing.T) {
131132

132133
for _, authMode := range authModes {
133134
t.Run(string(authMode), func(t *testing.T) {
134-
url, source, err := determineMetricsBackendURL(authMode, k8s.MetricsBackendThanos)
135+
url, source, err := determineMetricsBackendURL(context.Background(), authMode, k8s.MetricsBackendThanos)
135136
if err != nil {
136137
t.Errorf("unexpected error: %v", err)
137138
}
@@ -148,7 +149,7 @@ func TestDetermineMetricsBackendURL_EnvVarOverridesAll(t *testing.T) {
148149
func TestDetermineLokiURL(t *testing.T) {
149150
t.Run("explicit flag wins", func(t *testing.T) {
150151
t.Setenv("LOKI_URL", "http://from-env:3100")
151-
got, source, err := determineLokiURL(auth.AuthModeHeader, "http://from-flag:3100", false)
152+
got, source, err := determineLokiURL(context.Background(), auth.AuthModeHeader, "http://from-flag:3100", false)
152153
if err != nil {
153154
t.Fatalf("unexpected error: %v", err)
154155
}
@@ -159,7 +160,7 @@ func TestDetermineLokiURL(t *testing.T) {
159160

160161
t.Run("env used when flag missing", func(t *testing.T) {
161162
t.Setenv("LOKI_URL", "http://from-env:3100")
162-
got, source, err := determineLokiURL(auth.AuthModeHeader, "", false)
163+
got, source, err := determineLokiURL(context.Background(), auth.AuthModeHeader, "", false)
163164
if err != nil {
164165
t.Fatalf("unexpected error: %v", err)
165166
}
@@ -170,7 +171,7 @@ func TestDetermineLokiURL(t *testing.T) {
170171

171172
t.Run("kubeconfig falls back to default", func(t *testing.T) {
172173
t.Setenv("LOKI_URL", "")
173-
got, source, err := determineLokiURL(auth.AuthModeKubeConfig, "", false)
174+
got, source, err := determineLokiURL(context.Background(), auth.AuthModeKubeConfig, "", false)
174175
if err != nil {
175176
t.Fatalf("unexpected error: %v", err)
176177
}
@@ -181,7 +182,7 @@ func TestDetermineLokiURL(t *testing.T) {
181182

182183
t.Run("non-kubeconfig without URL returns unset", func(t *testing.T) {
183184
t.Setenv("LOKI_URL", "")
184-
got, source, err := determineLokiURL(auth.AuthModeHeader, "", false)
185+
got, source, err := determineLokiURL(context.Background(), auth.AuthModeHeader, "", false)
185186
if err != nil {
186187
t.Fatalf("unexpected error: %v", err)
187188
}
@@ -194,23 +195,23 @@ func TestDetermineLokiURL(t *testing.T) {
194195
func TestDetermineTempoURL(t *testing.T) {
195196
t.Run("explicit flag wins", func(t *testing.T) {
196197
t.Setenv("TEMPO_URL", "http://from-env:3200")
197-
got, source := determineTempoURL("http://from-flag:3200")
198+
got, source := determineTempoURL(context.Background(), "http://from-flag:3200")
198199
if got != "http://from-flag:3200" || source != "--traces.tempo-url flag" {
199200
t.Fatalf("unexpected result: %s (%s)", got, source)
200201
}
201202
})
202203

203204
t.Run("env used when flag missing", func(t *testing.T) {
204205
t.Setenv("TEMPO_URL", "http://from-env:3200")
205-
got, source := determineTempoURL("")
206+
got, source := determineTempoURL(context.Background(), "")
206207
if got != "http://from-env:3200" || source != "TEMPO_URL env var" {
207208
t.Fatalf("unexpected result: %s (%s)", got, source)
208209
}
209210
})
210211

211212
t.Run("no URL returns unset", func(t *testing.T) {
212213
t.Setenv("TEMPO_URL", "")
213-
got, source := determineTempoURL("")
214+
got, source := determineTempoURL(context.Background(), "")
214215
if got != "" || source != "unset" {
215216
t.Fatalf("unexpected result: %s (%s)", got, source)
216217
}

pkg/k8s/client.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"log/slog"
87
"time"
98

109
"k8s.io/client-go/kubernetes"
1110
"k8s.io/client-go/rest"
1211
"k8s.io/client-go/tools/clientcmd"
12+
13+
"github.com/rhobs/obs-mcp/pkg/logging"
1314
)
1415

1516
const (
@@ -68,33 +69,37 @@ func GetKubeClient() (*kubernetes.Clientset, error) {
6869
}
6970

7071
// GetMetricsBackendURL discovers the metrics backend endpoint in OpenShift.
71-
func GetMetricsBackendURL(backend MetricsBackend) (string, error) {
72+
func GetMetricsBackendURL(ctx context.Context, backend MetricsBackend) (string, error) {
73+
logger := logging.FromContext(ctx)
7274
if backend == MetricsBackendPrometheus {
73-
return discoverRoute(prometheusRouteName)
75+
return discoverRoute(ctx, prometheusRouteName)
7476
}
7577

76-
// Thanos with fallback to Prometheus
77-
url, err := discoverRoute(thanosQuerierRouteName)
78+
// Thanos with fallback to Prometheus — use getRouteURL directly to
79+
// avoid error-level logging for the expected "not found" case.
80+
url, err := getRouteURL(ctx, thanosQuerierRouteName)
7881
if err == nil {
82+
logger.Info("Successfully discovered route", "route", thanosQuerierRouteName, "url", url)
7983
return url, nil
8084
}
81-
slog.Info("Thanos route not found, falling back to prometheus", "error", err)
82-
return discoverRoute(prometheusRouteName)
85+
logger.Info("Thanos route not found, falling back to prometheus", "error", err)
86+
return discoverRoute(ctx, prometheusRouteName)
8387
}
8488

8589
// discoverRoute attempts to find a route and logs the result.
86-
func discoverRoute(routeName string) (string, error) {
87-
url, err := getRouteURL(routeName)
90+
func discoverRoute(ctx context.Context, routeName string) (string, error) {
91+
logger := logging.FromContext(ctx)
92+
url, err := getRouteURL(ctx, routeName)
8893
if err != nil {
89-
slog.Error("Failed to discover route", "route", routeName, "error", err)
94+
logger.Error("Failed to discover route", "route", routeName, "error", err)
9095
return "", err
9196
}
92-
slog.Info("Successfully discovered route", "route", routeName, "url", url)
97+
logger.Info("Successfully discovered route", "route", routeName, "url", url)
9398
return url, nil
9499
}
95100

96-
func getRouteURL(routeName string) (string, error) {
97-
ctx, cancel := context.WithTimeout(context.Background(), routeDiscoveryTimeout)
101+
func getRouteURL(ctx context.Context, routeName string) (string, error) {
102+
ctx, cancel := context.WithTimeout(ctx, routeDiscoveryTimeout)
98103
defer cancel()
99104

100105
kubeClient, err := GetKubeClient()
@@ -141,6 +146,6 @@ func parseHostFromRouteBody(body []byte) (string, error) {
141146
}
142147

143148
// GetAlertmanagerURL discovers the Alertmanager endpoint in OpenShift.
144-
func GetAlertmanagerURL() (string, error) {
145-
return discoverRoute(alertmanagerRouteName)
149+
func GetAlertmanagerURL(ctx context.Context) (string, error) {
150+
return discoverRoute(ctx, alertmanagerRouteName)
146151
}

tests/e2e/openshift_e2e_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package e2e
44

55
import (
6+
"context"
67
"encoding/json"
78
"net/http"
89
"os"
@@ -21,7 +22,7 @@ import (
2122
// TestRouteDiscovery_ThanosQuerier verifies that the thanos-querier route in
2223
// openshift-monitoring can be discovered and returns a valid https:// URL.
2324
func TestRouteDiscovery_ThanosQuerier(t *testing.T) {
24-
discoveredURL, err := k8s.GetMetricsBackendURL(k8s.MetricsBackendThanos)
25+
discoveredURL, err := k8s.GetMetricsBackendURL(context.Background(), k8s.MetricsBackendThanos)
2526
if err != nil {
2627
t.Fatalf("Failed to discover thanos-querier route: %v", err)
2728
}
@@ -32,7 +33,7 @@ func TestRouteDiscovery_ThanosQuerier(t *testing.T) {
3233
// TestRouteDiscovery_PrometheusK8s verifies that the prometheus-k8s route in
3334
// openshift-monitoring can be discovered when using the prometheus backend.
3435
func TestRouteDiscovery_PrometheusK8s(t *testing.T) {
35-
discoveredURL, err := k8s.GetMetricsBackendURL(k8s.MetricsBackendPrometheus)
36+
discoveredURL, err := k8s.GetMetricsBackendURL(context.Background(), k8s.MetricsBackendPrometheus)
3637
if err != nil {
3738
t.Fatalf("Failed to discover prometheus-k8s route: %v", err)
3839
}
@@ -43,7 +44,7 @@ func TestRouteDiscovery_PrometheusK8s(t *testing.T) {
4344
// TestRouteDiscovery_Alertmanager verifies that the alertmanager-main route in
4445
// openshift-monitoring can be discovered and returns a valid https:// URL.
4546
func TestRouteDiscovery_Alertmanager(t *testing.T) {
46-
discoveredURL, err := k8s.GetAlertmanagerURL()
47+
discoveredURL, err := k8s.GetAlertmanagerURL(context.Background())
4748
if err != nil {
4849
t.Fatalf("Failed to discover alertmanager-main route: %v", err)
4950
}
@@ -61,17 +62,17 @@ func TestRouteDiscovery_URLsAreReachable(t *testing.T) {
6162
}{
6263
{
6364
name: "thanos-querier",
64-
getURL: func() (string, error) { return k8s.GetMetricsBackendURL(k8s.MetricsBackendThanos) },
65+
getURL: func() (string, error) { return k8s.GetMetricsBackendURL(context.Background(), k8s.MetricsBackendThanos) },
6566
apiPath: "/api/v1/query?query=up",
6667
},
6768
{
6869
name: "prometheus-k8s",
69-
getURL: func() (string, error) { return k8s.GetMetricsBackendURL(k8s.MetricsBackendPrometheus) },
70+
getURL: func() (string, error) { return k8s.GetMetricsBackendURL(context.Background(), k8s.MetricsBackendPrometheus) },
7071
apiPath: "/api/v1/query?query=up",
7172
},
7273
{
7374
name: "alertmanager-main",
74-
getURL: k8s.GetAlertmanagerURL,
75+
getURL: func() (string, error) { return k8s.GetAlertmanagerURL(context.Background()) },
7576
apiPath: "/api/v2/status",
7677
},
7778
}

0 commit comments

Comments
 (0)