From 0191bc3821a50cef12613a53bd6d210aa1500faf Mon Sep 17 00:00:00 2001
From: Christoph Kluge <christoph.kluge@fau.de>
Date: Tue, 25 Feb 2025 10:21:48 +0100
Subject: [PATCH 1/7] Annotate and review log functions, add stdout writers

---
 pkg/log/log.go | 229 ++++++++++++++++++++++++++++---------------------
 1 file changed, 129 insertions(+), 100 deletions(-)

diff --git a/pkg/log/log.go b/pkg/log/log.go
index a40c656..a4e7703 100644
--- a/pkg/log/log.go
+++ b/pkg/log/log.go
@@ -84,109 +84,138 @@ func Init(lvl string, logdate bool) {
 	loglevel = lvl
 }
 
-/* PRINT */
-
-// Private helper
-func printStr(v ...interface{}) string {
-	return fmt.Sprint(v...)
-}
-
-// Uses Info() -> If errorpath required at some point:
-// Will need own writer with 'Output(2, out)' to correctly render path
-func Print(v ...interface{}) {
-	Info(v...)
-}
-
-func Debug(v ...interface{}) {
-	DebugLog.Output(2, printStr(v...))
-}
-
-func Info(v ...interface{}) {
-	InfoLog.Output(2, printStr(v...))
-}
-
-func Warn(v ...interface{}) {
-	WarnLog.Output(2, printStr(v...))
-}
-
-func Error(v ...interface{}) {
-	ErrLog.Output(2, printStr(v...))
-}
-
-// Writes panic stacktrace, but keeps application alive
-func Panic(v ...interface{}) {
-	ErrLog.Output(2, printStr(v...))
-	panic("Panic triggered ...")
-}
-
-func Crit(v ...interface{}) {
-	CritLog.Output(2, printStr(v...))
-}
-
-// Writes critical log, stops application
-func Fatal(v ...interface{}) {
-	CritLog.Output(2, printStr(v...))
-	os.Exit(1)
-}
-
-/* PRINT FORMAT*/
-
-// Private helper
-func printfStr(format string, v ...interface{}) string {
-	return fmt.Sprintf(format, v...)
-}
-
-// Uses Infof() -> If errorpath required at some point:
-// Will need own writer with 'Output(2, out)' to correctly render path
-func Printf(format string, v ...interface{}) {
-	Infof(format, v...)
-}
-
-func Debugf(format string, v ...interface{}) {
-	DebugLog.Output(2, printfStr(format, v...))
-}
-
-func Infof(format string, v ...interface{}) {
-	InfoLog.Output(2, printfStr(format, v...))
-}
-
-func Warnf(format string, v ...interface{}) {
-	WarnLog.Output(2, printfStr(format, v...))
-}
-
-func Errorf(format string, v ...interface{}) {
-	ErrLog.Output(2, printfStr(format, v...))
-}
-
-// Writes panic stacktrace, but keeps application alive
-func Panicf(format string, v ...interface{}) {
-	ErrLog.Output(2, printfStr(format, v...))
-	panic("Panic triggered ...")
-}
-
-func Critf(format string, v ...interface{}) {
-	CritLog.Output(2, printfStr(format, v...))
-}
-
-// Writes crit log, stops application
-func Fatalf(format string, v ...interface{}) {
-	CritLog.Output(2, printfStr(format, v...))
-	os.Exit(1)
-}
+/* HELPER */
 
 func Loglevel() string {
 	return loglevel
 }
 
-/* SPECIAL */
+/* PRIVATE HELPER */
 
-// func Finfof(w io.Writer, format string, v ...interface{}) {
-// 	if w != io.Discard {
-// 		if logDateTime {
-// 			currentTime := time.Now()
-// 			fmt.Fprintf(InfoWriter, currentTime.String()+InfoPrefix+format+"\n", v...)
-// 		} else {
-// 			fmt.Fprintf(InfoWriter, InfoPrefix+format+"\n", v...)
-// 		}
-// 	}
-// }
+// Return unformatted string
+func printStr(v ...interface{}) string {
+	return fmt.Sprint(v...)
+}
+
+// Return formatted string
+func printfStr(format string, v ...interface{}) string {
+	return fmt.Sprintf(format, v...)
+}
+
+/* PRINT */
+
+// Prints to STDOUT without string formatting; application continues.
+// Used for special cases not requiring log information like date or location.
+func Print(v ...interface{}) {
+	fmt.Fprint(os.Stdout, v...)
+}
+
+// Prints to STDOUT without string formatting; application exits with error code 0.
+// Used for exiting succesfully with message after expected outcome, e.g. successful single-call application runs.
+func Exit(v ...interface{}) {
+	fmt.Fprint(os.Stdout, v...)
+	os.Exit(0)
+}
+
+// Prints to STDOUT without string formatting; application exits with error code 1.
+// Used for terminating with message after to be expected errors, e.g. wrong arguments or during init().
+func Abort(v ...interface{}) {
+	fmt.Fprint(os.Stdout, v...)
+	os.Exit(1)
+}
+
+// Prints to DEBUG writer without string formatting; application continues.
+// Used for logging additional information, primarily for development.
+func Debug(v ...interface{}) {
+	DebugLog.Output(2, printStr(v...))
+}
+
+// Prints to INFO writer without string formatting; application continues.
+// Used for logging additional information, e.g. notable returns or common fail-cases.
+func Info(v ...interface{}) {
+	InfoLog.Output(2, printStr(v...))
+}
+
+// Prints to WARNING writer without string formatting; application continues.
+// Used for logging important information, e.g. uncommon edge-cases or administration related information.
+func Warn(v ...interface{}) {
+	WarnLog.Output(2, printStr(v...))
+}
+
+// Prints to ERROR writer without string formatting; application continues.
+// Used for logging errors, but code still can return default(s) or nil.
+func Error(v ...interface{}) {
+	ErrLog.Output(2, printStr(v...))
+}
+
+// Prints to CRITICAL writer without string formatting; application exits with error code 1.
+// Used for terminating on unexpected errors with date and code location.
+func Fatal(v ...interface{}) {
+	CritLog.Output(2, printStr(v...))
+	os.Exit(1)
+}
+
+// Prints to PANIC function without string formatting; application exits with panic.
+// Used for terminating on unexpected errors with stacktrace.
+func Panic(v ...interface{}) {
+	panic(printStr(v...))
+}
+
+/* PRINT FORMAT*/
+
+// Prints to STDOUT with string formatting; application continues.
+// Used for special cases not requiring log information like date or location.
+func Printf(format string, v ...interface{}) {
+	fmt.Fprintf(os.Stdout, format, v...)
+}
+
+// Prints to STDOUT with string formatting; application exits with error code 0.
+// Used for exiting succesfully with message after expected outcome, e.g. successful single-call application runs.
+func Exitf(format string, v ...interface{}) {
+	fmt.Fprintf(os.Stdout, format, v...)
+	os.Exit(0)
+}
+
+// Prints to STDOUT with string formatting; application exits with error code 1.
+// Used for terminating with message after to be expected errors, e.g. wrong arguments or during init().
+func Abortf(format string, v ...interface{}) {
+	fmt.Fprintf(os.Stdout, format, v...)
+	os.Exit(1)
+}
+
+// Prints to DEBUG writer with string formatting; application continues.
+// Used for logging additional information, primarily for development.
+func Debugf(format string, v ...interface{}) {
+	DebugLog.Output(2, printfStr(format, v...))
+}
+
+// Prints to INFO writer with string formatting; application continues.
+// Used for logging additional information, e.g. notable returns or common fail-cases.
+func Infof(format string, v ...interface{}) {
+	InfoLog.Output(2, printfStr(format, v...))
+}
+
+// Prints to WARNING writer with string formatting; application continues.
+// Used for logging important information, e.g. uncommon edge-cases or administration related information.
+func Warnf(format string, v ...interface{}) {
+	WarnLog.Output(2, printfStr(format, v...))
+}
+
+// Prints to ERROR writer with string formatting; application continues.
+// Used for logging errors, but code still can return default(s) or nil.
+func Errorf(format string, v ...interface{}) {
+	ErrLog.Output(2, printfStr(format, v...))
+}
+
+// Prints to CRITICAL writer with string formatting; application exits with error code 1.
+// Used for terminating on unexpected errors with date and code location.
+func Fatalf(format string, v ...interface{}) {
+	CritLog.Output(2, printfStr(format, v...))
+	os.Exit(1)
+}
+
+// Prints to PANIC function with string formatting; application exits with panic.
+// Used for terminating on unexpected errors with stacktrace.
+func Panicf(format string, v ...interface{}) {
+	panic(printfStr(format, v...))
+}

From d20954796802bb9f51d246eb18090c193f68c4d1 Mon Sep 17 00:00:00 2001
From: Christoph Kluge <christoph.kluge@fau.de>
Date: Wed, 26 Feb 2025 14:40:54 +0100
Subject: [PATCH 2/7] Remove dedicated fatal loglevel, change to Fprintln for
 unformatted

