fix: improve host system log filtering compatibility (#13373)

This commit is contained in:
ssongliu
2026-07-27 11:25:26 +08:00
committed by GitHub
parent 739686ff89
commit 26bd2d08be
21 changed files with 242 additions and 4 deletions

View File

@@ -22,6 +22,22 @@ func (b *BaseApi) GetSystemFiles(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Logs
// @Summary Get host system log status
// @Produce json
// @Success 200 {object} dto.SystemLogStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /logs/system/status [get]
func (b *BaseApi) GetSystemLogStatus(c *gin.Context) {
data, err := logService.GetSystemLogStatus()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Logs
// @Summary Read host logs
// @Accept json

View File

@@ -34,6 +34,13 @@ type SystemLogRes struct {
NextCursor string `json:"nextCursor"`
}
type SystemLogStatus struct {
Source string `json:"source"`
Version string `json:"version"`
KeywordFilterSupported bool `json:"keywordFilterSupported"`
Message string `json:"message"`
}
type SystemLogItem struct {
Timestamp int64 `json:"-"`
Cursor string `json:"-"`

View File

@@ -13,7 +13,9 @@ import (
"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"
)
@@ -40,6 +42,7 @@ type systemLogCursor struct {
type ILogService interface {
ListSystemLogFile() ([]string, error)
GetSystemLogStatus() (dto.SystemLogStatus, error)
ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, error)
ListRunningServices() ([]string, error)
}
@@ -74,6 +77,9 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
output, err := exec.CommandContext(ctx, journalctl, queryArgs...).CombinedOutput()
cancel()
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)))
}
content := strings.TrimSpace(string(output))
@@ -90,6 +96,62 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
return buildSystemLogResponse("journalctl", items, pageSize, cursor)
}
func (u *LogService) GetSystemLogStatus() (dto.SystemLogStatus, error) {
journalctl, err := exec.LookPath("journalctl")
if err != nil {
return dto.SystemLogStatus{
Source: "file",
KeywordFilterSupported: true,
}, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
versionOutput, err := exec.CommandContext(ctx, journalctl, "--version").CombinedOutput()
cancel()
if err != nil {
return dto.SystemLogStatus{}, fmt.Errorf("read journalctl version failed: %s", strings.TrimSpace(string(versionOutput)))
}
ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second)
helpOutput, err := exec.CommandContext(ctx, journalctl, "--help").CombinedOutput()
cancel()
if err != nil {
return dto.SystemLogStatus{}, fmt.Errorf("read journalctl capabilities failed: %s", strings.TrimSpace(string(helpOutput)))
}
supported := journalctlHelpSupportsGrep(string(helpOutput))
status := dto.SystemLogStatus{
Source: "journalctl",
Version: firstOutputLine(string(versionOutput)),
KeywordFilterSupported: supported,
}
if !supported {
status.Message = i18n.Get("ErrSystemLogKeywordFilterUnsupported")
}
return status, nil
}
func journalctlHelpSupportsGrep(help string) bool {
return strings.Contains(help, "--grep=")
}
func journalctlGrepUnsupported(keyword, output string) bool {
if strings.TrimSpace(keyword) == "" {
return false
}
output = strings.ToLower(output)
return strings.Contains(output, "grep") &&
(strings.Contains(output, "unrecognized option") || strings.Contains(output, "unknown option"))
}
func firstOutputLine(output string) string {
for _, line := range strings.Split(output, "\n") {
if line = strings.TrimSpace(line); line != "" {
return line
}
}
return ""
}
func buildJournalQueryArgs(req dto.SystemLogReq, startTime, endTime time.Time, pageSize int, cursor *systemLogCursor) []string {
args := []string{
"--no-pager", "--reverse", "--output=json",
@@ -104,7 +166,7 @@ func buildJournalQueryArgs(req dto.SystemLogReq, startTime, endTime time.Time, p
args = append(args, "-u", service)
}
if priority := strings.TrimSpace(req.Priority); priority != "" {
args = append(args, "--priority", priority)
args = append(args, "--priority", priority+".."+priority)
}
if keyword := strings.TrimSpace(req.Keyword); keyword != "" {
args = append(args, "--grep", keyword)

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Website monitoring log export'
LogExportNoData: 'No logs match the selected filters'
LogExportDiskSpace: 'Less than 1 GiB of disk space is available; the export was stopped'
LogExportRetentionInvalid: 'Export record retention must be between 1 and 30 days'
ErrSystemLogKeywordFilterUnsupported: 'The current system version does not support system log keyword filtering (requires systemd 237 or later)'
ErrStructTransform: 'Type conversion failed: {{ .err }}'
ErrNotSupportType: 'Unsupported type: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Exportación de registros de monitorización web'
LogExportNoData: 'Ningún registro coincide con los filtros seleccionados'
LogExportDiskSpace: 'Hay menos de 1 GiB disponible; la exportación se detuvo'
LogExportRetentionInvalid: 'La retención del historial de exportación debe ser de 1 a 30 días'
ErrSystemLogKeywordFilterUnsupported: 'La versión actual del sistema no admite el filtrado por palabras clave de los registros del sistema (requiere systemd 237 o posterior)'
ErrStructTransform: 'Error de conversión: {{ .err }}'
ErrNotSupportType: 'Tipo no soportado: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'خروجی لاگ پایش وب‌سایت'
LogExportNoData: 'هیچ لاگی با فیلترهای انتخاب‌شده مطابقت ندارد'
LogExportDiskSpace: 'فضای آزاد کمتر از ۱ گیگابایت است؛ خروجی متوقف شد'
LogExportRetentionInvalid: 'مدت نگهداری سوابق خروجی باید بین ۱ تا ۳۰ روز باشد'
ErrSystemLogKeywordFilterUnsupported: 'نسخه فعلی سیستم از فیلتر کلیدواژه در گزارش‌های سیستم پشتیبانی نمی‌کند (به systemd 237 یا جدیدتر نیاز است)'
ErrStructTransform: 'تبدیل نوع ناموفق بود: {{ .err }}'
ErrNotSupportType: 'نوع پشتیبانی نمی‌شود: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Web サイト監視ログのエクスポート'
LogExportNoData: '条件に一致するログがありません'
LogExportDiskSpace: '空き容量が 1 GiB 未満のため、エクスポートを停止しました'
LogExportRetentionInvalid: 'エクスポート履歴の保持期間は 130 日にしてください'
ErrSystemLogKeywordFilterUnsupported: '現在のシステムバージョンはシステムログのキーワードフィルタリングに対応していませんsystemd 237 以降が必要です)'
ErrStructTransform: '型変換エラー: {{ .err }}'
ErrNotSupportType: '未対応のタイプ: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: '웹사이트 모니터링 로그 내보내기'
LogExportNoData: '선택한 조건과 일치하는 로그가 없습니다'
LogExportDiskSpace: '사용 가능한 공간이 1 GiB 미만이어서 내보내기를 중지했습니다'
LogExportRetentionInvalid: '내보내기 기록 보관 기간은 1일에서 30일 사이여야 합니다'
ErrSystemLogKeywordFilterUnsupported: '현재 시스템 버전은 시스템 로그 키워드 필터링을 지원하지 않습니다(systemd 237 이상 필요)'
ErrStructTransform: '형 변환 실패: {{ .err }}'
ErrNotSupportType: '지원하지 않는 타입: {{ .name }}'

View File

@@ -17,6 +17,7 @@ MonitorLogExport: 'ສົ່ງອອກລັອກການຕິດຕາມ
LogExportNoData: 'ບໍ່ມີລັອກທີ່ກົງກັບເງື່ອນໄຂການກັ່ນຕອງ'
LogExportDiskSpace: 'ພື້ນທີ່ດິສກ໌ທີ່ໃຊ້ໄດ້ໜ້ອຍກວ່າ 1 GiB; ການສົ່ງອອກຖືກຢຸດແລ້ວ'
LogExportRetentionInvalid: 'ໄລຍະເກັບບັນທຶກການສົ່ງອອກຕ້ອງຢູ່ລະຫວ່າງ 1 ຫາ 30 ມື້'
ErrSystemLogKeywordFilterUnsupported: 'ເວີຊັນລະບົບປັດຈຸບັນບໍ່ຮອງຮັບການກັ່ນຕອງຄຳສຳຄັນຂອງບັນທຶກລະບົບ (ຕ້ອງການ systemd 237 ຫຼືໃໝ່ກວ່າ)'
ErrStructTransform: 'ການແປງປະເພດຂໍ້ມູນລົ້ມເຫຼວ: {{ .err }}'
ErrNotLogin: 'ຍັງບໍ່ໄດ້ເຂົ້າສູ່ລະບົບ: {{ .detail }}'
ErrPasswordExpired: 'ລະຫັດຜ່ານໝົດອາຍຸ: {{ .detail }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Eksport log pemantauan laman web'
LogExportNoData: 'Tiada log yang sepadan dengan penapis dipilih'
LogExportDiskSpace: 'Ruang tersedia kurang daripada 1 GiB; eksport dihentikan'
LogExportRetentionInvalid: 'Tempoh simpanan rekod eksport mestilah antara 1 hingga 30 hari'
ErrSystemLogKeywordFilterUnsupported: 'Versi sistem semasa tidak menyokong penapisan kata kunci log sistem (memerlukan systemd 237 atau lebih baharu)'
ErrStructTransform: 'Ralat penukaran: {{ .err }}'
ErrNotSupportType: 'Jenis tidak disokong: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Exportação de logs de monitoramento de sites'
LogExportNoData: 'Nenhum log corresponde aos filtros selecionados'
LogExportDiskSpace: 'Há menos de 1 GiB disponível; a exportação foi interrompida'
LogExportRetentionInvalid: 'A retenção dos registros de exportação deve ser de 1 a 30 dias'
ErrSystemLogKeywordFilterUnsupported: 'A versão atual do sistema não oferece suporte à filtragem de logs do sistema por palavra-chave (requer systemd 237 ou posterior)'
ErrStructTransform: 'Erro de conversão: {{ .err }}'
ErrNotSupportType: 'Tipo não suportado: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Экспорт журналов мониторинга веб
LogExportNoData: 'Нет журналов, соответствующих выбранным фильтрам'
LogExportDiskSpace: 'Доступно менее 1 GiB; экспорт остановлен'
LogExportRetentionInvalid: 'Срок хранения истории экспорта должен быть от 1 до 30 дней'
ErrSystemLogKeywordFilterUnsupported: 'Текущая версия системы не поддерживает фильтрацию системных журналов по ключевым словам (требуется systemd 237 или новее)'
ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
ErrNotSupportType: 'Тип не поддерживается: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: 'Web sitesi izleme günlüğü dışa aktarma'
LogExportNoData: 'Seçilen filtrelerle eşleşen günlük yok'
LogExportDiskSpace: 'Kullanılabilir alan 1 GiB altında; dışa aktarma durduruldu'
LogExportRetentionInvalid: 'Dışa aktarma kayıtları 1 ile 30 gün arasında saklanmalıdır'
ErrSystemLogKeywordFilterUnsupported: 'Mevcut sistem sürümü sistem günlüklerinde anahtar kelime filtrelemeyi desteklemiyor (systemd 237 veya üzeri gerekir)'
ErrStructTransform: 'Dönüşüm hatası: {{ .err }}'
ErrNotSupportType: 'Bu tür desteklenmiyor: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: '網站監控日誌匯出'
LogExportNoData: '沒有符合條件的日誌'
LogExportDiskSpace: '可用磁碟空間不足 1 GiB匯出任務已停止'
LogExportRetentionInvalid: '匯出記錄保留天數必須介於 1 到 30 天之間'
ErrSystemLogKeywordFilterUnsupported: '目前系統版本不支援系統日誌關鍵字篩選(需要 systemd 237 或更高版本)'
ErrStructTransform: '型別轉換失敗: {{ .err }}'
ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}'

View File

@@ -12,6 +12,7 @@ MonitorLogExport: "网站监控日志导出"
LogExportNoData: "没有符合条件的日志"
LogExportDiskSpace: "可用磁盘空间不足 1 GiB导出任务已停止"
LogExportRetentionInvalid: "导出记录保留天数必须在 1 到 30 天之间"
ErrSystemLogKeywordFilterUnsupported: "当前系统版本不支持系统日志关键词过滤(需要 systemd 237 或更高版本)"
ErrStructTransform: "类型转换失败: {{ .err }}"
ErrNotSupportType: "不支持当前类型: {{ .name }}"

View File

@@ -12,6 +12,7 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v2.ApiGroupApp.BaseApi
{
operationRouter.GET("/system/files", baseApi.GetSystemFiles)
operationRouter.GET("/system/status", baseApi.GetSystemLogStatus)
operationRouter.POST("/system/read", baseApi.ReadSystemLog)
operationRouter.GET("/system/services", baseApi.ListRunningServices)
operationRouter.POST("/tasks/search", baseApi.PageTasks)

View File

@@ -20388,6 +20388,33 @@ const docTemplate = `{
]
}
},
"/logs/system/status": {
"get": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SystemLogStatus"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Get host system log status",
"tags": [
"Logs"
]
}
},
"/logs/tasks/executing/count": {
"get": {
"responses": {
@@ -38946,6 +38973,23 @@ const docTemplate = `{
},
"type": "object"
},
"dto.SystemLogStatus": {
"properties": {
"keywordFilterSupported": {
"type": "boolean"
},
"message": {
"type": "string"
},
"source": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"dto.Tag": {
"properties": {
"key": {

View File

@@ -20384,6 +20384,33 @@
]
}
},
"/logs/system/status": {
"get": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SystemLogStatus"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Get host system log status",
"tags": [
"Logs"
]
}
},
"/logs/tasks/executing/count": {
"get": {
"responses": {
@@ -38942,6 +38969,23 @@
},
"type": "object"
},
"dto.SystemLogStatus": {
"properties": {
"keywordFilterSupported": {
"type": "boolean"
},
"message": {
"type": "string"
},
"source": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"dto.Tag": {
"properties": {
"key": {

View File

@@ -50,6 +50,13 @@ export namespace Log {
nextCursor: string;
}
export interface SystemLogStatus {
source: 'journalctl' | 'file';
version: string;
keywordFilterSupported: boolean;
message: string;
}
export interface SystemLogSearch {
pageSize: number;
cursor?: string;

View File

@@ -21,6 +21,11 @@ export const getSystemFiles = (node?: string) => {
return http.get<Array<string>>(`/logs/system/files${params}`);
};
export const getSystemLogStatus = (node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.get<Log.SystemLogStatus>(`/logs/system/status${query}`);
};
export const readSystemLogs = (params: Log.SystemLogSearch, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<Log.SystemLog>(`/logs/system/read${query}`, params);

View File

@@ -1,7 +1,33 @@
<template>
<div v-if="systemLogStatus" class="app-status card-interval">
<el-card>
<div class="flex w-full flex-col gap-4 md:flex-row">
<div class="flex flex-wrap gap-4 ml-3">
<el-tag effect="dark" type="success">{{ systemLogStatus.source }}</el-tag>
<el-tag v-if="systemLogStatus.version">
{{ $t('app.version') }}: {{ systemLogStatus.version }}
</el-tag>
</div>
</div>
</el-card>
</div>
<LayoutContent v-loading="loading" :title="$t('logs.hostSystem')">
<template #prompt>
<el-alert
v-if="systemLogStatus && !systemLogStatus.keywordFilterSupported"
type="info"
:title="systemLogStatus.message"
:closable="false"
/>
</template>
<template #rightToolBar>
<el-input v-model="keyword" class="p-w-200" clearable :placeholder="$t('logs.filter')" />
<el-input
v-model="keyword"
class="p-w-200"
clearable
:disabled="systemLogStatusLoading || !systemLogStatus?.keywordFilterSupported"
:placeholder="$t('logs.filter')"
/>
<el-date-picker
v-model="timeRange"
class="p-w-360"
@@ -75,7 +101,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { Log } from '@/api/interface/log';
import { listRunningServices, readSystemLogs } from '@/api/modules/log';
import { getSystemLogStatus, listRunningServices, readSystemLogs } from '@/api/modules/log';
import { useGlobalStore } from '@/composables/useGlobalStore';
import i18n from '@/lang';
import { shortcuts } from '@/utils/shortcuts';
@@ -88,6 +114,8 @@ const keyword = ref('');
const priority = ref('');
const service = ref('');
const services = ref<string[]>([]);
const systemLogStatus = ref<Log.SystemLogStatus>();
const systemLogStatusLoading = ref(true);
const detailOpen = ref(false);
const selectedLog = ref<Log.SystemLogItem>();
const timeRange = ref<[Date, Date]>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
@@ -175,6 +203,17 @@ const loadServices = async () => {
}
};
const loadSystemLogStatus = async () => {
try {
const res = await getSystemLogStatus(currentNode.value);
systemLogStatus.value = res.data;
} catch {
systemLogStatus.value = undefined;
} finally {
systemLogStatusLoading.value = false;
}
};
const scheduleWatch = () => {
timer = setTimeout(async () => {
if (!watching.value) return;
@@ -219,11 +258,12 @@ const priorityType = (value: string) => {
return 'info';
};
onMounted(() => {
onMounted(async () => {
const cachedPageSize = Number(localStorage.getItem(paginationConfig.cacheSizeKey));
if (pageSizes.includes(cachedPageSize)) {
paginationConfig.pageSize = cachedPageSize;
}
await loadSystemLogStatus();
resetAndLoadLogs();
loadServices();
});