Commit 695c74e

Karn Wong <[email protected]>
2024-08-05 07:56:07
refactor logs and errors tag: v0.1.5
1 parent 58d0279
cmd/generate/generate_key.go
@@ -4,27 +4,24 @@ import (
 	"crypto/rand"
 	"encoding/base64"
 	"fmt"
-	"log/slog"
 
 	"github.com/kahnwong/swissknife/utils"
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
 )
 
-// https://stackoverflow.com/questions/32349807/how-can-i-generate-a-random-int-using-the-crypto-rand-package/32351471#32351471
-func generateRandomBytes(n int) ([]byte, error) {
+func generateKey(n int) string {
+	// https://stackoverflow.com/questions/32349807/how-can-i-generate-a-random-int-using-the-crypto-rand-package/32351471#32351471
+	// generate random bytes
 	b := make([]byte, n)
 	_, err := rand.Read(b)
 	// Note that err == nil only if we read len(b) bytes.
 	if err != nil {
-		return nil, err
+		log.Fatal().Err(err).Msg("Failed to generate random bytes")
 	}
 
-	return b, nil
-}
-
-func generateKey(s int) (string, error) {
-	b, err := generateRandomBytes(s)
-	return base64.URLEncoding.EncodeToString(b), err
+	// convert to base64
+	return base64.URLEncoding.EncodeToString(b)
 }
 
 var generateKeyCmd = &cobra.Command{
@@ -32,11 +29,7 @@ var generateKeyCmd = &cobra.Command{
 	Short: "Generate key",
 	Long:  `Generate key. Re-implementation of "openssl rand -base64 48"". Result is copied to clipboard.`,
 	Run: func(cmd *cobra.Command, args []string) {
-		// main
-		key, err := generateKey(48)
-		if err != nil {
-			slog.Error("Error generating key")
-		}
+		key := generateKey(48)
 		utils.WriteToClipboard(key)
 		fmt.Printf("%s\n", key)
 	},
cmd/generate/generate_passphrase.go
@@ -2,25 +2,24 @@ package generate
 
 import (
 	"fmt"
-	"log/slog"
 	"strings"
 
 	"github.com/kahnwong/swissknife/utils"
-
+	"github.com/rs/zerolog/log"
 	"github.com/sethvargo/go-diceware/diceware"
 	"github.com/spf13/cobra"
 )
 
-func generatePassphrase() (string, error) {
+func generatePassphrase() string {
 	// Generate 6 words using the diceware algorithm.
 	list, err := diceware.Generate(6)
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Failed to generate passphrases")
 	}
 
 	res := strings.Join(list, "-")
 
-	return res, nil
+	return res
 }
 
 var generatePassphraseCmd = &cobra.Command{
@@ -28,11 +27,7 @@ var generatePassphraseCmd = &cobra.Command{
 	Short: "Generate passphrase",
 	Long:  `Generate passphrase. Result is copied to clipboard.`,
 	Run: func(cmd *cobra.Command, args []string) {
-		// main
-		passphrase, err := generatePassphrase()
-		if err != nil {
-			slog.Error("Error generating passphrase")
-		}
+		passphrase := generatePassphrase()
 		utils.WriteToClipboard(passphrase)
 		fmt.Printf("%s\n", passphrase)
 	},
cmd/generate/generate_password.go
@@ -2,23 +2,22 @@ package generate
 
 import (
 	"fmt"
-	"log/slog"
-
-	"github.com/sethvargo/go-password/password"
 
 	"github.com/kahnwong/swissknife/utils"
+	"github.com/rs/zerolog/log"
+	"github.com/sethvargo/go-password/password"
 	"github.com/spf13/cobra"
 )
 
-func generatePassword() (string, error) {
+func generatePassword() string {
 	// Generate a password that is 64 characters long with 10 digits, 10 symbols,
 	// allowing upper and lower case letters, disallowing repeat characters.
 	res, err := password.Generate(32, 10, 0, false, false)
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Failed to generate password")
 	}
 
-	return res, nil
+	return res
 }
 
 var generatePasswordCmd = &cobra.Command{
@@ -26,11 +25,7 @@ var generatePasswordCmd = &cobra.Command{
 	Short: "Generate password",
 	Long:  `Generate password. Result is copied to clipboard.`,
 	Run: func(cmd *cobra.Command, args []string) {
-		// main
-		password, err := generatePassword()
-		if err != nil {
-			slog.Error("Error generating password")
-		}
+		password := generatePassword()
 		utils.WriteToClipboard(password)
 		fmt.Printf("%s\n", password)
 	},
cmd/generate/generate_qrcode.go
@@ -6,33 +6,52 @@ import (
 	"strings"
 
 	"github.com/kahnwong/swissknife/utils"
+	"github.com/rs/zerolog/log"
 	qrcode "github.com/skip2/go-qrcode"
 	"github.com/spf13/cobra"
 )
 
-// helpers
-func generateQRCode(url string) (string, error) {
+func setURL(args []string) string {
+	var url string
+	if len(args) == 0 {
+		urlFromClipboard := utils.ReadFromClipboard()
+		if urlFromClipboard != "" {
+			if strings.HasPrefix(urlFromClipboard, "https://") {
+				url = urlFromClipboard
+			}
+		}
+	}
+	if url == "" {
+		if len(args) == 0 {
+			fmt.Println("Please specify URL")
+			os.Exit(1)
+		} else if len(args) == 1 {
+			url = args[0]
+		}
+	}
+
+	return url
+}
+
+func generateQRCode(url string) ([]byte, string) {
 	// init
 	var q *qrcode.QRCode
 	q, err := qrcode.New(url, qrcode.Medium)
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Failed to initialize QRCode object")
 	}
 
-	// generate pdf
+	// generate png
 	png, err := q.PNG(1024)
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Failed to generate QRCode PNG")
 	}
 
-	// copy to clipboard
-	utils.WriteToClipboardImage(png)
-
 	// for stdout
-	//content := q.ToString(false)
-	content := q.ToSmallString(false)
+	//stdout := q.ToString(false)
+	stdout := q.ToSmallString(false)
 
-	return content, nil
+	return png, stdout
 }
 
 var generateQRCodeCmd = &cobra.Command{
@@ -41,31 +60,13 @@ var generateQRCodeCmd = &cobra.Command{
 	Long:  `Generate QR code from URL (either as an arg or from clipboard) and copy resulting image to clipboard.`,
 	Run: func(cmd *cobra.Command, args []string) {
 		// set URL
-		var url string
-		if len(args) == 0 {
-			urlFromClipboard := utils.ReadFromClipboard()
-			if urlFromClipboard != "" {
-				if strings.HasPrefix(urlFromClipboard, "https://") {
-					url = urlFromClipboard
-				}
-			}
-		}
-		if url == "" {
-			if len(args) == 0 {
-				fmt.Println("Please specify URL")
-				os.Exit(1)
-			} else if len(args) == 1 {
-				url = args[0]
-			}
-		}
+		url := setURL(args)
 		fmt.Println(url)
 
 		// main
-		qrcodeStr, err := generateQRCode(url)
-		if err != nil {
-			fmt.Print(err)
-		}
-		fmt.Println(qrcodeStr)
+		png, stdout := generateQRCode(url)
+		utils.WriteToClipboardImage(png)
+		fmt.Println(stdout)
 	},
 }
 
cmd/generate/generate_ssh_key.go
@@ -6,71 +6,70 @@ import (
 	"encoding/base64"
 	"encoding/pem"
 	"fmt"
-	"log"
 	"os"
 	"path/filepath"
 
-	"golang.org/x/crypto/ssh"
-
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
+	"golang.org/x/crypto/ssh"
 )
 
 // helpers
 func writeStringToFile(filePath string, data string, permission os.FileMode) {
 	file, err := os.Create(filePath)
 	if err != nil {
-		log.Fatal(err)
+		log.Fatal().Err(err).Msgf("Failed to create file %s", filePath)
 	}
 
 	_, err = file.WriteString(data)
 	if err != nil {
-		log.Fatal(err)
+		log.Fatal().Err(err).Msgf("Failed to write to file %s", filePath)
 	}
 
 	err = file.Chmod(permission)
 	if err != nil {
-		fmt.Println("Error setting file permissions:", err)
-		return
+		log.Fatal().Err(err).Msgf("Failed to change file permissions")
 	}
 }
 
 func returnKeyPath(fileName string) string {
 	currentDir, err := os.Getwd()
 	if err != nil {
-		fmt.Println("Error:", err)
+		log.Fatal().Err(err).Msgf("Failed to get current directory")
 	}
 
 	keyPath := filepath.Join(currentDir, fileName)
-	keyPath = keyPath + ".pem"
+	keyPath = fmt.Sprintf("%s.pem", keyPath)
 
 	return keyPath
 }
 
 // main
-func generateSSHKeyEDSA() (string, string, error) {
+func generateSSHKeyEDSA() (string, string) {
 	// Generate a new Ed25519 private key
 	//// If rand is nil, crypto/rand.Reader will be used
 	public, private, err := ed25519.GenerateKey(nil)
 	if err != nil {
-		return "", "", err
-	}
-	p, err := ssh.MarshalPrivateKey(crypto.PrivateKey(private), "")
-	if err != nil {
-		return "", "", err
+		log.Fatal().Err(err).Msgf("Failed to generate ed25519 key")
 	}
 
 	// public key
 	publicKey, err := ssh.NewPublicKey(public)
 	if err != nil {
-		return "", "", err
+		log.Fatal().Err(err).Msgf("Failed to create public key")
 	}
-	publicKeyString := "ssh-ed25519" + " " + base64.StdEncoding.EncodeToString(publicKey.Marshal())
+	publicKeyString := fmt.Sprintf("ssh-ed25519 %s", base64.StdEncoding.EncodeToString(publicKey.Marshal()))
 
 	// private key
+	//// p stands for pem
+	p, err := ssh.MarshalPrivateKey(crypto.PrivateKey(private), "")
+	if err != nil {
+		log.Fatal().Err(err).Msgf("Failed to marshal private key")
+	}
 	privateKeyPem := pem.EncodeToMemory(p)
 	privateKeyString := string(privateKeyPem)
 
-	return publicKeyString, privateKeyString, nil
+	return publicKeyString, privateKeyString
 }
 
 var generateSSHKeyCmd = &cobra.Command{
@@ -85,14 +84,14 @@ var generateSSHKeyCmd = &cobra.Command{
 		}
 
 		// main
-		publicKeyString, privateKeyString, err := generateSSHKeyEDSA()
-		if err != nil {
-			fmt.Print(err)
-		}
+		publicKeyString, privateKeyString := generateSSHKeyEDSA()
 
 		// write key to file
-		writeStringToFile(fmt.Sprintf("%s.pub", args[0]), publicKeyString, 0644)
-		writeStringToFile(fmt.Sprintf("%s.pem", args[0]), privateKeyString, 0600)
+		publicKeyFilename := fmt.Sprintf("%s.pub", args[0])
+		writeStringToFile(publicKeyFilename, publicKeyString, 0644)
+
+		privateKeyFilename := fmt.Sprintf("%s.pem", args[0])
+		writeStringToFile(privateKeyFilename, privateKeyString, 0600)
 
 		fmt.Printf("SSH key created at: %s\n", returnKeyPath(args[0]))
 	},
cmd/get/get_iface.go
@@ -5,28 +5,28 @@ import (
 	"net"
 
 	netroute "github.com/libp2p/go-netroute"
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
 )
 
 // unix: `netstat -nr -f inet`
 // get iface-owned app: `ifconfig -v utun3 | grep "agent domain"`
 
-func getIface() (string, error) {
+func getIface() string {
 	r, err := netroute.New()
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Error initializing netroute")
 	}
 
 	iface, _, _, err := r.Route(
 		net.IPv4(104, 16, 133, 229), // cloudflare
 	)
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Error retrieving net route")
 	}
-
 	//fmt.Printf("%v, %v, %v, %v\n", iface, gw, src, err)
 
-	return iface.Name, nil
+	return iface.Name
 }
 
 var getIfaceCmd = &cobra.Command{
@@ -34,10 +34,7 @@ var getIfaceCmd = &cobra.Command{
 	Short: "Get iface",
 	Long:  `Get iface used for public internet access`,
 	Run: func(cmd *cobra.Command, args []string) {
-		iface, err := getIface()
-		if err != nil {
-			fmt.Println(err)
-		}
+		iface := getIface()
 		fmt.Println(iface)
 	},
 }
cmd/get/get_ip.go
@@ -8,13 +8,14 @@ import (
 
 	"github.com/carlmjohnson/requests"
 	"github.com/kahnwong/swissknife/color"
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
 )
 
-func getLocalIP() (string, error) {
+func getLocalIP() string {
 	conn, err := net.Dial("udp", "8.8.8.8:80")
 	if err != nil {
-		return "", err
+		log.Fatal().Err(err).Msg("Error on net.Dial")
 	}
 
 	localAddr := conn.LocalAddr().(*net.UDPAddr)
@@ -26,10 +27,10 @@ func getLocalIP() (string, error) {
 	if len(parts) > 0 {
 		localIP = parts[0]
 	} else {
-		fmt.Println("Invalid address format")
+		log.Error().Msg("Invalid address format")
 	}
 
-	return fmt.Sprintf("%v", localIP), nil
+	return localIP
 }
 
 type PublicIPResponse struct {
@@ -37,7 +38,7 @@ type PublicIPResponse struct {
 	Country string `json:"country"`
 }
 
-func getPublicIP() (PublicIPResponse, error) {
+func getPublicIP() PublicIPResponse {
 	var response PublicIPResponse
 	err := requests.
 		URL("https://api.country.is").
@@ -45,9 +46,10 @@ func getPublicIP() (PublicIPResponse, error) {
 		Fetch(context.Background())
 
 	if err != nil {
-		fmt.Println("Error getting public ip:", err)
+		log.Fatal().Err(err).Msg("Error getting public ip")
 	}
-	return response, nil
+
+	return response
 }
 
 var getIPCmd = &cobra.Command{
@@ -55,19 +57,11 @@ var getIPCmd = &cobra.Command{
 	Short: "Get IP information",
 	Long:  `Get IP information`,
 	Run: func(cmd *cobra.Command, args []string) {
-		localIP, err := getLocalIP()
-		if err != nil {
-			fmt.Println(err)
-		} else {
-			fmt.Printf("%s: %s\n", color.Green("Local IP"), localIP)
-		}
+		localIP := getLocalIP()
+		fmt.Printf("%s: %s\n", color.Green("Local IP"), localIP)
 
-		publicIP, err := getPublicIP()
-		if err != nil {
-			fmt.Println(err)
-		} else {
-			fmt.Printf("%s: %s (%s)\n", color.Green("Public IP"), publicIP.Ip, color.Blue(publicIP.Country))
-		}
+		publicIP := getPublicIP()
+		fmt.Printf("%s: %s (%s)\n", color.Green("Public IP"), publicIP.Ip, color.Blue(publicIP.Country))
 	},
 }
 
cmd/get/get_system_info.go
@@ -2,30 +2,20 @@ package get
 
 import (
 	"fmt"
-	"log"
 	"math"
 	"os/user"
 	"strconv"
 	"strings"
 
 	"github.com/kahnwong/swissknife/color"
-	"github.com/shirou/gopsutil/v4/disk"
-
-	"github.com/shirou/gopsutil/v4/mem"
-
+	"github.com/rs/zerolog/log"
 	"github.com/shirou/gopsutil/v4/cpu"
+	"github.com/shirou/gopsutil/v4/disk"
 	"github.com/shirou/gopsutil/v4/host"
+	"github.com/shirou/gopsutil/v4/mem"
 	"github.com/spf13/cobra"
 )
 
-func convertKBtoGB(v uint64) int {
-	return int(math.Round(float64(v) / float64(1024) / float64(1024) / float64(1024)))
-}
-
-func convertToPercent(v float64) int {
-	return int(math.Round(v * 100))
-}
-
 type SystemInfo struct {
 	Username string
 	Hostname string
@@ -36,100 +26,101 @@ type SystemInfo struct {
 	CPUThreads   int
 
 	// memory
-	MemoryUsed        int
-	MemoryTotal       int
-	MemoryUsedPercent int
+	MemoryUsed  int
+	MemoryTotal int
 
 	// disk
-	DiskUsed        int
-	DiskTotal       int
-	DiskUsedPercent int
+	DiskUsed  int
+	DiskTotal int
 }
 
-func getSystemInfo() (SystemInfo, error) {
+func convertKBtoGB(v uint64) int {
+	return int(math.Round(float64(v) / float64(1024) / float64(1024) / float64(1024)))
+}
+
+func convertToPercent(v float64) int {
+	return int(math.Round(v * 100))
+}
+
+func getSystemInfo() SystemInfo {
 	// get info
 	// ---- username ---- //
 	username, err := user.Current()
 	if err != nil {
-		log.Fatalf(err.Error())
+		log.Fatal().Err(err).Msg("Failed to get current user info")
 	}
 
 	// ---- system ---- //
 	// host
 	hostStat, err := host.Info()
 	if err != nil {
-		log.Fatalf(err.Error())
+		log.Fatal().Err(err).Msg("Failed to get host info")
 	}
 
 	// cpu
 	cpuStat, err := cpu.Info()
 	if err != nil {
-		log.Fatalf(err.Error())
+		log.Fatal().Err(err).Msg("Failed to get cpu info")
 	}
-
 	cpuThreads, err := cpu.Counts(true)
 	if err != nil {
-		log.Fatalf(err.Error())
+		log.Fatal().Err(err).Msg("Failed to get cpu threads info")
 	}
 
 	// memory
 	vmStat, err := mem.VirtualMemory()
 	if err != nil {
-		log.Fatalf(err.Error())
+		log.Fatal().Err(err).Msg("Failed to get memory info")
 	}
-
 	memoryUsed := convertKBtoGB(vmStat.Used)
 	memoryTotal := convertKBtoGB(vmStat.Total)
-	memoryUsedPercent := convertToPercent(float64(memoryUsed) / float64(memoryTotal))
 
 	// disk
 	diskStat, err := disk.Usage("/")
 	if err != nil {
-		log.Fatalf(err.Error())
+		log.Fatal().Err(err).Msg("Failed to get disk info")
 	}
-
 	diskUsed := convertKBtoGB(diskStat.Used)
 	diskTotal := convertKBtoGB(diskStat.Total)
-	diskUsedPercent := convertToPercent(float64(diskUsed) / float64(diskTotal))
 
 	return SystemInfo{
-		Username:          username.Username,
-		Hostname:          hostStat.Hostname,
-		Platform:          fmt.Sprintf("%s %s", hostStat.Platform, hostStat.PlatformVersion),
-		CPUModelName:      cpuStat[0].ModelName,
-		CPUThreads:        cpuThreads,
-		MemoryUsed:        memoryUsed,
-		MemoryTotal:       memoryTotal,
-		MemoryUsedPercent: memoryUsedPercent,
-		DiskUsed:          diskUsed,
-		DiskTotal:         diskTotal,
-		DiskUsedPercent:   diskUsedPercent,
-	}, err
+		Username:     username.Username,
+		Hostname:     hostStat.Hostname,
+		Platform:     fmt.Sprintf("%s %s", hostStat.Platform, hostStat.PlatformVersion),
+		CPUModelName: cpuStat[0].ModelName,
+		CPUThreads:   cpuThreads,
+		MemoryUsed:   memoryUsed,
+		MemoryTotal:  memoryTotal,
+		DiskUsed:     diskUsed,
+		DiskTotal:    diskTotal,
+	}
 }
 
 var getSystemInfoCmd = &cobra.Command{
 	Use:   "system-info",
 	Short: "Get system info",
 	Run: func(cmd *cobra.Command, args []string) {
-		systemInfo, err := getSystemInfo()
-		if err != nil {
-			fmt.Println(err)
-		}
+		systemInfo := getSystemInfo()
 
 		// format message
+		hostStdout := fmt.Sprintf("%s@%s", color.Green(systemInfo.Username), color.Green(systemInfo.Hostname))
+		linebreakStdout := strings.Repeat("-", len(systemInfo.Username)+len(systemInfo.Hostname)+1)
+		osStdout := fmt.Sprintf("%s: %s", color.Green("OS"), systemInfo.Platform)
+
 		cpuInfo := fmt.Sprintf("%s (%v)", systemInfo.CPUModelName, systemInfo.CPUThreads)
-		memoryInfo := fmt.Sprintf("%v/%v GB (%s)", systemInfo.MemoryUsed, systemInfo.MemoryTotal, color.Blue(strconv.Itoa(systemInfo.MemoryUsedPercent)+"%"))
-		diskInfo := fmt.Sprintf("%v/%v GB (%s)", systemInfo.DiskUsed, systemInfo.DiskTotal, color.Blue(strconv.Itoa(systemInfo.DiskUsedPercent)+"%"))
-
-		systemInfoStr := "" +
-			fmt.Sprintf("%s@%s\n", color.Green(systemInfo.Username), color.Green(systemInfo.Hostname)) +
-			strings.Repeat("-", len(systemInfo.Username)+len(systemInfo.Hostname)+1) + "\n" +
-			fmt.Sprintf("%s: %s\n", color.Green("OS"), systemInfo.Platform) +
-			fmt.Sprintf("%s: %s\n", color.Green("CPU"), cpuInfo) +
-			fmt.Sprintf("%s: %s\n", color.Green("Memory"), memoryInfo) +
-			fmt.Sprintf("%s: %s", color.Green("Disk"), diskInfo)
-
-		fmt.Println(systemInfoStr)
+		cpuStdout := fmt.Sprintf("%s: %s", color.Green("CPU"), cpuInfo)
+
+		memoryUsedPercent := convertToPercent(float64(systemInfo.MemoryUsed) / float64(systemInfo.MemoryTotal))
+		memoryInfo := fmt.Sprintf("%v/%v GB (%s)", systemInfo.MemoryUsed, systemInfo.MemoryTotal, color.Blue(strconv.Itoa(memoryUsedPercent)+"%"))
+		memoryStdout := fmt.Sprintf("%s: %s", color.Green("Memory"), memoryInfo)
+
+		diskUsedPercent := convertToPercent(float64(systemInfo.DiskUsed) / float64(systemInfo.DiskTotal))
+		diskInfo := fmt.Sprintf("%v/%v GB (%s)", systemInfo.DiskUsed, systemInfo.DiskTotal, color.Blue(strconv.Itoa(diskUsedPercent)+"%"))
+		diskStdout := fmt.Sprintf("%s: %s", color.Green("Disk"), diskInfo)
+
+		systemInfoStdout := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n", hostStdout, linebreakStdout, osStdout, cpuStdout, memoryStdout, diskStdout)
+
+		fmt.Println(systemInfoStdout)
 	},
 }
 
cmd/shouldideploytoday.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/carlmjohnson/requests"
 	"github.com/kahnwong/swissknife/color"
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
 )
 
@@ -28,7 +29,7 @@ func ShouldIDeployToday() ShouldIDeploy {
 		Fetch(context.Background())
 
 	if err != nil {
-		fmt.Println("Error calling ShouldIDeploy API:", err)
+		log.Fatal().Err(err).Msg("Error calling ShouldIDeploy API")
 	}
 
 	return response
@@ -37,7 +38,6 @@ func ShouldIDeployToday() ShouldIDeploy {
 var ShouldIDeployTodayCmd = &cobra.Command{
 	Use:   "shouldideploytoday",
 	Short: "Should I deploy today?",
-	Long:  `Should I deploy today?`,
 	Run: func(cmd *cobra.Command, args []string) {
 		response := ShouldIDeployToday()
 
cmd/speedtest.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/kahnwong/swissknife/color"
+	"github.com/rs/zerolog/log"
 	"github.com/showwin/speedtest-go/speedtest"
 	"github.com/spf13/cobra"
 )
@@ -23,17 +24,17 @@ var SpeedTestCmd = &cobra.Command{
 
 			err := s.PingTest(nil)
 			if err != nil {
-				return
+				log.Fatal().Err(err).Msg("Error pinging server")
 			}
 
 			err = s.DownloadTest()
 			if err != nil {
-				return
+				log.Fatal().Err(err).Msg("Error testing download speed")
 			}
 
 			err = s.UploadTest()
 			if err != nil {
-				return
+				log.Fatal().Err(err).Msg("Error testing upload speed")
 			}
 
 			fmt.Printf("" +
cmd/timer.go
@@ -3,13 +3,13 @@ package cmd
 
 import (
 	"fmt"
-	"log"
 	"os"
 	"strings"
 	"time"
 
 	"github.com/charmbracelet/bubbles/progress"
 	"github.com/charmbracelet/lipgloss"
+	"github.com/rs/zerolog/log"
 
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/spf13/cobra"
@@ -127,7 +127,7 @@ var TimerCmd = &cobra.Command{
 		// parse input
 		duration, err := time.ParseDuration(args[0])
 		if err != nil {
-			log.Fatal(err)
+			log.Fatal().Err(err).Msg("Error parsing duration")
 		}
 
 		// timer
@@ -136,7 +136,7 @@ var TimerCmd = &cobra.Command{
 
 		_, err = tea.NewProgram(&m).Run()
 		if err != nil {
-			log.Fatal(err)
+			log.Fatal().Err(err)
 		}
 	},
 }
go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/charmbracelet/lipgloss v0.12.1
 	github.com/fatih/color v1.17.0
 	github.com/libp2p/go-netroute v0.2.1
+	github.com/rs/zerolog v1.33.0
 	github.com/sethvargo/go-diceware v0.4.0
 	github.com/sethvargo/go-password v0.3.1
 	github.com/shirou/gopsutil/v4 v4.24.6
go.sum
@@ -18,6 +18,7 @@ github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXD
 github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
 github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4=
 github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -27,6 +28,7 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
 github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
 github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -43,6 +45,7 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
@@ -58,6 +61,7 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
 github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
 github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
 github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
@@ -66,6 +70,9 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
+github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sethvargo/go-diceware v0.4.0 h1:T9o5HaG+8Ae6We4LhItjzOSdTkW7hsikNexa5o837IQ=
 github.com/sethvargo/go-diceware v0.4.0/go.mod h1:Lg1SyPS7yQO6BBgTN5r4f2MUDkqGfLWsOjHPY0kA8iw=
@@ -125,6 +132,7 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=