diff --git a/agent/app/service/container.go b/agent/app/service/container.go index 7d7372753..4c2622343 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -15,6 +15,7 @@ import ( "os/exec" "path" "path/filepath" + "regexp" "sort" "strconv" "strings" @@ -32,7 +33,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/docker" - "github.com/1Panel-dev/1Panel/agent/utils/re" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/build" "github.com/docker/docker/api/types/container" @@ -53,6 +53,8 @@ import ( type ContainerService struct{} +var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>") + type IContainerService interface { Page(req dto.PageContainer) (int64, interface{}, error) List() []dto.ContainerOptions @@ -1036,6 +1038,7 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since if cmd.CheckIllegal(container, since, tail) { return buserr.New("ErrCmdIllegal") } + ctx := c.Request.Context() commandArg := []string{"logs", container} dockerCommand := global.CONF.DockerConfig.Command if containerType == "compose" { @@ -1065,10 +1068,11 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since } var dockerCmd *exec.Cmd if containerType == "compose" && dockerCommand == "docker-compose" { - dockerCmd = exec.Command("docker-compose", commandArg...) + dockerCmd = exec.CommandContext(ctx, "docker-compose", commandArg...) } else { - dockerCmd = exec.Command("docker", commandArg...) + dockerCmd = exec.CommandContext(ctx, "docker", commandArg...) } + dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} stdout, err := dockerCmd.StdoutPipe() if err != nil { return err @@ -1077,15 +1081,20 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since if err := dockerCmd.Start(); err != nil { return err } - defer func() { - if dockerCmd.Process != nil { - _ = dockerCmd.Process.Kill() - _ = dockerCmd.Wait() + done := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + killContainerLogProcess(dockerCmd) + case <-done: } }() + defer close(done) tempFile, err := os.CreateTemp("", "cmd_output_*.txt") if err != nil { + killContainerLogProcess(dockerCmd) + _ = dockerCmd.Wait() return err } defer tempFile.Close() @@ -1094,31 +1103,19 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since global.LOG.Errorf("os.Remove() failed: %v", err) } }() - errCh := make(chan error, 1) - go func() { - scanner := bufio.NewScanner(stdout) - var ansiRegex = re.GetRegex(re.AnsiEscapePattern) - for scanner.Scan() { - line := scanner.Text() - cleanLine := ansiRegex.ReplaceAllString(line, "") - if _, err := tempFile.WriteString(cleanLine + "\n"); err != nil { - errCh <- err - return - } + copyErr := copyContainerLogOutput(tempFile, stdout) + waitErr := dockerCmd.Wait() + if copyErr != nil { + return copyErr + } + if waitErr != nil { + if ctx.Err() != nil { + return ctx.Err() } - if err := scanner.Err(); err != nil { - errCh <- err - return - } - errCh <- nil - }() - select { - case err := <-errCh: - if err != nil { - global.LOG.Errorf("Error: %v", err) - } - case <-time.After(40 * time.Second): - global.LOG.Errorf("Download container logs timeout reached") + return waitErr + } + if _, err := tempFile.Seek(0, io.SeekStart); err != nil { + return err } info, _ := tempFile.Stat() @@ -1128,6 +1125,36 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since return nil } +func copyContainerLogOutput(dst io.Writer, src io.Reader) error { + reader := bufio.NewReader(src) + for { + line, err := reader.ReadString('\n') + if len(line) > 0 { + cleanLine := containerLogAnsiRegex.ReplaceAllString(line, "") + if _, writeErr := io.WriteString(dst, cleanLine); writeErr != nil { + return writeErr + } + } + if err != nil { + if err == io.EOF { + return nil + } + return err + } + } +} + +func killContainerLogProcess(command *exec.Cmd) { + if command == nil || command.Process == nil { + return + } + if pgid, err := syscall.Getpgid(command.Process.Pid); err == nil { + _ = syscall.Kill(-pgid, syscall.SIGKILL) + return + } + _ = command.Process.Kill() +} + func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) { client, err := docker.NewDockerClient() if err != nil { diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index b1bbcf5be..cdec3d304 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -115,7 +115,7 @@ export const inspect = (params: Container.ContainerInspect) => { export const DownloadFile = (params: Container.ContainerLogInfo) => { return http.download('/containers/download/log', params, { responseType: 'blob', - timeout: TimeoutEnum.T_60S, + timeout: TimeoutEnum.T_5M, }); };