Skip to content

Commit 554d2ab

Browse files
feat(task): Optimize Container Log Reading (#7575)
1 parent 8fe40b7 commit 554d2ab

File tree

16 files changed

+314
-511
lines changed

16 files changed

+314
-511
lines changed

agent/app/api/v2/container.go

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
77
"github.com/1Panel-dev/1Panel/agent/app/dto"
8-
"github.com/1Panel-dev/1Panel/agent/global"
98
"github.com/gin-gonic/gin"
109
"github.com/pkg/errors"
1110
)
@@ -470,34 +469,6 @@ func (b *BaseApi) Inspect(c *gin.Context) {
470469
helper.SuccessWithData(c, result)
471470
}
472471

473-
// @Tags Container
474-
// @Summary Container logs
475-
// @Description 容器日志
476-
// @Param container query string false "容器名称"
477-
// @Param since query string false "时间筛选"
478-
// @Param follow query string false "是否追踪"
479-
// @Param tail query string false "显示行号"
480-
// @Security ApiKeyAuth
481-
// @Router /containers/search/log [post]
482-
func (b *BaseApi) ContainerLogs(c *gin.Context) {
483-
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
484-
if err != nil {
485-
global.LOG.Errorf("gin context http handler failed, err: %v", err)
486-
return
487-
}
488-
defer wsConn.Close()
489-
490-
container := c.Query("container")
491-
since := c.Query("since")
492-
follow := c.Query("follow") == "true"
493-
tail := c.Query("tail")
494-
495-
if err := containerService.ContainerLogs(wsConn, "container", container, since, tail, follow); err != nil {
496-
_ = wsConn.WriteMessage(1, []byte(err.Error()))
497-
return
498-
}
499-
}
500-
501472
// @Description 下载容器日志
502473
// @Router /containers/download/log [post]
503474
func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
@@ -707,30 +678,38 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
707678
helper.SuccessWithData(c, nil)
708679
}
709680

