// Package monitoring یک ذخیره‌ساز سبک و درون‌حافظه‌ای برای دیدبانی درخواست‌هاست: // آخرین درخواست‌ها (برای دیباگ لحظه‌ای) + آمار تجمیعی (بار، زمان پاسخ، نرخ خطا). // عمداً بدون وابستگی بیرونی (بدون Prometheus/Redis) چون در این مقیاس یک حلقه‌ی // درون‌حافظه‌ای thread-safe کافیه؛ اگه بعداً مقیاس بزرگ‌تر شد، همین Store رو می‌شه // پشت یک exporter واقعی گذاشت. package monitoring import ( "sort" "sync" "time" ) const bufferSize = 1000 type RequestRecord struct { Time time.Time `json:"time"` Method string `json:"method"` Path string `json:"path"` StatusCode int `json:"status_code"` DurationMs float64 `json:"duration_ms"` ClientIP string `json:"client_ip"` UserID *uint `json:"user_id,omitempty"` } type Summary struct { TotalRequests int64 `json:"total_requests"` TotalErrors int64 `json:"total_errors"` ErrorRatePct float64 `json:"error_rate_pct"` AvgLatencyMs float64 `json:"avg_latency_ms"` P95LatencyMs float64 `json:"p95_latency_ms"` RequestsLastMin int `json:"requests_last_min"` StartedAt time.Time `json:"started_at"` UptimeSeconds float64 `json:"uptime_seconds"` } // Store یک حلقه‌ی ثابت‌اندازه (ring buffer) از آخرین درخواست‌ها به‌همراه // شمارنده‌های تجمیعیه. نوشتن/خواندن با RWMutex محافظت می‌شه؛ هزینه‌ی هر // درخواست فقط یک قفل کوتاه و بدون I/O روی دیسکه (سریع، مناسب مسیر داغ). type Store struct { mu sync.RWMutex buf []RequestRecord next int count int total int64 totalErr int64 sumLatency float64 startedAt time.Time } func NewStore() *Store { return &Store{ buf: make([]RequestRecord, bufferSize), startedAt: time.Now(), } } func (s *Store) Record(rec RequestRecord) { s.mu.Lock() defer s.mu.Unlock() s.buf[s.next] = rec s.next = (s.next + 1) % bufferSize if s.count < bufferSize { s.count++ } s.total++ s.sumLatency += rec.DurationMs if rec.StatusCode >= 400 { s.totalErr++ } } // Recent آخرین n درخواست رو برمی‌گردونه (جدیدترین اول). func (s *Store) Recent(n int) []RequestRecord { s.mu.RLock() defer s.mu.RUnlock() if n > s.count { n = s.count } out := make([]RequestRecord, 0, n) idx := s.next - 1 for i := 0; i < n; i++ { if idx < 0 { idx = bufferSize - 1 } out = append(out, s.buf[idx]) idx-- } return out } func (s *Store) Summary() Summary { s.mu.RLock() defer s.mu.RUnlock() var errRate, avgLatency float64 if s.total > 0 { errRate = float64(s.totalErr) / float64(s.total) * 100 avgLatency = s.sumLatency / float64(s.total) } cutoff := time.Now().Add(-1 * time.Minute) lastMin := 0 latencies := make([]float64, 0, s.count) for i := 0; i < s.count; i++ { r := s.buf[i] if r.Time.After(cutoff) { lastMin++ } latencies = append(latencies, r.DurationMs) } sort.Float64s(latencies) p95 := 0.0 if len(latencies) > 0 { idx := int(float64(len(latencies)) * 0.95) if idx >= len(latencies) { idx = len(latencies) - 1 } p95 = latencies[idx] } return Summary{ TotalRequests: s.total, TotalErrors: s.totalErr, ErrorRatePct: errRate, AvgLatencyMs: avgLatency, P95LatencyMs: p95, RequestsLastMin: lastMin, StartedAt: s.startedAt, UptimeSeconds: time.Since(s.startedAt).Seconds(), } }