Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 59 additions & 18 deletions agent/app/service/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"fmt"
"os"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"
"time"

"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/re"
Expand Down Expand Up @@ -73,20 +73,22 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
return u.readFileSystemLog(req, startTime, endTime, pageSize, cursor)
}
queryArgs := buildJournalQueryArgs(req, startTime, endTime, pageSize, cursor)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
output, err := exec.CommandContext(ctx, journalctl, queryArgs...).CombinedOutput()
cancel()
output, err := executeJournalQuery(journalctl, queryArgs)
if err != nil {
if journalctlGrepUnsupported(req.Keyword, string(output)) {
return dto.SystemLogRes{}, buserr.New("ErrSystemLogKeywordFilterUnsupported")
}
return dto.SystemLogRes{}, fmt.Errorf("read host system logs failed: %s", strings.TrimSpace(string(output)))
return handleJournalQueryError(req, output, err, func() ([]byte, error) {
probeReq := req
probeReq.Keyword = ""
probeReq.Priority = ""
probeReq.Service = ""
probeArgs := buildJournalQueryArgs(probeReq, startTime, endTime, 1, cursor)
return executeJournalQuery(journalctl, probeArgs)
})
}
content := strings.TrimSpace(string(output))
if content == "" || strings.HasPrefix(content, "-- No entries --") {
return dto.SystemLogRes{Source: "journalctl", Items: []dto.SystemLogItem{}}, nil
}
items := parseJournalLogItems(content)
items := trimJournalLogItemsToStartTime(parseJournalLogItems(content), startTime)
if cursor != nil && cursor.JournalCursor == "" {
items, err = skipSystemLogCursorItems(items, *cursor)
if err != nil {
Expand All @@ -96,6 +98,12 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
return buildSystemLogResponse("journalctl", items, pageSize, cursor)
}

func executeJournalQuery(journalctl string, queryArgs []string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
return exec.CommandContext(ctx, journalctl, queryArgs...).CombinedOutput()
}

func (u *LogService) GetSystemLogStatus() (dto.SystemLogStatus, error) {
journalctl, err := exec.LookPath("journalctl")
if err != nil {
Expand Down Expand Up @@ -134,13 +142,44 @@ func journalctlHelpSupportsGrep(help string) bool {
return strings.Contains(help, "--grep=")
}

func journalctlGrepUnsupported(keyword, output string) bool {
if strings.TrimSpace(keyword) == "" {
return false
func handleJournalQueryError(
req dto.SystemLogReq,
output []byte,
queryErr error,
probe func() ([]byte, error),
) (dto.SystemLogRes, error) {
if hasSystemLogFilter(req) && probe != nil {
probeOutput, probeErr := probe()
if probeErr == nil {
return dto.SystemLogRes{Source: "journalctl", Items: []dto.SystemLogItem{}}, nil
}
return dto.SystemLogRes{}, newJournalQueryError(probeOutput, probeErr)
}
Comment thread
ssongliu marked this conversation as resolved.
output = strings.ToLower(output)
return strings.Contains(output, "grep") &&
(strings.Contains(output, "unrecognized option") || strings.Contains(output, "unknown option"))
return dto.SystemLogRes{}, newJournalQueryError(output, queryErr)
}

func newJournalQueryError(output []byte, queryErr error) error {
message := strings.TrimSpace(string(output))
if message == "" && queryErr != nil {
message = queryErr.Error()
}
return fmt.Errorf("read host system logs failed: %s", message)
}

func trimJournalLogItemsToStartTime(items []dto.SystemLogItem, startTime time.Time) []dto.SystemLogItem {
startTimestamp := startTime.UnixMicro()
for i, item := range items {
if item.Timestamp < startTimestamp {
return items[:i]
}
}
return items
}

func hasSystemLogFilter(req dto.SystemLogReq) bool {
return strings.TrimSpace(req.Keyword) != "" ||
strings.TrimSpace(req.Priority) != "" ||
strings.TrimSpace(req.Service) != ""
}

func firstOutputLine(output string) string {
Expand All @@ -155,12 +194,14 @@ func firstOutputLine(output string) string {
func buildJournalQueryArgs(req dto.SystemLogReq, startTime, endTime time.Time, pageSize int, cursor *systemLogCursor) []string {
args := []string{
"--no-pager", "--reverse", "--output=json",
"--since", formatJournalQueryTime(startTime),
}
if cursor != nil && cursor.JournalCursor != "" {
args = append(args, "--after-cursor="+cursor.JournalCursor)
} else {
args = append(args, "--until", formatJournalQueryTime(endTime))
args = append(args,
"--since", formatJournalQueryTime(startTime),
"--until", formatJournalQueryTime(endTime),
)
}
if service := strings.TrimSpace(req.Service); service != "" {
args = append(args, "-u", service)
Expand All @@ -169,7 +210,7 @@ func buildJournalQueryArgs(req dto.SystemLogReq, startTime, endTime time.Time, p
args = append(args, "--priority", priority+".."+priority)
}
if keyword := strings.TrimSpace(req.Keyword); keyword != "" {
args = append(args, "--grep", keyword)
args = append(args, "--grep", regexp.QuoteMeta(keyword), "--case-sensitive=no")
}
queryLines := pageSize + 1
if cursor != nil && cursor.JournalCursor == "" {
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/views/log/host-system/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<TableRefresh @search="resetAndLoadLogs" />
</template>
<template #main>
<ComplexTable :data="logs" :pagination-config="paginationConfig" @row-click="openDetail">
<ComplexTable :data="logs" :pagination-config="paginationConfig" @row-click="openDetail" :heightDiff="370">
<el-table-column prop="time" :label="$t('commons.table.date')" width="220" show-overflow-tooltip />
<el-table-column :label="$t('logs.priority')" width="120">
<template #default="{ row }">
Expand Down Expand Up @@ -265,9 +265,7 @@ onMounted(async () => {
if (pageSizes.includes(cachedPageSize)) {
paginationConfig.pageSize = cachedPageSize;
}
await loadSystemLogStatus();
resetAndLoadLogs();
loadServices();
await Promise.allSettled([loadSystemLogStatus(), resetAndLoadLogs(), loadServices()]);
});
onUnmounted(() => {
if (timer) clearTimeout(timer);
Expand Down
Loading