refactor: use slog instead of log
Some checks are pending
Go CI Pipeline / ci (push) Waiting to run
Release Builds / GoReleaser build (push) Successful in 1m4s

This commit is contained in:
Patryk Hegenberg 2025-06-11 22:41:48 +02:00
parent fcffccc145
commit d8743e54c1
8 changed files with 180 additions and 165 deletions

101
app.go
View file

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log" "log/slog"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -44,7 +44,7 @@ func (a *App) Close() error {
func (a *App) connect() (*SSHConnection, error) { // Rückgabetyp geändert func (a *App) connect() (*SSHConnection, error) { // Rückgabetyp geändert
if err := a.timeStore.StartTracking(TagWork); err != nil { if err := a.timeStore.StartTracking(TagWork); err != nil {
log.Printf("WARN: Failed to start time tracking for '%s': %v", TagWork, err) slog.Warn(fmt.Sprintf("Failed to start time tracking for '%s': %v", TagWork, err))
} }
a.wakeWorkstation() a.wakeWorkstation()
@ -54,24 +54,31 @@ func (a *App) connect() (*SSHConnection, error) { // Rückgabetyp geändert
return nil, fmt.Errorf("failed to establish primary SSH connection: %w", err) return nil, fmt.Errorf("failed to establish primary SSH connection: %w", err)
} }
log.Println("INFO: SSH connection established. Setting up tunnels...") // slog.Info("SSH connection established. Setting up tunnels...")
slog.Info("SSH connection established. Setting up tunnels...")
sshForwarder := NewPortForwarder(sshCon.client, "2048", "22", a.cfg.WorkstationIP) sshForwarder := NewPortForwarder(sshCon.client, "2048", "22", a.cfg.WorkstationIP)
go func() { go func() {
log.Println("INFO: Starting SSH forwarder (local :2048 -> remote workstation:22)") // slog.Info("Starting SSH forwarder (local :2048 -> remote workstation:22)")
slog.Info("Starting SSH forwarder (local :2048 -> remote workstation:22)")
if err := sshForwarder.forward(); err != nil { if err := sshForwarder.forward(); err != nil {
log.Printf("ERROR: SSH forwarder failed: %v", err) // slog.Error(fmt.Sprintf("SSH forwarder failed: %v", err)
slog.Error(fmt.Sprintf("SSH forwarder failed: %v", err))
} }
log.Println("INFO: SSH forwarder stopped.") // slog.Info("SSH forwarder stopped.")
slog.Info("SSH forwarder stopped.")
}() }()
rdpForwarder := NewPortForwarder(sshCon.client, "6000", "3389", a.cfg.WorkstationIP) rdpForwarder := NewPortForwarder(sshCon.client, "6000", "3389", a.cfg.WorkstationIP)
go func() { go func() {
log.Println("INFO: Starting RDP forwarder (local :6000 -> remote workstation:3389)") // slog.Info("Starting RDP forwarder (local :6000 -> remote workstation:3389)")
slog.Info("Starting RDP forwarder (local :6000 -> remote workstation:3389)")
if err := rdpForwarder.forward(); err != nil { if err := rdpForwarder.forward(); err != nil {
log.Printf("ERROR: RDP forwarder failed: %v", err) // slog.Error(fmt.Sprintf("RDP forwarder failed: %v", err)
slog.Error(fmt.Sprintf("ERROR: RDP forwarder failed: %v", err))
} }
log.Println("INFO: RDP forwarder stopped.") // slog.Info("RDP forwarder stopped.")
slog.Info("RDP forwarder stopped.")
}() }()
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
@ -80,22 +87,22 @@ func (a *App) connect() (*SSHConnection, error) { // Rückgabetyp geändert
} }
func (a *App) runCommand(name string, args ...string) error { func (a *App) runCommand(name string, args ...string) error {
log.Printf("INFO: Executing command: %s %s", name, strings.Join(args, " ")) slog.Info(fmt.Sprintf("Executing command: %s %s", name, strings.Join(args, " ")))
cmd := exec.Command(name, args...) cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
log.Printf("ERROR: Command failed: %s %s -> %v", name, strings.Join(args, " "), err) slog.Error(fmt.Sprintf("Command failed: %s %s -> %v", name, strings.Join(args, " "), err))
return fmt.Errorf("command execution failed: %w", err) return fmt.Errorf("command execution failed: %w", err)
} }
log.Printf("INFO: Command finished successfully: %s", name) slog.Info(fmt.Sprintf("Command finished successfully: %s", name))
return nil return nil
} }
func (a *App) wakeWorkstation() { func (a *App) wakeWorkstation() {
log.Println("INFO: Attempting to wake workstation...") slog.Info("Attempting to wake workstation...")
innerSSHCmd := fmt.Sprintf("ssh -tt %s@%s \"wakeonlan %s && echo 'Wake-on-LAN packet sent.' && exit\"", innerSSHCmd := fmt.Sprintf("ssh -tt %s@%s \"wakeonlan %s && echo 'Wake-on-LAN packet sent.' && exit\"",
a.cfg.JumpUser, a.cfg.JumpUser,
a.cfg.JumpHost, a.cfg.JumpHost,
@ -109,14 +116,14 @@ func (a *App) wakeWorkstation() {
} }
if err := a.runCommand("ssh", outerSSHCmd...); err != nil { if err := a.runCommand("ssh", outerSSHCmd...); err != nil {
log.Println("WARN: Failed to send Wake-on-LAN packet via SSH jump. Workstation might already be awake or command failed.") slog.Warn("Failed to send Wake-on-LAN packet via SSH jump. Workstation might already be awake or command failed.")
} else { } else {
log.Println("INFO: Wake-on-LAN command executed.") slog.Info("Wake-on-LAN command executed.")
} }
} }
func (a *App) connectToJump() { func (a *App) connectToJump() {
log.Println("INFO: Connecting to Jump Host with Port Forwarding...") slog.Info("Connecting to Jump Host with Port Forwarding...")
sshArgs := []string{ sshArgs := []string{
"-tt", "-tt",
"-L", fmt.Sprintf("2048:%s:22", a.cfg.WorkstationHost), "-L", fmt.Sprintf("2048:%s:22", a.cfg.WorkstationHost),
@ -128,7 +135,7 @@ func (a *App) connectToJump() {
} }
func (a *App) connectToWorkstation() { func (a *App) connectToWorkstation() {
log.Println("INFO: Connecting to Workstation via local tunnel (localhost:2048)...") slog.Info("Connecting to Workstation via local tunnel (localhost:2048)...")
sshArgs := []string{ sshArgs := []string{
"-tt", "-tt",
"-L", fmt.Sprintf("6000:%s:3389", a.cfg.WorkstationHost), "-L", fmt.Sprintf("6000:%s:3389", a.cfg.WorkstationHost),
@ -140,7 +147,7 @@ func (a *App) connectToWorkstation() {
} }
func (a *App) startRDPConnection() { func (a *App) startRDPConnection() {
log.Println("INFO: Starting RDP connection to localhost:6000...") slog.Info("Starting RDP connection to localhost:6000...")
rdpCommand := fmt.Sprintf("xfreerdp /u:%s /p:%s /v:127.0.0.1:6000 /size:3000x1350 +clipboard /dynamic-resolution", rdpCommand := fmt.Sprintf("xfreerdp /u:%s /p:%s /v:127.0.0.1:6000 /size:3000x1350 +clipboard /dynamic-resolution",
a.cfg.RDPUser, a.cfg.RDPUser,
a.cfg.SSHPassword, a.cfg.SSHPassword,
@ -182,7 +189,7 @@ func (a *App) makeChoice() {
fmt.Println("Operation cancelled.") fmt.Println("Operation cancelled.")
return return
} }
log.Printf("ERROR: Form execution failed: %v", err) slog.Error(fmt.Sprintf("Form execution failed: %v", err))
return return
} }
@ -191,30 +198,30 @@ func (a *App) makeChoice() {
a.connect() a.connect()
case "stop work": case "stop work":
if err := a.timeStore.StopTracking(); err != nil { if err := a.timeStore.StopTracking(); err != nil {
log.Printf("ERROR: Failed to stop time tracking: %v", err) slog.Error(fmt.Sprintf("Failed to stop time tracking: %v", err))
} }
if err := a.killForwardings(); err != nil { if err := a.killForwardings(); err != nil {
log.Printf("WARN: Could not kill all forwardings: %v", err) slog.Warn(fmt.Sprintf("Could not kill all forwardings: %v", err))
} }
case "start break": case "start break":
if err := a.timeStore.StartTracking(TagBreak); err != nil { if err := a.timeStore.StartTracking(TagBreak); err != nil {
log.Printf("ERROR: Failed to start break tracking: %v", err) slog.Error(fmt.Sprintf("Failed to start break tracking: %v", err))
} }
case "stop break": case "stop break":
if err := a.timeStore.StartTracking(TagWork); err != nil { if err := a.timeStore.StartTracking(TagWork); err != nil {
log.Printf("ERROR: Failed to stop break (start work): %v", err) slog.Error(fmt.Sprintf("Failed to stop break (start work): %v", err))
} }
case "show day summary": case "show day summary":
if err := a.timeStore.ShowSummary("today"); err != nil { if err := a.timeStore.ShowSummary("today"); err != nil {
log.Printf("ERROR: Failed to show day summary: %v", err) slog.Error(fmt.Sprintf("Failed to show day summary: %v", err))
} }
case "show week summary": case "show week summary":
if err := a.timeStore.ShowSummary("week"); err != nil { if err := a.timeStore.ShowSummary("week"); err != nil {
log.Printf("ERROR: Failed to show week summary: %v", err) slog.Error(fmt.Sprintf("ERROR: Failed to show week summary: %v", err))
} }
case "show month summary": case "show month summary":
if err := a.timeStore.ShowSummary("month"); err != nil { if err := a.timeStore.ShowSummary("month"); err != nil {
log.Printf("ERROR: Failed to show month summary: %v", err) slog.Error(fmt.Sprintf("Failed to show month summary: %v", err))
} }
case "export": case "export":
filename := "Arbeitszeiten_" + time.Now().Format("2006") + ".xlsx" filename := "Arbeitszeiten_" + time.Now().Format("2006") + ".xlsx"
@ -222,7 +229,7 @@ func (a *App) makeChoice() {
filename = a.flags.ExportName filename = a.flags.ExportName
} }
if err := a.timeStore.ExportSummary(filename); err != nil { if err := a.timeStore.ExportSummary(filename); err != nil {
log.Printf("ERROR: Failed to export summary to '%s': %v", filename, err) slog.Error(fmt.Sprintf("Failed to export summary to '%s': %v", filename, err))
} }
case "connect to jump": case "connect to jump":
a.connectToJump() a.connectToJump()
@ -234,15 +241,15 @@ func (a *App) makeChoice() {
a.wakeWorkstation() a.wakeWorkstation()
case "kill tunnels": case "kill tunnels":
if err := a.killForwardings(); err != nil { if err := a.killForwardings(); err != nil {
log.Printf("ERROR: Failed to kill forwardings: %v", err) slog.Error(fmt.Sprintf("Failed to kill forwardings: %v", err))
} else { } else {
log.Println("INFO: Attempted to kill processes on ports 2048 and 6000.") slog.Info("Attempted to kill processes on ports 2048 and 6000.")
} }
case "exit": case "exit":
fmt.Println("Exiting.") fmt.Println("Exiting.")
return return
default: default:
log.Printf("WARN: Unhandled choice '%s'", choice) slog.Warn(fmt.Sprintf("Unhandled choice '%s'", choice))
} }
if choice != "exit" && choice != "connect to jump" && choice != "connect to workstation" && choice != "start rdp connection" { if choice != "exit" && choice != "connect to jump" && choice != "connect to workstation" && choice != "start rdp connection" {
@ -257,7 +264,7 @@ func (a *App) getSSHAuth() ssh.AuthMethod {
keyBytes, err := os.ReadFile(keyPath) keyBytes, err := os.ReadFile(keyPath)
if err != nil { if err != nil {
log.Printf("ERROR: Unable to read private key '%s': %v", keyPath, err) slog.Error(fmt.Sprintf("Unable to read private key '%s': %v", keyPath, err))
return nil return nil
} }
@ -265,19 +272,19 @@ func (a *App) getSSHAuth() ssh.AuthMethod {
key, err = ssh.ParsePrivateKey(keyBytes) key, err = ssh.ParsePrivateKey(keyBytes)
if err != nil { if err != nil {
if _, ok := err.(*ssh.PassphraseMissingError); ok { if _, ok := err.(*ssh.PassphraseMissingError); ok {
log.Printf("INFO: Private key '%s' requires a passphrase. Trying with RDP password from config.", keyPath) slog.Info(fmt.Sprintf("Private key '%s' requires a passphrase. Trying with RDP password from config.", keyPath))
key, err = ssh.ParsePrivateKeyWithPassphrase(keyBytes, []byte(a.cfg.RDPPassword)) key, err = ssh.ParsePrivateKeyWithPassphrase(keyBytes, []byte(a.cfg.RDPPassword))
if err != nil { if err != nil {
log.Printf("ERROR: Unable to parse private key '%s' with passphrase: %v", keyPath, err) slog.Error(fmt.Sprintf("Unable to parse private key '%s' with passphrase: %v", keyPath, err))
return nil return nil
} }
} else { } else {
log.Printf("ERROR: Unable to parse private key '%s': %v", keyPath, err) slog.Error(fmt.Sprintf("Unable to parse private key '%s': %v", keyPath, err))
return nil return nil
} }
} }
log.Printf("INFO: Successfully loaded private key '%s'", keyPath) slog.Info(fmt.Sprintf("Successfully loaded private key '%s'", keyPath))
return ssh.PublicKeys(key) return ssh.PublicKeys(key)
} }
@ -295,13 +302,13 @@ func (a *App) newSSHConnection() (*SSHConnection, error) {
} }
target := fmt.Sprintf("%s:%d", a.cfg.SSHHost, a.cfg.SSHPort) target := fmt.Sprintf("%s:%d", a.cfg.SSHHost, a.cfg.SSHPort)
log.Printf("INFO: Dialing SSH to %s...", target) slog.Info(fmt.Sprintf("Dialing SSH to %s...", target))
client, err := ssh.Dial("tcp", target, sshConfig) client, err := ssh.Dial("tcp", target, sshConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("SSH dial to %s failed: %w", target, err) return nil, fmt.Errorf("SSH dial to %s failed: %w", target, err)
} }
log.Printf("INFO: SSH connection to %s successful.", target) slog.Info(fmt.Sprintf("SSH connection to %s successful.", target))
session, err := client.NewSession() session, err := client.NewSession()
if err != nil { if err != nil {
@ -320,16 +327,16 @@ func (a *App) killForwardings() error {
killedSomething := false killedSomething := false
var lastErr error var lastErr error
log.Println("INFO: Attempting to kill processes listening on ports:", strings.Join(ports, ", ")) slog.Info(fmt.Sprintf("Attempting to kill processes listening on ports: %v", strings.Join(ports, ", ")))
for _, port := range ports { for _, port := range ports {
cmd := exec.Command("lsof", "-i", "tcp:"+port, "-t") cmd := exec.Command("lsof", "-i", "tcp:"+port, "-t")
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
log.Printf("INFO: No process found listening on port %s.", port) slog.Info(fmt.Sprintf("No process found listening on port %s.", port))
} else { } else {
log.Printf("WARN: 'lsof' command failed for port %s: %v", port, err) slog.Warn(fmt.Sprintf("'lsof' command failed for port %s: %v", port, err))
lastErr = fmt.Errorf("lsof failed for port %s: %w", port, err) lastErr = fmt.Errorf("lsof failed for port %s: %w", port, err)
} }
continue continue
@ -341,29 +348,29 @@ func (a *App) killForwardings() error {
if pid == "" { if pid == "" {
continue continue
} }
log.Printf("INFO: Found process PID %s on port %s. Attempting to kill...", pid, port) slog.Info(fmt.Sprintf("Found process PID %s on port %s. Attempting to kill...", pid, port))
killCmd := exec.Command("kill", pid) killCmd := exec.Command("kill", pid)
if err := killCmd.Run(); err != nil { if err := killCmd.Run(); err != nil {
log.Printf("WARN: Failed to kill PID %s (port %s): %v. Trying kill -9...", pid, port, err) slog.Warn(fmt.Sprintf("Failed to kill PID %s (port %s): %v. Trying kill -9...", pid, port, err))
forceKillCmd := exec.Command("kill", "-9", pid) forceKillCmd := exec.Command("kill", "-9", pid)
if err := forceKillCmd.Run(); err != nil { if err := forceKillCmd.Run(); err != nil {
log.Printf("ERROR: Failed to force kill PID %s (port %s): %v", pid, port, err) slog.Error(fmt.Sprintf("Failed to force kill PID %s (port %s): %v", pid, port, err))
lastErr = fmt.Errorf("kill -9 failed for PID %s: %w", pid, err) lastErr = fmt.Errorf("kill -9 failed for PID %s: %w", pid, err)
} else { } else {
log.Printf("INFO: Force killed PID %s (port %s).", pid, port) slog.Info(fmt.Sprintf("Force killed PID %s (port %s).", pid, port))
killedSomething = true killedSomething = true
} }
} else { } else {
log.Printf("INFO: Killed PID %s (port %s).", pid, port) slog.Info(fmt.Sprintf("Killed PID %s (port %s).", pid, port))
killedSomething = true killedSomething = true
} }
} }
} }
if killedSomething { if killedSomething {
log.Println("INFO: Finished attempting to kill forwarding processes.") slog.Info("Finished attempting to kill forwarding processes.")
} else { } else {
log.Println("INFO: No forwarding processes found or killed.") slog.Info("No forwarding processes found or killed.")
} }
return lastErr return lastErr

134
cmd.go
View file

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log" "log/slog"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
@ -51,11 +51,11 @@ Use --background (-b) to keep tunnels running in the background without auto-con
} }
defer func() { defer func() {
log.Println("INFO: Closing SSH connection to jump host (defer)...") slog.Info("Closing SSH connection to jump host (defer)...")
if err := sshCon.Close(); err != nil { if err := sshCon.Close(); err != nil {
log.Printf("WARN: Error closing SSH connection in defer: %v", err) slog.Warn(fmt.Sprintf("Error closing SSH connection in defer: %v", err))
} else { } else {
log.Println("INFO: SSH connection closed via defer.") slog.Info("SSH connection closed via defer.")
} }
}() }()
@ -70,11 +70,11 @@ Use --background (-b) to keep tunnels running in the background without auto-con
<-sigChan <-sigChan
fmt.Println("\nINFO: Received interrupt signal. Shutting down background process...") fmt.Println("\nINFO: Received interrupt signal. Shutting down background process...")
log.Println("INFO: Received signal, cleanup via defer sshCon.Close() will run.") slog.Info("Received signal, cleanup via defer sshCon.Close() will run.")
if err := a.timeStore.StopTracking(); err != nil { if err := a.timeStore.StopTracking(); err != nil {
log.Printf("WARN: Failed to stop time tracking: %v", err) slog.Warn(fmt.Sprintf("Failed to stop time tracking: %v", err))
} else { } else {
log.Println("INFO: Time tracking stopped.") slog.Info("Time tracking stopped.")
} }
fmt.Println("INFO: Background shutdown complete.") fmt.Println("INFO: Background shutdown complete.")
@ -83,11 +83,11 @@ Use --background (-b) to keep tunnels running in the background without auto-con
a.connectToWorkstation() a.connectToWorkstation()
fmt.Println("INFO: Workstation SSH session finished.") fmt.Println("INFO: Workstation SSH session finished.")
log.Println("INFO: Foreground session ended, cleanup via defer sshCon.Close() will run.") slog.Info("Foreground session ended, cleanup via defer sshCon.Close() will run.")
if err := a.timeStore.StopTracking(); err != nil { if err := a.timeStore.StopTracking(); err != nil {
log.Printf("WARN: Failed to stop time tracking: %v", err) slog.Warn(fmt.Sprintf("Failed to stop time tracking: %v", err))
} else { } else {
log.Println("INFO: Time tracking stopped.") slog.Info("Time tracking stopped.")
} }
} }
@ -108,13 +108,13 @@ func (a *App) stopCommand() *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Stopping workday procedures...") fmt.Println("Stopping workday procedures...")
if err := a.timeStore.StopTracking(); err != nil { if err := a.timeStore.StopTracking(); err != nil {
log.Printf("ERROR: Failed to stop time tracking: %v", err) slog.Error(fmt.Sprintf("Failed to stop time tracking: %v", err))
} else { } else {
fmt.Println("Time tracking stopped.") fmt.Println("Time tracking stopped.")
} }
if err := a.killForwardings(); err != nil { if err := a.killForwardings(); err != nil {
log.Printf("WARN: Could not kill all forwarding processes: %v", err) slog.Warn(fmt.Sprintf("Could not kill all forwarding processes: %v", err))
} else { } else {
fmt.Println("Attempted to stop SSH tunnels.") fmt.Println("Attempted to stop SSH tunnels.")
} }
@ -160,7 +160,7 @@ This also stops any currently running timer.`,
default: default:
fmt.Printf("Attempting to start tracking interval '%s'...\n", tag) fmt.Printf("Attempting to start tracking interval '%s'...\n", tag)
if err := a.timeStore.StartTracking(tag); err != nil { if err := a.timeStore.StartTracking(tag); err != nil {
log.Printf("ERROR: Failed to start tracking '%s': %v", tag, err) slog.Error(fmt.Sprintf("Failed to start tracking '%s': %v", tag, err))
return fmt.Errorf("could not start tracking '%s': %w", tag, err) return fmt.Errorf("could not start tracking '%s': %w", tag, err)
} }
return nil // Erfolg return nil // Erfolg
@ -174,7 +174,7 @@ This also stops any currently running timer.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Starting break...") fmt.Println("Starting break...")
if err := a.timeStore.StartTracking(TagBreak); err != nil { if err := a.timeStore.StartTracking(TagBreak); err != nil {
log.Printf("ERROR: Failed to start break tracking: %v", err) slog.Error(fmt.Sprintf("Failed to start break tracking: %v", err))
return fmt.Errorf("could not start break: %w", err) return fmt.Errorf("could not start break: %w", err)
} }
return nil return nil
@ -204,27 +204,27 @@ Export: Use the --export flag or the 'export' subcommand.`,
filename := a.flags.ExportName filename := a.flags.ExportName
if filename == "" || filename == "Arbeitszeiten.xlsx" { if filename == "" || filename == "Arbeitszeiten.xlsx" {
filename = "Arbeitszeiten_" + time.Now().Format("2006") + ".xlsx" filename = "Arbeitszeiten_" + time.Now().Format("2006") + ".xlsx"
log.Printf("INFO: No export name specified, using default: %s", filename) slog.Info(fmt.Sprintf("No export name specified, using default: %s", filename))
} }
fmt.Printf("Exporting yearly timetable to '%s'...\n", filename) fmt.Printf("Exporting yearly timetable to '%s'...\n", filename)
if err := a.timeStore.ExportSummary(filename); err != nil { if err := a.timeStore.ExportSummary(filename); err != nil {
log.Printf("ERROR: Failed to export summary to '%s': %v", filename, err) slog.Error(fmt.Sprintf("Failed to export summary to '%s': %v", filename, err))
fmt.Printf("Error: Could not export to '%s'.\n", filename) fmt.Printf("Error: Could not export to '%s'.\n", filename)
} }
} else if a.flags.ShowWeek { } else if a.flags.ShowWeek {
fmt.Println("Showing weekly summary...") fmt.Println("Showing weekly summary...")
if err := a.timeStore.ShowSummary("week"); err != nil { if err := a.timeStore.ShowSummary("week"); err != nil {
log.Printf("ERROR: Failed to show week summary: %v", err) slog.Error(fmt.Sprintf("Failed to show week summary: %v", err))
} }
} else if a.flags.ShowMonth { } else if a.flags.ShowMonth {
fmt.Println("Showing monthly summary...") fmt.Println("Showing monthly summary...")
if err := a.timeStore.ShowSummary("month"); err != nil { if err := a.timeStore.ShowSummary("month"); err != nil {
log.Printf("ERROR: Failed to show month summary: %v", err) slog.Error(fmt.Sprintf("Failed to show month summary: %v", err))
} }
} else { } else {
fmt.Printf("Showing summary for period: %s...\n", period) fmt.Printf("Showing summary for period: %s...\n", period)
if err := a.timeStore.ShowSummary(period); err != nil { if err := a.timeStore.ShowSummary(period); err != nil {
log.Printf("ERROR: Failed to show summary for '%s': %v", period, err) slog.Error(fmt.Sprintf("Failed to show summary for '%s': %v", period, err))
} }
} }
}, },
@ -248,7 +248,7 @@ Export: Use the --export flag or the 'export' subcommand.`,
} }
fmt.Printf("Exporting yearly timetable to '%s'...\n", filename) fmt.Printf("Exporting yearly timetable to '%s'...\n", filename)
if err := a.timeStore.ExportSummary(filename); err != nil { if err := a.timeStore.ExportSummary(filename); err != nil {
log.Printf("ERROR: Failed to export summary to '%s': %v", filename, err) slog.Error(fmt.Sprintf("Failed to export summary to '%s': %v", filename, err))
fmt.Printf("Error: Could not export to '%s'.\n", filename) fmt.Printf("Error: Could not export to '%s'.\n", filename)
} }
}, },
@ -321,12 +321,12 @@ Example: workctl import-timew /path/to/timew-summary.txt`,
count, err := a.runImport(filepath) count, err := a.runImport(filepath)
if err != nil { if err != nil {
log.Printf("ERROR: Import failed: %v", err) slog.Error(fmt.Sprintf("Import failed: %v", err))
return fmt.Errorf("import failed: %w", err) return fmt.Errorf("import failed: %w", err)
} }
fmt.Printf("Successfully imported %d time entries.\n", count) fmt.Printf("Successfully imported %d time entries.\n", count)
log.Printf("INFO: Successfully imported %d time entries from %s", count, filepath) slog.Info(fmt.Sprintf("Successfully imported %d time entries from %s", count, filepath))
return nil return nil
}, },
} }
@ -353,8 +353,8 @@ func (a *App) runImport(filepath string) (int, error) {
} }
defer stmt.Close() defer stmt.Close()
var current_date_str string var currentDateStr string
imported_count := 0 importedCount := 0
location := time.Local location := time.Local
for i, line := range lines { for i, line := range lines {
@ -364,94 +364,94 @@ func (a *App) runImport(filepath string) (int, error) {
} }
fields := strings.Fields(line) fields := strings.Fields(line)
var tag, start_str, end_str string var tag, startStr, endStr string
has_date := false hasDate := false
if len(fields) >= 7 && strings.Contains(fields[1], "-") && len(fields[1]) == 10 { if len(fields) >= 7 && strings.Contains(fields[1], "-") && len(fields[1]) == 10 {
current_date_str = fields[1] currentDateStr = fields[1]
tag = fields[3] tag = fields[3]
start_str = fields[4] startStr = fields[4]
end_str = fields[5] endStr = fields[5]
has_date = true hasDate = true
} else if len(fields) >= 4 && strings.Contains(fields[1], ":") && strings.Contains(fields[2], ":") { } else if len(fields) >= 4 && strings.Contains(fields[1], ":") && strings.Contains(fields[2], ":") {
if current_date_str == "" { if currentDateStr == "" {
log.Printf("WARN: Skipping line without preceding date: %s", line) slog.Warn(fmt.Sprintf("Skipping line without preceding date: %s", line))
continue continue
} }
tag = fields[0] tag = fields[0]
start_str = fields[1] startStr = fields[1]
end_str = fields[2] endStr = fields[2]
has_date = false hasDate = false
} else if len(fields) >= 6 && strings.Contains(fields[1], "-") && len(fields[1]) == 10 { } else if len(fields) >= 6 && strings.Contains(fields[1], "-") && len(fields[1]) == 10 {
current_date_str = fields[1] currentDateStr = fields[1]
tag = fields[3] tag = fields[3]
start_str = fields[4] startStr = fields[4]
end_str = fields[5] endStr = fields[5]
has_date = true hasDate = true
if start_str == "0:00:00" && end_str == "0:00:00" { if startStr == "0:00:00" && endStr == "0:00:00" {
start_time, err := time.ParseInLocation("2006-01-02", current_date_str, location) startTime, err := time.ParseInLocation("2006-01-02", currentDateStr, location)
if err != nil { if err != nil {
log.Printf("WARN: Skipping line with invalid date '%s': %v", current_date_str, err) slog.Warn(fmt.Sprintf("Skipping line with invalid date '%s': %v", currentDateStr, err))
continue continue
} }
end_time := start_time.Add(24 * time.Hour) endTime := startTime.Add(24 * time.Hour)
_, err = stmt.Exec(tag, start_time, end_time) _, err = stmt.Exec(tag, startTime, endTime)
if err != nil { if err != nil {
log.Printf("ERROR: Failed to insert full-day entry for %s (%s): %v", current_date_str, tag, err) slog.Error(fmt.Sprintf("Failed to insert full-day entry for %s (%s): %v", currentDateStr, tag, err))
} else { } else {
imported_count++ importedCount++
} }
continue continue
} }
} else { } else {
log.Printf("WARN: Skipping unrecognized line format: %s", line) slog.Warn(fmt.Sprintf("Skipping unrecognized line format: %s", line))
continue continue
} }
if end_str == "-" { if endStr == "-" {
log.Printf("INFO: Skipping currently running entry: %s", line) slog.Info(fmt.Sprintf("Skipping currently running entry: %s", line))
continue continue
} }
start_datetime_str := current_date_str + " " + start_str startDatetimeStr := currentDateStr + " " + startStr
end_datetime_str := current_date_str + " " + end_str endDatetimeStr := currentDateStr + " " + endStr
start_time, err_start := time.ParseInLocation("2006-01-02 15:04:05", start_datetime_str, location) startTime, errStart := time.ParseInLocation("2006-01-02 15:04:05", startDatetimeStr, location)
end_time, err_end := time.ParseInLocation("2006-01-02 15:04:05", end_datetime_str, location) endTime, errEnd := time.ParseInLocation("2006-01-02 15:04:05", endDatetimeStr, location)
if err_start != nil || err_end != nil { if errStart != nil || errEnd != nil {
log.Printf("WARN: Skipping line with invalid date/time format ('%s' / '%s'): %v / %v", start_datetime_str, end_datetime_str, err_start, err_end) slog.Warn(fmt.Sprintf("Skipping line with invalid date/time format ('%s' / '%s'): %v / %v", startDatetimeStr, endDatetimeStr, errStart, errEnd))
continue continue
} }
if end_time.Before(start_time) { if endTime.Before(startTime) {
if has_date { if hasDate {
log.Printf("WARN: End time is before start time on the same date line, skipping: %s", line) slog.Warn(fmt.Sprintf("End time is before start time on the same date line, skipping: %s", line))
continue continue
} }
} }
db_tag := strings.ToLower(tag) dbTag := strings.ToLower(tag)
switch db_tag { switch dbTag {
case "work": case "work":
db_tag = TagWork dbTag = TagWork
case "break": case "break":
db_tag = TagBreak dbTag = TagBreak
} }
_, err = stmt.Exec(db_tag, start_time, end_time) _, err = stmt.Exec(dbTag, startTime, endTime)
if err != nil { if err != nil {
log.Printf("ERROR: Failed to insert entry for %s (%s, %s -> %s): %v", current_date_str, db_tag, start_time.Format(time.RFC3339), end_time.Format(time.RFC3339), err) slog.Error(fmt.Sprintf("Failed to insert entry for %s (%s, %s -> %s): %v", currentDateStr, dbTag, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339), err))
} else { } else {
imported_count++ importedCount++
} }
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return imported_count, fmt.Errorf("failed to commit transaction: %w", err) return importedCount, fmt.Errorf("failed to commit transaction: %w", err)
} }
return imported_count, nil return importedCount, nil
} }

