From 826fd544cc6ae1a995dd0bd47c6b4f8f82caa448 Mon Sep 17 00:00:00 2001 From: Anton Tolchanov Date: Fri, 6 Feb 2026 16:55:25 +0000 Subject: [PATCH] tsweb/varz: only export numeric expvar.Map values Currently the expvar exporter attempts to write expvar.String, which breaks the Prometheus metric page. Updates tailscale/corp#36552 Signed-off-by: Anton Tolchanov --- tsweb/varz/varz.go | 14 ++++++++++++-- tsweb/varz/varz_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/tsweb/varz/varz.go b/tsweb/varz/varz.go index d6100672c..a2286c760 100644 --- a/tsweb/varz/varz.go +++ b/tsweb/varz/varz.go @@ -245,11 +245,21 @@ func writePromExpVar(w io.Writer, prefix string, kv expvar.KeyValue) { if label != "" && typ != "" { fmt.Fprintf(w, "# TYPE %s %s\n", name, typ) v.Do(func(kv expvar.KeyValue) { - fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value) + switch kv.Value.(type) { + case *expvar.Int, *expvar.Float: + fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value) + default: + fmt.Fprintf(w, "# skipping %q expvar map key %q with unknown value type %T\n", name, kv.Key, kv.Value) + } }) } else { v.Do(func(kv expvar.KeyValue) { - fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value) + switch kv.Value.(type) { + case *expvar.Int, *expvar.Float: + fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value) + default: + fmt.Fprintf(w, "# skipping %q expvar map key %q with unknown value type %T\n", name, kv.Key, kv.Value) + } }) } } diff --git a/tsweb/varz/varz_test.go b/tsweb/varz/varz_test.go index 6505ba985..770144016 100644 --- a/tsweb/varz/varz_test.go +++ b/tsweb/varz/varz_test.go @@ -180,6 +180,43 @@ func TestVarzHandler(t *testing.T) { }, "# TYPE m counter\nm{label=\"bar\"} 2\nm{label=\"foo\"} 1\n", }, + { + "metrics_label_map_float", + "float_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Init() + f := new(expvar.Float) + f.Set(1.5) + m.Set("a", f) + return m + }(), + "float_map_a 1.5\n", + }, + { + "metrics_label_map_int", + "int_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Init() + f := new(expvar.Int) + f.Set(55) + m.Set("a", f) + return m + }(), + "int_map_a 55\n", + }, + { + "metrics_label_map_string", + "string_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Init() + m.Set("a", expvar.NewString("foo")) + return m + }(), + "# skipping \"string_map\" expvar map key \"a\" with unknown value type *expvar.String\n", + }, { "metrics_label_map_untyped", "control_save_config", @@ -298,6 +335,12 @@ foo_foo_b 1 api_status_code 42 `) + "\n", }, + { + "string_expvar_is_not_exported", + "foo_string", + new(expvar.String), + "# skipping expvar \"foo_string\" (Go type *expvar.String) with undeclared Prometheus type\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {