Refactor golangci-lint configuration and improve error handling
- Updated .golangci.yml to enable default linters and refine suppression rules, enhancing code quality visibility. - Improved error handling in cmd/root.go by explicitly discarding low-value error messages during fatal exits for consistency with errcheck posture. - Added best-effort error handling in various locations across the codebase, ensuring that non-critical errors are logged without affecting overall functionality. - Introduced a new writeMetricsText function to streamline metrics output, improving code clarity and maintainability.
This commit is contained in:
+52
-44
@@ -59,7 +59,7 @@ func NewServiceManager() *ServiceManager {
|
||||
`Steam`,
|
||||
},
|
||||
}
|
||||
sm.AddService(steamConfig)
|
||||
_ = sm.AddService(steamConfig) // error impossible: hardcoded patterns are valid regexes (user-provided services validated in AddService)
|
||||
|
||||
return sm
|
||||
}
|
||||
@@ -362,7 +362,7 @@ func (sc *SteamCache) streamCachedResponse(w http.ResponseWriter, r *http.Reques
|
||||
w.WriteHeader(http.StatusPartialContent)
|
||||
|
||||
// Send the range data
|
||||
w.Write(rangeData)
|
||||
_, _ = w.Write(rangeData) // client write error ignored (disconnect during range body send is not actionable)
|
||||
|
||||
logger.Logger.Info().
|
||||
Str("cache_key", cacheKey).
|
||||
@@ -397,7 +397,7 @@ func (sc *SteamCache) streamCachedResponse(w http.ResponseWriter, r *http.Reques
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
// Stream the full response body
|
||||
w.Write(bodyData)
|
||||
_, _ = w.Write(bodyData) // client write error ignored (disconnect during full cached body send is not actionable)
|
||||
|
||||
logger.Logger.Info().
|
||||
Str("cache_key", cacheKey).
|
||||
@@ -1108,17 +1108,17 @@ func (sc *SteamCache) Run() error {
|
||||
resp, err := sc.client.Get(sc.upstream)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close() // best-effort body close on connectivity error path; primary err returned
|
||||
}
|
||||
logger.Logger.Error().Err(err).Str("upstream", sc.upstream).Msg("Failed upstream connectivity check")
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close() // best-effort body close on non-OK status; primary error returned
|
||||
logger.Logger.Error().Int("status_code", resp.StatusCode).Str("upstream", sc.upstream).Msg("Upstream connectivity check returned non-OK status")
|
||||
return fmt.Errorf("upstream returned status %d", resp.StatusCode)
|
||||
}
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close() // best-effort; success path close before ListenAndServe
|
||||
}
|
||||
|
||||
sc.server.Handler = sc
|
||||
@@ -1143,7 +1143,7 @@ func (sc *SteamCache) Run() error {
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
sc.server.Shutdown(ctx)
|
||||
_ = sc.server.Shutdown(ctx) // shutdown error secondary (ListenAndServe already terminated or context done); logged elsewhere if fatal
|
||||
sc.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
@@ -1226,6 +1226,39 @@ func (sc *SteamCache) ResetMetrics() {
|
||||
sc.metrics.Reset()
|
||||
}
|
||||
|
||||
// writeMetricsText emits the Prometheus-style text metrics to the ResponseWriter.
|
||||
// All fmt.Fprintf errors are intentionally discarded via _ = : this is a best-effort
|
||||
// read-only debug endpoint; client disconnects or write errors during metrics dump
|
||||
// are not actionable (do not affect cache correctness or require retries).
|
||||
func writeMetricsText(w http.ResponseWriter, stats *metrics.Stats) {
|
||||
_, _ = fmt.Fprintf(w, "# SteamCache2 Metrics\n")
|
||||
_, _ = fmt.Fprintf(w, "total_requests %d\n", stats.TotalRequests)
|
||||
_, _ = fmt.Fprintf(w, "cache_hits %d\n", stats.CacheHits)
|
||||
_, _ = fmt.Fprintf(w, "cache_misses %d\n", stats.CacheMisses)
|
||||
_, _ = fmt.Fprintf(w, "cache_coalesced %d\n", stats.CacheCoalesced)
|
||||
_, _ = fmt.Fprintf(w, "errors %d\n", stats.Errors)
|
||||
_, _ = fmt.Fprintf(w, "rate_limited %d\n", stats.RateLimited)
|
||||
_, _ = fmt.Fprintf(w, "upstream_errors %d\n", stats.UpstreamErrors)
|
||||
_, _ = fmt.Fprintf(w, "cache_write_failures %d\n", stats.CacheWriteFailures)
|
||||
_, _ = fmt.Fprintf(w, "memory_cache_hits %d\n", stats.MemoryCacheHits)
|
||||
_, _ = fmt.Fprintf(w, "disk_cache_hits %d\n", stats.DiskCacheHits)
|
||||
_, _ = fmt.Fprintf(w, "promotions %d\n", stats.Promotions)
|
||||
_, _ = fmt.Fprintf(w, "evictions %d\n", stats.Evictions)
|
||||
for svc, cnt := range stats.ServiceErrors {
|
||||
_, _ = fmt.Fprintf(w, "service_errors{service=%q} %d\n", svc, cnt)
|
||||
}
|
||||
for svc, cnt := range stats.ServiceRequests {
|
||||
_, _ = fmt.Fprintf(w, "service_requests{service=%q} %d\n", svc, cnt)
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "hit_rate %.4f\n", stats.HitRate)
|
||||
_, _ = fmt.Fprintf(w, "avg_response_time_ms %.2f\n", float64(stats.AvgResponseTime.Nanoseconds())/1e6)
|
||||
_, _ = fmt.Fprintf(w, "total_bytes_served %d\n", stats.TotalBytesServed)
|
||||
_, _ = fmt.Fprintf(w, "total_bytes_cached %d\n", stats.TotalBytesCached)
|
||||
_, _ = fmt.Fprintf(w, "memory_cache_size %d\n", stats.MemoryCacheSize)
|
||||
_, _ = fmt.Fprintf(w, "disk_cache_size %d\n", stats.DiskCacheSize)
|
||||
_, _ = fmt.Fprintf(w, "uptime_seconds %.2f\n", stats.Uptime.Seconds())
|
||||
}
|
||||
|
||||
func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP := getClientIP(r, sc.trustedProxies)
|
||||
|
||||
@@ -1288,7 +1321,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Msg("LanCache heartbeat request")
|
||||
w.Header().Add("X-LanCache-Processed-By", "SteamCache2")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
w.Write(nil)
|
||||
_, _ = w.Write(nil) // client write error ignored (heartbeat path; nil write is no-op)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1297,32 +1330,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
stats := sc.GetMetrics()
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "# SteamCache2 Metrics\n")
|
||||
fmt.Fprintf(w, "total_requests %d\n", stats.TotalRequests)
|
||||
fmt.Fprintf(w, "cache_hits %d\n", stats.CacheHits)
|
||||
fmt.Fprintf(w, "cache_misses %d\n", stats.CacheMisses)
|
||||
fmt.Fprintf(w, "cache_coalesced %d\n", stats.CacheCoalesced)
|
||||
fmt.Fprintf(w, "errors %d\n", stats.Errors)
|
||||
fmt.Fprintf(w, "rate_limited %d\n", stats.RateLimited)
|
||||
fmt.Fprintf(w, "upstream_errors %d\n", stats.UpstreamErrors)
|
||||
fmt.Fprintf(w, "cache_write_failures %d\n", stats.CacheWriteFailures)
|
||||
fmt.Fprintf(w, "memory_cache_hits %d\n", stats.MemoryCacheHits)
|
||||
fmt.Fprintf(w, "disk_cache_hits %d\n", stats.DiskCacheHits)
|
||||
fmt.Fprintf(w, "promotions %d\n", stats.Promotions)
|
||||
fmt.Fprintf(w, "evictions %d\n", stats.Evictions)
|
||||
for svc, cnt := range stats.ServiceErrors {
|
||||
fmt.Fprintf(w, "service_errors{service=%q} %d\n", svc, cnt)
|
||||
}
|
||||
for svc, cnt := range stats.ServiceRequests {
|
||||
fmt.Fprintf(w, "service_requests{service=%q} %d\n", svc, cnt)
|
||||
}
|
||||
fmt.Fprintf(w, "hit_rate %.4f\n", stats.HitRate)
|
||||
fmt.Fprintf(w, "avg_response_time_ms %.2f\n", float64(stats.AvgResponseTime.Nanoseconds())/1e6)
|
||||
fmt.Fprintf(w, "total_bytes_served %d\n", stats.TotalBytesServed)
|
||||
fmt.Fprintf(w, "total_bytes_cached %d\n", stats.TotalBytesCached)
|
||||
fmt.Fprintf(w, "memory_cache_size %d\n", stats.MemoryCacheSize)
|
||||
fmt.Fprintf(w, "disk_cache_size %d\n", stats.DiskCacheSize)
|
||||
fmt.Fprintf(w, "uptime_seconds %.2f\n", stats.Uptime.Seconds())
|
||||
writeMetricsText(w, stats)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1371,7 +1379,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Try to serve from cache
|
||||
file, err := sc.vfs.Open(cachePath)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
defer func() { _ = file.Close() }() // best-effort close of cache file reader; error secondary (data already read or connection issue)
|
||||
|
||||
// Read the entire cached file
|
||||
cachedData, err := io.ReadAll(file)
|
||||
@@ -1381,7 +1389,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Str("url", urlPath).
|
||||
Err(err).
|
||||
Msg("Failed to read cached file - removing corrupted entry")
|
||||
sc.vfs.Delete(cachePath)
|
||||
_ = sc.vfs.Delete(cachePath) // best-effort cleanup of corrupt entry; failure non-fatal (logged)
|
||||
} else {
|
||||
// Deserialize using new format
|
||||
cacheFile, err := deserializeCacheFile(cachedData)
|
||||
@@ -1392,7 +1400,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Str("url", urlPath).
|
||||
Err(err).
|
||||
Msg("Failed to deserialize cache file - removing corrupted entry")
|
||||
sc.vfs.Delete(cachePath)
|
||||
_ = sc.vfs.Delete(cachePath) // best-effort cleanup of corrupt entry; failure non-fatal (logged)
|
||||
} else {
|
||||
// Track cache hit metrics
|
||||
sc.metrics.IncrementCacheHits()
|
||||
@@ -1472,7 +1480,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-LanCache-Status", "HIT-COALESCED")
|
||||
w.Header().Set("X-LanCache-Processed-By", "SteamCache2")
|
||||
w.WriteHeader(coalescedReq.statusCode)
|
||||
w.Write(responseData)
|
||||
_, _ = w.Write(responseData) // client write error ignored (disconnect during coalesced response send is not actionable)
|
||||
|
||||
// Track coalesced cache hit metrics
|
||||
sc.metrics.IncrementCacheCoalesced()
|
||||
@@ -1573,7 +1581,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Logger.Error().Err(err).Str("url", req.URL.String()).Msg("Failed to fetch the requested URL")
|
||||
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close() // best-effort close on upstream fetch error; primary error logged/returned
|
||||
}
|
||||
// Complete coalesced request with error
|
||||
if isNew {
|
||||
@@ -1589,7 +1597,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Logger.Error().Int("status_code", resp.StatusCode).Str("url", req.URL.String()).Msg("Failed to fetch the requested URL (non-OK status after retries)")
|
||||
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close() // best-effort close on non-OK upstream; primary error path
|
||||
// Complete coalesced request with error
|
||||
if isNew {
|
||||
coalescedReq.complete(nil, fmt.Errorf("upstream returned status %d", resp.StatusCode))
|
||||
@@ -1601,7 +1609,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Failed to fetch the requested URL", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }() // best-effort close for success upstream response body (standard handler cleanup)
|
||||
|
||||
// Fast path: Flexible lightweight validation for all files
|
||||
// Multiple validation layers ensure data integrity without blocking legitimate Steam content
|
||||
@@ -1716,7 +1724,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Stream the response body to client
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
w.Write(bodyData)
|
||||
_, _ = w.Write(bodyData) // client write error ignored (disconnect during MISS body send is not actionable)
|
||||
|
||||
// Track cache miss metrics
|
||||
sc.metrics.IncrementCacheMisses()
|
||||
@@ -1751,7 +1759,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Store the serialized cache data
|
||||
cacheWriter, err := sc.vfs.Create(cachePath, int64(len(cacheData)))
|
||||
if err == nil {
|
||||
defer cacheWriter.Close()
|
||||
defer func() { _ = cacheWriter.Close() }() // best-effort close of cache writer; errors on close (e.g. final sync) logged via prior write checks or non-fatal
|
||||
|
||||
// Write the serialized cache data
|
||||
bytesWritten, cacheErr := cacheWriter.Write(cacheData)
|
||||
@@ -1766,7 +1774,7 @@ func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Msg("Cache write failed or incomplete - removing corrupted entry")
|
||||
sc.metrics.IncrementCacheWriteFailures()
|
||||
sc.metrics.IncrementServiceError("cache_write")
|
||||
sc.vfs.Delete(cachePath)
|
||||
_ = sc.vfs.Delete(cachePath) // best-effort removal of partial corrupt cache entry on write failure; non-fatal. Deferred cacheWriter.Close() (from earlier in block) runs after this on error unwind path (harmless per DiskFS design)
|
||||
} else {
|
||||
// Track successful cache write
|
||||
sc.metrics.AddBytesCached(int64(len(cacheData)))
|
||||
|
||||
Reference in New Issue
Block a user