package services import ( "bytes" "context" "encoding/json" "net/http" "os" "time" "strings" "fotbal-club/internal/config" "fotbal-club/pkg/httpclient" ) type ErrorEvent struct { Origin string `json:"origin"` Language string `json:"language"` Severity string `json:"severity,omitempty"` Message string `json:"message"` Stack string `json:"stack,omitempty"` Component string `json:"component,omitempty"` File string `json:"file,omitempty"` Line int `json:"line,omitempty"` Column int `json:"column,omitempty"` URL string `json:"url,omitempty"` Method string `json:"method,omitempty"` Status int `json:"status,omitempty"` RequestID string `json:"request_id,omitempty"` UserID *uint `json:"user_id,omitempty"` Session string `json:"session_token,omitempty"` Tags map[string]string `json:"tags,omitempty"` Context map[string]interface{} `json:"context,omitempty"` Env string `json:"env,omitempty"` Version string `json:"version,omitempty"` Hostname string `json:"hostname,omitempty"` OccurredAt time.Time `json:"occurred_at"` } type ErrorReporter struct { client *http.Client endpoint string token string hostname string appEnv string } func NewErrorReporter(cfg *config.Config) *ErrorReporter { if cfg == nil { return nil } endpoint := cfg.ErrorIngestURL if strings.TrimSpace(endpoint) == "" { if cfg.AppEnv == "production" { endpoint = "https://errors.tdvorak.dev/api/v1/errors" } else { base := strings.TrimRight(cfg.PublicAPIBaseURL, "/") if base != "" { endpoint = base + "/errors" } } } if strings.TrimSpace(endpoint) == "" { return nil } host, _ := os.Hostname() return &ErrorReporter{ client: httpclient.FastClient(), endpoint: endpoint, token: cfg.ErrorIngestToken, hostname: host, appEnv: cfg.AppEnv, } } func (r *ErrorReporter) Report(ctx context.Context, ev *ErrorEvent) { if r == nil || ev == nil || r.endpoint == "" { return } if ev.Env == "" { ev.Env = r.appEnv } if ev.Hostname == "" { ev.Hostname = r.hostname } if ev.OccurredAt.IsZero() { ev.OccurredAt = time.Now() } b, err := json.Marshal(ev) if err != nil { return } ctx2, cancel := context.WithTimeout(ctx, 4*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx2, http.MethodPost, r.endpoint, bytes.NewReader(b)) if err != nil { return } req.Header.Set("Content-Type", "application/json") if r.token != "" { req.Header.Set("Authorization", "Bearer "+r.token) } _, _ = r.client.Do(req) }