refactor: remove metrics from jikan client

This commit is contained in:
2026-06-23 15:08:46 +02:00
committed by Milas Holsting
parent 546ab66b1a
commit 0d53d5efdc
4 changed files with 10 additions and 46 deletions

View File

@@ -10,32 +10,27 @@ import (
) )
type Store struct { type Store struct {
db db.Querier db db.Querier
metrics *observability.Metrics
} }
func NewStore(queries db.Querier, metrics *observability.Metrics) *Store { func NewStore(queries db.Querier) *Store {
return &Store{db: queries, metrics: metrics} return &Store{db: queries}
} }
// Get retrieves a fresh cached value by key. // Get retrieves a fresh cached value by key.
func (s *Store) Get(parentCtx context.Context, key string, out any) bool { func (s *Store) Get(parentCtx context.Context, key string, out any) bool {
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second) ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
defer cancel() defer cancel()
defer s.observeStats(parentCtx)
data, err := s.db.GetJikanCache(ctx, key) data, err := s.db.GetJikanCache(ctx, key)
if err != nil { if err != nil {
s.metrics.ObserveCache("jikan", "miss")
return false return false
} }
if err := json.Unmarshal([]byte(data), out); err != nil { if err := json.Unmarshal([]byte(data), out); err != nil {
s.metrics.ObserveCache("jikan", "miss")
return false return false
} }
s.metrics.ObserveCache("jikan", "hit")
return true return true
} }
@@ -43,20 +38,16 @@ func (s *Store) Get(parentCtx context.Context, key string, out any) bool {
func (s *Store) GetStale(parentCtx context.Context, key string, out any) bool { func (s *Store) GetStale(parentCtx context.Context, key string, out any) bool {
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second) ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
defer cancel() defer cancel()
defer s.observeStats(parentCtx)
data, err := s.db.GetJikanCacheStale(ctx, key) data, err := s.db.GetJikanCacheStale(ctx, key)
if err != nil { if err != nil {
s.metrics.ObserveCache("jikan_stale", "miss")
return false return false
} }
if err := json.Unmarshal([]byte(data), out); err != nil { if err := json.Unmarshal([]byte(data), out); err != nil {
s.metrics.ObserveCache("jikan_stale", "miss")
return false return false
} }
s.metrics.ObserveCache("jikan_stale", "hit")
return true return true
} }
@@ -64,7 +55,6 @@ func (s *Store) GetStale(parentCtx context.Context, key string, out any) bool {
func (s *Store) Set(parentCtx context.Context, key string, data any, ttl time.Duration) { func (s *Store) Set(parentCtx context.Context, key string, data any, ttl time.Duration) {
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second) ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
defer cancel() defer cancel()
defer s.observeStats(parentCtx)
bytes, err := json.Marshal(data) bytes, err := json.Marshal(data)
if err != nil { if err != nil {
@@ -87,22 +77,3 @@ func (s *Store) Set(parentCtx context.Context, key string, data any, ttl time.Du
) )
} }
} }
func (s *Store) observeStats(parentCtx context.Context) {
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
defer cancel()
stats, err := s.db.GetJikanCacheStats(ctx)
if err != nil {
observability.LogJSON(
observability.LogLevelWarn,
"jikan_cache_stats",
"jikan",
"",
nil,
err,
)
return
}
s.metrics.ObserveJikanCacheStats(stats.TotalRows, stats.ExpiredRows, stats.OldestExpiresAtSeconds)
}

View File

