feat(system-logs): enhance ANSI log viewer with log level colorization

- Add LOG_LEVEL_COLORS configuration mapping for DEBUG, INFO, WARNING, WARN, ERROR, and CRITICAL levels
- Implement hasAnsiCodes() function to detect presence of ANSI escape sequences in log content
- Add colorizeLogContent() function to parse plain text logs and apply color styling based on log levels
- Support dual-mode log parsing: ANSI color codes and plain text log level detection
- Rename converter to ansiConverter for clarity and consistency
- Change newline handling from true to false for manual line break control
- Apply color-coded styling to timestamps (gray), log levels (level-specific colors), and messages
- Add bold font-weight styling for CRITICAL level logs for better visibility
This commit is contained in:
yyhuni
2026-01-05 16:27:31 +08:00
parent 283b28b46a
commit ba3a9b709d

View File

@@ -8,11 +8,21 @@ interface AnsiLogViewerProps {
className?: string
}
// 日志级别颜色配置
const LOG_LEVEL_COLORS: Record<string, string> = {
DEBUG: "#4ec9b0", // cyan
INFO: "#6a9955", // green
WARNING: "#dcdcaa", // yellow
WARN: "#dcdcaa", // yellow
ERROR: "#f44747", // red
CRITICAL: "#f44747", // red (bold handled separately)
}
// 创建 ANSI 转换器实例
const converter = new AnsiToHtml({
const ansiConverter = new AnsiToHtml({
fg: "#d4d4d4",
bg: "#1e1e1e",
newline: true,
newline: false, // 我们自己处理换行
escapeXML: true,
colors: {
0: "#1e1e1e", // black
@@ -34,14 +44,57 @@ const converter = new AnsiToHtml({
},
})
// 检测内容是否包含 ANSI 颜色码
function hasAnsiCodes(text: string): boolean {
// ANSI 转义序列通常以 ESC[ 开头(\x1b[ 或 \u001b[
return /\x1b\[|\u001b\[/.test(text)
}
// 解析纯文本日志内容,为日志级别添加颜色
function colorizeLogContent(content: string): string {
// 匹配日志格式: [时间] [级别] [模块:行号] 消息
// 例如: [2025-01-05 10:30:00] [INFO] [apps.scan:123] 消息内容
const logLineRegex = /^(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]) (\[(DEBUG|INFO|WARNING|WARN|ERROR|CRITICAL)\]) (.*)$/
return content
.split("\n")
.map((line) => {
const match = line.match(logLineRegex)
if (match) {
const [, timestamp, levelBracket, level, rest] = match
const color = LOG_LEVEL_COLORS[level] || "#d4d4d4"
// ansiConverter.toHtml 已经处理了 HTML 转义
const escapedTimestamp = ansiConverter.toHtml(timestamp)
const escapedLevelBracket = ansiConverter.toHtml(levelBracket)
const escapedRest = ansiConverter.toHtml(rest)
// 时间戳灰色,日志级别带颜色,其余默认色
return `<span style="color:#808080">${escapedTimestamp}</span> <span style="color:${color};font-weight:${level === "CRITICAL" ? "bold" : "normal"}">${escapedLevelBracket}</span> ${escapedRest}`
}
// 非标准格式的行,也进行 HTML 转义
return ansiConverter.toHtml(line)
})
.join("\n")
}
export function AnsiLogViewer({ content, className }: AnsiLogViewerProps) {
const containerRef = useRef<HTMLPreElement>(null)
const isAtBottomRef = useRef(true) // 跟踪用户是否在底部
// 将 ANSI 转换为 HTML
// 解析日志并添加颜色
// 支持两种模式ANSI 颜色码和纯文本日志级别解析
const htmlContent = useMemo(() => {
if (!content) return ""
return converter.toHtml(content)
// 如果包含 ANSI 颜色码,直接转换
if (hasAnsiCodes(content)) {
return ansiConverter.toHtml(content)
}
// 否则解析日志级别添加颜色
return colorizeLogContent(content)
}, [content])
// 监听滚动事件,检测用户是否在底部