From 3a68b8475885efb7854b3787ec45779080bce928 Mon Sep 17 00:00:00 2001 From: MoeShin Date: Tue, 3 Feb 2026 19:59:39 +0000 Subject: [PATCH] feat: Show container log timestamps --- agent/app/api/v2/container.go | 5 ++++- agent/app/dto/container.go | 2 ++ agent/app/service/container.go | 10 ++++++++-- core/cmd/server/docs/docs.go | 6 ++++++ core/cmd/server/docs/swagger.json | 6 ++++++ frontend/src/components/log/container/index.vue | 15 +++++++++++++-- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/agent/app/api/v2/container.go b/agent/app/api/v2/container.go index 0a9c868c8694..6dce46034211 100644 --- a/agent/app/api/v2/container.go +++ b/agent/app/api/v2/container.go @@ -529,7 +529,7 @@ func (b *BaseApi) DownloadContainerLogs(c *gin.Context) { if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), c) + err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), req.Timestamp, c) if err != nil { helper.InternalServer(c, err) } @@ -737,6 +737,7 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) { // @Param since query string false "时间筛选" // @Param follow query string false "是否追踪" // @Param tail query string false "显示行号" +// @Param timestamp query string false "是否显示时间" // @Success 200 // @Security ApiKeyAuth // @Security Timestamp @@ -750,6 +751,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) { since := c.Query("since") follow := c.Query("follow") == "true" tail := c.Query("tail") + timestamp := c.Query("timestamp") == "true" container := c.Query("container") compose := c.Query("compose") @@ -759,6 +761,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) { Since: since, Follow: follow, Tail: tail, + Timestamp: timestamp, Type: "container", } if compose != "" { diff --git a/agent/app/dto/container.go b/agent/app/dto/container.go index 75566736b57d..0642765d3fd3 100644 --- a/agent/app/dto/container.go +++ b/agent/app/dto/container.go @@ -304,6 +304,7 @@ type ContainerLog struct { Container string `json:"container" validate:"required"` Since string `json:"since"` Tail uint `json:"tail"` + Timestamp bool `json:"timestamp"` ContainerType string `json:"containerType"` } @@ -313,5 +314,6 @@ type StreamLog struct { Since string Follow bool Tail string + Timestamp bool Type string } diff --git a/agent/app/service/container.go b/agent/app/service/container.go index 7dce6705dbf4..59f1d1adf07c 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -74,7 +74,7 @@ type IContainerService interface { ContainerCommit(req dto.ContainerCommit) error ContainerLogClean(req dto.OperationWithName) error ContainerOperation(req dto.ContainerOperation) error - DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error + DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error ContainerStats(id string) (*dto.ContainerStats, error) Inspect(req dto.InspectReq) (string, error) DeleteNetwork(req dto.BatchDelete) error @@ -952,6 +952,9 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<- if params.Follow { cmdArgs = append(cmdArgs, "-f") } + if params.Timestamp { + cmdArgs = append(cmdArgs, "-t") + } if params.Tail != "0" { cmdArgs = append(cmdArgs, "--tail", params.Tail) } @@ -1048,7 +1051,7 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<- _ = dockerCmd.Wait() } -func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error { +func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error { if cmd.CheckIllegal(container, since, tail) { return buserr.New("ErrCmdIllegal") } @@ -1076,6 +1079,9 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since commandArg = append(commandArg, "--since") commandArg = append(commandArg, since) } + if timestamp { + commandArg = append(commandArg, "-t") + } var dockerCmd *exec.Cmd if containerType == "compose" && dockerCommand == "docker-compose" { dockerCmd = exec.Command("docker-compose", commandArg...) diff --git a/core/cmd/server/docs/docs.go b/core/cmd/server/docs/docs.go index 984dfaacebb3..b0ee87206df5 100644 --- a/core/cmd/server/docs/docs.go +++ b/core/cmd/server/docs/docs.go @@ -4594,6 +4594,12 @@ const docTemplate = `{ "in": "query", "name": "tail", "type": "string" + }, + { + "description": "是否显示时间", + "in": "query", + "name": "timestamp", + "type": "string" } ], "responses": { diff --git a/core/cmd/server/docs/swagger.json b/core/cmd/server/docs/swagger.json index a661603ee63e..817f1571f59e 100644 --- a/core/cmd/server/docs/swagger.json +++ b/core/cmd/server/docs/swagger.json @@ -4590,6 +4590,12 @@ "in": "query", "name": "tail", "type": "string" + }, + { + "description": "是否显示时间", + "in": "query", + "name": "timestamp", + "type": "string" } ], "responses": { diff --git a/frontend/src/components/log/container/index.vue b/frontend/src/components/log/container/index.vue index e6995a752307..cb7fb659c0f0 100644 --- a/frontend/src/components/log/container/index.vue +++ b/frontend/src/components/log/container/index.vue @@ -17,6 +17,11 @@ {{ $t('commons.button.watch') }} +
+ + {{ $t('commons.table.date') }} + +
{{ $t('commons.button.download') }} @@ -79,6 +84,10 @@ const props = defineProps({ type: Boolean, default: false, }, + defaultIsShowTimestamp: { + type: Boolean, + default: false, + }, }); const styleVars = computed(() => ({ @@ -91,6 +100,7 @@ const logs = ref([]); let eventSource: EventSource | null = null; const logSearch = reactive({ isWatch: props.defaultFollow ? true : true, + isShowTimestamp: props.defaultIsShowTimestamp, container: '', mode: 'all', tail: props.defaultFollow ? 0 : 100, @@ -152,9 +162,9 @@ const searchLogs = async () => { if (props.node && props.node !== '') { currentNode = props.node; } - let url = `/api/v2/containers/search/log?container=${logSearch.container}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}&operateNode=${currentNode}`; + let url = `/api/v2/containers/search/log?container=${logSearch.container}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}×tamp=${logSearch.isShowTimestamp}&operateNode=${currentNode}`; if (logSearch.compose !== '') { - url = `/api/v2/containers/search/log?compose=${logSearch.compose}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}&operateNode=${currentNode}`; + url = `/api/v2/containers/search/log?compose=${logSearch.compose}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}×tamp=${logSearch.isShowTimestamp}&operateNode=${currentNode}`; } eventSource = new EventSource(url); eventSource.onmessage = (event: MessageEvent) => { @@ -192,6 +202,7 @@ const onDownload = async () => { container: container, since: logSearch.mode, tail: logSearch.tail, + timestamp: logSearch.isShowTimestamp, containerType: containerType, }; let addItem = {};