184 lines
4.5 KiB
Go
184 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"arclineit/arcline-uptime/internal/alert"
|
|
"arclineit/arcline-uptime/internal/config"
|
|
"arclineit/arcline-uptime/internal/monitor"
|
|
"arclineit/arcline-uptime/internal/scheduler"
|
|
"arclineit/arcline-uptime/internal/store"
|
|
"arclineit/arcline-uptime/internal/web"
|
|
)
|
|
|
|
var version = "dev"
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
switch os.Args[1] {
|
|
case "start":
|
|
cmdStart(os.Args[2:])
|
|
case "check":
|
|
cmdCheck(os.Args[2:])
|
|
case "list":
|
|
cmdList(os.Args[2:])
|
|
case "version":
|
|
fmt.Println(version)
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown command %q\n\n", os.Args[1])
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func printUsage() {
|
|
fmt.Fprintf(os.Stderr, "arcline-uptime %s\n\nUsage:\n", version)
|
|
fmt.Fprintf(os.Stderr, " arcline-uptime start [--config FILE] [--db FILE]\n")
|
|
fmt.Fprintf(os.Stderr, " arcline-uptime check [--config FILE] [--monitor NAME]\n")
|
|
fmt.Fprintf(os.Stderr, " arcline-uptime list [--config FILE]\n")
|
|
fmt.Fprintf(os.Stderr, " arcline-uptime version\n")
|
|
}
|
|
|
|
func cmdStart(args []string) {
|
|
fs := flag.NewFlagSet("start", flag.ExitOnError)
|
|
configPath := fs.String("config", "uptime.yaml", "path to config file")
|
|
dbPath := fs.String("db", "uptime.db", "path to SQLite database")
|
|
fs.Parse(args)
|
|
|
|
cfg, err := config.Load(*configPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Configure structured logging as early as possible.
|
|
setupLogging(cfg.Global.LogFormat)
|
|
|
|
db, err := store.Open(*dbPath)
|
|
if err != nil {
|
|
slog.Error("open database", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer db.Close()
|
|
|
|
checkers, err := monitor.BuildCheckers(cfg.Monitors, time.Duration(cfg.Global.Timeout)*time.Second)
|
|
if err != nil {
|
|
slog.Error("build monitors", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
alerters, err := alert.BuildNamedAlerters(cfg.Alerts)
|
|
if err != nil {
|
|
slog.Error("build alerters", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
if cfg.Dashboard.Enabled {
|
|
srv := web.NewServer(cfg, db)
|
|
go func() {
|
|
slog.Info("dashboard listening", "addr", cfg.Dashboard.Listen)
|
|
if err := srv.ListenAndServe(); err != nil {
|
|
slog.Error("dashboard server", "error", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
sched := scheduler.New(checkers, alerters, cfg.Monitors, db, cfg.Global)
|
|
slog.Info("starting",
|
|
"monitors", len(checkers),
|
|
"interval", cfg.Global.CheckInterval,
|
|
"retention_days", cfg.Global.RetentionDays,
|
|
)
|
|
sched.Start(ctx)
|
|
|
|
<-ctx.Done()
|
|
slog.Info("shutting down")
|
|
sched.Wait()
|
|
}
|
|
|
|
func cmdCheck(args []string) {
|
|
fs := flag.NewFlagSet("check", flag.ExitOnError)
|
|
configPath := fs.String("config", "uptime.yaml", "path to config file")
|
|
monitorName := fs.String("monitor", "", "monitor name (omit to check all)")
|
|
fs.Parse(args)
|
|
|
|
cfg, err := config.Load(*configPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
checkers, err := monitor.BuildCheckers(cfg.Monitors, time.Duration(cfg.Global.Timeout)*time.Second)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "monitors: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
exitCode := 0
|
|
for _, c := range checkers {
|
|
if *monitorName != "" && c.Name() != *monitorName {
|
|
continue
|
|
}
|
|
r := c.Check()
|
|
if r.Up {
|
|
fmt.Printf("[OK] %-30s %dms\n", r.MonitorName, r.ResponseTime.Milliseconds())
|
|
} else {
|
|
fmt.Printf("[DOWN] %-30s %s\n", r.MonitorName, r.Error)
|
|
exitCode = 1
|
|
}
|
|
}
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func cmdList(args []string) {
|
|
fs := flag.NewFlagSet("list", flag.ExitOnError)
|
|
configPath := fs.String("config", "uptime.yaml", "path to config file")
|
|
fs.Parse(args)
|
|
|
|
cfg, err := config.Load(*configPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("%-30s %-6s %-10s %-40s\n", "NAME", "TYPE", "INTERVAL", "TARGET")
|
|
fmt.Printf("%s %s %s %s\n",
|
|
strings.Repeat("-", 30), strings.Repeat("-", 6),
|
|
strings.Repeat("-", 10), strings.Repeat("-", 40))
|
|
|
|
for _, m := range cfg.Monitors {
|
|
target := m.URL
|
|
if target == "" {
|
|
target = fmt.Sprintf("%s:%d", m.Host, m.Port)
|
|
}
|
|
interval := fmt.Sprintf("%ds", m.Interval)
|
|
fmt.Printf("%-30s %-6s %-10s %-40s\n", m.Name, m.Type, interval, target)
|
|
}
|
|
}
|
|
|
|
func setupLogging(format string) {
|
|
var h slog.Handler
|
|
opts := &slog.HandlerOptions{Level: slog.LevelInfo}
|
|
if format == "json" {
|
|
h = slog.NewJSONHandler(os.Stdout, opts)
|
|
} else {
|
|
h = slog.NewTextHandler(os.Stdout, opts)
|
|
}
|
|
slog.SetDefault(slog.New(h))
|
|
}
|