710-
// @Tags Container Compose
711-
// @Summary Container Compose logs
712-
// @Description docker-compose 日志
713-
// @Param compose query string false "compose 文件地址"
681+
// @Tags Container
682+
// @Summary Container logs
683+
// @Description 容器日志
684+
// @Param container query string false "容器名称"
714685
// @Param since query string false "时间筛选"
715686
// @Param follow query string false "是否追踪"
716687
// @Param tail query string false "显示行号"
717688
// @Security ApiKeyAuth
718-
// @Router /containers/compose/search/log [get]
719-
func (b *BaseApi) ComposeLogs(c *gin.Context) {
720-
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
721-
if err != nil {
722-
global.LOG.Errorf("gin context http handler failed, err: %v", err)
723-
return
724-
}
725-
defer wsConn.Close()
689+
// @Router /containers/search/log [post]
690+
func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
691+
c.Header("Content-Type", "text/event-stream")
692+
c.Header("Cache-Control", "no-cache")
693+
c.Header("Connection", "keep-alive")
694+
c.Header("Transfer-Encoding", "chunked")
726695

727-
compose := c.Query("compose")
728696
since := c.Query("since")
729697
follow := c.Query("follow") == "true"
730698
tail := c.Query("tail")
731699

732-
if err := containerService.ContainerLogs(wsConn, "compose", compose, since, tail, follow); err != nil {
733-
_ = wsConn.WriteMessage(1, []byte(err.Error()))
734-
return
700+
container := c.Query("container")
701+
compose := c.Query("compose")
702+
streamLog := dto.StreamLog{
703+
Compose: compose,
704+
Container: container,
705+
Since: since,
706+
Follow: follow,
707+
Tail: tail,
708+
Type: "container",
735709
}
710+
if compose != "" {
711+
streamLog.Type = "compose"
712+
}
713+
714+
containerService.StreamLogs(c, streamLog)
736715
}

agent/app/dto/container.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,12 @@ type ContainerLog struct {
268268
Tail uint `json:"tail"`
269269
ContainerType string `json:"containerType"`
270270
}
271+
272+
type StreamLog struct {
273+
Compose string
274+
Container string
275+
Since string
276+
Follow bool
277+
Tail string
278+
Type string
279+
}

agent/app/service/container.go

Lines changed: 71 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/base64"
77
"encoding/json"
88
"fmt"
9+
"github.com/gin-gonic/gin"
910
"io"
1011
"net/http"
1112
"net/url"
@@ -19,9 +20,6 @@ import (
1920
"sync"
2021
"syscall"
2122
"time"
22-
"unicode/utf8"
23-
24-
"github.com/gin-gonic/gin"
2523

2624
"github.com/pkg/errors"
2725

@@ -44,7 +42,6 @@ import (
4442
"github.com/docker/docker/api/types/volume"
4543
"github.com/docker/docker/client"
4644
"github.com/docker/go-connections/nat"
47-
"github.com/gorilla/websocket"
4845
v1 "github.com/opencontainers/image-spec/specs-go/v1"
4946
"github.com/shirou/gopsutil/v3/cpu"
5047
"github.com/shirou/gopsutil/v3/mem"
@@ -74,7 +71,6 @@ type IContainerService interface {
7471
ContainerCommit(req dto.ContainerCommit) error
7572
ContainerLogClean(req dto.OperationWithName) error
7673
ContainerOperation(req dto.ContainerOperation) error
77-
ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error
7874
DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error
7975
ContainerStats(id string) (*dto.ContainerStats, error)
8076
Inspect(req dto.InspectReq) (string, error)
@@ -87,6 +83,8 @@ type IContainerService interface {
8783
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
8884

8985
LoadContainerLogs(req dto.OperationWithNameAndType) string
86+
87+
StreamLogs(ctx *gin.Context, params dto.StreamLog)
9088
}
9189

9290
func NewIContainerService() IContainerService {
@@ -794,87 +792,87 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
794792
return nil
795793
}
796794

797-
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error {
798-
defer func() { wsConn.Close() }()
799-
if cmd.CheckIllegal(container, since, tail) {
800-
return buserr.New(constant.ErrCmdIllegal)
795+
func (u *ContainerService) StreamLogs(ctx *gin.Context, params dto.StreamLog) {
796+
messageChan := make(chan string, 1024)
797+
errorChan := make(chan error, 1)
798+
799+
go collectLogs(params, messageChan, errorChan)
800+
801+
ctx.Stream(func(w io.Writer) bool {
802+
select {
803+
case msg, ok := <-messageChan:
804+
if !ok {
805+
return false
806+
}
807+
_, err := fmt.Fprintf(w, "data: %v\n\n", msg)
808+
if err != nil {
809+
return false
810+
}
811+
return true
812+
case err := <-errorChan:
813+
_, err = fmt.Fprintf(w, "data: {\"event\": \"error\", \"data\": \"%s\"}\n\n", err.Error())
814+
if err != nil {
815+
return false
816+
}
817+
return false
818+
case <-ctx.Request.Context().Done():
819+
return false
820+
}
821+
})
822+
}
823+
824+
func collectLogs(params dto.StreamLog, messageChan chan<- string, errorChan chan<- error) {
825+
defer close(messageChan)
826+
defer close(errorChan)
827+
828+
var cmdArgs []string
829+
if params.Type == "compose" {
830+
cmdArgs = []string{"compose", "-f", params.Compose}
801831
}
802-
commandName := "docker"
803-
commandArg := []string{"logs", container}
804-
if containerType == "compose" {
805-
commandArg = []string{"compose", "-f", container, "logs"}
832+
cmdArgs = append(cmdArgs, "logs")
833+
if params.Follow {
834+
cmdArgs = append(cmdArgs, "-f")
806835
}
807-
if tail != "0" {
808-
commandArg = append(commandArg, "--tail")
809-
commandArg = append(commandArg, tail)
836+
if params.Tail != "all" {
837+
cmdArgs = append(cmdArgs, "--tail", params.Tail)
810838
}
811-
if since != "all" {
812-
commandArg = append(commandArg, "--since")
813-
commandArg = append(commandArg, since)
839+
if params.Since != "all" {
840+
cmdArgs = append(cmdArgs, "--since", params.Since)
814841
}
815-
if follow {
816-
commandArg = append(commandArg, "-f")
817-
}
818-
if !follow {
819-
cmd := exec.Command(commandName, commandArg...)
820-
cmd.Stderr = cmd.Stdout
821-
stdout, _ := cmd.CombinedOutput()
822-
if !utf8.Valid(stdout) {
823-
return errors.New("invalid utf8")
824-
}
825-
if err := wsConn.WriteMessage(websocket.TextMessage, stdout); err != nil {
826-
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
827-
}
828-
return nil
842+
if params.Container != "" {
843+
cmdArgs = append(cmdArgs, params.Container)
829844
}
845+
cmd := exec.Command("docker", cmdArgs...)
830846

831-
cmd := exec.Command(commandName, commandArg...)
832847
stdout, err := cmd.StdoutPipe()
833848
if err != nil {
834-
_ = cmd.Process.Signal(syscall.SIGTERM)
835-
return err
849+
errorChan <- fmt.Errorf("failed to get stdout pipe: %v", err)
850+
return
836851
}
837-
cmd.Stderr = cmd.Stdout
838852
if err := cmd.Start(); err != nil {
839-
_ = cmd.Process.Signal(syscall.SIGTERM)
840-
return err
853+
errorChan <- fmt.Errorf("failed to start command: %v", err)
854+
return
841855
}
842-
exitCh := make(chan struct{})
843-
go func() {
844-
_, wsData, _ := wsConn.ReadMessage()
845-
if string(wsData) == "close conn" {
846-
_ = cmd.Process.Signal(syscall.SIGTERM)
847-
exitCh <- struct{}{}
848-
}
849-
}()
850856

851-
go func() {
852-
buffer := make([]byte, 1024)
853-
for {
854-
select {
855-
case <-exitCh:
856-
return
857-
default:
858-
n, err := stdout.Read(buffer)
859-
if err != nil {
860-
if err == io.EOF {
861-
return
862-
}
863-
global.LOG.Errorf("read bytes from log failed, err: %v", err)
864-
return
865-
}
866-
if !utf8.Valid(buffer[:n]) {
867-
continue
868-
}
869-
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
870-
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
871-
return
872-
}
873-
}
857+
scanner := bufio.NewScanner(stdout)
858+
lineNumber := 0
859+
860+
for scanner.Scan() {
861+
lineNumber++
862+
message := scanner.Text()
863+
select {
864+
case messageChan <- message:
865+
case <-time.After(time.Second):
866+
errorChan <- fmt.Errorf("message channel blocked")
867+
return
874868
}
875-
}()
876-
_ = cmd.Wait()
877-
return nil
869+
}
870+
871+
if err := scanner.Err(); err != nil {
872+
errorChan <- fmt.Errorf("scanner error: %v", err)
873+
return
874+
}
875+
cmd.Wait()
878876
}
879877

880878
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error {

agent/init/migration/migrate.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import (
88
)
99

1010
func Init() {
11+
InitAgentDB()
12+
InitTaskDB()
13+
global.LOG.Info("Migration run successfully")
14+
}
15+
16+
func InitAgentDB() {
1117
m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{
1218
migrations.AddTable,
1319
migrations.AddMonitorTable,
@@ -20,5 +26,14 @@ func Init() {
2026
global.LOG.Error(err)
2127
panic(err)
2228
}
23-
global.LOG.Info("Migration run successfully")
29+
}
30+
31+
func InitTaskDB() {
32+
m := gormigrate.New(global.TaskDB, gormigrate.DefaultOptions, []*gormigrate.Migration{
33+
migrations.AddTaskTable,
34+
})
35+
if err := m.Migrate(); err != nil {
36+
global.LOG.Error(err)
37+
panic(err)
38+
}
2439
}

agent/init/migration/migrations/init.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,12 @@ var InitPHPExtensions = &gormigrate.Migration{
217217
return nil
218218
},
219219
}
220+
221+
var AddTaskTable = &gormigrate.Migration{
222+
ID: "20241226-add-task",
223+
Migrate: func(tx *gorm.DB) error {
224+
return tx.AutoMigrate(
225+
&model.Task{},
226+
)
227+
},
228+
}

agent/router/ro_container.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
2323
baRouter.POST("/list", baseApi.ListContainer)
2424
baRouter.GET("/status", baseApi.LoadContainerStatus)
2525
baRouter.GET("/list/stats", baseApi.ContainerListStats)
26-
baRouter.GET("/search/log", baseApi.ContainerLogs)
26+
baRouter.GET("/search/log", baseApi.ContainerStreamLogs)
2727
baRouter.POST("/download/log", baseApi.DownloadContainerLogs)
2828
baRouter.GET("/limit", baseApi.LoadResourceLimit)
2929
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
@@ -46,7 +46,6 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
4646
baRouter.POST("/compose/test", baseApi.TestCompose)
4747
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
4848
baRouter.POST("/compose/update", baseApi.ComposeUpdate)
49-
baRouter.GET("/compose/search/log", baseApi.ComposeLogs)
5049

5150
baRouter.GET("/template", baseApi.ListComposeTemplate)
5251
baRouter.POST("/template/search", baseApi.SearchComposeTemplate)

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"vue-codemirror": "^6.1.1",
5656
"vue-demi": "^0.14.6",
5757
"vue-i18n": "^9.13.1",
58-
"vue-router": "^4.3.3"
58+
"vue-router": "^4.3.3",
59+
"vue-virtual-scroller": "^2.0.0-beta.8"
5960
},
6061
"devDependencies": {
6162
"@types/node": "^20.14.8",

0 commit comments

Comments
 (0)