M cmd/felu/main.go => cmd/felu/main.go +14 -9
@@ 7,14 7,16 @@
package main
import (
- "log"
+ "log/slog"
"net/http"
+ "os"
"strconv"
"time"
"git.src.quest/~skye/felu-ddns/internal/config"
"git.src.quest/~skye/felu-ddns/internal/db"
"git.src.quest/~skye/felu-ddns/internal/dns"
+ "git.src.quest/~skye/felu-ddns/internal/log"
"git.src.quest/~skye/felu-ddns/internal/routers"
"github.com/alexedwards/scs/v2"
"golang.org/x/sync/errgroup"
@@ 28,16 30,19 @@ var (
)
func main() {
- log.Print("[felu] Starting up...")
-
config.InitConfig()
+ log.InitDefaultLogger(config.FeluConfig.LogLevel)
+ slog.Info("Starting up...")
+
if err := db.InitDB(); err != nil {
- log.Fatalf("[felu] Failed to initialize database: '%s'", err)
+ slog.Error("Failed to initialize database", slog.Any("err", err))
+ os.Exit(1)
}
defer db.DBConn.Close()
if err := db.InitAdminUser(); err != nil {
- log.Fatalf("[felu] Failed to initialize admin user: '%s'", err)
+ slog.Error("Failed to initialize admin user", slog.Any("err", err))
+ os.Exit(1)
}
sessionManager = scs.New()
@@ 57,12 62,12 @@ func main() {
WriteTimeout: 10 * time.Second,
}
- log.Printf("[felu] Serving frontend at '%s'", config.FeluConfig.FrontendBindAddr)
+ slog.Info("Serving frontend", slog.String("addr", config.FeluConfig.FrontendBindAddr))
g.Go(func() error {
return frontend.ListenAndServe()
})
- log.Printf("[felu] Serving api at '%s'", config.FeluConfig.APIBindAddr)
+ slog.Info("Serving API", slog.String("addr", config.FeluConfig.APIBindAddr))
g.Go(func() error {
return api.ListenAndServe()
})
@@ 70,12 75,12 @@ func main() {
dnsIP := config.FeluConfig.DNSBindIP
dnsPort := strconv.Itoa(int(config.FeluConfig.DNSBindPort))
dnsAddr := dnsIP + ":" + dnsPort
- log.Printf("[felu] Serving DNS at '%s'", dnsAddr)
+ slog.Info("Serving DNS", slog.String("addr", dnsAddr))
g.Go(func() error {
return dns.Run(dnsAddr)
})
if err := g.Wait(); err != nil {
- log.Fatalf("[felu] Error while running: %s", err)
+ slog.Info("Error while running", slog.Any("err", err))
}
}
M internal/config/config.go => internal/config/config.go +4 -0
@@ 18,6 18,8 @@ type config struct {
// Initial password for the admin user, only used if no admin account (e.g. first boot)
InitialAdminPwd string
+ LogLevel string
+
// Data directory, with trailing slash
DataDir string
@@ 38,6 40,8 @@ func InitConfig() {
InitialAdminEmail: util.LoadEnvStr("FELU_INITIAL_ADMIN_EMAIL", "admin@example.com"),
InitialAdminPwd: util.LoadEnvStr("FELU_INITIAL_ADMIN_PWD", "feluadmin"),
+ LogLevel: util.LoadEnvStr("FELU_LOG_LEVEL", "info"),
+
DataDir: util.LoadEnvStr("FELU_DB_PATH", "/var/felu/"),
FrontendBindAddr: util.LoadEnvStr("FELU_FRONTEND_BIND_ADDR", "0.0.0.0:8080"),
M internal/db/db.go => internal/db/db.go +3 -0
@@ 8,6 8,7 @@ package db
import (
"database/sql"
+ "log/slog"
"git.src.quest/~skye/felu-ddns/internal/config"
_ "github.com/mattn/go-sqlite3"
@@ 36,10 37,12 @@ func InitAdminUser() error {
defer rows.Close()
if rows.Next() {
// There is at least one...
+ slog.Info("Existing admin user found")
return nil
}
// Since we're here, it's assumed no admin accounts exist
+ slog.Info("Creating initial admin user")
err = CreateAdmin(config.FeluConfig.InitialAdminEmail, config.FeluConfig.InitialAdminPwd)
if err != nil {
return err
M internal/db/migrations.go => internal/db/migrations.go +12 -9
@@ 8,13 8,14 @@ package db
import (
"fmt"
- "log"
+ "log/slog"
+ "os"
)
var migrationsTable string = "schema_migrations"
func runMigrations() {
- log.Print("[felu/db] Running migrations")
+ slog.Info("Running migrations")
var schemaVersion int = 0
@@ 26,19 27,20 @@ func runMigrations() {
migrationsTable)
err := DBConn.QueryRow(schemaVersionQuery).Scan(&schemaVersion)
if err != nil {
- log.Print("[felu/db] No schema version found, running all migrations")
+ slog.Info("No schema version found, running all migrations")
} else {
- log.Printf("[felu/db] Currently on schema version %d", schemaVersion)
+ slog.Info("Current schema", slog.Int("version", schemaVersion))
}
migrations := migrations()
if schemaVersion != len(migrations) {
for i := 0; i < len(migrations); i++ {
if i >= schemaVersion {
- log.Printf("[felu/db] Running migration %d", i + 1) // + 1 is just a visual thing
+ slog.Info("Running migration", slog.Int("version", i + 1)) // + 1 is just a visual thing
_, err := DBConn.Exec(migrations[i])
if err != nil {
- log.Fatalf("[felu/db] Migration %d failed to run!", i)
+ slog.Error("Migration failed to run!", slog.Int("version", i))
+ os.Exit(1)
}
}
}
@@ 51,12 53,13 @@ func runMigrations() {
migrationsTable, schemaVersion)
_, err = DBConn.Exec(schemaMigrationInsertQuery)
if err != nil {
- log.Fatal("[felu/db] Migrations ran, but was not able to create migration entry")
+ slog.Error("Migrations ran, but was not able to create migration entry")
+ os.Exit(1)
} else {
- log.Print("[felu/db] Migrations ran successfully")
+ slog.Info("Migrations ran successfully")
}
} else {
- log.Printf("[felu/db] Already on schema version %d, no migrations to run", schemaVersion)
+ slog.Info("No migrations to run")
}
}
M internal/dns/query.go => internal/dns/query.go +2 -2
@@ 7,7 7,7 @@
package dns
import (
- "log"
+ "log/slog"
"net"
"strings"
@@ 25,7 25,7 @@ func parseQuery(m *dns.Msg, r *dns.Msg) {
}
func handleARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
- log.Printf("[felu/dns] Query for '%s'", q.Name)
+ slog.Info("A Record Query", slog.String("qname", q.Name))
if index := strings.IndexByte(q.Name, '.'); index >= 0 {
aRecord, err := db.FetchDomainARecord(q.Name[:index])
M internal/handlers/auth.go => internal/handlers/auth.go +1 -2
@@ 7,7 7,6 @@
package handlers
import (
- "log"
"net/http"
"git.src.quest/~skye/felu-ddns/internal/db"
@@ 24,8 23,8 @@ func AuthLogin(sm *scs.SessionManager) gin.HandlerFunc {
return func(c *gin.Context) {
data := &postAuthLoginData{}
if err := c.Bind(data); err != nil {
- log.Printf("[felu] ERROR: Could not bind login details: %v", err)
c.String(http.StatusBadRequest, "Could not bind login details")
+ c.Abort()
return
}
M internal/handlers/domains.go => internal/handlers/domains.go +1 -2
@@ 7,7 7,6 @@
package handlers
import (
- "log"
"net"
"net/http"
"regexp"
@@ 26,8 25,8 @@ func PostDomain() gin.HandlerFunc {
return func(c *gin.Context) {
data := &postDomainData{}
if err := c.Bind(data); err != nil {
- log.Printf("[felu] ERROR: Could not bind domain data: %v", err)
c.String(http.StatusBadRequest, "Could not bind domain data")
+ c.Abort()
return
}
M internal/handlers/user.go => internal/handlers/user.go +2 -3
@@ 7,7 7,6 @@
package handlers
import (
- "log"
"net/http"
"git.src.quest/~skye/felu-ddns/internal/db"
@@ 25,8 24,8 @@ func PostUserPassword() gin.HandlerFunc {
return func(c *gin.Context) {
data := &postUserPasswordData{}
if err := c.Bind(data); err != nil {
- log.Printf("[felu] ERROR: Could not bind password data: %v", err)
c.String(http.StatusBadRequest, "Could not bind password data")
+ c.Abort()
return
}
@@ 74,8 73,8 @@ func PostUserEmail() gin.HandlerFunc {
return func(c *gin.Context) {
data := &postUserEmailData{}
if err := c.Bind(data); err != nil {
- log.Printf("[felu] ERROR: Could not bind email data: %v", err)
c.String(http.StatusBadRequest, "Could not bind email data")
+ c.Abort()
return
}
M internal/handlers/users.go => internal/handlers/users.go +1 -2
@@ 7,7 7,6 @@
package handlers
import (
- "log"
"net/http"
"git.src.quest/~skye/felu-ddns/internal/db"
@@ 23,8 22,8 @@ func PostUser() gin.HandlerFunc {
return func(c *gin.Context) {
data := &postUserData{}
if err := c.Bind(data); err != nil {
- log.Printf("[felu] ERROR: Could not bind user data: %v", err)
c.String(http.StatusBadRequest, "Could not bind user data")
+ c.Abort()
return
}
A internal/log/log.go => internal/log/log.go +44 -0
@@ 0,0 1,44 @@
+/*
+ * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
+ * more information.
+ */
+package log
+
+import (
+ "log/slog"
+ "os"
+ "path/filepath"
+)
+
+func InitDefaultLogger(logLevel string) {
+ var feluLogLevel = new(slog.LevelVar)
+ switch logLevel {
+ case "debug":
+ feluLogLevel.Set(slog.LevelDebug)
+ case "info":
+ feluLogLevel.Set(slog.LevelInfo)
+ case "warning":
+ feluLogLevel.Set(slog.LevelWarn)
+ case "error":
+ feluLogLevel.Set(slog.LevelError)
+ }
+
+ replace := func(groups []string, a slog.Attr) slog.Attr {
+ if a.Key == slog.SourceKey {
+ source := a.Value.Any().(*slog.Source)
+ source.File = filepath.Base(source.File)
+ }
+
+ return a
+ }
+
+ logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
+ Level: feluLogLevel,
+ AddSource: true,
+ ReplaceAttr: replace,
+ }))
+
+ slog.SetDefault(logger)
+}