DEVELOPMENT ENVIRONMENT

~liljamo/felu

129b1c960bc41052a3ea052ecdfe9eb73d9dd66f — Jonni Liljamo 11 months ago cfac1e9 0.1.1
feat: some logging improvements
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)
}