refactor: remove metrics from jikan client
This commit is contained in:
35
integrations/jikan/cache/store.go
vendored
35
integrations/jikan/cache/store.go
vendored
@@ -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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user