added more detailed debug logging
This commit is contained in:
@@ -178,9 +178,16 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
|
||||
}
|
||||
m.logger.Printf("[INFO] Deploying stack %s with %d env vars: [%s]", req.StackName, len(env), strings.Join(envKeys, ", "))
|
||||
|
||||
// Check which images are available locally before pulling
|
||||
if m.isDebug() {
|
||||
m.checkLocalImages(req.StackName, stackDir)
|
||||
}
|
||||
|
||||
// Run docker compose up -d
|
||||
start := time.Now()
|
||||
_, composeErr := m.composeExecWithEnv(stackDir, env, "up", "-d")
|
||||
if composeErr != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s deploy failed after %.1fs: %v", req.StackName, time.Since(start).Seconds(), composeErr)
|
||||
// Deployment failed — keep app.yaml for debugging but mark as not deployed
|
||||
appCfg.Deployed = false
|
||||
_ = SaveAppConfig(stackDir, appCfg)
|
||||
@@ -196,7 +203,12 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s deployed successfully", req.StackName)
|
||||
m.logger.Printf("[INFO] Stack %s deployed successfully (took %.1fs)", req.StackName, time.Since(start).Seconds())
|
||||
|
||||
// Post-deploy container state check (async, non-blocking)
|
||||
deployEnv := m.stackEnv(stackDir)
|
||||
m.logPostStartStatus(req.StackName, stackDir, deployEnv)
|
||||
|
||||
return deployWarning, m.RefreshStatus()
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,14 @@ func (m *Manager) ScanStacks() error {
|
||||
}
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Scanned stacks: %d found", len(m.stacks))
|
||||
deployedCount := 0
|
||||
for _, s := range m.stacks {
|
||||
if s.Deployed {
|
||||
deployedCount++
|
||||
}
|
||||
}
|
||||
m.logger.Printf("[INFO] Scanned stacks: %d found (%d deployed, %d available)",
|
||||
len(m.stacks), deployedCount, len(m.stacks)-deployedCount)
|
||||
return m.refreshStatusLocked()
|
||||
}
|
||||
|
||||
@@ -357,15 +364,18 @@ func (m *Manager) StartStack(name string) error {
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Starting stack: %s", name)
|
||||
start := time.Now()
|
||||
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
env := m.stackEnv(dir)
|
||||
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "up", "-d"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s start failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("starting stack %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s started", name)
|
||||
m.logger.Printf("[INFO] Stack %s started successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logPostStartStatus(name, dir, env)
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
|
||||
@@ -380,13 +390,15 @@ func (m *Manager) StopStack(name string) error {
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stopping stack: %s", name)
|
||||
start := time.Now()
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
|
||||
if _, err := m.composeExec(dir, "down"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s stop failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("stopping stack %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s stopped", name)
|
||||
m.logger.Printf("[INFO] Stack %s stopped successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
|
||||
@@ -397,13 +409,17 @@ func (m *Manager) RestartStack(name string) error {
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Restarting stack: %s", name)
|
||||
start := time.Now()
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
|
||||
if _, err := m.composeExec(dir, "restart"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s restart failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("restarting stack %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s restarted", name)
|
||||
m.logger.Printf("[INFO] Stack %s restarted successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
env := m.stackEnv(dir)
|
||||
m.logPostStartStatus(name, dir, env)
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
|
||||
@@ -414,18 +430,26 @@ func (m *Manager) UpdateStack(name string) error {
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Updating stack: %s", name)
|
||||
start := time.Now()
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
env := m.stackEnv(dir)
|
||||
|
||||
if m.isDebug() {
|
||||
m.checkLocalImages(name, dir)
|
||||
}
|
||||
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "pull"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s update (pull) failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("pulling images for %s: %w", name, err)
|
||||
}
|
||||
|
||||
if _, err := m.composeExecCustomEnv(dir, env, "up", "-d", "--remove-orphans"); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s update (up) failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
|
||||
return fmt.Errorf("recreating %s: %w", name, err)
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s updated", name)
|
||||
m.logger.Printf("[INFO] Stack %s updated successfully (took %.1fs)", name, time.Since(start).Seconds())
|
||||
m.logPostStartStatus(name, dir, env)
|
||||
return m.RefreshStatus()
|
||||
}
|
||||
|
||||
@@ -442,11 +466,20 @@ func (m *Manager) GetLogs(name string, lines int) (string, error) {
|
||||
lines = 1000
|
||||
}
|
||||
|
||||
m.logger.Printf("[DEBUG] Fetching logs for %s (tail %d)", name, lines)
|
||||
|
||||
dir := filepath.Dir(stack.ComposePath)
|
||||
output, err := m.composeExec(dir, "logs", "--tail", fmt.Sprintf("%d", lines), "--no-color")
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Failed to fetch logs for %s: %v", name, err)
|
||||
return "", fmt.Errorf("getting logs for %s: %w", name, err)
|
||||
}
|
||||
|
||||
if len(output) == 0 {
|
||||
m.logger.Printf("[DEBUG] Logs result for %s: 0 bytes returned (empty)", name)
|
||||
} else {
|
||||
m.logger.Printf("[DEBUG] Logs result for %s: %d bytes returned", name, len(output))
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -489,7 +522,30 @@ func (m *Manager) composeExecCustomEnv(dir string, env []string, args ...string)
|
||||
if env != nil {
|
||||
cmd.Env = env
|
||||
} else {
|
||||
cmd.Env = m.stackEnv(dir)
|
||||
env = m.stackEnv(dir)
|
||||
cmd.Env = env
|
||||
}
|
||||
|
||||
// Log env var keys at debug level
|
||||
if m.isDebug() {
|
||||
var appKeys []string
|
||||
sysCount := 0
|
||||
for _, e := range env {
|
||||
parts := strings.SplitN(e, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
key := parts[0]
|
||||
// Only log non-system env vars (skip PATH, HOME, etc.)
|
||||
if strings.ToUpper(key) == key && !strings.HasPrefix(key, "_") {
|
||||
appKeys = append(appKeys, key)
|
||||
} else {
|
||||
sysCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(appKeys) > 0 {
|
||||
m.logger.Printf("[DEBUG] Env vars for compose: [%s] (%d app + %d system)",
|
||||
strings.Join(appKeys, ", "), len(appKeys), sysCount)
|
||||
}
|
||||
}
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -498,10 +554,25 @@ func (m *Manager) composeExecCustomEnv(dir string, env []string, args ...string)
|
||||
|
||||
m.logger.Printf("[DEBUG] Running: %s %s (in %s)", m.composeCmd, strings.Join(args, " "), dir)
|
||||
|
||||
start := time.Now()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return stdout.String(), fmt.Errorf("%w\nstderr: %s", err, stderr.String())
|
||||
elapsed := time.Since(start)
|
||||
exitCode := -1
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
exitCode = exitErr.ExitCode()
|
||||
}
|
||||
m.logger.Printf("[ERROR] Command failed: %s %s (in %s) — exit code %d (took %.1fs)",
|
||||
m.composeCmd, strings.Join(args, " "), dir, exitCode, elapsed.Seconds())
|
||||
if stdoutStr := truncateStr(stdout.String(), 500); stdoutStr != "" {
|
||||
m.logger.Printf("[ERROR] stdout: %s", stdoutStr)
|
||||
}
|
||||
if stderrStr := truncateStr(stderr.String(), 500); stderrStr != "" {
|
||||
m.logger.Printf("[ERROR] stderr: %s", stderrStr)
|
||||
}
|
||||
return stdout.String(), fmt.Errorf("exit code %d\nstderr: %s", exitCode, truncateStr(stderr.String(), 500))
|
||||
}
|
||||
|
||||
m.logger.Printf("[DEBUG] Command completed: %s %s (took %.1fs)", m.composeCmd, strings.Join(args, " "), time.Since(start).Seconds())
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
@@ -519,6 +590,88 @@ func (m *Manager) execCommand(name string, args ...string) (string, error) {
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
// isDebug returns true if logging level is "debug".
|
||||
func (m *Manager) isDebug() bool {
|
||||
return m.cfg.Logging.Level == "debug"
|
||||
}
|
||||
|
||||
// truncateStr truncates a string to maxLen characters, appending "..." if truncated.
|
||||
func truncateStr(s string, maxLen int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
|
||||
// logPostStartStatus queries container states after a start/deploy operation
|
||||
// and logs them. This runs asynchronously to avoid blocking the HTTP response.
|
||||
func (m *Manager) logPostStartStatus(name, stackDir string, env []string) {
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
output, err := m.composeExecCustomEnv(stackDir, env, "ps", "-a", "--format", "table {{.Name}}\t{{.Image}}\t{{.State}}\t{{.Status}}")
|
||||
if err != nil {
|
||||
m.logger.Printf("[WARN] Post-start status check failed for %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(lines) <= 1 {
|
||||
m.logger.Printf("[WARN] Post-start status for %s: no containers found", name)
|
||||
return
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Stack %s post-start status:", name)
|
||||
// Skip header line
|
||||
for _, line := range lines[1:] {
|
||||
m.logger.Printf("[INFO] %s", line)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// checkLocalImages parses docker-compose.yml for image: lines and checks which
|
||||
// are available locally. Informational only — logs results but never fails.
|
||||
func (m *Manager) checkLocalImages(name, stackDir string) {
|
||||
composePath := filepath.Join(stackDir, "docker-compose.yml")
|
||||
data, err := os.ReadFile(composePath)
|
||||
if err != nil {
|
||||
composePath = filepath.Join(stackDir, "docker-compose.yaml")
|
||||
data, err = os.ReadFile(composePath)
|
||||
if err != nil {
|
||||
m.logger.Printf("[DEBUG] Could not read compose file for image check: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var images []string
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "image:") {
|
||||
img := strings.TrimSpace(strings.TrimPrefix(trimmed, "image:"))
|
||||
img = strings.Trim(img, "\"'")
|
||||
if img != "" && !strings.Contains(img, "${") {
|
||||
images = append(images, img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
m.logger.Printf("[DEBUG] No static image references found in %s compose file", name)
|
||||
return
|
||||
}
|
||||
|
||||
m.logger.Printf("[INFO] Deploying stack %s — checking %d images...", name, len(images))
|
||||
for _, img := range images {
|
||||
cmd := exec.Command("docker", "image", "inspect", img)
|
||||
if err := cmd.Run(); err != nil {
|
||||
m.logger.Printf("[DEBUG] %s — not found locally, will pull", img)
|
||||
} else {
|
||||
m.logger.Printf("[DEBUG] %s — found locally", img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Memory helpers ---
|
||||
|
||||
// ParseMemoryMB parses a memory string like "500M", "1G", "1.5G", "1024M", "768"
|
||||
|
||||
Reference in New Issue
Block a user