@@ -39,21 +39,20 @@ const jikanSlowLogThreshold = 750 * time.Millisecond
type APIError = jtransport.APIError type APIError = jtransport.APIError
func NewClient(cfg config.Config, queries *db.Queries, metrics *observability.Metrics) *Client { func NewClient(cfg config.Config, queries *db.Queries) *Client {
limiter := rate.NewLimiter(400 * time.Millisecond) limiter := rate.NewLimiter(400 * time.Millisecond)
client := &Client{ client := &Client{
baseURL: "https://api.jikan.moe/v4", baseURL: "https://api.jikan.moe/v4",
db: queries, db: queries,
retrySignal: make(chan struct{}, 1), retrySignal: make(chan struct{}, 1),
refreshSem: make(chan struct{}, 4), refreshSem: make(chan struct{}, 4),
cache: jcache.NewStore(queries, metrics), cache: jcache.NewStore(queries),
traceEnabled: cfg.JikanTrace, traceEnabled: cfg.JikanTrace,
randomPool: make([]Anime, 0), randomPool: make([]Anime, 0),
} }
client.fetcher = jtransport.NewClient(jtransport.Config{ client.fetcher = jtransport.NewClient(jtransport.Config{
HTTPClient: jtransport.NewHTTPClient(), HTTPClient: jtransport.NewHTTPClient(),
Limiter: limiter, Limiter: limiter,
Metrics: metrics,
TraceEnabled: client.jikanTraceEnabled, TraceEnabled: client.jikanTraceEnabled,
}) })

View File

@@ -7,7 +7,6 @@ import (
"io" "io"
"mal/internal/config" "mal/internal/config"
"mal/internal/db" "mal/internal/db"
"mal/internal/observability"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@@ -31,7 +30,7 @@ func TestGetWithCacheReturnsStaleAndRefreshesAsync(t *testing.T) {
}() }()
queries := db.New(sqlDB) queries := db.New(sqlDB)
client := NewClient(config.Config{}, queries, observability.NewMetrics()) client := NewClient(config.Config{}, queries)
stale := TopAnimeResponse{Data: []Anime{{MalID: 1, Title: "stale"}}} stale := TopAnimeResponse{Data: []Anime{{MalID: 1, Title: "stale"}}}
insertCachedResponse(t, sqlDB, "top:1", stale, time.Now().Add(-time.Hour)) insertCachedResponse(t, sqlDB, "top:1", stale, time.Now().Add(-time.Hour))
@@ -65,7 +64,7 @@ func TestGetWithCacheAllowsEmptySearchResults(t *testing.T) {
}() }()
queries := db.New(sqlDB) queries := db.New(sqlDB)
client := NewClient(config.Config{}, queries, observability.NewMetrics()) client := NewClient(config.Config{}, queries)
client.fetcher.HTTPClient = &http.Client{ client.fetcher.HTTPClient = &http.Client{
Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
body := `{"pagination":{"has_next_page":false},"data":[]}` body := `{"pagination":{"has_next_page":false},"data":[]}`
@@ -95,7 +94,7 @@ func TestLoadCachedRandomPoolIgnoresExpiredAnimeCache(t *testing.T) {
}() }()
queries := db.New(sqlDB) queries := db.New(sqlDB)
client := NewClient(config.Config{}, queries, observability.NewMetrics()) client := NewClient(config.Config{}, queries)
insertCachedAnime(t, sqlDB, "anime:1", Anime{MalID: 1, Title: "fresh"}, time.Now().Add(time.Hour)) insertCachedAnime(t, sqlDB, "anime:1", Anime{MalID: 1, Title: "fresh"}, time.Now().Add(time.Hour))
insertCachedAnime(t, sqlDB, "anime:2", Anime{MalID: 2, Title: "expired"}, time.Now().Add(-time.Hour)) insertCachedAnime(t, sqlDB, "anime:2", Anime{MalID: 2, Title: "expired"}, time.Now().Add(-time.Hour))

View File

@@ -22,14 +22,12 @@ const slowLogThreshold = 750 * time.Millisecond
type Client struct { type Client struct {
HTTPClient *http.Client HTTPClient *http.Client
Limiter *rate.Limiter Limiter *rate.Limiter
Metrics *observability.Metrics
TraceEnabled func() bool TraceEnabled func() bool
} }
type Config struct { type Config struct {
HTTPClient *http.Client HTTPClient *http.Client
Limiter *rate.Limiter Limiter *rate.Limiter
Metrics *observability.Metrics
TraceEnabled func() bool TraceEnabled func() bool
} }
@@ -58,7 +56,6 @@ func NewClient(cfg Config) *Client {
return &Client{ return &Client{
HTTPClient: cfg.HTTPClient, HTTPClient: cfg.HTTPClient,
Limiter: cfg.Limiter, Limiter: cfg.Limiter,
Metrics: cfg.Metrics,
TraceEnabled: cfg.TraceEnabled, TraceEnabled: cfg.TraceEnabled,
} }
} }
@@ -94,12 +91,10 @@ func (c *Client) FetchWithRetry(ctx context.Context, urlStr string, out any) err
maxRetries := 5 maxRetries := 5
startedAt := time.Now() startedAt := time.Now()
attempts := 0 attempts := 0
endpoint := metricsEndpoint(urlStr)
logAndReturn := func(statusCode int, err error) error { logAndReturn := func(statusCode int, err error) error {
if isDoneContextError(ctx, err) { if isDoneContextError(ctx, err) {
return err return err
} }
c.Metrics.ObserveJikanRequest(endpoint, statusCode, time.Since(startedAt), err)
c.logUpstream(urlStr, statusCode, attempts, startedAt, err) c.logUpstream(urlStr, statusCode, attempts, startedAt, err)
return err return err
} }
@@ -298,7 +293,7 @@ func (c *Client) logUpstream(urlStr string, statusCode int, attempts int, starte
"", "",
map[string]any{ map[string]any{
"url": urlStr, "url": urlStr,
"endpoint": metricsEndpoint(urlStr), "endpoint": endpointLabel(urlStr),
"status": statusCode, "status": statusCode,
"attempts": attempts, "attempts": attempts,
"duration_ms": float64(duration.Microseconds()) / 1000, "duration_ms": float64(duration.Microseconds()) / 1000,
@@ -307,7 +302,7 @@ func (c *Client) logUpstream(urlStr string, statusCode int, attempts int, starte
) )
} }
func metricsEndpoint(urlStr string) string { func endpointLabel(urlStr string) string {
trimmed := strings.TrimSpace(urlStr) trimmed := strings.TrimSpace(urlStr)
if trimmed == "" { if trimmed == "" {
return "unknown" return "unknown"