Repository: skupperproject/skupper
Pull request #1758 (2024-10-30 18:40:52)

Enhanced logging and error messages for nonkube bootstrap


Patch:
diff --git a/pkg/nonkube/bundle/site_state_renderer.go b/pkg/nonkube/bundle/site_state_renderer.go
index 5cca695b..0f8e3464 100644
--- a/pkg/nonkube/bundle/site_state_renderer.go
+++ b/pkg/nonkube/bundle/site_state_renderer.go
@@ -2,7 +2,9 @@ package bundle
 
 import (
 	_ "embed"
+	"encoding/json"
 	"fmt"
+	"log/slog"
 	"os"
 	"path"
 
@@ -126,12 +128,24 @@ func (s *SiteStateRenderer) prepareContainers() error {
 		//      validate whether CPU and memory thresholds can be
 		//      set to the container
 	}
+	logger := common.NewLogger()
+	if logger.Enabled(nil, slog.LevelDebug) {
+		for name, newContainer := range s.containers {
+			containerJson, _ := json.Marshal(newContainer)
+			logger.Debug("container prepared:",
+				slog.String("name", name),
+				slog.String("container", string(containerJson)),
+			)
+		}
+	}
 	return nil
 }
 
 func (s *SiteStateRenderer) createContainerScript() error {
+	logger := common.NewLogger()
 	scriptsPath := api.GetInternalBundleOutputPath(s.siteState.Site.Namespace, api.RuntimeScriptsPath)
 	scriptContent := containersToShell(s.containers)
+	logger.Debug("writing containers_create.sh", slog.String("path", scriptsPath))
 	err := os.WriteFile(path.Join(scriptsPath, "containers_create.sh"), scriptContent, 0755)
 	if err != nil {
 		return fmt.Errorf("failed to create containers script: %v", err)
@@ -140,6 +154,7 @@ func (s *SiteStateRenderer) createContainerScript() error {
 }
 
 func (s *SiteStateRenderer) createBundle() error {
+	var logger = common.NewLogger()
 	bundlesHomeDir := api.GetDefaultOutputBundlesPath()
 	siteHomeDir := api.GetDefaultBundleOutputPath(s.siteState.Site.Namespace)
 	tarball := utils.NewTarball()
@@ -162,6 +177,7 @@ func (s *SiteStateRenderer) createBundle() error {
 			OutputPath: bundlesHomeDir,
 		}
 	}
+	logger.Debug("generating bundle:", slog.String("path", bundlesHomeDir), slog.String("site", s.siteState.Site.Name))
 	err = generator.Generate(tarball, string(s.Platform))
 	if err != nil {
 		return fmt.Errorf("failed to generate site bundle (%q): %v", s.siteState.Site.Name, err)
@@ -170,7 +186,9 @@ func (s *SiteStateRenderer) createBundle() error {
 }
 
 func (s *SiteStateRenderer) removeSiteFiles() error {
+	logger := common.NewLogger()
 	siteHomeDir := api.GetDefaultBundleOutputPath(s.siteState.Site.Namespace)
+	logger.Debug("removing temporary bundle home", slog.String("path", siteHomeDir))
 	err := os.RemoveAll(siteHomeDir)
 	if err != nil {
 		return fmt.Errorf("file to remove temporary site directory %q: %v", siteHomeDir, err)
@@ -179,7 +197,9 @@ func (s *SiteStateRenderer) removeSiteFiles() error {
 }
 
 func (s *SiteStateRenderer) createFreePortScript() error {
+	logger := common.NewLogger()
 	scriptsPath := api.GetInternalBundleOutputPath(s.siteState.Site.Namespace, api.RuntimeScriptsPath)
+	logger.Debug("writing freeport.sh", slog.String("path", scriptsPath))
 	err := os.WriteFile(path.Join(scriptsPath, "router_free_port.py"), []byte(FreePortScript), 0755)
 	if err != nil {
 		return fmt.Errorf("failed to create router_free_port.py script: %v", err)
diff --git a/pkg/nonkube/bundle/systemd.go b/pkg/nonkube/bundle/systemd.go
index 1a072616..74defad0 100644
--- a/pkg/nonkube/bundle/systemd.go
+++ b/pkg/nonkube/bundle/systemd.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	_ "embed"
 	"fmt"
+	"log/slog"
 	"os"
 	"path"
 	"text/template"
@@ -15,6 +16,7 @@ import (
 
 func CreateSystemdServices(siteState *api.SiteState) error {
 	var err error
+	var logger = common.NewLogger()
 	serviceTemplates := map[string]string{
 		"systemd":   common.SystemdServiceTemplate,
 		"container": common.SystemdContainerServiceTemplate,
@@ -36,6 +38,7 @@ func CreateSystemdServices(siteState *api.SiteState) error {
 			return fmt.Errorf("failed to execute %s service template: %w", platform, err)
 		}
 		serviceFile := path.Join(scriptsPath, fmt.Sprintf("skupper.service.%s", platform))
+		logger.Debug("writing systemd service file", slog.String("path", serviceFile))
 		err = os.WriteFile(serviceFile, buf.Bytes(), 0644)
 		if err != nil {
 			return fmt.Errorf("failed to write %s service file: %w", platform, err)
diff --git a/pkg/nonkube/common/claim_redeemer.go b/pkg/nonkube/common/claim_redeemer.go
index c1225661..e7de0d09 100644
--- a/pkg/nonkube/common/claim_redeemer.go
+++ b/pkg/nonkube/common/claim_redeemer.go
@@ -6,6 +6,7 @@ import (
 	"crypto/x509"
 	"fmt"
 	"io"
+	"log/slog"
 	"net/http"
 	"strings"
 	"time"
@@ -21,11 +22,15 @@ import (
 func RedeemClaims(siteState *api.SiteState) error {
 	var errs []error
 
+	logger := NewLogger()
 	for name, claim := range siteState.Claims {
 		err := redeemAccessToken(claim, siteState)
 		if err != nil {
 			errs = append(errs, fmt.Errorf("failed to redeem claim %s: %w", name, err))
-			fmt.Printf("RedeemClaims: failed to redeem claim: %s: %s\n", name, err)
+			logger.Error("RedeemClaims: failed to redeem claim",
+				slog.String("name", name),
+				slog.String("error", err.Error()),
+			)
 		}
 	}
 
diff --git a/pkg/nonkube/common/fs_config_renderer.go b/pkg/nonkube/common/fs_config_renderer.go
index bd609d42..bc20108d 100644
--- a/pkg/nonkube/common/fs_config_renderer.go
+++ b/pkg/nonkube/common/fs_config_renderer.go
@@ -3,7 +3,7 @@ package common
 import (
 	"errors"
 	"fmt"
-	"log"
+	"log/slog"
 	"os"
 	"path"
 	"strconv"
@@ -67,6 +67,7 @@ func NewFileSystemConfigurationRenderer(outputPath string) *FileSystemConfigurat
 // Render simply renders the given site state as configuration files.
 func (c *FileSystemConfigurationRenderer) Render(siteState *api.SiteState) error {
 	var err error
+	logger := NewLogger()
 	outputPath := c.GetOutputPath(siteState)
 	if c.SslProfileBasePath == "" {
 		c.SslProfileBasePath = DefaultSslProfileBasePath
@@ -117,7 +118,9 @@ func (c *FileSystemConfigurationRenderer) Render(siteState *api.SiteState) error
 	// Saving runtime platform
 	if !c.Bundle {
 		content := fmt.Sprintf("platform: %s\n", c.Platform)
-		err = os.WriteFile(path.Join(outputPath, string(api.RuntimeSiteStatePath), "platform.yaml"), []byte(content), 0644)
+		platformPath := path.Join(outputPath, string(api.RuntimeSiteStatePath), "platform.yaml")
+		logger.Debug("writing platform", slog.String("platform", c.Platform), slog.String("path", platformPath))
+		err = os.WriteFile(platformPath, []byte(content), 0644)
 		if err != nil {
 			return fmt.Errorf("failed to write runtime platform: %w", err)
 		}
@@ -127,6 +130,11 @@ func (c *FileSystemConfigurationRenderer) Render(siteState *api.SiteState) error
 	for raName, ra := range siteState.RouterAccesses {
 		for _, role := range ra.Spec.Roles {
 			if role.Name != "normal" {
+				logger.Debug("site endpoint configured:",
+					slog.String("host", ra.Spec.BindHost),
+					slog.String("role", role.Name),
+					slog.Int("port", role.Port),
+				)
 				endpoints = append(endpoints, v2alpha1.Endpoint{
 					Name: fmt.Sprintf("%s-%s", raName, role.Name),
 					Host: ra.Spec.BindHost,
@@ -162,23 +170,27 @@ func (c *FileSystemConfigurationRenderer) GetInputPath(siteState *api.SiteState)
 }
 
 func (c *FileSystemConfigurationRenderer) MarshalSiteStates(loadedSiteState, runtimeSiteState *api.SiteState) error {
+	logger := NewLogger()
 	if loadedSiteState != nil {
 		outputPath := c.GetOutputPath(loadedSiteState)
 		sourcesPath := path.Join(outputPath, string(api.LoadedSiteStatePath))
 		inputSourcesPath := path.Join(outputPath, string(api.InputSiteStatePath))
-		existingSources, _ := new(utils.DirectoryReader).ReadDir(inputSourcesPath, nil)
+		existingLoadedSources, _ := new(utils.DirectoryReader).ReadDir(sourcesPath, nil)
+		existingInputSources, _ := new(utils.DirectoryReader).ReadDir(inputSourcesPath, nil)
 		// when sources are already defined, we back them up
-		if len(existingSources) > 0 {
+		if len(existingLoadedSources) > 0 {
 			tb := utils.NewTarball()
-			err := tb.AddFiles(inputSourcesPath)
+			err := tb.AddFiles(sourcesPath)
 			if err != nil {
 				return fmt.Errorf("unable to backup existing sources: %s", err)
 			}
-			tbFile := path.Join(outputPath, "input", "sources.backup.tar.gz")
+			tbFile := path.Join(outputPath, "sources.backup.tar.gz")
+			logger.Debug("saving loaded state sources", slog.String("path", tbFile))
 			err = tb.Save(tbFile)
 			if err != nil {
 				return fmt.Errorf("unable to backup sources.backup.tar.gz: %s", err)
 			}
+			logger.Debug("clean up previous loaded state", slog.String("path", sourcesPath))
 			err = os.RemoveAll(sourcesPath)
 			if err != nil {
 				return fmt.Errorf("unable to remove former loaded state: %s", err)
@@ -186,17 +198,21 @@ func (c *FileSystemConfigurationRenderer) MarshalSiteStates(loadedSiteState, run
 			if err = os.Mkdir(sourcesPath, 0755); err != nil {
 				return fmt.Errorf("unable to recreate sources directory %s: %s", sourcesPath, err)
 			}
-		} else {
+		}
+		if len(existingInputSources) == 0 {
+			logger.Debug("creating input sources", slog.String("path", inputSourcesPath))
 			if err := api.MarshalSiteState(*loadedSiteState, inputSourcesPath); err != nil {
 				return err
 			}
 		}
+		logger.Debug("saving loaded state", slog.String("path", sourcesPath))
 		if err := api.MarshalSiteState(*loadedSiteState, sourcesPath); err != nil {
 			return err
 		}
 	}
 	outputPath := c.GetOutputPath(runtimeSiteState)
 	runtimeStatePath := path.Join(outputPath, string(api.RuntimeSiteStatePath))
+	logger.Debug("saving runtime state", slog.String("path", runtimeStatePath))
 	if err := api.MarshalSiteState(*runtimeSiteState, runtimeStatePath); err != nil {
 		return err
 	}
@@ -204,9 +220,12 @@ func (c *FileSystemConfigurationRenderer) MarshalSiteStates(loadedSiteState, run
 }
 
 func CleanupNamespaceForReload(namespace string) error {
+	logger := NewLogger()
+	logger.Debug("cleaning up namespace for reload")
 	// Re-create internal configuration directories
 	for _, dir := range reloadDirectories {
 		outputPath := api.GetInternalOutputPath(namespace, dir)
+		logger.Debug("recreating directory:", slog.String("path", outputPath))
 		if err := os.RemoveAll(outputPath); err != nil {
 			return fmt.Errorf("failed to remove directory %s: %w", outputPath, err)
 		}
@@ -238,6 +257,7 @@ func RestoreNamespaceData(data []byte) error {
 }
 
 func (c *FileSystemConfigurationRenderer) createTokens(siteState *api.SiteState) error {
+	logger := NewLogger()
 	tokens := make([]*api.Token, 0)
 	for name, linkAccess := range siteState.RouterAccesses {
 		noInterRouterRole := linkAccess.FindRole("inter-router") == nil
@@ -270,10 +290,11 @@ func (c *FileSystemConfigurationRenderer) createTokens(siteState *api.SiteState)
 		tokenPath := path.Join(outputPath, string(api.RuntimeTokenPath), tokenFileName)
 		tokenYaml, err := token.Marshal()
 		if err != nil {
-			return fmt.Errorf("unable to marshal token: %v", err)
+			return fmt.Errorf("unable to marshal static link: %v", err)
 		}
+		logger.Debug("writing static link", slog.String("path", tokenPath))
 		if err := os.WriteFile(tokenPath, tokenYaml, 0644); err != nil {
-			return fmt.Errorf("unable to create token file %s: %v", tokenPath, err)
+			return fmt.Errorf("unable to create static link file %s: %v", tokenPath, err)
 		}
 	}
 	return nil
@@ -289,6 +310,7 @@ func (c *FileSystemConfigurationRenderer) createRouterConfig(siteState *api.Site
 	}
 	outputPath := c.GetOutputPath(siteState)
 	routerConfigFileName := path.Join(outputPath, string(api.ConfigRouterPath), "skrouterd.json")
+	NewLogger().Debug("Writing router configuration", slog.String("path", routerConfigFileName))
 	err = os.WriteFile(routerConfigFileName, []byte(routerConfigJson), 0644)
 	if err != nil {
 		return fmt.Errorf("unable to write router config file: %v", err)
@@ -298,6 +320,7 @@ func (c *FileSystemConfigurationRenderer) createRouterConfig(siteState *api.Site
 
 func (c *FileSystemConfigurationRenderer) createTlsCertificates(siteState *api.SiteState) error {
 	var err error
+	var logger = NewLogger()
 	writeSecretFilesIgnore := func(basePath string, secret *corev1.Secret, ignoreExisting bool) error {
 		baseDir, err := os.Open(basePath)
 		if err != nil {
@@ -323,10 +346,11 @@ func (c *FileSystemConfigurationRenderer) createTlsCertificates(siteState *api.S
 				// ignoring existing certificate
 				_ = certFile.Close()
 				if ignoreExisting {
-					log.Printf("warning: %s will not be overwritten", certFileName)
+					logger.Warn("certificate will not be overwritten", slog.String("path", certFileName))
 					continue
 				}
 			}
+			logger.Debug("writing certificate", slog.String("path", certFileName))
 			err = os.WriteFile(certFileName, data, 0640)
 			if err != nil {
 				return fmt.Errorf("error writing %s: %v", certFileName, err)
diff --git a/pkg/nonkube/common/log.go b/pkg/nonkube/common/log.go
new file mode 100644
index 00000000..66357a89
--- /dev/null
+++ b/pkg/nonkube/common/log.go
@@ -0,0 +1,10 @@
+package common
+
+import (
+	"log/slog"
+)
+
+func NewLogger() *slog.Logger {
+	slog.SetLogLoggerLevel(slog.LevelInfo)
+	return slog.New(slog.Default().Handler())
+}
diff --git a/pkg/nonkube/common/site_state_renderer_common.go b/pkg/nonkube/common/site_state_renderer_common.go
index f549f6f6..543c407e 100644
--- a/pkg/nonkube/common/site_state_renderer_common.go
+++ b/pkg/nonkube/common/site_state_renderer_common.go
@@ -64,6 +64,8 @@ func copySiteStateMap[T any](m map[string]T) map[string]T {
 
 func CreateRouterAccess(siteState *api.SiteState) error {
 	if !siteState.HasRouterAccess() {
+		logger := NewLogger()
+		logger.Debug("Creating skupper-local RouterAccess")
 		name := fmt.Sprintf("skupper-local")
 		var port = 5671
 		var err error
@@ -72,6 +74,7 @@ func CreateRouterAccess(siteState *api.SiteState) error {
 			if err != nil {
 				return err
 			}
+			logger.Debug("Free TCP port discovered", "port", port)
 		}
 		siteState.CreateRouterAccess(name, port)
 	}
diff --git a/pkg/nonkube/common/site_state_validator.go b/pkg/nonkube/common/site_state_validator.go
index 4ee60996..92980b31 100644
--- a/pkg/nonkube/common/site_state_validator.go
+++ b/pkg/nonkube/common/site_state_validator.go
@@ -79,7 +79,7 @@ func (s *SiteStateValidator) validateRouterAccesses(routerAccesses map[string]*v
 			}
 		}
 		if len(routerAccess.Spec.Roles) == 0 {
-			return fmt.Errorf("invalid router access: roles are required")
+			return fmt.Errorf("invalid router access: %s - roles are required", routerAccess.Name)
 		}
 		for _, role := range routerAccess.Spec.Roles {
 			if !utils.StringSliceContains(validLinkAccessRoles, role.Name) {
@@ -135,12 +135,12 @@ func (s *SiteStateValidator) validateListeners(listeners map[string]*v2alpha1.Li
 			return fmt.Errorf("invalid listener name: %w", err)
 		}
 		if listener.Spec.Host == "" || listener.Spec.Port == 0 {
-			return fmt.Errorf("host and port are required")
+			return fmt.Errorf("invalid listener: %s - host and port are required", listener.Name)
 		}
 		ip := net.ParseIP(listener.Spec.Host)
 		validHostname := hostnameRfc1123Regex.MatchString(listener.Spec.Host)
 		if ip == nil && !validHostname {
-			return fmt.Errorf("invalid listener host: %s - a valid IP address or hostname is expected", listener.Spec.Host)
+			return fmt.Errorf("invalid listener host: %s - a valid IP address or hostname is expected (listener: %q)", listener.Spec.Host, name)
 		}
 		if utils.IntSliceContains(hostPorts[listener.Spec.Host], listener.Spec.Port) {
 			return fmt.Errorf("port %d is already mapped for host %q (listener: %q)", listener.Spec.Port, listener.Spec.Host, name)
@@ -159,12 +159,12 @@ func (s *SiteStateValidator) validateConnectors(connectors map[string]*v2alpha1.
 			return fmt.Errorf("invalid connector name: %w", err)
 		}
 		if connector.Spec.Host == "" || connector.Spec.Port == 0 {
-			return fmt.Errorf("connector host and port are required")
+			return fmt.Errorf("connector host and port are required (connector: %q)", connector.Name)
 		}
 		ip := net.ParseIP(connector.Spec.Host)
 		validHostname := hostnameRfc1123Regex.MatchString(connector.Spec.Host)
 		if ip == nil && !validHostname {
-			return fmt.Errorf("invalid connector host: %s - a valid IP address or hostname is expected", connector.Spec.Host)
+			return fmt.Errorf("invalid connector host: %s - a valid IP address or hostname is expected (connector: %q)", connector.Spec.Host, connector.Name)
 		}
 		if connector.Spec.RoutingKey == "" {
 			return fmt.Errorf("routingKey is missing for connector: %s", connector.Name)
diff --git a/pkg/nonkube/common/startup.go b/pkg/nonkube/common/startup.go
index 8ba6293a..440e7327 100644
--- a/pkg/nonkube/common/startup.go
+++ b/pkg/nonkube/common/startup.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	_ "embed"
 	"fmt"
+	"log/slog"
 	"os"
 	"path"
 	"text/template"
@@ -72,12 +73,15 @@ func GetStartupScripts(args StartupScriptsArgs, pathProvider api.InternalPathPro
 func (s *startupScripts) Create() error {
 	var startBuf bytes.Buffer
 	var stopBuf bytes.Buffer
+	logger := NewLogger()
+	logger.Debug("creating startup scripts")
 
 	startTemplate := template.Must(template.New("start").Parse(s.StartScript))
 	if err := startTemplate.Execute(&startBuf, s); err != nil {
 		return fmt.Errorf("failed to create start script: %w", err)
 	}
 	startFileName := path.Join(s.path, s.GetStartFileName())
+	logger.Debug("writing start script", slog.String("path", startFileName))
 	err := os.WriteFile(startFileName, startBuf.Bytes(), 0755)
 	if err != nil {
 		return err
@@ -87,6 +91,7 @@ func (s *startupScripts) Create() error {
 		return fmt.Errorf("failed to create stop script: %w", err)
 	}
 	stopFileName := path.Join(s.path, s.GetStopFileName())
+	logger.Debug("writing stop script", slog.String("path", stopFileName))
 	err = os.WriteFile(stopFileName, stopBuf.Bytes(), 0755)
 	if err != nil {
 		return err
diff --git a/pkg/nonkube/common/systemd.go b/pkg/nonkube/common/systemd.go
index 2d6d0bd4..5caa64b3 100644
--- a/pkg/nonkube/common/systemd.go
+++ b/pkg/nonkube/common/systemd.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	_ "embed"
 	"fmt"
+	"log/slog"
 	"os"
 	"os/exec"
 	"path"
@@ -83,9 +84,11 @@ func (s *systemdServiceInfo) Create() error {
 		}
 		return fmt.Errorf(msg)
 	}
-
+	var logger = NewLogger()
+	logger.Debug("creating systemd service")
 	var buf = new(bytes.Buffer)
 	var service *template.Template
+	logger.Debug("using service template for:", slog.String("platform", s.platform))
 	if s.platform == string(types.PlatformSystemd) {
 		service = template.Must(template.New(s.GetServiceName()).Parse(SystemdServiceTemplate))
 	} else {
@@ -106,6 +109,7 @@ func (s *systemdServiceInfo) Create() error {
 
 	// Saving systemd user service
 	serviceName := s.GetServiceName()
+	logger.Debug("writing service file", slog.String("path", s.getServiceFile()))
 	err = os.WriteFile(s.getServiceFile(), buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("unable to write unit file (%s): %w", s.getServiceFile(), err)
@@ -113,6 +117,7 @@ func (s *systemdServiceInfo) Create() error {
 
 	// Only enable when running locally
 	if !api.IsRunningInContainer() {
+		logger.Debug("enabling systemd service", slog.String("name", serviceName))
 		return s.enableService(serviceName)
 	}
 
@@ -134,25 +139,32 @@ func (s *systemdServiceInfo) Remove() error {
 		return fmt.Errorf("SystemD is not enabled at user level")
 	}
 
+	logger := NewLogger()
+
 	// Stopping systemd user service
 	if !api.IsRunningInContainer() {
+		logger.Debug("stopping service", slog.String("name", s.GetServiceName()))
 		cmd := s.getCmdStopSystemdService(s.GetServiceName())
 		_ = cmd.Run()
 
 		// Disabling systemd user service
+		logger.Debug("disabling service", slog.String("name", s.GetServiceName()))
 		cmd = s.getCmdDisableSystemdService(s.GetServiceName())
 		_ = cmd.Run()
 	}
 
 	// Removing the .service file
+	logger.Debug("removing service", slog.String("path", s.getServiceFile()))
 	_ = os.Remove(s.getServiceFile())
 
 	// Reloading systemd user daemon
 	if !api.IsRunningInContainer() {
+		logger.Debug("reloading systemd daemon")
 		cmd := s.getCmdReloadSystemdDaemon()
 		_ = cmd.Run()
 
 		// Resetting failed status
+		logger.Debug("resetting failed systemd service", slog.String("name", s.GetServiceName()))
 		cmd = s.getCmdResetFailedSystemService(s.GetServiceName())
 		_ = cmd.Run()
 	}
diff --git a/pkg/nonkube/compat/site_state_renderer.go b/pkg/nonkube/compat/site_state_renderer.go
index 9b250bb3..25f0f114 100644
--- a/pkg/nonkube/compat/site_state_renderer.go
+++ b/pkg/nonkube/compat/site_state_renderer.go
@@ -2,7 +2,9 @@ package compat
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
+	"log/slog"
 	"os"
 	"path"
 	"time"
@@ -28,6 +30,7 @@ type SiteStateRenderer struct {
 
 func (s *SiteStateRenderer) Render(loadedSiteState *api.SiteState, reload bool) error {
 	var err error
+	var logger = common.NewLogger()
 	var validator api.SiteStateValidator = &common.SiteStateValidator{}
 	err = validator.Validate(loadedSiteState)
 	if err != nil {
@@ -47,29 +50,43 @@ func (s *SiteStateRenderer) Render(loadedSiteState *api.SiteState, reload bool)
 		// when reload is successful, backupData must be nil
 		if backupData == nil {
 			for _, temporaryName := range s.stoppedContainers {
+				logger.Debug("Removing temporary container ", slog.String("name", temporaryName))
 				err = s.cli.ContainerRemove(temporaryName)
 				if err != nil {
-					fmt.Printf("Failed to remove temporary container %s: %v\n", temporaryName, err)
+					logger.Error("Failed to remove temporary container",
+						slog.String("name", temporaryName),
+						slog.String("error", err.Error()),
+					)
 				}
 			}
 			return
 		}
-		fmt.Println("Bootstrap failed, restoring previous state")
+		logger.Error("Bootstrap failed, restoring namespace")
 		err := common.RestoreNamespaceData(backupData)
 		if err != nil {
-			fmt.Printf("Error restoring namespace data for %q - %s\n", loadedSiteState.GetNamespace(), err)
+			logger.Error("Error restoring namespace data:",
+				slog.String("namespace", loadedSiteState.GetNamespace()),
+				slog.String("error", err.Error()),
+			)
 			return
 		}
 		for originalName, temporaryName := range s.stoppedContainers {
 			if temporaryName != originalName {
 				err = s.cli.ContainerRename(temporaryName, originalName)
 				if err != nil {
-					fmt.Printf("Error restoring container name from %q to %q - %s\n", temporaryName, originalName, err)
+					logger.Error("Error restoring container:",
+						slog.String("temporary", temporaryName),
+						slog.String("name", originalName),
+						slog.String("error", err.Error()),
+					)
 				}
 			}
 			err = s.cli.ContainerStart(originalName)
 			if err != nil {
-				fmt.Printf("Error starting container %q - %s\n", originalName, err)
+				logger.Error("Error starting container",
+					slog.String("name", originalName),
+					slog.String("error", err.Error()),
+				)
 			}
 		}
 	}()
@@ -211,12 +228,27 @@ func (s *SiteStateRenderer) prepareContainers() error {
 		//      validate whether CPU and memory thresholds can be
 		//      set to the container
 	}
+	logger := common.NewLogger()
+	if logger.Enabled(nil, slog.LevelDebug) {
+		for name, newContainer := range s.containers {
+			containerJson, _ := json.Marshal(newContainer)
+			logger.Debug("container prepared:",
+				slog.String("name", name),
+				slog.String("container", string(containerJson)),
+			)
+		}
+	}
 	return nil
 }
 
 func (s *SiteStateRenderer) pullImages(ctx context.Context) error {
 	var err error
+	var logger = common.NewLogger()
 	for component, skupperContainer := range s.containers {
+		logger.Debug("pulling:",
+			slog.String("image", skupperContainer.Image),
+			slog.String("component", component),
+		)
 		err = s.cli.ImagePull(ctx, skupperContainer.Image)
 		if err != nil {
 			return fmt.Errorf("failed to pull %s image %s: %w", component, skupperContainer.Image, err)
@@ -245,7 +277,12 @@ func (s *SiteStateRenderer) createContainers() error {
 			return fmt.Errorf("container %s already exists (component: %s)", skupperContainer.Name, component)
 		}
 	}
+	logger := common.NewLogger()
 	for component, skupperContainer := range s.containers {
+		logger.Debug("creating container",
+			slog.String("component", component),
+			slog.String("name", skupperContainer.Name),
+		)
 		err = s.cli.ContainerCreate(&skupperContainer)
 		if err != nil {
 			return fmt.Errorf("failed to create %q container (%s): %w", component, skupperContainer.Name, err)
@@ -256,8 +293,13 @@ func (s *SiteStateRenderer) createContainers() error {
 
 func (s *SiteStateRenderer) startContainers() error {
 	var err error
+	var logger = common.NewLogger()
 	defer s.cleanupContainers(err)
 	for component, skupperContainer := range s.containers {
+		logger.Debug("starting container",
+			slog.String("component", component),
+			slog.String("name", skupperContainer.Name),
+		)
 		err = s.cli.ContainerStart(skupperContainer.Name)
 		if err != nil {
 			return fmt.Errorf("failed to start %s container %q: %w", component, skupperContainer.Name, err)
diff --git a/pkg/nonkube/systemd/site_state_renderer.go b/pkg/nonkube/systemd/site_state_renderer.go
index 6e0af91a..6f1d42f9 100644
--- a/pkg/nonkube/systemd/site_state_renderer.go
+++ b/pkg/nonkube/systemd/site_state_renderer.go
@@ -2,6 +2,7 @@ package systemd
 
 import (
 	"fmt"
+	"log/slog"
 	"os"
 
 	"github.com/skupperproject/skupper/api/types"
@@ -18,6 +19,7 @@ type SiteStateRenderer struct {
 
 func (s *SiteStateRenderer) Render(loadedSiteState *api.SiteState, reload bool) error {
 	var err error
+	var logger = common.NewLogger()
 	var validator api.SiteStateValidator = &common.SiteStateValidator{}
 	err = validator.Validate(loadedSiteState)
 	if err != nil {
@@ -34,15 +36,20 @@ func (s *SiteStateRenderer) Render(loadedSiteState *api.SiteState, reload bool)
 		if backupData == nil {
 			return
 		}
-		fmt.Println("Bootstrap failed, restoring previous state")
+		logger.Error("Bootstrap failed, restoring namespace")
 		err := common.RestoreNamespaceData(backupData)
 		if err != nil {
-			fmt.Printf("Error restoring namespace data for %q - %s\n", loadedSiteState.GetNamespace(), err)
+			logger.Error("Error restoring namespace data:",
+				slog.String("namespace", loadedSiteState.GetNamespace()),
+				slog.String("error", err.Error()),
+			)
 			return
 		}
 		err = s.createSystemdService()
 		if err != nil {
-			fmt.Printf("Error recovering systemd service info - %s\n", err)
+			logger.Error("Error recovering systemd service info:",
+				slog.String("error", err.Error()),
+			)
 		}
 	}()
 


Test patch:
diff --git a/pkg/nonkube/common/site_state_validator_test.go b/pkg/nonkube/common/site_state_validator_test.go
index 492b8ab4..bec91013 100644
--- a/pkg/nonkube/common/site_state_validator_test.go
+++ b/pkg/nonkube/common/site_state_validator_test.go
@@ -66,7 +66,7 @@ func TestSiteStateValidator_Validate(t *testing.T) {
 				}
 			}),
 			valid:         false,
-			errorContains: "invalid router access: roles are required",
+			errorContains: "invalid router access: link-access-one - roles are required",
 		},
 		{
 			info: "invalid-link-access-roles-invalid-role",