feat: implement observability and metrics (Task 22)
- Add metrics package tracking cache hit-rate, API quota, circuit breaker trips, search time - Integrate metrics with cache layer, yandex client, and API handlers - Add /metrics HTTP endpoint for Prometheus-compatible metrics exposure - Write tests for metrics functionality across cache, yandex, and API handlers - Update test files to support new metrics infrastructure
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"trip-planner/internal/metrics"
|
||||
)
|
||||
|
||||
// Client represents a Yandex Schedules API client with rate limiting,
|
||||
@@ -19,6 +21,7 @@ type Client struct {
|
||||
rateLimiter *tokenBucket
|
||||
circuitBreaker *circuitBreaker
|
||||
retryConfig *retryConfig
|
||||
metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// tokenBucket implements a token bucket rate limiter.
|
||||
@@ -111,6 +114,13 @@ func WithRetryConfig(maxRetries int, baseBackoff, maxBackoff time.Duration, jitt
|
||||
}
|
||||
}
|
||||
|
||||
// WithMetrics sets the metrics recorder for the client.
|
||||
func WithMetrics(m *metrics.Metrics) Option {
|
||||
return func(c *Client) {
|
||||
c.metrics = m
|
||||
}
|
||||
}
|
||||
|
||||
// Do executes a Yandex API request with rate limiting, circuit breaking, and retry.
|
||||
func (c *Client) Do(ctx context.Context, method, path string, query map[string]string) (*Response, error) {
|
||||
// Apply rate limiting
|
||||
@@ -128,6 +138,7 @@ func (c *Client) Do(ctx context.Context, method, path string, query map[string]s
|
||||
for attempt := 0; attempt <= c.retryConfig.maxRetries; attempt++ {
|
||||
// Check circuit breaker on each retry attempt
|
||||
if !c.circuitBreaker.allow() {
|
||||
c.metrics.RecordCircuitBreakerTrip()
|
||||
return nil, fmt.Errorf("circuit breaker is open")
|
||||
}
|
||||
|
||||
@@ -155,6 +166,7 @@ func (c *Client) Do(ctx context.Context, method, path string, query map[string]s
|
||||
}
|
||||
|
||||
c.circuitBreaker.recordFailure() // final failure
|
||||
c.metrics.RecordCircuitBreakerTrip()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -248,7 +260,6 @@ func (e *APIError) Error() string {
|
||||
return fmt.Sprintf("API error %d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// newAPIError creates an APIError from an HTTP response.
|
||||
func newAPIError(code int, message string) *APIError {
|
||||
return &APIError{Code: code, Message: message}
|
||||
}
|
||||
@@ -320,9 +331,6 @@ func newCircuitBreaker() *circuitBreaker {
|
||||
}
|
||||
|
||||
|
||||
// ResetCircuitBreaker resets the circuit breaker to its initial closed state.
|
||||
// This is useful for testing or recovery scenarios where the circuit needs to be
|
||||
// manually reset without waiting for the timeout.
|
||||
func (cb *circuitBreaker) ResetCircuitBreaker() {
|
||||
cb.mu.Lock()
|
||||
defer cb.mu.Unlock()
|
||||
@@ -403,4 +411,4 @@ func applyJitter(backoff time.Duration) time.Duration {
|
||||
func randFloat64() float64 {
|
||||
// Use math/rand with a seed based on function call index for variability
|
||||
return rand.Float64()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user