Skip to content

Commit 09f159a

Browse files
committed
refactor(metrics): use prometheus client and labels
Refactors the metrics package to use the official prometheus golang client. Tool names are now added as labels instead of custom metrics to simplify aggregate queries. PR #48
1 parent dcf2dc4 commit 09f159a

File tree

7 files changed

+296
-349
lines changed

7 files changed

+296
-349
lines changed

app/metrics/daily_metrics_test.go

Lines changed: 55 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package metrics
22

33
import (
4-
"bytes"
54
"fmt"
5+
"net/http/httptest"
66
"strings"
77
"testing"
88
"time"
@@ -14,150 +14,85 @@ func TestDailyMetrics(t *testing.T) {
1414
AutoCleanup: false,
1515
})
1616

17-
today := time.Now().UTC().Format("2006-01-02")
18-
1917
// Test daily increment methods
2018
m.IncrementDaily("tool_calls_quotes")
2119
m.IncrementDaily("tool_calls_quotes")
2220
m.IncrementDailyBy("tool_calls_login", 3)
2321
m.IncrementDaily("tool_errors_quotes_api_error")
2422

25-
// Test regular increment (non-daily)
26-
m.Increment("legacy_counter")
23+
// Test that metrics were created by checking HTTP handler output
24+
handler := m.HTTPHandler()
25+
req := httptest.NewRequest("GET", "/metrics", nil)
26+
w := httptest.NewRecorder()
27+
handler(w, req)
2728

28-
// Check internal storage with date suffix
29-
expectedQuotesKey := fmt.Sprintf("tool_calls_quotes_%s", today)
30-
expectedLoginKey := fmt.Sprintf("tool_calls_login_%s", today)
31-
expectedErrorKey := fmt.Sprintf("tool_errors_quotes_api_error_%s", today)
29+
output := w.Body.String()
3230

33-
if got := m.GetCounterValue(expectedQuotesKey); got != 2 {
34-
t.Errorf("Expected tool_calls_quotes_%s = 2, got %d", today, got)
35-
}
36-
37-
if got := m.GetCounterValue(expectedLoginKey); got != 3 {
38-
t.Errorf("Expected tool_calls_login_%s = 3, got %d", today, got)
39-
}
31+
today := time.Now().UTC().Format("2006-01-02")
4032

41-
if got := m.GetCounterValue(expectedErrorKey); got != 1 {
42-
t.Errorf("Expected tool_errors_quotes_api_error_%s = 1, got %d", today, got)
33+
expectedMetrics := []struct {
34+
name string
35+
value string
36+
}{
37+
{"tool_calls_quotes", "2"}, // incremented twice
38+
{"tool_calls_login", "3"}, // incremented by 3
39+
{"tool_errors_quotes_api_error", "1"}, // incremented once
4340
}
4441

45-
if got := m.GetCounterValue("legacy_counter"); got != 1 {
46-
t.Errorf("Expected legacy_counter = 1, got %d", got)
42+
for _, metric := range expectedMetrics {
43+
expectedPattern := fmt.Sprintf(`%s{date="%s",service="test-service"} %s`, metric.name, today, metric.value)
44+
if !strings.Contains(output, expectedPattern) {
45+
t.Errorf("Expected output to contain: %s\nGot: %s", expectedPattern, output)
46+
}
4747
}
4848
}
4949

50-
func TestDailyMetricsPrometheusOutput(t *testing.T) {
50+
func TestDailyMetricsWithLabels(t *testing.T) {
5151
m := New(Config{
5252
ServiceName: "test-service",
5353
AutoCleanup: false,
5454
})
5555

56-
today := time.Now().UTC().Format("2006-01-02")
57-
58-
// Add some daily metrics
59-
m.IncrementDaily("tool_calls_quotes")
60-
m.IncrementDaily("tool_calls_quotes")
61-
m.IncrementDaily("tool_errors_quotes_api_error")
62-
63-
// Add a regular metric
64-
m.Increment("legacy_counter")
65-
66-
// Generate Prometheus output
67-
var buf bytes.Buffer
68-
m.WritePrometheus(&buf)
69-
output := buf.String()
70-
71-
// Check that daily metrics have date labels
72-
expectedQuotesLine := fmt.Sprintf(`tool_calls_quotes_total{date="%s",service="test-service"} 2`, today)
73-
expectedErrorLine := fmt.Sprintf(`tool_errors_quotes_api_error_total{date="%s",service="test-service"} 1`, today)
74-
expectedLegacyLine := `legacy_counter_total{service="test-service"} 1`
75-
76-
if !strings.Contains(output, expectedQuotesLine) {
77-
t.Errorf("Expected output to contain: %s\nGot: %s", expectedQuotesLine, output)
78-
}
79-
80-
if !strings.Contains(output, expectedErrorLine) {
81-
t.Errorf("Expected output to contain: %s\nGot: %s", expectedErrorLine, output)
82-
}
83-
84-
if !strings.Contains(output, expectedLegacyLine) {
85-
t.Errorf("Expected output to contain: %s\nGot: %s", expectedLegacyLine, output)
86-
}
87-
}
88-
89-
func TestIsDailyMetric(t *testing.T) {
90-
m := New(Config{ServiceName: "test"})
91-
92-
tests := []struct {
93-
key string
94-
expected bool
95-
}{
96-
{"tool_calls_quotes_2025-08-05", true},
97-
{"tool_errors_login_session_error_2025-12-31", true},
98-
{"legacy_counter", false},
99-
{"tool_calls_quotes", false},
100-
{"tool_calls_quotes_20250805", false}, // Wrong date format
101-
{"tool_calls_quotes_2025-8-5", false}, // Wrong date format
102-
{"", false},
103-
{"_2025-08-05", false}, // Empty base name
104-
}
105-
106-
for _, tt := range tests {
107-
t.Run(tt.key, func(t *testing.T) {
108-
if got := m.isDailyMetric(tt.key); got != tt.expected {
109-
t.Errorf("isDailyMetric(%q) = %v, want %v", tt.key, got, tt.expected)
110-
}
111-
})
112-
}
113-
}
56+
m.IncrementDailyWithLabels("tool_calls", map[string]string{
57+
"tool": "quotes",
58+
"session_type": "mcp",
59+
})
60+
m.IncrementDailyWithLabels("tool_calls", map[string]string{
61+
"tool": "quotes",
62+
"session_type": "mcp",
63+
})
11464

115-
func TestParseDailyMetric(t *testing.T) {
116-
m := New(Config{ServiceName: "test"})
65+
m.IncrementDailyWithLabels("tool_errors", map[string]string{
66+
"tool": "quotes",
67+
"error_type": "api_error",
68+
"session_type": "mcp",
69+
})
11770

118-
tests := []struct {
119-
key string
120-
expectedBase string
121-
expectedDate string
122-
}{
123-
{"tool_calls_quotes_2025-08-05", "tool_calls_quotes", "2025-08-05"},
124-
{"tool_errors_login_session_error_2025-12-31", "tool_errors_login_session_error", "2025-12-31"},
125-
{"legacy_counter", "", ""}, // Not a daily metric
126-
{"tool_calls_quotes_20250805", "", ""}, // Wrong date format
127-
}
71+
handler := m.HTTPHandler()
72+
req := httptest.NewRequest("GET", "/metrics", nil)
73+
w := httptest.NewRecorder()
74+
handler(w, req)
12875

129-
for _, tt := range tests {
130-
t.Run(tt.key, func(t *testing.T) {
131-
gotBase, _, gotDate := m.parseDailyMetric(tt.key)
132-
if gotBase != tt.expectedBase || gotDate != tt.expectedDate {
133-
t.Errorf("parseDailyMetric(%q) = (%q, %q), want (%q, %q)",
134-
tt.key, gotBase, gotDate, tt.expectedBase, tt.expectedDate)
135-
}
136-
})
137-
}
76+
output := w.Body.String()
77+
today := time.Now().UTC().Format("2006-01-02")
13878

139-
// Test session type parsing
140-
sessionTypeTests := []struct {
141-
key string
142-
expectedBase string
143-
expectedSession string
144-
expectedDate string
79+
expectedPatterns := []struct {
80+
description string
81+
pattern string
14582
}{
146-
{"tool_calls_quotes_sse_2025-08-05", "tool_calls_quotes", "sse", "2025-08-05"},
147-
{"tool_calls_quotes_mcp_2025-08-05", "tool_calls_quotes", "mcp", "2025-08-05"},
148-
{"tool_calls_quotes_stdio_2025-08-05", "tool_calls_quotes", "stdio", "2025-08-05"},
149-
{"tool_calls_quotes_unknown_2025-08-05", "tool_calls_quotes", "unknown", "2025-08-05"},
150-
{"tool_calls_quotes_2025-08-05", "tool_calls_quotes", "", "2025-08-05"}, // No session type
151-
{"tool_errors_login_session_error_sse_2025-12-31", "tool_errors_login_session_error", "sse", "2025-12-31"},
83+
{
84+
"tool calls with correct value and labels",
85+
fmt.Sprintf(`tool_calls_total{date="%s",service="test-service",session_type="mcp",tool="quotes"} 2`, today),
86+
},
87+
{
88+
"tool errors with correct value and labels",
89+
fmt.Sprintf(`tool_errors_total{date="%s",error_type="api_error",service="test-service",session_type="mcp",tool="quotes"} 1`, today),
90+
},
15291
}
15392

154-
for _, tt := range sessionTypeTests {
155-
t.Run(tt.key+"_session_type", func(t *testing.T) {
156-
gotBase, gotSession, gotDate := m.parseDailyMetric(tt.key)
157-
if gotBase != tt.expectedBase || gotSession != tt.expectedSession || gotDate != tt.expectedDate {
158-
t.Errorf("parseDailyMetric(%q) = (%q, %q, %q), want (%q, %q, %q)",
159-
tt.key, gotBase, gotSession, gotDate, tt.expectedBase, tt.expectedSession, tt.expectedDate)
160-
}
161-
})
93+
for _, expected := range expectedPatterns {
94+
if !strings.Contains(output, expected.pattern) {
95+
t.Errorf("Expected output to contain %s: %s\nFull output: %s", expected.description, expected.pattern, output)
96+
}
16297
}
16398
}

0 commit comments

Comments
 (0)