View file

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -43,7 +43,7 @@ func loadConfig() (Config, error) {
workConfigPath := filepath.Join(configPath, "work") workConfigPath := filepath.Join(configPath, "work")
configFile := filepath.Join(workConfigPath, "config.toml") configFile := filepath.Join(workConfigPath, "config.toml")
if err := os.MkdirAll(workConfigPath, 0750); err != nil { if err := os.MkdirAll(workConfigPath, 0o750); err != nil {
return cfg, fmt.Errorf("could not create config directory '%s': %w", workConfigPath, err) return cfg, fmt.Errorf("could not create config directory '%s': %w", workConfigPath, err)
} }
@ -55,7 +55,7 @@ func loadConfig() (Config, error) {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok { if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return cfg, fmt.Errorf("error reading config file '%s': %w", configFile, err) return cfg, fmt.Errorf("error reading config file '%s': %w", configFile, err)
} }
log.Printf("INFO: Config file '%s' not found, using defaults/env vars.", configFile) slog.Info(fmt.Sprintf("Config file '%s' not found, using defaults/env vars.", configFile))
} }
if err := viper.UnmarshalKey("default", &cfg); err != nil { if err := viper.UnmarshalKey("default", &cfg); err != nil {

View file

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log" "log/slog"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -35,7 +35,6 @@ func aggregateEntriesToDailySummaries(entries []TimeEntry, yearStart, yearEnd ti
now := time.Now().In(location) now := time.Now().In(location)
currentDay := yearStart currentDay := yearStart
log.Println(currentDay)
for currentDay.Before(yearEnd) { for currentDay.Before(yearEnd) {
dayStr := currentDay.Format("2006-01-02") dayStr := currentDay.Format("2006-01-02")
weekday := currentDay.Weekday() weekday := currentDay.Weekday()
@ -56,7 +55,7 @@ func aggregateEntriesToDailySummaries(entries []TimeEntry, yearStart, yearEnd ti
for _, entry := range entries { for _, entry := range entries {
if entry.StartTime.IsZero() { if entry.StartTime.IsZero() {
log.Printf("WARN: Skipping entry with zero start time (ID: %d)", entry.ID) slog.Warn(fmt.Sprintf("Skipping entry with zero start time (ID: %d)", entry.ID))
continue continue
} }
@ -100,7 +99,7 @@ func aggregateEntriesToDailySummaries(entries []TimeEntry, yearStart, yearEnd ti
summary, exists := dailyMap[dayStr] summary, exists := dailyMap[dayStr]
if !exists { if !exists {
log.Printf("WARN: Day %s not found in initial map during entry processing (ID: %d)", dayStr, entry.ID) slog.Warn(fmt.Sprintf("Day %s not found in initial map during entry processing (ID: %d)", dayStr, entry.ID))
loopTime = dayEnd loopTime = dayEnd
continue continue
} }
@ -141,7 +140,7 @@ func aggregateEntriesToDailySummaries(entries []TimeEntry, yearStart, yearEnd ti
case TagBreak: case TagBreak:
summary.BreakDuration += segmentDuration summary.BreakDuration += segmentDuration
default: default:
log.Printf("INFO: Encountered unknown tag '%s' during interval processing for entry ID %d on %s. Counting duration as 'work'.", entry.Tag, entry.ID, dayStr) slog.Info(fmt.Sprintf("Encountered unknown tag '%s' during interval processing for entry ID %d on %s. Counting duration as 'work'.", entry.Tag, entry.ID, dayStr))
summary.WorkDuration += segmentDuration summary.WorkDuration += segmentDuration
if summary.WorkStart == "" || timeStr < summary.WorkStart { if summary.WorkStart == "" || timeStr < summary.WorkStart {
summary.WorkStart = timeStr summary.WorkStart = timeStr
@ -261,7 +260,7 @@ func getSollExcelTime(dayOfWeek string) any {
sollDur, err := time.Parse("15:04", sollString) sollDur, err := time.Parse("15:04", sollString)
if err != nil { if err != nil {
log.Printf("ERROR: Could not parse hardcoded soll string '%s': %v", sollString, err) slog.Error(fmt.Sprintf("Could not parse hardcoded soll string '%s': %v", sollString, err))
return nil return nil
} }
return float64(sollDur.Hour())/24.0 + float64(sollDur.Minute())/(24.0*60.0) return float64(sollDur.Hour())/24.0 + float64(sollDur.Minute())/(24.0*60.0)
@ -271,7 +270,7 @@ func writeExcelSheet(entries []ExcelEntry, name string) error {
f := excelize.NewFile() f := excelize.NewFile()
defer func() { defer func() {
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
log.Printf("ERROR: Failed to close excel file handle: %v", err) slog.Error(fmt.Sprintf("Failed to close excel file handle: %v", err))
} }
}() }()

View file

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"log" "log/slog"
"net" "net"
"sync" "sync"
@ -31,28 +31,28 @@ func (pf *PortForwarder) forward() error {
localAddr := "127.0.0.1:" + pf.localPort localAddr := "127.0.0.1:" + pf.localPort
remoteAddr := net.JoinHostPort(pf.remoteHost, pf.remotePort) remoteAddr := net.JoinHostPort(pf.remoteHost, pf.remotePort)
pf.logf("INFO: Starting port forwarder: local %s -> remote %s (via SSH)", localAddr, remoteAddr) pf.logf("INFO", "Starting port forwarder: local %s -> remote %s (via SSH)", localAddr, remoteAddr)
listener, err := net.Listen("tcp", localAddr) listener, err := net.Listen("tcp", localAddr)
if err != nil { if err != nil {
pf.logf("ERROR: Failed to open local listener on %s: %v", localAddr, err) pf.logf("ERROR", "Failed to open local listener on %s: %v", localAddr, err)
return fmt.Errorf("failed to listen on %s: %w", localAddr, err) return fmt.Errorf("failed to listen on %s: %w", localAddr, err)
} }
defer listener.Close() defer listener.Close()
pf.logf("INFO: Listener active on %s", localAddr) pf.logf("INFO", "Listener active on %s", localAddr)
for { for {
localConn, err := listener.Accept() localConn, err := listener.Accept()
if err != nil { if err != nil {
if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" { if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" {
pf.logf("INFO: Listener on %s closed, stopping forwarder.", localAddr) pf.logf("INFO", "Listener on %s closed, stopping forwarder.", localAddr)
return nil return nil
} }
pf.logf("ERROR: Failed to accept incoming connection on %s: %v", localAddr, err) pf.logf("ERROR", "Failed to accept incoming connection on %s: %v", localAddr, err)
continue continue
} }
pf.logf("INFO: Accepted connection from %s on %s", localConn.RemoteAddr(), localAddr) pf.logf("INFO", "Accepted connection from %s on %s", localConn.RemoteAddr(), localAddr)
go pf.handleConnection(localConn, remoteAddr) go pf.handleConnection(localConn, remoteAddr)
} }
} }
@ -60,14 +60,14 @@ func (pf *PortForwarder) forward() error {
func (pf *PortForwarder) handleConnection(localConn net.Conn, remoteAddr string) { func (pf *PortForwarder) handleConnection(localConn net.Conn, remoteAddr string) {
defer localConn.Close() defer localConn.Close()
pf.logf("INFO: Dialing remote host %s via SSH tunnel for %s", remoteAddr, localConn.RemoteAddr()) pf.logf("INFO", "Dialing remote host %s via SSH tunnel for %s", remoteAddr, localConn.RemoteAddr())
remoteConn, err := pf.sshCon.Dial("tcp", remoteAddr) remoteConn, err := pf.sshCon.Dial("tcp", remoteAddr)
if err != nil { if err != nil {
pf.logf("ERROR: Failed to dial remote host %s via SSH: %v", remoteAddr, err) pf.logf("ERROR", "Failed to dial remote host %s via SSH: %v", remoteAddr, err)
return return
} }
defer remoteConn.Close() defer remoteConn.Close()
pf.logf("INFO: Connection to %s established. Starting data copy.", remoteAddr) pf.logf("INFO", "Connection to %s established. Starting data copy.", remoteAddr)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
@ -78,7 +78,7 @@ func (pf *PortForwarder) handleConnection(localConn net.Conn, remoteAddr string)
bytesCopied, err := io.Copy(localConn, remoteConn) bytesCopied, err := io.Copy(localConn, remoteConn)
if err != nil { if err != nil {
} }
pf.logf("INFO: Finished copying remote->local (%d bytes) for %s", bytesCopied, localConn.RemoteAddr()) pf.logf("INFO", "Finished copying remote->local (%d bytes) for %s", bytesCopied, localConn.RemoteAddr())
}() }()
go func() { go func() {
@ -87,15 +87,22 @@ func (pf *PortForwarder) handleConnection(localConn net.Conn, remoteAddr string)
bytesCopied, err := io.Copy(remoteConn, localConn) bytesCopied, err := io.Copy(remoteConn, localConn)
if err != nil { if err != nil {
} }
pf.logf("INFO: Finished copying local->remote (%d bytes) for %s", bytesCopied, localConn.RemoteAddr()) pf.logf("INFO", "Finished copying local->remote (%d bytes) for %s", bytesCopied, localConn.RemoteAddr())
}() }()
wg.Wait() wg.Wait()
pf.logf("INFO: Closing forwarded connection for %s", localConn.RemoteAddr()) pf.logf("INFO", "Closing forwarded connection for %s", localConn.RemoteAddr())
} }
func (pf *PortForwarder) logf(format string, v ...any) { func (pf *PortForwarder) logf(level, format string, v ...any) {
pf.logMutex.Lock() pf.logMutex.Lock()
defer pf.logMutex.Unlock() defer pf.logMutex.Unlock()
log.Printf(format, v...) switch level {
case "INFO":
slog.Info(format, v...)
case "WARN":
slog.Warn(format, v...)
case "ERROR":
slog.Error(format, v...)
}
} }

View file

@ -1,18 +1,20 @@
package main package main
import ( import (
"log" "fmt"
"log/slog"
"os" "os"
) )
func main() { func main() {
app, err := NewApp() app, err := NewApp()
if err != nil { if err != nil {
log.Fatalf("ERROR: Unable to setup application: %v", err) slog.Error(fmt.Sprintf("Unable to setup application: %v", err))
os.Exit(1)
} }
defer func() { defer func() {
if err := app.Close(); err != nil { if err := app.Close(); err != nil {
log.Printf("ERROR: Failed to close application resources: %v", err) slog.Error(fmt.Sprintf("Failed to close application resources: %v", err))
} }
}() }()

4
ssh.go
View file

@ -1,7 +1,7 @@
package main package main
import ( import (
"log" "log/slog"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -12,7 +12,7 @@ type SSHConnection struct {
func (s *SSHConnection) Close() error { func (s *SSHConnection) Close() error {
if s.client != nil { if s.client != nil {
log.Println("DEBUG: Closing SSH client connection.") slog.Debug("Closing SSH client connection.")
return s.client.Close() return s.client.Close()
} }
return nil return nil

View file

@ -3,7 +3,7 @@ package main
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"log" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -37,7 +37,7 @@ func NewTimeStore(cfg Config) (*TimeStore, error) {
return nil, fmt.Errorf("could not determine database path: %w", err) return nil, fmt.Errorf("could not determine database path: %w", err)
} }
log.Printf("INFO: Using database at: %s", dbPath) slog.Info(fmt.Sprintf("Using database at: %s", dbPath))
db, err := sql.Open("sqlite", fmt.Sprintf("%s?_pragma=journal_mode(WAL)", dbPath)) db, err := sql.Open("sqlite", fmt.Sprintf("%s?_pragma=journal_mode(WAL)", dbPath))
if err != nil { if err != nil {
@ -66,7 +66,7 @@ func NewTimeStore(cfg Config) (*TimeStore, error) {
createIndexSQL := `CREATE INDEX IF NOT EXISTS idx_time_entries_start_time ON time_entries (start_time);` createIndexSQL := `CREATE INDEX IF NOT EXISTS idx_time_entries_start_time ON time_entries (start_time);`
if _, err = db.Exec(createIndexSQL); err != nil { if _, err = db.Exec(createIndexSQL); err != nil {
log.Printf("WARN: Failed to create index on start_time: %v", err) slog.Warn(fmt.Sprintf("Failed to create index on start_time: %v", err))
} }
return &TimeStore{db: db, dbPath: dbPath}, nil return &TimeStore{db: db, dbPath: dbPath}, nil
@ -80,7 +80,7 @@ func ensureDatabasePath(_ Config) (string, error) {
workConfigDir := filepath.Join(configDir, "work") workConfigDir := filepath.Join(configDir, "work")
dbPath := filepath.Join(workConfigDir, "worktime.sqlite") dbPath := filepath.Join(workConfigDir, "worktime.sqlite")
if err := os.MkdirAll(workConfigDir, 0750); err != nil { if err := os.MkdirAll(workConfigDir, 0o750); err != nil {
return "", fmt.Errorf("failed to create config directory '%s': %w", workConfigDir, err) return "", fmt.Errorf("failed to create config directory '%s': %w", workConfigDir, err)
} }
@ -89,7 +89,7 @@ func ensureDatabasePath(_ Config) (string, error) {
func (ts *TimeStore) Close() error { func (ts *TimeStore) Close() error {
if ts.db != nil { if ts.db != nil {
log.Printf("INFO: Closing database connection to %s", ts.dbPath) slog.Info(fmt.Sprintf("Closing database connection to %s", ts.dbPath))
return ts.db.Close() return ts.db.Close()
} }
return nil return nil
@ -108,7 +108,7 @@ func (ts *TimeStore) stopCurrentEntry(now time.Time) (bool, error) {
} }
if rowsAffected > 1 { if rowsAffected > 1 {
log.Printf("WARN: Stopped %d entries. Expected 0 or 1. Manual DB check might be needed.", rowsAffected) slog.Warn(fmt.Sprintf("Stopped %d entries. Expected 0 or 1. Manual DB check might be needed.", rowsAffected))
} }
return rowsAffected > 0, nil return rowsAffected > 0, nil
} }
@ -124,7 +124,7 @@ func (ts *TimeStore) StartTracking(tag string) error {
return err return err
} }
if stopped { if stopped {
log.Println("INFO: Stopped previous time entry.") slog.Info("Stopped previous time entry.")
} }
query := `INSERT INTO time_entries (tag, start_time, end_time) VALUES (?, ?, NULL);` query := `INSERT INTO time_entries (tag, start_time, end_time) VALUES (?, ?, NULL);`
@ -132,7 +132,7 @@ func (ts *TimeStore) StartTracking(tag string) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to start tracking tag '%s': %w", tag, err) return fmt.Errorf("failed to start tracking tag '%s': %w", tag, err)
} }
log.Printf("INFO: Started tracking: %s at %s", tag, now.Format(time.RFC3339)) slog.Info(fmt.Sprintf("Started tracking: %s at %s", tag, now.Format(time.RFC3339)))
return nil return nil
} }
@ -143,9 +143,9 @@ func (ts *TimeStore) StopTracking() error {
return err return err
} }
if stopped { if stopped {
log.Printf("INFO: Stopped tracking at %s", now.Format(time.RFC3339)) slog.Info(fmt.Sprintf("Stopped tracking at %s", now.Format(time.RFC3339)))
} else { } else {
log.Println("INFO: No active time entry found to stop.") slog.Info("No active time entry found to stop.")
} }
return nil return nil
} }
@ -273,7 +273,7 @@ func getTimeRangeFromPeriod(period string) (time.Time, time.Time) {
end := start.AddDate(0, 0, 1) end := start.AddDate(0, 0, 1)
return start, end return start, end
} }
log.Printf("WARN: Unrecognized period string '%s'. Cannot calculate time range.", period) slog.Warn(fmt.Sprintf("Unrecognized period string '%s'. Cannot calculate time range.", period))
return time.Time{}, time.Time{} return time.Time{}, time.Time{}
} }
} }
@ -329,14 +329,14 @@ func (ts *TimeStore) ShowSummary(period string) error {
} }
func (ts *TimeStore) ExportSummary(filename string) error { func (ts *TimeStore) ExportSummary(filename string) error {
log.Printf("INFO: Starting export to '%s'...", filename) slog.Info(fmt.Sprintf("Starting export to '%s'...", filename))
currentYear := time.Now().Year() currentYear := time.Now().Year()
location := time.Local location := time.Local
yearStart := time.Date(currentYear, 1, 1, 0, 0, 0, 0, location) yearStart := time.Date(currentYear, 1, 1, 0, 0, 0, 0, location)
yearEnd := yearStart.AddDate(1, 0, 0) yearEnd := yearStart.AddDate(1, 0, 0)
log.Printf("INFO: Exporting data for year %d (%s to %s)", currentYear, yearStart.Format("2006-01-02"), yearEnd.Format("2006-01-02")) slog.Info(fmt.Sprintf("Exporting data for year %d (%s to %s)", currentYear, yearStart.Format("2006-01-02"), yearEnd.Format("2006-01-02")))
query := ` query := `
SELECT id, tag, start_time, end_time SELECT id, tag, start_time, end_time
@ -362,7 +362,7 @@ func (ts *TimeStore) ExportSummary(filename string) error {
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
return fmt.Errorf("error during export row iteration: %w", err) return fmt.Errorf("error during export row iteration: %w", err)
} }
log.Printf("INFO: Found %d potentially relevant time entries for year %d.", len(entries), currentYear) slog.Info(fmt.Sprintf("Found %d potentially relevant time entries for year %d.", len(entries), currentYear))
dailySummaries, err := aggregateEntriesToDailySummaries(entries, yearStart, yearEnd) dailySummaries, err := aggregateEntriesToDailySummaries(entries, yearStart, yearEnd)
if err != nil { if err != nil {
@ -372,17 +372,17 @@ func (ts *TimeStore) ExportSummary(filename string) error {
excelEntries := convertDailyToExcelEntries(dailySummaries) excelEntries := convertDailyToExcelEntries(dailySummaries)
if len(excelEntries) == 0 { if len(excelEntries) == 0 {
log.Println("WARN: No daily summaries generated for the export period.") slog.Warn("No daily summaries generated for the export period.")
fmt.Println("No data available to generate the export for the specified period.") fmt.Println("No data available to generate the export for the specified period.")
return nil return nil
} }
log.Printf("INFO: Generated %d daily entries for the Excel export.", len(excelEntries)) slog.Info(fmt.Sprintf("Generated %d daily entries for the Excel export.", len(excelEntries)))
if err := writeExcelSheet(excelEntries, filename); err != nil { // Aufruf der geänderten Funktion if err := writeExcelSheet(excelEntries, filename); err != nil { // Aufruf der geänderten Funktion
return fmt.Errorf("failed to write excel sheet '%s': %w", filename, err) return fmt.Errorf("failed to write excel sheet '%s': %w", filename, err)
} }
log.Printf("INFO: Successfully exported timetable to %s", filename) slog.Info(fmt.Sprintf("Successfully exported timetable to %s", filename))
fmt.Printf("Successfully exported timetable to %s\n", filename) fmt.Printf("Successfully exported timetable to %s\n", filename)
return nil return nil
} }
@ -399,16 +399,16 @@ func (ts *TimeStore) LogFullDay(tag string, date time.Time) error {
dayEnd := dayStart.Add(24 * time.Hour) dayEnd := dayStart.Add(24 * time.Hour)
dayStr := dayStart.Format("2006-01-02") dayStr := dayStart.Format("2006-01-02")
log.Printf("INFO: Attempting to log '%s' for the full day %s", tag, dayStr) slog.Info(fmt.Sprintf("Attempting to log '%s' for the full day %s", tag, dayStr))
// 1. Stoppe den aktuell laufenden Timer (falls vorhanden) // 1. Stoppe den aktuell laufenden Timer (falls vorhanden)
// Wir verwenden dayStart als Zeitpunkt für das Stoppen, um Konsistenz zu wahren // Wir verwenden dayStart als Zeitpunkt für das Stoppen, um Konsistenz zu wahren
stopped, err := ts.stopCurrentEntry(dayStart) stopped, err := ts.stopCurrentEntry(dayStart)
if err != nil { if err != nil {
// Nur loggen, weitermachen. Der Nutzer will diesen Tag ja explizit setzen. // Nur loggen, weitermachen. Der Nutzer will diesen Tag ja explizit setzen.
log.Printf("WARN: Failed to stop current entry before logging full day '%s': %v", tag, err) slog.Warn(fmt.Sprintf("Failed to stop current entry before logging full day '%s': %v", tag, err))
} else if stopped { } else if stopped {
log.Printf("INFO: Stopped active timer before logging '%s' for %s.", tag, dayStr) slog.Info(fmt.Sprintf("Stopped active timer before logging '%s' for %s.", tag, dayStr))
} }
tx, err := ts.db.Begin() tx, err := ts.db.Begin()
@ -436,7 +436,7 @@ func (ts *TimeStore) LogFullDay(tag string, date time.Time) error {
} }
titleCaser := cases.Title(language.English) titleCaser := cases.Title(language.English)
log.Printf("INFO: Successfully logged full day entry: Tag='%s', Start='%s', End='%s'", tag, dayStart.Format(time.RFC3339), dayEnd.Format(time.RFC3339)) slog.Info(fmt.Sprintf("Successfully logged full day entry: Tag='%s', Start='%s', End='%s'", tag, dayStart.Format(time.RFC3339), dayEnd.Format(time.RFC3339)))
fmt.Printf("Successfully logged '%s' for %s.\n", titleCaser.String(tag), dayStr) // Benutzerfeedback fmt.Printf("Successfully logged '%s' for %s.\n", titleCaser.String(tag), dayStr) // Benutzerfeedback
return nil return nil
} }