package agent import ( "bufio" "io" mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1" "git.wntrmute.dev/mc/mcp/internal/registry" "git.wntrmute.dev/mc/mcp/internal/runtime" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // Logs streams container logs for a service component. func (a *Agent) Logs(req *mcpv1.LogsRequest, stream mcpv1.McpAgentService_LogsServer) error { if req.GetService() == "" { return status.Error(codes.InvalidArgument, "service name is required") } // Resolve component name. component := req.GetComponent() if component == "" { components, err := registry.ListComponents(a.DB, req.GetService()) if err != nil { return status.Errorf(codes.Internal, "list components: %v", err) } if len(components) == 0 { return status.Error(codes.NotFound, "no components found for service") } component = components[0].Name } containerName := ContainerNameFor(req.GetService(), component) podman, ok := a.Runtime.(*runtime.Podman) if !ok { return status.Error(codes.Internal, "logs requires podman runtime") } cmd := podman.Logs(stream.Context(), containerName, int(req.GetTail()), req.GetFollow(), req.GetTimestamps(), req.GetSince()) a.Logger.Info("running podman logs", "container", containerName, "args", cmd.Args) // Podman writes container stdout to its stdout and container stderr // to its stderr. Merge both into a single pipe. pr, pw := io.Pipe() cmd.Stdout = pw cmd.Stderr = pw if err := cmd.Start(); err != nil { pw.Close() return status.Errorf(codes.Internal, "start podman logs: %v", err) } // Close the write end when the command exits so the scanner finishes. go func() { err := cmd.Wait() if err != nil { a.Logger.Warn("podman logs exited", "container", containerName, "error", err) } pw.Close() }() scanner := bufio.NewScanner(pr) for scanner.Scan() { if err := stream.Send(&mcpv1.LogsResponse{ Data: append(scanner.Bytes(), '\n'), }); err != nil { _ = cmd.Process.Kill() return err } } return nil }