diff --git a/cmd/banforge/command/daemon.go b/cmd/banforge/command/daemon.go index f92d11d..6e5ebd1 100644 --- a/cmd/banforge/command/daemon.go +++ b/cmd/banforge/command/daemon.go @@ -10,6 +10,7 @@ import ( "github.com/d3m0k1d/BanForge/internal/config" "github.com/d3m0k1d/BanForge/internal/judge" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" "github.com/d3m0k1d/BanForge/internal/parser" "github.com/d3m0k1d/BanForge/internal/storage" "github.com/spf13/cobra" @@ -60,10 +61,13 @@ var DaemonCmd = &cobra.Command{ log.Error("Failed to load config", "error", err) os.Exit(1) } - _, err = config.LoadMetricsConfig() - if err != nil { - log.Error("Failed to load metrics config", "error", err) - os.Exit(1) + + if cfg.Metrics.Enabled { + go func() { + if err := metrics.StartMetricsServer(cfg.Metrics.Port); err != nil { + log.Error("Failed to start metrics server", "error", err) + } + }() } var b blocker.BlockerEngine fw := cfg.Firewall.Name diff --git a/internal/blocker/firewalld.go b/internal/blocker/firewalld.go index f9a799c..eb27e6e 100644 --- a/internal/blocker/firewalld.go +++ b/internal/blocker/firewalld.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" ) type Firewalld struct { @@ -23,20 +24,24 @@ func (f *Firewalld) Ban(ip string) error { if err != nil { return err } + metrics.IncBanAttempt("firewalld") // #nosec G204 - ip is validated cmd := exec.Command("firewall-cmd", "--zone=drop", "--add-source", ip, "--permanent") output, err := cmd.CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Add source " + ip + " " + string(output)) output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Reload " + string(output)) + metrics.IncBan("firewalld") return nil } @@ -45,20 +50,24 @@ func (f *Firewalld) Unban(ip string) error { if err != nil { return err } + metrics.IncUnbanAttempt("firewalld") // #nosec G204 - ip is validated cmd := exec.Command("firewall-cmd", "--zone=drop", "--remove-source", ip, "--permanent") output, err := cmd.CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Remove source " + ip + " " + string(output)) output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Reload " + string(output)) + metrics.IncUnban("firewalld") return nil } @@ -70,6 +79,7 @@ func (f *Firewalld) PortOpen(port int, protocol string) error { return fmt.Errorf("invalid protocol") } s := strconv.Itoa(port) + metrics.IncPortOperation("open", protocol) cmd := exec.Command( "firewall-cmd", "--zone=public", @@ -79,12 +89,14 @@ func (f *Firewalld) PortOpen(port int, protocol string) error { output, err := cmd.CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Add port " + s + " " + string(output)) output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Reload " + string(output)) @@ -99,6 +111,7 @@ func (f *Firewalld) PortClose(port int, protocol string) error { return fmt.Errorf("invalid protocol") } s := strconv.Itoa(port) + metrics.IncPortOperation("close", protocol) cmd := exec.Command( "firewall-cmd", "--zone=public", @@ -107,11 +120,13 @@ func (f *Firewalld) PortClose(port int, protocol string) error { ) output, err := cmd.CombinedOutput() if err != nil { + metrics.IncError() return err } f.logger.Info("Remove port " + s + " " + string(output)) output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput() if err != nil { + metrics.IncError() return err } f.logger.Info("Reload " + string(output)) diff --git a/internal/blocker/iptables.go b/internal/blocker/iptables.go index 83421f5..58bb8fd 100644 --- a/internal/blocker/iptables.go +++ b/internal/blocker/iptables.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" ) type Iptables struct { @@ -24,6 +25,7 @@ func (f *Iptables) Ban(ip string) error { if err != nil { return err } + metrics.IncBanAttempt("iptables") err = validateConfigPath(f.config) if err != nil { return err @@ -36,11 +38,13 @@ func (f *Iptables) Ban(ip string) error { "ip", ip, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } f.logger.Info("IP banned", "ip", ip, "output", string(output)) + metrics.IncBan("iptables") err = validateConfigPath(f.config) if err != nil { @@ -54,6 +58,7 @@ func (f *Iptables) Ban(ip string) error { "config_path", f.config, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } f.logger.Info("config saved", @@ -67,6 +72,7 @@ func (f *Iptables) Unban(ip string) error { if err != nil { return err } + metrics.IncUnbanAttempt("iptables") err = validateConfigPath(f.config) if err != nil { return err @@ -79,11 +85,13 @@ func (f *Iptables) Unban(ip string) error { "ip", ip, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } f.logger.Info("IP unbanned", "ip", ip, "output", string(output)) + metrics.IncUnban("iptables") err = validateConfigPath(f.config) if err != nil { @@ -97,6 +105,7 @@ func (f *Iptables) Unban(ip string) error { "config_path", f.config, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } f.logger.Info("config saved", @@ -112,11 +121,13 @@ func (f *Iptables) PortOpen(port int, protocol string) error { return nil } s := strconv.Itoa(port) + metrics.IncPortOperation("open", protocol) // #nosec G204 - managed by system adminstartor cmd := exec.Command("iptables", "-A", "INPUT", "-p", protocol, "--dport", s, "-j", "ACCEPT") output, err := cmd.CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Add port " + s + " " + string(output)) @@ -128,6 +139,7 @@ func (f *Iptables) PortOpen(port int, protocol string) error { "config_path", f.config, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } } @@ -141,11 +153,13 @@ func (f *Iptables) PortClose(port int, protocol string) error { return nil } s := strconv.Itoa(port) + metrics.IncPortOperation("close", protocol) // #nosec G204 - managed by system adminstartor cmd := exec.Command("iptables", "-D", "INPUT", "-p", protocol, "--dport", s, "-j", "ACCEPT") output, err := cmd.CombinedOutput() if err != nil { f.logger.Error(err.Error()) + metrics.IncError() return err } f.logger.Info("Add port " + s + " " + string(output)) @@ -157,6 +171,7 @@ func (f *Iptables) PortClose(port int, protocol string) error { "config_path", f.config, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } } diff --git a/internal/blocker/nftables.go b/internal/blocker/nftables.go index ff8c733..4e7b4fb 100644 --- a/internal/blocker/nftables.go +++ b/internal/blocker/nftables.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" ) type Nftables struct { @@ -26,6 +27,7 @@ func (n *Nftables) Ban(ip string) error { if err != nil { return err } + metrics.IncBanAttempt("nftables") // #nosec G204 - ip is validated cmd := exec.Command("nft", "add", "rule", "inet", "banforge", "banned", "ip", "saddr", ip, "drop") @@ -35,16 +37,19 @@ func (n *Nftables) Ban(ip string) error { "ip", ip, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } n.logger.Info("IP banned", "ip", ip) + metrics.IncBan("nftables") err = saveNftablesConfig(n.config) if err != nil { n.logger.Error("failed to save config", "config_path", n.config, "error", err.Error()) + metrics.IncError() return err } @@ -57,17 +62,20 @@ func (n *Nftables) Unban(ip string) error { if err != nil { return err } + metrics.IncUnbanAttempt("nftables") handle, err := n.findRuleHandle(ip) if err != nil { n.logger.Error("failed to find rule handle", "ip", ip, "error", err.Error()) + metrics.IncError() return err } if handle == "" { n.logger.Warn("no rule found for IP", "ip", ip) + metrics.IncError() return fmt.Errorf("no rule found for IP %s", ip) } // #nosec G204 - handle is extracted from nftables output and validated @@ -80,16 +88,19 @@ func (n *Nftables) Unban(ip string) error { "handle", handle, "error", err.Error(), "output", string(output)) + metrics.IncError() return err } n.logger.Info("IP unbanned", "ip", ip, "handle", handle) + metrics.IncUnban("nftables") err = saveNftablesConfig(n.config) if err != nil { n.logger.Error("failed to save config", "config_path", n.config, "error", err.Error()) + metrics.IncError() return err } @@ -172,9 +183,11 @@ func (n *Nftables) PortOpen(port int, protocol string) error { if port >= 0 && port <= 65535 { if protocol != "tcp" && protocol != "udp" { n.logger.Error("invalid protocol") + metrics.IncError() return fmt.Errorf("invalid protocol") } s := strconv.Itoa(port) + metrics.IncPortOperation("open", protocol) // #nosec G204 - managed by system adminstartor cmd := exec.Command( "nft", @@ -191,6 +204,7 @@ func (n *Nftables) PortOpen(port int, protocol string) error { output, err := cmd.CombinedOutput() if err != nil { n.logger.Error(err.Error()) + metrics.IncError() return err } n.logger.Info("Add port " + s + " " + string(output)) @@ -199,6 +213,7 @@ func (n *Nftables) PortOpen(port int, protocol string) error { n.logger.Error("failed to save config", "config_path", n.config, "error", err.Error()) + metrics.IncError() return err } } @@ -209,9 +224,11 @@ func (n *Nftables) PortClose(port int, protocol string) error { if port >= 0 && port <= 65535 { if protocol != "tcp" && protocol != "udp" { n.logger.Error("invalid protocol") + metrics.IncError() return fmt.Errorf("invalid protocol") } s := strconv.Itoa(port) + metrics.IncPortOperation("close", protocol) // #nosec G204 - managed by system adminstartor cmd := exec.Command( "nft", @@ -228,6 +245,7 @@ func (n *Nftables) PortClose(port int, protocol string) error { output, err := cmd.CombinedOutput() if err != nil { n.logger.Error(err.Error()) + metrics.IncError() return err } n.logger.Info("Add port " + s + " " + string(output)) @@ -236,6 +254,7 @@ func (n *Nftables) PortClose(port int, protocol string) error { n.logger.Error("failed to save config", "config_path", n.config, "error", err.Error()) + metrics.IncError() return err } diff --git a/internal/blocker/ufw.go b/internal/blocker/ufw.go index 2def949..df02a35 100644 --- a/internal/blocker/ufw.go +++ b/internal/blocker/ufw.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" ) type Ufw struct { @@ -23,6 +24,7 @@ func (u *Ufw) Ban(ip string) error { if err != nil { return err } + metrics.IncBanAttempt("ufw") // #nosec G204 - ip is validated cmd := exec.Command("ufw", "--force", "deny", "from", ip) output, err := cmd.CombinedOutput() @@ -31,10 +33,12 @@ func (u *Ufw) Ban(ip string) error { "ip", ip, "error", err.Error(), "output", string(output)) + metrics.IncError() return fmt.Errorf("failed to ban IP %s: %w", ip, err) } u.logger.Info("IP banned", "ip", ip, "output", string(output)) + metrics.IncBan("ufw") return nil } func (u *Ufw) Unban(ip string) error { @@ -42,6 +46,7 @@ func (u *Ufw) Unban(ip string) error { if err != nil { return err } + metrics.IncUnbanAttempt("ufw") // #nosec G204 - ip is validated cmd := exec.Command("ufw", "--force", "delete", "deny", "from", ip) output, err := cmd.CombinedOutput() @@ -50,10 +55,12 @@ func (u *Ufw) Unban(ip string) error { "ip", ip, "error", err.Error(), "output", string(output)) + metrics.IncError() return fmt.Errorf("failed to unban IP %s: %w", ip, err) } u.logger.Info("IP unbanned", "ip", ip, "output", string(output)) + metrics.IncUnban("ufw") return nil } @@ -61,14 +68,17 @@ func (u *Ufw) PortOpen(port int, protocol string) error { if port >= 0 && port <= 65535 { if protocol != "tcp" && protocol != "udp" { u.logger.Error("invalid protocol") + metrics.IncError() return fmt.Errorf("invalid protocol") } s := strconv.Itoa(port) + metrics.IncPortOperation("open", protocol) // #nosec G204 - managed by system adminstartor cmd := exec.Command("ufw", "allow", s+"/"+protocol) output, err := cmd.CombinedOutput() if err != nil { u.logger.Error(err.Error()) + metrics.IncError() return err } u.logger.Info("Add port " + s + " " + string(output)) @@ -80,14 +90,17 @@ func (u *Ufw) PortClose(port int, protocol string) error { if port >= 0 && port <= 65535 { if protocol != "tcp" && protocol != "udp" { u.logger.Error("invalid protocol") + metrics.IncError() return nil } s := strconv.Itoa(port) + metrics.IncPortOperation("close", protocol) // #nosec G204 - managed by system adminstartor cmd := exec.Command("ufw", "deny", s+"/"+protocol) output, err := cmd.CombinedOutput() if err != nil { u.logger.Error(err.Error()) + metrics.IncError() return err } u.logger.Info("Add port " + s + " " + string(output)) diff --git a/internal/config/appconf.go b/internal/config/appconf.go index 26436d0..4d9015f 100644 --- a/internal/config/appconf.go +++ b/internal/config/appconf.go @@ -9,25 +9,8 @@ import ( "time" "github.com/BurntSushi/toml" - "github.com/d3m0k1d/BanForge/internal/metrics" ) -func LoadMetricsConfig() (*Metrics, error) { - cfg := &Metrics{} - _, err := toml.DecodeFile("/etc/banforge/config.toml", cfg) - if err != nil { - return nil, fmt.Errorf("failed to decode config: %w", err) - } - - if cfg.Enabled && cfg.Port > 0 && cfg.Port < 65535 { - go metrics.StartMetricsServer(cfg.Port) - } else if cfg.Enabled { - fmt.Println("Metrics enabled but port invalid, not starting server") - } - - return cfg, nil -} - func LoadRuleConfig() ([]Rule, error) { const rulesDir = "/etc/banforge/rules.d" diff --git a/internal/judge/judge.go b/internal/judge/judge.go index 350e677..0dd82be 100644 --- a/internal/judge/judge.go +++ b/internal/judge/judge.go @@ -8,6 +8,7 @@ import ( "github.com/d3m0k1d/BanForge/internal/blocker" "github.com/d3m0k1d/BanForge/internal/config" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" "github.com/d3m0k1d/BanForge/internal/storage" ) @@ -85,19 +86,23 @@ func (j *Judge) Tribunal() { banned, err := j.db_r.IsBanned(entry.IP) if err != nil { j.logger.Error("Failed to check ban status", "ip", entry.IP, "error", err) + metrics.IncError() break } if banned { j.logger.Info("IP already banned", "ip", entry.IP) + metrics.IncLogParsed() break } exceeded, err := j.db_rq.IsMaxRetryExceeded(entry.IP, rule.MaxRetry) if err != nil { j.logger.Error("Failed to check retry count", "ip", entry.IP, "error", err) + metrics.IncError() break } if !exceeded { j.logger.Info("Max retry not exceeded", "ip", entry.IP) + metrics.IncLogParsed() break } err = j.db_w.AddBan(entry.IP, rule.BanTime, rule.Name) @@ -116,6 +121,7 @@ func (j *Judge) Tribunal() { if err := j.Blocker.Ban(entry.IP); err != nil { j.logger.Error("Failed to ban IP at firewall", "ip", entry.IP, "error", err) + metrics.IncError() break } j.logger.Info( @@ -127,6 +133,7 @@ func (j *Judge) Tribunal() { "ban_time", rule.BanTime, ) + metrics.IncBan(rule.ServiceName) break } } @@ -147,12 +154,16 @@ func (j *Judge) UnbanChecker() { ips, err := j.db_w.RemoveExpiredBans() if err != nil { j.logger.Error(fmt.Sprintf("Failed to check expired bans: %v", err)) + metrics.IncError() continue } for _, ip := range ips { if err := j.Blocker.Unban(ip); err != nil { j.logger.Error(fmt.Sprintf("Failed to unban IP at firewall: %v", err)) + metrics.IncError() + } else { + metrics.IncUnban("judge") } } } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index e409463..e5a63c5 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net/http" + "strconv" "sync" "time" ) @@ -39,6 +40,58 @@ func IncLogParsed() { metricsMu.Unlock() } +func IncError() { + metricsMu.Lock() + metrics["error_count"]++ + metricsMu.Unlock() +} + +func IncBanAttempt(firewall string) { + metricsMu.Lock() + metrics["ban_attempt_count"]++ + metrics[firewall+"_ban_attempts"]++ + metricsMu.Unlock() +} + +func IncUnbanAttempt(firewall string) { + metricsMu.Lock() + metrics["unban_attempt_count"]++ + metrics[firewall+"_unban_attempts"]++ + metricsMu.Unlock() +} + +func IncPortOperation(operation string, protocol string) { + metricsMu.Lock() + key := "port_" + operation + "_" + protocol + metrics[key]++ + metricsMu.Unlock() +} + +func IncParserEvent(service string) { + metricsMu.Lock() + metrics[service+"_parsed_events"]++ + metricsMu.Unlock() +} + +func IncScannerEvent(service string) { + metricsMu.Lock() + metrics[service+"_scanner_events"]++ + metricsMu.Unlock() +} + +func IncDBOperation(operation string, table string) { + metricsMu.Lock() + key := "db_" + operation + "_" + table + metrics[key]++ + metricsMu.Unlock() +} + +func IncRequestCount(service string) { + metricsMu.Lock() + metrics[service+"_request_count"]++ + metricsMu.Unlock() +} + func MetricsHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { metricsMu.RLock() @@ -58,12 +111,12 @@ func MetricsHandler() http.Handler { }) } -func StartMetricsServer(port int) { +func StartMetricsServer(port int) error { mux := http.NewServeMux() mux.Handle("/metrics", MetricsHandler()) server := &http.Server{ - Addr: fmt.Sprintf(":%d", port), + Addr: "localhost:" + strconv.Itoa(port), Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, @@ -72,6 +125,7 @@ func StartMetricsServer(port int) { log.Printf("Starting metrics server on %s", server.Addr) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Printf("Metrics server error: %v", err) + return fmt.Errorf("metrics server failed: %w", err) } + return nil } diff --git a/internal/parser/ApacheParser.go b/internal/parser/ApacheParser.go index 2fda8e3..c8fe53a 100644 --- a/internal/parser/ApacheParser.go +++ b/internal/parser/ApacheParser.go @@ -4,6 +4,7 @@ import ( "regexp" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" "github.com/d3m0k1d/BanForge/internal/storage" ) @@ -50,6 +51,7 @@ func (p *ApacheParser) Parse(eventCh <-chan Event, resultCh chan<- *storage.LogE Status: status, Method: method, } + metrics.IncParserEvent("apache") p.logger.Info( "Parsed apache log entry", "ip", matches[1], diff --git a/internal/parser/NginxParser.go b/internal/parser/NginxParser.go index 7e94144..a754d91 100644 --- a/internal/parser/NginxParser.go +++ b/internal/parser/NginxParser.go @@ -4,6 +4,7 @@ import ( "regexp" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" "github.com/d3m0k1d/BanForge/internal/storage" ) @@ -40,6 +41,7 @@ func (p *NginxParser) Parse(eventCh <-chan Event, resultCh chan<- *storage.LogEn Status: status, Method: method, } + metrics.IncParserEvent("nginx") p.logger.Info( "Parsed nginx log entry", "ip", diff --git a/internal/parser/parser.go b/internal/parser/parser.go index f24cf2b..2984ec2 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -11,6 +11,7 @@ import ( "time" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" ) type Event struct { @@ -137,6 +138,7 @@ func (s *Scanner) Start() { default: if s.scanner.Scan() { + metrics.IncScannerEvent("scanner") s.ch <- Event{ Data: s.scanner.Text(), } @@ -144,6 +146,7 @@ func (s *Scanner) Start() { } else { if err := s.scanner.Err(); err != nil { s.logger.Error("Scanner error") + metrics.IncError() return } } diff --git a/internal/parser/sshd.go b/internal/parser/sshd.go index 69154ca..4679eaa 100644 --- a/internal/parser/sshd.go +++ b/internal/parser/sshd.go @@ -4,6 +4,7 @@ import ( "regexp" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" "github.com/d3m0k1d/BanForge/internal/storage" ) @@ -36,6 +37,7 @@ func (p *SshdParser) Parse(eventCh <-chan Event, resultCh chan<- *storage.LogEnt Status: "Failed", Method: matches[4], // method auth } + metrics.IncParserEvent("ssh") p.logger.Info( "Parsed ssh log entry", "ip", diff --git a/internal/storage/ban_db.go b/internal/storage/ban_db.go index 4553f19..87acc4a 100644 --- a/internal/storage/ban_db.go +++ b/internal/storage/ban_db.go @@ -8,6 +8,7 @@ import ( "github.com/d3m0k1d/BanForge/internal/config" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" "github.com/jedib0t/go-pretty/v6/table" _ "modernc.org/sqlite" ) @@ -37,6 +38,7 @@ func (d *BanWriter) CreateTable() error { if err != nil { return err } + metrics.IncDBOperation("create_table", "bans") d.logger.Info("Created tables") return nil } @@ -45,6 +47,7 @@ func (d *BanWriter) AddBan(ip string, ttl string, reason string) error { duration, err := config.ParseDurationWithYears(ttl) if err != nil { d.logger.Error("Invalid duration format", "ttl", ttl, "error", err) + metrics.IncError() return fmt.Errorf("invalid duration: %w", err) } @@ -60,9 +63,11 @@ func (d *BanWriter) AddBan(ip string, ttl string, reason string) error { ) if err != nil { d.logger.Error("Failed to add ban", "error", err) + metrics.IncError() return err } + metrics.IncDBOperation("insert", "bans") return nil } @@ -70,8 +75,10 @@ func (d *BanWriter) RemoveBan(ip string) error { _, err := d.db.Exec("DELETE FROM bans WHERE ip = ?", ip) if err != nil { d.logger.Error("Failed to remove ban", "error", err) + metrics.IncError() return err } + metrics.IncDBOperation("delete", "bans") return nil } @@ -85,6 +92,7 @@ func (w *BanWriter) RemoveExpiredBans() ([]string, error) { ) if err != nil { w.logger.Error("Failed to get expired bans", "error", err) + metrics.IncError() return nil, err } defer func() { @@ -113,6 +121,7 @@ func (w *BanWriter) RemoveExpiredBans() ([]string, error) { ) if err != nil { w.logger.Error("Failed to remove expired bans", "error", err) + metrics.IncError() return nil, err } @@ -123,6 +132,7 @@ func (w *BanWriter) RemoveExpiredBans() ([]string, error) { if rowsAffected > 0 { w.logger.Info("Removed expired bans", "count", rowsAffected, "ips", len(ips)) + metrics.IncDBOperation("delete_expired", "bans") } return ips, nil @@ -169,8 +179,10 @@ func (d *BanReader) IsBanned(ip string) (bool, error) { return false, nil } if err != nil { + metrics.IncError() return false, fmt.Errorf("failed to check ban status: %w", err) } + metrics.IncDBOperation("select", "bans") return true, nil } @@ -183,6 +195,7 @@ func (d *BanReader) BanList() error { rows, err := d.db.Query("SELECT ip, banned_at, reason, expired_at FROM bans") if err != nil { d.logger.Error("Failed to get ban list", "error", err) + metrics.IncError() return err } for rows.Next() { @@ -194,12 +207,14 @@ func (d *BanReader) BanList() error { err := rows.Scan(&ip, &bannedAt, &reason, &expiredAt) if err != nil { d.logger.Error("Failed to get ban list", "error", err) + metrics.IncError() return err } t.AppendRow(table.Row{count, ip, bannedAt, reason, expiredAt}) } t.Render() + metrics.IncDBOperation("select", "bans") return nil } diff --git a/internal/storage/requests_db.go b/internal/storage/requests_db.go index 40d7c2d..5e0e90a 100644 --- a/internal/storage/requests_db.go +++ b/internal/storage/requests_db.go @@ -4,6 +4,7 @@ import ( "database/sql" "github.com/d3m0k1d/BanForge/internal/logger" + "github.com/d3m0k1d/BanForge/internal/metrics" _ "modernc.org/sqlite" ) @@ -59,8 +60,10 @@ func (r *RequestReader) IsMaxRetryExceeded(ip string, maxRetry int) (bool, error err := r.db.QueryRow("SELECT COUNT(*) FROM requests WHERE ip = ?", ip).Scan(&count) if err != nil { r.logger.Error("error query count: " + err.Error()) + metrics.IncError() return false, err } r.logger.Info("Current request count for IP", "ip", ip, "count", count, "maxRetry", maxRetry) + metrics.IncDBOperation("select", "requests") return count >= maxRetry, nil } diff --git a/internal/storage/writer.go b/internal/storage/writer.go index d822eb3..1947ab4 100644 --- a/internal/storage/writer.go +++ b/internal/storage/writer.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "time" + + "github.com/d3m0k1d/BanForge/internal/metrics" ) func WriteReq(db *RequestWriter, resultCh <-chan *LogEntry) { @@ -61,6 +63,9 @@ func WriteReq(db *RequestWriter, resultCh <-chan *LogEntry) { ) if err != nil { db.logger.Error(fmt.Errorf("failed to insert entry: %w", err).Error()) + metrics.IncError() + } else { + metrics.IncRequestCount(entry.Service) } } @@ -69,6 +74,7 @@ func WriteReq(db *RequestWriter, resultCh <-chan *LogEntry) { } batch = batch[:0] + metrics.IncDBOperation("insert", "requests") return err }() if err != nil {