Appearance
BizLogViewer 日志查看器
等宽流式日志查看:级别着色(debug/info/success/warning/danger)、关键字过滤 + 命中高亮、级别过滤、自动滚动可暂停、行号、wrap 换行模式;万行级数据走虚拟滚动(只渲染可视窗口);一键下载当前过滤结果为 .log。任务运行日志、网关访问日志、构建部署输出、审计留档。
任务运行日志
全部级别
110:00:01任务启动:order-sync
210:00:02fetch成功拉取上游订单 1200 条成功
310:00:04警告3 条订单缺少收货地址,已跳过
410:00:06db调试批量写入 batch#1 rows=500
510:00:08错误写入 batch#2 失败:连接超时
610:00:09正在重试 batch#2 ...
710:00:12成功任务完成:成功 1197 / 跳过 3,耗时 11.2s
结构化条目(time / level / tag / message)+ 内置工具栏。
<script setup lang="ts">
/** 场景:任务运行日志——结构化条目(时间/级别/标签)混合渲染,自动滚动到底 */
import { ref } from 'vue'
import { BizLogViewer } from 'vue3-ep-biz-lib'
const logs = ref([
{ time: '10:00:01', level: 'info' as const, message: '任务启动:order-sync' },
{ time: '10:00:02', level: 'success' as const, tag: 'fetch', message: '拉取上游订单 1200 条成功' },
{ time: '10:00:04', level: 'warning' as const, message: '3 条订单缺少收货地址,已跳过' },
{ time: '10:00:06', level: 'debug' as const, tag: 'db', message: '批量写入 batch#1 rows=500' },
{ time: '10:00:08', level: 'danger' as const, message: '写入 batch#2 失败:连接超时' },
{ time: '10:00:09', level: 'info' as const, message: '正在重试 batch#2 ...' },
{ time: '10:00:12', level: 'success' as const, message: '任务完成:成功 1197 / 跳过 3,耗时 11.2s' },
])
</script>
<template>
<div style="max-width: 760px">
<BizLogViewer :logs="logs" />
</div>
</template>
显示代码
级别过滤 + 关键字高亮
全部级别
1[nginx] GET /api/order 200 12ms
2[nginx] GET /api/pay 500 3201ms
3[app] pay callback verify failed: signature mismatch
4[nginx] POST /api/pay/callback 200 5ms
5[app] order#8821 stock not enough
6[nginx] GET /api/order 200 9ms
在工具栏输入「pay」试试:命中片段黄色高亮,条数统计同步过滤结果。
纯字符串日志也可直接传入;输入「pay」查看高亮。
<script setup lang="ts">
/** 场景:级别过滤 + 关键字高亮——排障时只看错误并定位关键字 */
import { ref } from 'vue'
import { BizLogViewer } from 'vue3-ep-biz-lib'
const logs = ref<string[]>([
'[nginx] GET /api/order 200 12ms',
'[nginx] GET /api/pay 500 3201ms',
'[app] pay callback verify failed: signature mismatch',
'[nginx] POST /api/pay/callback 200 5ms',
'[app] order#8821 stock not enough',
'[nginx] GET /api/order 200 9ms',
])
</script>
<template>
<div style="max-width: 760px">
<BizLogViewer :logs="logs" :height="240" />
<p style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
在工具栏输入「pay」试试:命中片段黄色高亮,条数统计同步过滤结果。
</p>
</div>
</template>
显示代码
实时日志流
全部级别
1监听已建立,等待构建事件...
定时追加 + 自动滚动;关掉「自动滚动」开关可回看历史不被拽底。
<script setup lang="ts">
/** 场景:实时日志流——定时追加 + 自动滚动开关(关掉开关可回看历史不被拽到底部) */
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { ElButton } from 'element-plus'
import { BizLogViewer } from 'vue3-ep-biz-lib'
import type { BizLogViewerInstance } from 'vue3-ep-biz-lib'
const logs = ref<string[]>(['监听已建立,等待构建事件...'])
const viewerRef = ref<BizLogViewerInstance>()
let timer: number | undefined
let step = 0
const STEPS = [
'[build] 拉取代码 main@827332d',
'[build] 依赖安装完成 (12s)',
'[build] vite build 开始',
'[build] transforming 1024 modules',
'[build] render chunks 87/87',
'[deploy] 产物上传 EdgeOne ...',
'[deploy] 预发环境验证通过',
'[deploy] 发布完成,耗时 96s',
]
onMounted(() => {
timer = window.setInterval(() => {
logs.value.push(STEPS[step % STEPS.length])
step += 1
if (step > 20) {
window.clearInterval(timer)
timer = undefined
logs.value.push('[done] 日志流结束')
}
}, 900)
})
onBeforeUnmount(() => {
if (timer !== undefined) window.clearInterval(timer)
})
</script>
<template>
<div style="max-width: 760px">
<BizLogViewer ref="viewerRef" :logs="logs" :height="260" />
<div style="margin-top: 8px; display: flex; gap: 12px; align-items: center">
<ElButton size="small" @click="viewerRef?.scrollToBottom()">滚动到底部</ElButton>
<ElButton size="small" @click="viewerRef?.clearFilter()">清空过滤条件</ElButton>
</div>
</div>
</template>
显示代码
万行虚拟滚动
共 10,000 条 直接拖动滚动条到底部,DOM 中仅渲染可视区附近的几十行
全部级别
100:00:00错误请求 #1 处理完成,耗时 1ms
200:00:01请求 #2 处理完成,耗时 2ms
300:00:02请求 #3 处理完成,耗时 3ms
400:00:03成功请求 #4 处理完成,耗时 4ms
500:00:04请求 #5 处理完成,耗时 5ms
600:00:05调试请求 #6 处理完成,耗时 6ms
700:00:06成功请求 #7 处理完成,耗时 7ms
800:00:07请求 #8 处理完成,耗时 8ms
900:00:08请求 #9 处理完成,耗时 9ms
1000:00:09成功请求 #10 处理完成,耗时 10ms
1100:00:10调试请求 #11 处理完成,耗时 11ms
1200:00:11警告请求 #12 处理完成,耗时 12ms
1300:00:12成功请求 #13 处理完成,耗时 13ms
1400:00:13请求 #14 处理完成,耗时 14ms
1500:00:14请求 #15 处理完成,耗时 15ms
1600:00:15调试请求 #16 处理完成,耗时 16ms
1700:00:16请求 #17 处理完成,耗时 17ms
1800:00:17错误请求 #18 处理完成,耗时 18ms
1900:00:18成功请求 #19 处理完成,耗时 19ms
10,000 条日志滚动全程流畅(DOM 仅渲染可视区附近几十行)。
<script setup lang="ts">
/** 场景:万行级日志——虚拟滚动只渲染可视窗口,滚动全程流畅 */
import { ref } from 'vue'
import { ElTag } from 'element-plus'
import { BizLogViewer } from 'vue3-ep-biz-lib'
import type { BizLogLevel } from 'vue3-ep-biz-lib'
const LEVELS: BizLogLevel[] = ['info', 'success', 'warning', 'danger', 'debug']
const logs = ref(
Array.from({ length: 10000 }, (_, i) => {
const level = LEVELS[i % 17 === 0 ? 3 : i % 11 === 0 ? 2 : i % 5 === 0 ? 4 : i % 3 === 0 ? 1 : 0]
return {
time: `${String(Math.floor(i / 3600) % 24).padStart(2, '0')}:${String(Math.floor(i / 60) % 60).padStart(2, '0')}:${String(i % 60).padStart(2, '0')}`,
level,
message: `请求 #${i + 1} 处理完成,耗时 ${(i % 200 + 1)}ms`,
}
})
)
</script>
<template>
<div style="max-width: 760px">
<div style="margin-bottom: 8px; display: flex; gap: 8px; align-items: center">
<ElTag type="info" size="small">共 10,000 条</ElTag>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
直接拖动滚动条到底部,DOM 中仅渲染可视区附近的几十行
</span>
</div>
<BizLogViewer :logs="logs" :height="300" />
</div>
</template>
显示代码
下载过滤结果
全部级别
109:12:00auditadmin 登录成功(IP 10.8.2.31)
209:15:22audit警告guest 尝试访问 /admin/config 被拒绝
309:16:01audit错误guest 连续 5 次失败,触发临时封禁
409:20:44audit成功admin 修改权限组「运营」成功
工具栏「下载」导出的是当前过滤后的日志(先过滤关键字再下载即可只留关注的片段)。
审计场景一键下载当前过滤结果为 .log 文件。
<script setup lang="ts">
/** 场景:审计留档——一键下载当前过滤结果为 .log 文件 */
import { ref } from 'vue'
import { BizLogViewer } from 'vue3-ep-biz-lib'
const logs = ref([
{ time: '09:12:00', level: 'info' as const, tag: 'audit', message: 'admin 登录成功(IP 10.8.2.31)' },
{ time: '09:15:22', level: 'warning' as const, tag: 'audit', message: 'guest 尝试访问 /admin/config 被拒绝' },
{ time: '09:16:01', level: 'danger' as const, tag: 'audit', message: 'guest 连续 5 次失败,触发临时封禁' },
{ time: '09:20:44', level: 'success' as const, tag: 'audit', message: 'admin 修改权限组「运营」成功' },
])
</script>
<template>
<div style="max-width: 760px">
<BizLogViewer :logs="logs" :height="220" />
<p style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
工具栏「下载」导出的是<strong>当前过滤后</strong>的日志(先过滤关键字再下载即可只留关注的片段)。
</p>
</div>
</template>
显示代码
换行模式(长堆栈)
全部级别
114:02:11错误TypeError: Cannot read properties of undefined (reading 'orderId') at renderOrder (/app/src/views/order/List.vue:132:19) at callWithErrorHandling (/app/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:199:19)
214:02:12警告组件 OrderList 渲染异常已由错误边界捕获,页面其余模块不受影响
wrap 开启换行完整展示超长堆栈(此时关闭虚拟滚动,适合中低频日志)。<script setup lang="ts">
/** 场景:换行模式——长堆栈信息完整展示(wrap 开启后禁用虚拟滚动,适合中低频日志) */
import { ref } from 'vue'
import { BizLogViewer } from 'vue3-ep-biz-lib'
const logs = ref([
{
time: '14:02:11',
level: 'danger' as const,
message:
'TypeError: Cannot read properties of undefined (reading \'orderId\') at renderOrder (/app/src/views/order/List.vue:132:19) at callWithErrorHandling (/app/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:199:19)',
},
{
time: '14:02:12',
level: 'warning' as const,
message: '组件 OrderList 渲染异常已由错误边界捕获,页面其余模块不受影响',
},
])
</script>
<template>
<div style="max-width: 760px">
<BizLogViewer :logs="logs" :height="280" wrap :line-height="22" />
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| logs | (string | BizLogItem)[] | [] | 日志集合,字符串与结构化条目可混用 |
| height | number | 320 | 视口高度(px) |
| auto-scroll | boolean | true | 新日志到达时自动滚动到底(工具栏可关) |
| show-toolbar | boolean | true | 是否展示工具栏 |
| show-line-numbers | boolean | true | 是否展示行号 |
| show-download | boolean | true | 是否展示下载按钮 |
| line-height | number | 22 | 行高(虚拟滚动基准) |
| wrap | boolean | false | 换行模式(关闭虚拟滚动) |
ts
interface BizLogItem {
time?: string // 时间戳文本(业务侧格式化)
level?: 'debug' | 'info' | 'success' | 'warning' | 'danger'
tag?: string // 附加标签(网关名 / 线程名)
message: string // 正文(必填)
}1
2
3
4
5
6
2
3
4
5
6
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| scrollToTop | () => Promise<void> | 滚动到顶部 |
| scrollToBottom | () => Promise<void> | 滚动到底部 |
| clearFilter | () => void | 清空关键字与级别过滤 |
| filteredCount | ComputedRef<number> | 当前过滤后条数 |