Merge pull request #1152 from AstrBotDevs/feat-log-filter

 feat: 更新日志发布机制,支持日志级别和内容的字典格式,增加日志筛选功能
This commit is contained in:
Soulter
2025-04-05 15:49:09 +08:00
committed by GitHub
5 changed files with 102 additions and 16 deletions
+8 -3
View File
@@ -108,11 +108,12 @@ class LogBroker:
"""
self.subscribers.remove(q)
def publish(self, log_entry: str):
def publish(self, log_entry: dict):
"""发布新日志到所有订阅者, 使用非阻塞方式投递, 避免一个订阅者阻塞整个系统
Args:
log_entry (str): 日志消息, 可以是字符串或字典
log_entry (dict): 日志消息, 包含日志级别和日志内容.
example: {"level": "INFO", "data": "This is a log message.", "time": "2023-10-01 12:00:00"}
"""
self.log_cache.append(log_entry)
for q in self.subscribers:
@@ -140,7 +141,11 @@ class LogQueueHandler(logging.Handler):
record (logging.LogRecord): 日志记录对象, 包含日志信息
"""
log_entry = self.format(record)
self.log_broker.publish(log_entry)
self.log_broker.publish({
"level": record.levelname,
"time": record.asctime,
"data": log_entry,
})
class LogManager:
+1 -1
View File
@@ -20,7 +20,7 @@ class LogRoute(Route):
message = await queue.get()
payload = {
"type": "log",
"data": message,
**message # see astrbot/core/log.py
}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
except asyncio.CancelledError:
@@ -3,9 +3,20 @@ import { useCommonStore } from '@/stores/common';
</script>
<template>
<div id="term"
style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:auto">
<div>
<!-- 添加筛选级别控件 -->
<div class="filter-controls mb-2">
<v-chip-group v-model="selectedLevels" column multiple>
<v-chip v-for="level in logLevels" :key="level" :color="getLevelColor(level)" filter
:text-color="level === 'DEBUG' || level === 'INFO' ? 'black' : 'white'">
{{ level }}
</v-chip>
</v-chip-group>
</div>
<div id="term" style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:auto; height: 100%">
</div>
</div>
</template>
<script>
@@ -25,7 +36,16 @@ export default {
'default': 'color: #FFFFFF;'
},
logCache: useCommonStore().getLogCache(),
historyNum_: -1
historyNum_: -1,
logLevels: ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
selectedLevels: [0, 1, 2, 3, 4], // 默认选中所有级别
levelColors: {
'DEBUG': 'grey',
'INFO': 'blue-lighten-3',
'WARNING': 'amber',
'ERROR': 'red',
'CRITICAL': 'purple'
}
}
},
props: {
@@ -37,7 +57,16 @@ export default {
watch: {
logCache: {
handler(val) {
this.printLog(val[this.logCache.length - 1])
const lastLog = val[this.logCache.length - 1];
if (lastLog && this.isLevelSelected(lastLog.level)) {
this.printLog(lastLog.data);
}
},
deep: true
},
selectedLevels: {
handler() {
this.refreshDisplay();
},
deep: true
}
@@ -50,6 +79,31 @@ export default {
}
},
methods: {
getLevelColor(level) {
return this.levelColors[level] || 'grey';
},
isLevelSelected(level) {
for (let i = 0; i < this.selectedLevels.length; ++i) {
let level_ = this.logLevels[this.selectedLevels[i]]
if (level_ === level) {
return true;
}
}
return false;
},
refreshDisplay() {
// 清空现有的显示
const termElement = document.getElementById('term');
if (termElement) {
termElement.innerHTML = '';
}
// 重新显示符合筛选条件的日志
this.init();
},
delayInit() {
if (this.logCache.length === 0) {
setTimeout(() => {
@@ -64,18 +118,21 @@ export default {
this.historyNum_ = parseInt(this.historyNum)
let i = 0
for (let log of this.logCache) {
if (this.isLevelSelected(log.level)) { // 只显示选中级别的日志
if (this.historyNum_ != -1 && i >= this.logCache.length - this.historyNum_) {
this.printLog(log)
this.printLog(log.data)
++i
} else if (this.historyNum_ == -1) {
this.printLog(log)
this.printLog(log.data)
}
}
}
},
toggleAutoScroll() {
this.autoScroll = !this.autoScroll;
},
printLog(log) {
// append 一个 span 标签到 termblock 的方式
let ele = document.getElementById('term')
@@ -88,14 +145,38 @@ export default {
break
}
}
span.style = style + 'display: block; font-size: 12px; font-family: Consolas, monospace; white-space: pre-wrap;'
span.classList.add('fade-in')
span.innerText = log
span.innerText = `${log}`;
ele.appendChild(span)
if (this.autoScroll) {
if (this.autoScroll ) {
ele.scrollTop = ele.scrollHeight
}
}
},
}
</script>
</script>
<style scoped>
.filter-controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 8px;
}
.fade-in {
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
+2 -2
View File
@@ -71,8 +71,8 @@ export const useCommonStore = defineStore({
let data_json = JSON.parse(data)
if (data_json.type === 'log') {
let log = data_json.data
this.log_cache.push(log);
// let log = data_json.data
this.log_cache.push(data_json);
if (this.log_cache.length > this.log_cache_max_len) {
this.log_cache.shift();
}
+1 -1
View File
@@ -44,7 +44,7 @@ import axios from 'axios';
</v-dialog>
</div>
</div>
<ConsoleDisplayer ref="consoleDisplayer" style="height: calc(100vh - 160px); " />
<ConsoleDisplayer ref="consoleDisplayer" style="height: calc(100vh - 220px); " />
</div>
</template>
<script>