From 0886bcd310959e28cd0961cdb852fec38d3d1563 Mon Sep 17 00:00:00 2001 From: John Bro <42930107+john1298308460@users.noreply.github.com> Date: Tue, 25 Jun 2024 18:17:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=B9=E5=99=A8=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E4=BC=98=E5=8C=96=20(#5557)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 容器日志下载优化 Co-authored-by: zhoujunhong <1298308460@qq.com> --- backend/app/api/v1/container.go | 14 ++++ backend/app/dto/container.go | 7 ++ backend/app/service/container.go | 78 +++++++++++++++++++ backend/router/ro_container.go | 1 + frontend/src/api/interface/container.ts | 7 ++ frontend/src/api/modules/container.ts | 7 ++ frontend/src/components/compose-log/index.vue | 21 ++++- .../views/container/container/log/index.vue | 22 +++++- 8 files changed, 152 insertions(+), 5 deletions(-) diff --git a/backend/app/api/v1/container.go b/backend/app/api/v1/container.go index f62838ddf..5c8213662 100644 --- a/backend/app/api/v1/container.go +++ b/backend/app/api/v1/container.go @@ -7,6 +7,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/global" "github.com/gin-gonic/gin" "github.com/pkg/errors" + "strconv" ) // @Tags Container @@ -460,6 +461,19 @@ func (b *BaseApi) ContainerLogs(c *gin.Context) { } } +// @Description 下载容器日志 +// @Router /containers/download/log [post] +func (b *BaseApi) DownloadContainerLogs(c *gin.Context) { + var req dto.ContainerLog + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), c) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + } +} + // @Tags Container Network // @Summary Page networks // @Description 获取容器网络列表分页 diff --git a/backend/app/dto/container.go b/backend/app/dto/container.go index 36c05f884..0ead8b5a5 100644 --- a/backend/app/dto/container.go +++ b/backend/app/dto/container.go @@ -225,3 +225,10 @@ type ComposeUpdate struct { Path string `json:"path" validate:"required"` Content string `json:"content" validate:"required"` } + +type ContainerLog struct { + Container string `json:"container" validate:"required"` + Since string `json:"since"` + Tail uint `json:"tail"` + ContainerType string `json:"containerType"` +} diff --git a/backend/app/service/container.go b/backend/app/service/container.go index 3104c1fe8..b40421392 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -1,11 +1,15 @@ package service import ( + "bufio" "context" "encoding/base64" "encoding/json" "fmt" + "github.com/gin-gonic/gin" "io" + "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -64,6 +68,7 @@ type IContainerService interface { ContainerLogClean(req dto.OperationWithName) error ContainerOperation(req dto.ContainerOperation) error ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error + DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error ContainerStats(id string) (*dto.ContainerStats, error) Inspect(req dto.InspectReq) (string, error) DeleteNetwork(req dto.BatchDelete) error @@ -769,6 +774,79 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType, return nil } +func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error { + if cmd.CheckIllegal(container, since, tail) { + return buserr.New(constant.ErrCmdIllegal) + } + commandName := "docker" + commandArg := []string{"logs", container} + if containerType == "compose" { + commandName = "docker-compose" + commandArg = []string{"-f", container, "logs"} + } + if tail != "0" { + commandArg = append(commandArg, "--tail") + commandArg = append(commandArg, tail) + } + if since != "all" { + commandArg = append(commandArg, "--since") + commandArg = append(commandArg, since) + } + + cmd := exec.Command(commandName, commandArg...) + stdout, err := cmd.StdoutPipe() + if err != nil { + _ = cmd.Process.Signal(syscall.SIGTERM) + return err + } + cmd.Stderr = cmd.Stdout + if err := cmd.Start(); err != nil { + _ = cmd.Process.Signal(syscall.SIGTERM) + return err + } + + tempFile, err := os.CreateTemp("", "cmd_output_*.txt") + if err != nil { + return err + } + defer tempFile.Close() + defer func() { + if err := os.Remove(tempFile.Name()); err != nil { + global.LOG.Errorf("os.Remove() failed: %v", err) + } + }() + errCh := make(chan error) + go func() { + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + if _, err := tempFile.WriteString(line + "\n"); err != nil { + errCh <- err + return + } + } + 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(3 * time.Second): + global.LOG.Errorf("Timeout reached") + } + info, _ := tempFile.Stat() + + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), tempFile) + return nil +} + func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) { client, err := docker.NewDockerClient() if err != nil { diff --git a/backend/router/ro_container.go b/backend/router/ro_container.go index 27f536f24..7764bda90 100644 --- a/backend/router/ro_container.go +++ b/backend/router/ro_container.go @@ -26,6 +26,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { baRouter.POST("/list", baseApi.ListContainer) baRouter.GET("/list/stats", baseApi.ContainerListStats) baRouter.GET("/search/log", baseApi.ContainerLogs) + baRouter.POST("/download/log", baseApi.DownloadContainerLogs) baRouter.GET("/limit", baseApi.LoadResourceLimit) baRouter.POST("/clean/log", baseApi.CleanContainerLog) baRouter.POST("/load/log", baseApi.LoadContainerLog) diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index 55f0edae1..fb9563405 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -316,4 +316,11 @@ export namespace Container { logMaxSize: string; logMaxFile: string; } + + export interface ContainerLogInfo { + container: string; + since: string; + tail: number; + containerType: string; + } } diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index 080a91809..c84e35cb9 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -52,6 +52,13 @@ export const inspect = (params: Container.ContainerInspect) => { return http.post(`/containers/inspect`, params); }; +export const DownloadFile = (params: Container.ContainerLogInfo) => { + return http.download('/containers/download/log', params, { + responseType: 'blob', + timeout: TimeoutEnum.T_40S, + }); +}; + // image export const searchImage = (params: SearchWithPage) => { return http.post>(`/containers/image/search`, params); diff --git a/frontend/src/components/compose-log/index.vue b/frontend/src/components/compose-log/index.vue index 2510a2aa3..25c67d79f 100644 --- a/frontend/src/components/compose-log/index.vue +++ b/frontend/src/components/compose-log/index.vue @@ -58,7 +58,7 @@