优化log显示效果

This commit is contained in:
liuwei
2025-05-28 17:18:07 +08:00
parent e05e7fc1d2
commit bedae250ab

View File

@@ -5,46 +5,31 @@
{% block content %} {% block content %}
<div> <div>
<el-card class="log-card"> <el-card class="log-card">
<template slot="header"> <div slot="header" class="clearfix">
<div class="flex items-center justify-between"> <span>日志查看</span>
<span class="text-lg font-semibold">日志查看</span> <el-radio-group v-model="logType" size="small" style="margin-left: 20px;" @change="loadLogs">
<div class="flex items-center space-x-4"> <el-radio-button label="info">信息日志</el-radio-button>
<el-radio-group v-model="logType" size="small" @change="loadLogs"> <el-radio-button label="error">错误日志</el-radio-button>
<el-radio-button label="info">信息日志</el-radio-button> <el-radio-button label="debug">调试日志</el-radio-button>
<el-radio-button label="error">错误日志</el-radio-button> </el-radio-group>
<el-radio-button label="debug">调试日志</el-radio-button> <el-select v-model="logLines" size="small" style="margin-left: 20px;" @change="loadLogs">
</el-radio-group> <el-option label="最近100行" :value="100"></el-option>
<el-select v-model="logLines" size="small" @change="loadLogs"> <el-option label="最近500行" :value="500"></el-option>
<el-option label="最近100行" :value="100"></el-option> <el-option label="最近1000行" :value="1000"></el-option>
<el-option label="最近500行" :value="500"></el-option> </el-select>
<el-option label="最近1000行" :value="1000"></el-option> <el-select v-model="refreshInterval" size="small" style="margin-left: 20px; width: 100px;" @change="handleRefreshInterval">
</el-select> <el-option label="1秒" :value="1"></el-option>
<el-select v-model="refreshInterval" size="small" style="width: 100px;" @change="handleRefreshInterval"> <el-option label="3秒" :value="3"></el-option>
<el-option label="手动" :value="0"></el-option> <el-option label="5秒" :value="5"></el-option>
<el-option label="1秒" :value="1"></el-option> <el-option label="10秒" :value="10"></el-option>
<el-option label="3秒" :value="3"></el-option> <el-option label="60秒" :value="60"></el-option>
<el-option label="5秒" :value="5"></el-option> </el-select>
<el-option label="10秒" :value="10"></el-option> <el-button style="float: right; padding: 3px 0" type="text" @click="loadLogs">刷新</el-button>
<el-option label="60秒" :value="60"></el-option> </div>
</el-select>
<el-button size="small" type="primary" @click="loadLogs">刷新</el-button>
</div>
</div>
</template>
<div v-loading="loading" class="relative"> <div v-loading="loading">
<div class="mb-4 flex items-center"> <div v-if="logContent && logContent.length > 0" class="log-content">
<el-input v-model="searchQuery" placeholder="搜索日志内容" clearable @input="filterLogs" size="small" style="width: 300px;"></el-input> <pre>{% raw %}{{ logContent.join('') }}{% endraw %}</pre>
<el-checkbox v-model="autoScroll" class="ml-4">自动滚动到底部</el-checkbox>
</div>
<div v-if="filteredLogs.length > 0" class="log-content">
{% raw %}
<div v-for="(log, index) in filteredLogs" :key="index" :class="['log-entry', `log-level-${log.level}`]">
<span class="log-timestamp">{{ formatTimestamp(log.timestamp) }}</span>
<span class="log-level" :class="{'text-blue-500': log.level === 'INFO', 'text-yellow-500': log.level === 'WARNING', 'text-red-500': log.level === 'ERROR'}">{{ log.level }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
{% endraw %}
</div> </div>
<div v-else class="empty-log"> <div v-else class="empty-log">
<el-empty description="暂无日志内容"></el-empty> <el-empty description="暂无日志内容"></el-empty>
@@ -65,12 +50,9 @@
logType: 'info', logType: 'info',
logLines: 100, logLines: 100,
logContent: [], logContent: [],
filteredLogs: [], refreshInterval: 0, // 新增定时刷新间隔0为不自动刷新
searchQuery: '', refreshTimer: null, // 新增,定时器句柄
autoScroll: true, currentView: '9' // 设置当前菜单项
refreshInterval: 0,
refreshTimer: null,
currentView: '9'
} }
}, },
mounted() { mounted() {
@@ -80,73 +62,30 @@
this.clearRefreshTimer(); this.clearRefreshTimer();
}, },
methods: { methods: {
async loadLogs() { loadLogs() {
this.loading = true; this.loading = true;
try { axios.get(`/api/wx_logs?type=${this.logType}&lines=${this.logLines}`)
const response = await axios.get(`/api/wx_logs?type=${this.logType}&lines=${this.logLines}`); .then(response => {
if (response.data.success) { if (response.data.success) {
// 解析文本日志行为对象数组 this.logContent = response.data.data.content || [];
this.logContent = this.parseLogLines(response.data.data.content || []); // 日志内容更新后自动滚动到底部
this.filterLogs(); this.$nextTick(() => {
this.scrollToBottom(); const logDiv = this.$el.querySelector('.log-content');
} else { if (logDiv) {
this.$message.error('加载日志失败'); logDiv.scrollTop = logDiv.scrollHeight;
} }
} catch (error) { });
console.error('加载日志出错:', error); } else {
this.$message.error('加载日志出错'); this.$message.error('加载日志失败');
} finally {
this.loading = false;
}
},
parseLogLines(lines) {
return lines.map(line => {
// 解析日志行格式如2025-05-28 17:10:04.758 | INFO | module:func:line - message
const parts = line.split(' | ');
if (parts.length < 4) {
return { timestamp: '', level: '', message: line }; // 格式不正确时保留原始行
}
const [timestamp, level, , message] = parts;
return {
timestamp: timestamp.trim(),
level: level.trim(),
message: message.trim()
};
}).filter(log => log.timestamp && log.level && log.message); // 过滤无效日志
},
filterLogs() {
if (!this.searchQuery) {
this.filteredLogs = this.logContent;
} else {
const query = this.searchQuery.toLowerCase();
this.filteredLogs = this.logContent.filter(log =>
(log.message && log.message.toLowerCase().includes(query)) ||
(log.level && log.level.toLowerCase().includes(query)) ||
(log.timestamp && log.timestamp.toLowerCase().includes(query))
);
}
this.$nextTick(() => this.scrollToBottom());
},
formatTimestamp(timestamp) {
if (!timestamp) return '';
try {
return new Date(timestamp).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
} catch (e) {
return timestamp; // 解析失败时返回原始时间戳
}
},
scrollToBottom() {
if (this.autoScroll) {
this.$nextTick(() => {
const logDiv = this.$el.querySelector('.log-content');
if (logDiv) {
logDiv.scrollTop = logDiv.scrollHeight;
} }
})
.catch(error => {
console.error('加载日志出错:', error);
this.$message.error('加载日志出错');
})
.finally(() => {
this.loading = false;
}); });
}
}, },
handleRefreshInterval() { handleRefreshInterval() {
this.clearRefreshTimer(); this.clearRefreshTimer();
@@ -166,53 +105,30 @@
}); });
</script> </script>
<style scoped> <style>
.log-card { .log-card {
margin: 20px; margin-bottom: 20px;
border-radius: 8px; }
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.log-content { .log-content {
max-height: 600px; max-height: 700px;
overflow-y: auto; overflow-y: auto;
background-color: #f8fafc; overflow-x: auto;
padding: 16px; background-color: #f5f5f5;
border-radius: 4px; padding: 10px;
font-family: 'Courier New', Courier, monospace; border-radius: 4px;
} }
.log-entry { .log-content pre {
display: flex; margin: 0;
padding: 8px 0; white-space: pre;
border-bottom: 1px solid #e2e8f0; word-break: normal;
} word-wrap: normal;
font-family: monospace;
}
.log-timestamp { .empty-log {
flex: 0 0 180px; padding: 40px 0;
color: #6b7280; }
font-size: 0.9rem;
}
.log-level {
flex: 0 0 100px;
font-weight: 600;
font-size: 0.9rem;
}
.log-message {
flex: 1;
word-break: break-all;
font-size: 0.9rem;
}
.log-level-INFO { background-color: #e6f3ff; }
.log-level-WARNING { background-color: #fefcbf; }
.log-level-ERROR { background-color: #fee2e2; }
.empty-log {
padding: 40px 0;
text-align: center;
}
</style> </style>
{% endblock %} {% endblock %}