Add structured daemon lifecycle logs

VM start, image build, and network/setup failures were hard to diagnose because bangerd emitted almost no lifecycle logs and the Firecracker SDK logger was discarded. This adds a daemon-wide JSON logger with configurable log level so failures leave breadcrumbs instead of only side effects.

Log the main daemon and VM lifecycle stages, preserve raw Firecracker and image-build helper output in dedicated files, and include those log paths in daemon status and returned errors. Bridge SDK logrus output into the daemon logger at debug level so low-level Firecracker diagnostics are available without making normal info logs unreadable.

Validation: go test ./... and make build. Left unrelated worktree changes out of this commit, including internal/api/types.go, the deleted shell scripts, and my-rootfs.ext4.
This commit is contained in:
Thales Maciel 2026-03-16 16:16:28 -03:00
parent 5018bc6170
commit 644e60d739
No known key found for this signature in database
GPG key ID: 33112E6833C34679
13 changed files with 746 additions and 31 deletions

View file

@ -61,10 +61,10 @@ func newDaemonCommand() *cobra.Command {
}
ping, pingErr := rpc.Call[api.PingResult](cmd.Context(), layout.SocketPath, "ping", api.Empty{})
if pingErr != nil {
_, err = fmt.Fprintf(cmd.OutOrStdout(), "stopped\nsocket: %s\n", layout.SocketPath)
_, err = fmt.Fprintf(cmd.OutOrStdout(), "stopped\nsocket: %s\nlog: %s\n", layout.SocketPath, layout.DaemonLog)
return err
}
_, err = fmt.Fprintf(cmd.OutOrStdout(), "running\npid: %d\nsocket: %s\n", ping.PID, layout.SocketPath)
_, err = fmt.Fprintf(cmd.OutOrStdout(), "running\npid: %d\nsocket: %s\nlog: %s\n", ping.PID, layout.SocketPath, layout.DaemonLog)
return err
},
},

View file

@ -1,6 +1,7 @@
package cli
import (
"bytes"
"os"
"path/filepath"
"reflect"
@ -164,6 +165,32 @@ func TestDaemonOutdated(t *testing.T) {
}
}
func TestDaemonStatusIncludesLogPathWhenStopped(t *testing.T) {
configHome := filepath.Join(t.TempDir(), "config")
stateHome := filepath.Join(t.TempDir(), "state")
runtimeHome := filepath.Join(t.TempDir(), "runtime")
t.Setenv("XDG_CONFIG_HOME", configHome)
t.Setenv("XDG_STATE_HOME", stateHome)
t.Setenv("XDG_RUNTIME_DIR", runtimeHome)
cmd := NewBangerCommand()
var stdout bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stdout)
cmd.SetArgs([]string{"daemon", "status"})
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute: %v", err)
}
output := stdout.String()
if !strings.Contains(output, "stopped\n") {
t.Fatalf("output = %q, want stopped status", output)
}
if !strings.Contains(output, "log: "+filepath.Join(stateHome, "banger", "bangerd.log")) {
t.Fatalf("output = %q, want daemon log path", output)
}
}
func TestAbsolutizeImageBuildPaths(t *testing.T) {
dir := t.TempDir()
prev, err := os.Getwd()