---
 cmd/cc-backend/cli.go |  8 ++++----
 pkg/log/log.go        | 13 ++++++-------
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/cmd/cc-backend/cli.go b/cmd/cc-backend/cli.go
index 8bc6681..8d9e7e6 100644
--- a/cmd/cc-backend/cli.go
+++ b/cmd/cc-backend/cli.go
@@ -12,7 +12,7 @@ var (
 )
 
 func cliInit() {
-	flag.BoolVar(&flagInit, "init", false, "Setup var directory, initialize swlite database file, config.json and .env")
+	flag.BoolVar(&flagInit, "init", false, "Setup var directory, initialize sqlite database file, config.json and .env")
 	flag.BoolVar(&flagReinitDB, "init-db", false, "Go through job-archive and re-initialize the 'job', 'tag', and 'jobtag' tables (all running jobs will be lost!)")
 	flag.BoolVar(&flagSyncLDAP, "sync-ldap", false, "Sync the 'hpc_user' table with ldap")
 	flag.BoolVar(&flagServer, "server", false, "Start a server, continues listening on port after initialization and argument handling")
@@ -24,10 +24,10 @@ func cliInit() {
 	flag.BoolVar(&flagForceDB, "force-db", false, "Force database version, clear dirty flag and exit")
 	flag.BoolVar(&flagLogDateTime, "logdate", false, "Set this flag to add date and time to log messages")
 	flag.StringVar(&flagConfigFile, "config", "./config.json", "Specify alternative path to `config.json`")
-	flag.StringVar(&flagNewUser, "add-user", "", "Add a new user. Argument format: `<username>:[admin,support,manager,api,user]:<password>`")
-	flag.StringVar(&flagDelUser, "del-user", "", "Remove user by `username`")
+	flag.StringVar(&flagNewUser, "add-user", "", "Add a new user. Argument format: <username>:[admin,support,manager,api,user]:<password>")
+	flag.StringVar(&flagDelUser, "del-user", "", "Remove a existing user. Argument format: <username>")
 	flag.StringVar(&flagGenJWT, "jwt", "", "Generate and print a JWT for the user specified by its `username`")
 	flag.StringVar(&flagImportJob, "import-job", "", "Import a job. Argument format: `<path-to-meta.json>:<path-to-data.json>,...`")
-	flag.StringVar(&flagLogLevel, "loglevel", "warn", "Sets the logging level: `[debug,info,warn (default),err,fatal,crit]`")
+	flag.StringVar(&flagLogLevel, "loglevel", "warn", "Sets the logging level: `[debug, info (default), warn, err, crit]`")
 	flag.Parse()
 }
diff --git a/pkg/log/log.go b/pkg/log/log.go
index a4e7703..ef14535 100644
--- a/pkg/log/log.go
+++ b/pkg/log/log.go
@@ -46,12 +46,12 @@ var loglevel string = "info"
 /* CONFIG */
 
 func Init(lvl string, logdate bool) {
-
+	// Discard I/O for all writers below selected loglevel; <CRITICAL> is always written.
 	switch lvl {
 	case "crit":
 		ErrWriter = io.Discard
 		fallthrough
-	case "err", "fatal":
+	case "err":
 		WarnWriter = io.Discard
 		fallthrough
 	case "warn":
@@ -63,8 +63,7 @@ func Init(lvl string, logdate bool) {
 		// Nothing to do...
 		break
 	default:
-		fmt.Printf("pkg/log: Flag 'loglevel' has invalid value %#v\npkg/log: Will use default loglevel 'debug'\n", lvl)
-		//SetLogLevel("debug")
+		fmt.Printf("pkg/log: Flag 'loglevel' has invalid value %#v\npkg/log: Will use default loglevel '%s'\n", lvl, loglevel)
 	}
 
 	if !logdate {
@@ -107,20 +106,20 @@ func printfStr(format string, v ...interface{}) string {
 // Prints to STDOUT without string formatting; application continues.
 // Used for special cases not requiring log information like date or location.
 func Print(v ...interface{}) {
-	fmt.Fprint(os.Stdout, v...)
+	fmt.Fprintln(os.Stdout, v...)
 }
 
 // Prints to STDOUT without string formatting; application exits with error code 0.
 // Used for exiting succesfully with message after expected outcome, e.g. successful single-call application runs.
 func Exit(v ...interface{}) {
-	fmt.Fprint(os.Stdout, v...)
+	fmt.Fprintln(os.Stdout, v...)
 	os.Exit(0)
 }
 
 // Prints to STDOUT without string formatting; application exits with error code 1.
 // Used for terminating with message after to be expected errors, e.g. wrong arguments or during init().
 func Abort(v ...interface{}) {
-	fmt.Fprint(os.Stdout, v...)
+	fmt.Fprintln(os.Stdout, v...)
 	os.Exit(1)
 }
 

From fc0c76bd77803495a05d9d995627302ab3400490 Mon Sep 17 00:00:00 2001
From: Christoph Kluge <christoph.kluge@fau.de>
Date: Wed, 26 Feb 2025 15:20:25 +0100
Subject: [PATCH 3/7] Apply new log funtion to init and main, review or add
 logtexts

---
 cmd/cc-backend/init.go | 12 +++----
 cmd/cc-backend/main.go | 75 ++++++++++++++++++++++++------------------
 2 files changed, 48 insertions(+), 39 deletions(-)

diff --git a/cmd/cc-backend/init.go b/cmd/cc-backend/init.go
index 5a00a11..f899ec1 100644
--- a/cmd/cc-backend/init.go
+++ b/cmd/cc-backend/init.go
@@ -5,7 +5,6 @@
 package main
 
 import (
-	"fmt"
 	"os"
 
 	"github.com/ClusterCockpit/cc-backend/internal/repository"
@@ -62,24 +61,23 @@ const configString = `
 
 func initEnv() {
 	if util.CheckFileExists("var") {
-		fmt.Print("Directory ./var already exists. Exiting!\n")
-		os.Exit(0)
+		log.Exit("Directory ./var already exists. Cautiously exiting application initialization.")
 	}
 
 	if err := os.WriteFile("config.json", []byte(configString), 0o666); err != nil {
-		log.Fatalf("Writing config.json failed: %s", err.Error())
+		log.Abortf("Could not write default ./config.json with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
 	}
 
 	if err := os.WriteFile(".env", []byte(envString), 0o666); err != nil {
-		log.Fatalf("Writing .env failed: %s", err.Error())
+		log.Abortf("Could not write default ./.env file with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
 	}
 
 	if err := os.Mkdir("var", 0o777); err != nil {
-		log.Fatalf("Mkdir var failed: %s", err.Error())
+		log.Abortf("Could not create default ./var folder with permissions '0o777'. Application initialization failed, exited.\nError: %s\n", err.Error())
 	}
 
 	err := repository.MigrateDB("sqlite3", "./var/job.db")
 	if err != nil {
-		log.Fatalf("Initialize job.db failed: %s", err.Error())
+		log.Abortf("Could not initialize default sqlite3 database as './var/job.db'. Application initialization failed, exited.\nError: %s\n", err.Error())
 	}
 }
diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go
index 33bab07..62a9b9b 100644
--- a/cmd/cc-backend/main.go
+++ b/cmd/cc-backend/main.go
@@ -61,15 +61,23 @@ func main() {
 	// Apply config flags for pkg/log
 	log.Init(flagLogLevel, flagLogDateTime)
 
+	// If init flag set, run tasks here before any file dependencies cause errors
+	if flagInit {
+		initEnv()
+		log.Exit("Successfully setup environment!\n" +
+			"Please review config.json and .env and adjust it to your needs.\n" +
+			"Add your job-archive at ./var/job-archive.")
+	}
+
 	// See https://github.com/google/gops (Runtime overhead is almost zero)
 	if flagGops {
 		if err := agent.Listen(agent.Options{}); err != nil {
-			log.Fatalf("gops/agent.Listen failed: %s", err.Error())
+			log.Abortf("Could not start gops agent with 'gops/agent.Listen(agent.Options{})'. Application startup failed, exited.\nError: %s\n", err.Error())
 		}
 	}
 
 	if err := runtimeEnv.LoadEnv("./.env"); err != nil && !os.IsNotExist(err) {
-		log.Fatalf("parsing './.env' file failed: %s", err.Error())
+		log.Abortf("Could not parse existing .env file at location './.env'. Application startup failed, exited.\nError: %s\n", err.Error())
 	}
 
 	// Initialize sub-modules and handle command line flags.
@@ -87,37 +95,29 @@ func main() {
 	if flagMigrateDB {
 		err := repository.MigrateDB(config.Keys.DBDriver, config.Keys.DB)
 		if err != nil {
-			log.Fatal(err)
+			log.Abortf("MigrateDB Failed: Could not migrate '%s' database at location '%s' to version %d.\nError: %s\n", config.Keys.DBDriver, config.Keys.DB, repository.Version, err.Error())
 		}
-		os.Exit(0)
+		log.Exitf("MigrateDB Success: Migrated '%s' database at location '%s' to version %d.\n", config.Keys.DBDriver, config.Keys.DB, repository.Version)
 	}
 
 	if flagRevertDB {
 		err := repository.RevertDB(config.Keys.DBDriver, config.Keys.DB)
 		if err != nil {
-			log.Fatal(err)
+			log.Abortf("RevertDB Failed: Could not revert '%s' database at location '%s' to version %d.\nError: %s\n", config.Keys.DBDriver, config.Keys.DB, (repository.Version - 1), err.Error())
 		}
-		os.Exit(0)
+		log.Exitf("RevertDB Success: Reverted '%s' database at location '%s' to version %d.\n", config.Keys.DBDriver, config.Keys.DB, (repository.Version - 1))
 	}
 
 	if flagForceDB {
 		err := repository.ForceDB(config.Keys.DBDriver, config.Keys.DB)
 		if err != nil {
-			log.Fatal(err)
+			log.Abortf("ForceDB Failed: Could not force '%s' database at location '%s' to version %d.\nError: %s\n", config.Keys.DBDriver, config.Keys.DB, repository.Version, err.Error())
 		}
-		os.Exit(0)
+		log.Exitf("ForceDB Success: Forced '%s' database at location '%s' to version %d.\n", config.Keys.DBDriver, config.Keys.DB, repository.Version)
 	}
 
 	repository.Connect(config.Keys.DBDriver, config.Keys.DB)
 
-	if flagInit {
-		initEnv()
-		fmt.Print("Successfully setup environment!\n")
-		fmt.Print("Please review config.json and .env and adjust it to your needs.\n")
-		fmt.Print("Add your job-archive at ./var/job-archive.\n")
-		os.Exit(0)
-	}
-
 	if !config.Keys.DisableAuthentication {
 
 		auth.Init()
@@ -125,20 +125,27 @@ func main() {
 		if flagNewUser != "" {
 			parts := strings.SplitN(flagNewUser, ":", 3)
 			if len(parts) != 3 || len(parts[0]) == 0 {
-				log.Fatal("invalid argument format for user creation")
+				log.Abortf("Add User: Could not parse supplied argument format: No changes.\n"+
+					"Want: <username>:[admin,support,manager,api,user]:<password>\n"+
+					"Have: %s\n", flagNewUser)
 			}
 
 			ur := repository.GetUserRepository()
 			if err := ur.AddUser(&schema.User{
 				Username: parts[0], Projects: make([]string, 0), Password: parts[2], Roles: strings.Split(parts[1], ","),
 			}); err != nil {
-				log.Fatalf("adding '%s' user authentication failed: %v", parts[0], err)
+				log.Abortf("Add User: Could not add new user authentication for '%s' and roles '%s'.\nError: %s\n", parts[0], parts[1], err.Error())
+			} else {
+				log.Printf("Add User: Added new user '%s' with roles '%s'.\n", parts[0], parts[1])
 			}
 		}
+
 		if flagDelUser != "" {
 			ur := repository.GetUserRepository()
 			if err := ur.DelUser(flagDelUser); err != nil {
-				log.Fatalf("deleting user failed: %v", err)
+				log.Abortf("Delete User: Could not delete user '%s' from DB.\nError: %s\n", flagDelUser, err.Error())
+			} else {
+				log.Printf("Delete User: Deleted user '%s' from DB.\n", flagDelUser)
 			}
 		}
 
@@ -146,60 +153,64 @@ func main() {
 
 		if flagSyncLDAP {
 			if authHandle.LdapAuth == nil {
-				log.Fatal("cannot sync: LDAP authentication is not configured")
+				log.Abort("Sync LDAP: LDAP authentication is not configured, could not synchronize. No changes, exited.")
 			}
 
 			if err := authHandle.LdapAuth.Sync(); err != nil {
-				log.Fatalf("LDAP sync failed: %v", err)
+				log.Abortf("Sync LDAP: Could not synchronize, failed with error.\nError: %s\n", err.Error())
 			}
-			log.Info("LDAP sync successfull")
+			log.Print("Sync LDAP: LDAP synchronization successfull.")
 		}
 
 		if flagGenJWT != "" {
 			ur := repository.GetUserRepository()
 			user, err := ur.GetUser(flagGenJWT)
 			if err != nil {
-				log.Fatalf("could not get user from JWT: %v", err)
+				log.Abortf("JWT: Could not get supplied user '%s' from DB. No changes, exited.\nError: %s\n", flagGenJWT, err.Error())
 			}
 
 			if !user.HasRole(schema.RoleApi) {
-				log.Warnf("user '%s' does not have the API role", user.Username)
+				log.Warnf("JWT: User '%s' does not have the role 'api'. REST API endpoints will return error!\n", user.Username)
 			}
 
 			jwt, err := authHandle.JwtAuth.ProvideJWT(user)
 			if err != nil {
-				log.Fatalf("failed to provide JWT to user '%s': %v", user.Username, err)
+				log.Abortf("JWT: User '%s' found in DB, but failed to provide JWT.\nError: %s\n", user.Username, err.Error())
 			}
 
-			fmt.Printf("MAIN > JWT for '%s': %s\n", user.Username, jwt)
+			log.Printf("JWT: Successfully generated JWT for user '%s': %s\n", user.Username, jwt)
 		}
 
 	} else if flagNewUser != "" || flagDelUser != "" {
-		log.Fatal("arguments --add-user and --del-user can only be used if authentication is enabled")
+		log.Abort("Error: Arguments '--add-user' and '--del-user' can only be used if authentication is enabled. No changes, exited.")
 	}
 
 	if err := archive.Init(config.Keys.Archive, config.Keys.DisableArchive); err != nil {
-		log.Fatalf("failed to initialize archive: %s", err.Error())
+		log.Abortf("Init: Failed to initialize archive.\nError: %s\n", err.Error())
 	}
 
 	if err := metricdata.Init(); err != nil {
-		log.Fatalf("failed to initialize metricdata repository: %s", err.Error())
+		log.Abortf("Init: Failed to initialize metricdata repository.\nError %s\n", err.Error())
 	}
 
 	if flagReinitDB {
 		if err := importer.InitDB(); err != nil {
-			log.Fatalf("failed to re-initialize repository DB: %s", err.Error())
+			log.Abortf("Init DB: Failed to re-initialize repository DB.\nError: %s\n", err.Error())
+		} else {
+			log.Print("Init DB: Sucessfully re-initialized repository DB.")
 		}
 	}
 
 	if flagImportJob != "" {
 		if err := importer.HandleImportFlag(flagImportJob); err != nil {
-			log.Fatalf("job import failed: %s", err.Error())
+			log.Abortf("Import Job: Job import failed.\nError: %s\n", err.Error())
+		} else {
+			log.Printf("Import Job: Imported Job '%s' into DB.\n", flagImportJob)
 		}
 	}
 
 	if !flagServer {
-		return
+		log.Exit("No errors, server flag not set. Exiting cc-backend.")
 	}
 
 	archiver.Start(repository.GetJobRepository())

From bd0cc69668655b11d0e8c92a67fa593e8c89f7be Mon Sep 17 00:00:00 2001
From: Christoph Kluge <christoph.kluge@fau.de>
Date: Thu, 27 Feb 2025 18:10:04 +0100
Subject: [PATCH 4/7] Review fatalf log calls and messages

---
 cmd/cc-backend/server.go            | 14 +++++++-------
 internal/api/rest.go                |  2 --
 internal/config/config.go           | 10 +++++-----
 internal/repository/dbConnection.go | 14 ++++++--------
 internal/repository/migration.go    |  4 ++--
 internal/repository/userConfig.go   |  2 +-
 internal/taskManager/taskManager.go |  2 +-
 tools/archive-manager/main.go       |  3 +--
 web/web.go                          |  6 ++----
 9 files changed, 25 insertions(+), 32 deletions(-)

diff --git a/cmd/cc-backend/server.go b/cmd/cc-backend/server.go
index 0770e81..1408162 100644
--- a/cmd/cc-backend/server.go
+++ b/cmd/cc-backend/server.go
@@ -64,7 +64,7 @@ func serverInit() {
 			case string:
 				return fmt.Errorf("MAIN > Panic: %s", e)
 			case error:
-				return fmt.Errorf("MAIN > Panic caused by: %w", e)
+				return fmt.Errorf("MAIN > Panic caused by: %s", e.Error())
 			}
 
 			return errors.New("MAIN > Internal server error (panic)")
@@ -268,7 +268,7 @@ func serverStart() {
 	// Start http or https server
 	listener, err := net.Listen("tcp", config.Keys.Addr)
 	if err != nil {
-		log.Fatalf("starting http listener failed: %v", err)
+		log.Abortf("Server Start: Starting http listener on '%s' failed.\nError: %s\n", config.Keys.Addr, err.Error())
 	}
 
 	if !strings.HasSuffix(config.Keys.Addr, ":80") && config.Keys.RedirectHttpTo != "" {
@@ -281,7 +281,7 @@ func serverStart() {
 		cert, err := tls.LoadX509KeyPair(
 			config.Keys.HttpsCertFile, config.Keys.HttpsKeyFile)
 		if err != nil {
-			log.Fatalf("loading X509 keypair failed: %v", err)
+			log.Abortf("Server Start: Loading X509 keypair failed. Check options 'https-cert-file' and 'https-key-file' in 'config.json'.\nError: %s\n", err.Error())
 		}
 		listener = tls.NewListener(listener, &tls.Config{
 			Certificates: []tls.Certificate{cert},
@@ -292,20 +292,20 @@ func serverStart() {
 			MinVersion:               tls.VersionTLS12,
 			PreferServerCipherSuites: true,
 		})
-		fmt.Printf("HTTPS server listening at %s...", config.Keys.Addr)
+		log.Printf("HTTPS server listening at %s...\n", config.Keys.Addr)
 	} else {
-		fmt.Printf("HTTP server listening at %s...", config.Keys.Addr)
+		log.Printf("HTTP server listening at %s...\n", config.Keys.Addr)
 	}
 	//
 	// Because this program will want to bind to a privileged port (like 80), the listener must
 	// be established first, then the user can be changed, and after that,
 	// the actual http server can be started.
 	if err := runtimeEnv.DropPrivileges(config.Keys.Group, config.Keys.User); err != nil {
-		log.Fatalf("error while preparing server start: %s", err.Error())
+		log.Abortf("Server Start: Error while preparing server start.\nError: %s\n", err.Error())
 	}
 
 	if err = server.Serve(listener); err != nil && err != http.ErrServerClosed {
-		log.Fatalf("starting server failed: %v", err)
+		log.Abortf("Server Start: Starting server failed.\nError: %s\n", err.Error())
 	}
 }
 
diff --git a/internal/api/rest.go b/internal/api/rest.go
index b76da0b..73cd316 100644
--- a/internal/api/rest.go
+++ b/internal/api/rest.go
@@ -1423,8 +1423,6 @@ func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request)
 	rw.Header().Set("Content-Type", "text/plain")
 	key, value := r.FormValue("key"), r.FormValue("value")
 
-	// fmt.Printf("REST > KEY: %#v\nVALUE: %#v\n", key, value)
-
 	if err := repository.GetUserCfgRepo().UpdateConfig(key, value, repository.GetUserFromContext(r.Context())); err != nil {
 		http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
 		return
diff --git a/internal/config/config.go b/internal/config/config.go
index 4f1a8c3..31760c7 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -7,9 +7,9 @@ package config
 import (
 	"bytes"
 	"encoding/json"
-	"log"
 	"os"
 
+	"github.com/ClusterCockpit/cc-backend/pkg/log"
 	"github.com/ClusterCockpit/cc-backend/pkg/schema"
 )
 
@@ -53,20 +53,20 @@ func Init(flagConfigFile string) {
 	raw, err := os.ReadFile(flagConfigFile)
 	if err != nil {
 		if !os.IsNotExist(err) {
-			log.Fatalf("CONFIG ERROR: %v", err)
+			log.Abortf("Config Init: Could not read config file '%s'.\nError: %s\n", flagConfigFile, err.Error())
 		}
 	} else {
 		if err := schema.Validate(schema.Config, bytes.NewReader(raw)); err != nil {
-			log.Fatalf("Validate config: %v\n", err)
+			log.Abortf("Config Init: Could not validate config file '%s'.\nError: %s\n", flagConfigFile, err.Error())
 		}
 		dec := json.NewDecoder(bytes.NewReader(raw))
 		dec.DisallowUnknownFields()
 		if err := dec.Decode(&Keys); err != nil {
-			log.Fatalf("could not decode: %v", err)
+			log.Abortf("Config Init: Could not decode config file '%s'.\nError: %s\n", flagConfigFile, err.Error())
 		}
 
 		if Keys.Clusters == nil || len(Keys.Clusters) < 1 {
-			log.Fatal("At least one cluster required in config!")
+			log.Abort("Config Init: At least one cluster required in config. Exited with error.")
 		}
 	}
 }
diff --git a/internal/repository/dbConnection.go b/internal/repository/dbConnection.go
index 418eef9..0e3f29d 100644
--- a/internal/repository/dbConnection.go
+++ b/internal/repository/dbConnection.go
@@ -59,17 +59,15 @@ func Connect(driver string, db string) {
 			} else {
 				dbHandle, err = sqlx.Open("sqlite3", opts.URL)
 			}
-			if err != nil {
-				log.Fatal(err)
-			}
 		case "mysql":
 			opts.URL += "?multiStatements=true"
 			dbHandle, err = sqlx.Open("mysql", opts.URL)
-			if err != nil {
-				log.Fatalf("sqlx.Open() error: %v", err)
-			}
 		default:
-			log.Fatalf("unsupported database driver: %s", driver)
+			log.Abortf("DB Connection: Unsupported database driver '%s'.\n", driver)
+		}
+
+		if err != nil {
+			log.Abortf("DB Connection: Could not connect to '%s' database with sqlx.Open().\nError: %s\n", driver, err.Error())
 		}
 
 		dbHandle.SetMaxOpenConns(opts.MaxOpenConnections)
@@ -80,7 +78,7 @@ func Connect(driver string, db string) {
 		dbConnInstance = &DBConnection{DB: dbHandle, Driver: driver}
 		err = checkDBVersion(driver, dbHandle.DB)
 		if err != nil {
-			log.Fatal(err)
+			log.Abortf("DB Connection: Failed DB version check.\nError: %s\n", err.Error())
 		}
 	})
 }
diff --git a/internal/repository/migration.go b/internal/repository/migration.go
index d32a624..0b2591e 100644
--- a/internal/repository/migration.go
+++ b/internal/repository/migration.go
@@ -54,7 +54,7 @@ func checkDBVersion(backend string, db *sql.DB) error {
 			return err
 		}
 	default:
-		log.Fatalf("unsupported database backend: %s", backend)
+		log.Abortf("Migration: Unsupported database backend '%s'.\n", backend)
 	}
 
 	v, dirty, err := m.Version()
@@ -102,7 +102,7 @@ func getMigrateInstance(backend string, db string) (m *migrate.Migrate, err erro
 			return m, err
 		}
 	default:
-		log.Fatalf("unsupported database backend: %s", backend)
+		log.Abortf("Migration: Unsupported database backend '%s'.\n", backend)
 	}
 
 	return m, nil
diff --git a/internal/repository/userConfig.go b/internal/repository/userConfig.go
index d2ab1d2..5d43071 100644
--- a/internal/repository/userConfig.go
+++ b/internal/repository/userConfig.go
@@ -35,7 +35,7 @@ func GetUserCfgRepo() *UserCfgRepo {
 
 		lookupConfigStmt, err := db.DB.Preparex(`SELECT confkey, value FROM configuration WHERE configuration.username = ?`)
 		if err != nil {
-			log.Fatalf("db.DB.Preparex() error: %v", err)
+			log.Fatalf("User Config: Call 'db.DB.Preparex()' failed.\nError: %s\n", err.Error())
 		}
 
 		userCfgRepoInstance = &UserCfgRepo{
diff --git a/internal/taskManager/taskManager.go b/internal/taskManager/taskManager.go
index 101fc4a..2004e0d 100644
--- a/internal/taskManager/taskManager.go
+++ b/internal/taskManager/taskManager.go
@@ -40,7 +40,7 @@ func Start() {
 	jobRepo = repository.GetJobRepository()
 	s, err = gocron.NewScheduler()
 	if err != nil {
-		log.Fatalf("Error while creating gocron scheduler: %s", err.Error())
+		log.Abortf("Taskmanager Start: Could not create gocron scheduler.\nError: %s\n", err.Error())
 	}
 
 	if config.Keys.StopJobsExceedingWalltime > 0 {
diff --git a/tools/archive-manager/main.go b/tools/archive-manager/main.go
index 1a80712..7c842ff 100644
--- a/tools/archive-manager/main.go
+++ b/tools/archive-manager/main.go
@@ -22,8 +22,7 @@ func parseDate(in string) int64 {
 	if in != "" {
 		t, err := time.ParseInLocation(shortForm, in, loc)
 		if err != nil {
-			fmt.Printf("date parse error %v", err)
-			os.Exit(0)
+			log.Abortf("Archive Manager Main: Date parse failed with input: '%s'\nError: %s\n", in, err.Error())
 		}
 		return t.Unix()
 	}
diff --git a/web/web.go b/web/web.go
index 45d8646..f543e53 100644
--- a/web/web.go
+++ b/web/web.go
@@ -26,8 +26,7 @@ var frontendFiles embed.FS
 func ServeFiles() http.Handler {
 	publicFiles, err := fs.Sub(frontendFiles, "frontend/public")
 	if err != nil {
-		log.Fatalf("WEB/WEB > cannot find frontend public files")
-		panic(err)
+		log.Abortf("Serve Files: Could not find 'frontend/public' file directory.\nError: %s\n", err.Error())
 	}
 	return http.FileServer(http.FS(publicFiles))
 }
@@ -75,8 +74,7 @@ func init() {
 		templates[strings.TrimPrefix(path, "templates/")] = template.Must(template.Must(base.Clone()).ParseFS(templateFiles, path))
 		return nil
 	}); err != nil {
-		log.Fatalf("WEB/WEB > cannot find frontend template files")
-		panic(err)
+		log.Abortf("Web init(): Could not find frontend template files.\nError: %s\n", err.Error())
 	}
 
 	_ = base

From 92b4159f9ea98f38122b1de413ae6c525db287e6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 08:35:15 +0000
Subject: [PATCH 5/7] Bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2

Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 go.mod | 3 ++-
 go.sum | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index 7ca48a8..79d89b3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,7 @@
 module github.com/ClusterCockpit/cc-backend
 
 go 1.23.5
+toolchain go1.24.1
 
 require (
 	github.com/99designs/gqlgen v0.17.66
@@ -10,7 +11,7 @@ require (
 	github.com/go-co-op/gocron/v2 v2.16.0
 	github.com/go-ldap/ldap/v3 v3.4.10
 	github.com/go-sql-driver/mysql v1.9.0
-	github.com/golang-jwt/jwt/v5 v5.2.1
+	github.com/golang-jwt/jwt/v5 v5.2.2
 	github.com/golang-migrate/migrate/v4 v4.18.2
 	github.com/google/gops v0.3.28
 	github.com/gorilla/handlers v1.5.2
diff --git a/go.sum b/go.sum
index 04d165a..33189c0 100644
--- a/go.sum
+++ b/go.sum
@@ -83,8 +83,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx
 github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
-github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
 github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=

From 0d689c7dff66127b89d9d2b1c7168dbab8628aa0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 08:45:16 +0000
Subject: [PATCH 6/7] Bump @babel/runtime from 7.26.0 to 7.27.0 in
 /web/frontend

Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.0 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 web/frontend/package-lock.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json
index 4b89d34..0b31f40 100644
--- a/web/frontend/package-lock.json
+++ b/web/frontend/package-lock.json
@@ -59,9 +59,9 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
-      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+      "version": "7.27.0",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
+      "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
       "license": "MIT",
       "dependencies": {
         "regenerator-runtime": "^0.14.0"

From 1064f5e4a8ae65e7027d30200abf383c3537f679 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 09:01:59 +0000
Subject: [PATCH 7/7] Bump golang.org/x/net from 0.35.0 to 0.36.0

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.36.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index 79d89b3..2e2aa36 100644
--- a/go.mod
+++ b/go.mod
@@ -79,7 +79,7 @@ require (
 	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
 	go.uber.org/atomic v1.11.0 // indirect
 	golang.org/x/mod v0.23.0 // indirect
-	golang.org/x/net v0.35.0 // indirect
+	golang.org/x/net v0.36.0 // indirect
 	golang.org/x/sync v0.11.0 // indirect
 	golang.org/x/sys v0.30.0 // indirect
 	golang.org/x/text v0.22.0 // indirect
diff --git a/go.sum b/go.sum
index 33189c0..e1725ed 100644
--- a/go.sum
+++ b/go.sum
@@ -279,8 +279,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
 golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
 golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=