logs.go

109 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
package commands

import (
	"flag"
	"fmt"
	"log"
	"os"
)

const logsUsage = `Stream logs from a deployed service

Usage:
  congo logs [flags] [service]

Flags:
  --server <name>  Instance to read from (auto-selects if only one)
  --tail <n>       Number of lines to show (default: 100)
  --follow         Follow log output (Ctrl+C to stop)

Examples:
  congo logs                   # list services on default instance
  congo logs web-app           # show recent logs for web-app
  congo logs web-app --follow  # stream logs
  congo logs --server web-1    # target a specific instance
`

func Logs() {
	fs := flag.NewFlagSet("logs", flag.ExitOnError)
	fs.Usage = func() { fmt.Print(logsUsage) }

	serverName := fs.String("server", "", "Instance to read from")
	tail := fs.String("tail", "100", "Number of lines")
	follow := fs.Bool("follow", false, "Follow log output")

	fs.Parse(os.Args[2:])

	cfg, err := loadInfraConfig(".")
	if err != nil {
		log.Fatalf("load infra.json: %v", err)
	}

	all := cfg.AllInstances()

	if len(all) == 0 {
		log.Fatal("No instances deployed. Use 'congo launch --new <server-type>' first.")
	}

	// Select instance
	var target *Instance
	if *serverName != "" {
		for i := range all {
			if all[i].Name == *serverName {
				target = &all[i]
				break
			}
		}
		if target == nil {
			fmt.Fprintf(os.Stderr, "Instance %q not found. Available instances:\n", *serverName)
			for _, inst := range all {
				fmt.Fprintf(os.Stderr, "  %s (%s)\n", inst.Name, inst.IP)
			}
			os.Exit(1)
		}
	} else if len(all) == 1 {
		target = &all[0]
	} else {
		fmt.Println("Multiple instances found. Use --server <name>:")
		for _, inst := range all {
			fmt.Printf("  %s (%s)\n", inst.Name, inst.IP)
		}
		os.Exit(1)
	}

	server := target.toServer()
	service := fs.Arg(0)

	// No service specified — list running containers
	if service == "" {
		fmt.Printf("Services on %s (%s):\n\n", target.Name, target.IP)
		out, err := server.SSH("docker", "ps", "--format", "table {{.Names}}\t{{.Status}}\t{{.Ports}}")
		if err != nil {
			log.Fatalf("docker ps: %v", err)
		}
		fmt.Println(out)
		fmt.Println("\nUsage: congo logs <service-name>")
		return
	}

	// Build docker logs command
	args := []string{"docker", "logs"}
	if *follow {
		args = append(args, "-f")
	}
	args = append(args, "--tail", *tail, service)

	if *follow {
		// Stream via interactive SSH (connects stdout/stderr/stdin)
		fmt.Printf("Streaming logs from %s on %s...\n\n", service, target.Name)
		if err := server.Connect(args...); err != nil {
			os.Exit(1)
		}
	} else {
		out, err := server.SSH(args...)
		if err != nil {
			log.Fatalf("logs: %v", err)
		}
		fmt.Print(out)
	}
}