Vue 3 单页应用在用户浏览器中运行时,会产生大量的错误堆栈、接口异常、性能指标和用户行为数据。很多团队习惯把这些日志直接通过后端接口收集,但这种方式会让 Web 服务承担额外的写入压力和字段清洗成本,而且不同环境、不同版本的日志格式往往难以统一。Fluent Bit 是一个用 C 语言编写的轻量级日志处理器,常驻内存只有几十 MB,非常适合放在日志链路的入口处做接收、过滤和转发。把 Fluent Bit 引入 Vue 3 工程化体系后,可以形成浏览器采集、Fluent Bit 清洗、可观测平台存储的清晰链路。

这条链路的核心思路是:前端不直接对接 Elasticsearch 或 Loki,而是先把日志推给 Fluent Bit 的 HTTP 接口,由它统一处理后再异步写入存储端。这样做的好处是前端只需要关心发送动作,字段转换、脱敏、丢弃噪声数据都交给 Fluent Bit 完成,运维侧也能独立升级日志管道而不影响业务代码。
一、为什么选择 Fluent Bit 而不是直接写入日志后端
前端日志最大的特点是突发性强。页面发布后如果出现某个全局错误,短时间内可能产生成百上千条重复上报。直接把这些请求写入 Elasticsearch 或数据库,容易造成写入队列堆积,甚至拖垮日志存储服务。Fluent Bit 的 input 插件带有缓冲和背压机制,可以在流量高峰时先把日志缓存在内存或磁盘,再按下游承受能力逐步发送。
另一个原因是多环境支持。开发、测试、生产环境的日志字段往往不同,比如生产环境需要补充用户 ID、版本号、灰度标识,而开发环境可能只需要保留简化的调试信息。Fluent Bit 可以通过 modify 和 lua 过滤器对不同 tag 的日志做差异化处理,不用在前端代码里写一堆 if 判断。相比 Logstash,Fluent Bit 的部署包小得多,启动速度也更快,在 Kubernetes 或裸机上都能轻松运行。
在 Vue 3 工程里引入 Fluent Bit,并不是要在浏览器中运行它,而是把它作为日志管道的一个独立组件。前端 SDK 负责把日志整理成统一 JSON 格式,通过 HTTP 上报到 Fluent Bit 监听的端口。这样前端业务代码与日志处理逻辑解耦,也为后续接入更多数据源留出空间。
二、设计 Vue 3 日志 SDK:从 console 到结构化 JSON
浏览器的 console 输出本身是无结构的,Fluent Bit 如果直接接收这种纯文本,后续检索会很困难。因此第一步是在 Vue 3 项目中封装一个小型日志采集模块,统一捕获 console.error、console.warn、未捕获异常、未处理的 Promise 拒绝以及路由切换事件。
下面这段代码演示了如何实现一个基础 Logger。它会把所有日志统一成包含 timestamp、level、message、url、stack 等字段的 JSON 对象,并缓存在数组中,当达到一定数量或页面即将卸载时批量上报。
const LOG_ENDPOINT = '/api/logs';
const MAX_QUEUE_SIZE = 20;
const logger = {
queue: [],
timer: null,
init() {
const originalError = console.error.bind(console);
const originalWarn = console.warn.bind(console);
console.error = (...args) => {
this.capture('error', args.join(' '));
originalError(...args);
};
console.warn = (...args) => {
this.capture('warn', args.join(' '));
originalWarn(...args);
};
window.addEventListener('error', (event) => {
this.capture('error', event.message, {
stack: event.error ? event.error.stack : '',
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});
window.addEventListener('unhandledrejection', (event) => {
this.capture('error', 'Unhandled promise rejection: ' + event.reason);
});
window.addEventListener('beforeunload', () => {
this.flush(true);
});
},
capture(level, message, extra = {}) {
this.queue.push({
timestamp: new Date().toISOString(),
level,
message,
url: location.href,
userAgent: navigator.userAgent,
...extra
});
if (this.queue.length >= MAX_QUEUE_SIZE) {
this.flush(false);
}
},
flush(useBeacon) {
if (!this.queue.length) return;
const payload = JSON.stringify(this.queue);
this.queue = [];
if (useBeacon && navigator.sendBeacon) {
navigator.sendBeacon(LOG_ENDPOINT, new Blob([payload], { type: 'application/json' }));
} else {
fetch(LOG_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true
});
}
}
};
export default logger;
上面的代码中,箭头函数写成 => 是 HTML 转义后的显示结果,实际运行时会还原为 =>。如果你希望更细粒度的控制,可以在 Vue Router 的 afterEach 钩子里记录页面浏览事件,并在 Axios 拦截器中记录接口耗时。这些数据同样通过 logger.capture 方法进入统一的队列。
SDK 的关键点是批量发送和 keepalive。浏览器在页面关闭时,普通的 fetch 请求可能会被中断,sendBeacon 则专门用于这种场景,能保证最后一批日志被送出。生产环境建议将 LOG_ENDPOINT 指向独立的日志域名,避免影响业务主域名的请求。
三、Fluent Bit 配置:HTTP 输入、解析与过滤
Fluent Bit 提供了丰富的插件,其中 http 输入插件正好适合接收前端 JSON 日志。它会在指定端口监听 HTTP POST 请求,把请求体内容当作一条日志记录。下面是一份最小化的 fluent-bit.conf 配置。
[INPUT]
name http
listen 0.0.0.0
port 9880
tag frontend.logs
buffer_max_size 512k
[FILTER]
name parser
match frontend.logs
key_name log
parser vue_json
[OUTPUT]
name stdout
match *
这段配置中,INPUT 段使用 http 插件监听 9880 端口,所有发往这个端口的请求都会被打上 frontend.logs 标签。FILTER 段使用 parser 插件,从 log 字段中按照 vue_json 解析器提取结构化数据。OUTPUT 暂时输出到 stdout,方便调试。
要让 parser 正常工作,还需要在 fluent-bit.conf 同目录或引用的 parsers.conf 中定义 vue_json 解析器。它可以使用 json 解析器直接解析 JSON 字符串。对于前端 SDK 上报的数组格式,可以在 Lua 过滤器中拆分成多条日志,或者让 SDK 逐条上报。更推荐的做法是 SDK 每条日志单独发送,虽然请求数多一点,但 Fluent Bit 处理单条 JSON 的效率更高,也便于后续按条过滤。
[PARSER]
Name vue_json
Format json
Time_Key timestamp
Time_Format %Y-%m-%dT%H:%M:%S.%LZ
实际生产中,我们还需要对日志做字段补充和噪声过滤。modify 过滤器可以给日志添加当前环境标识、重命名字段,甚至删除敏感字段。lua 过滤器则适合处理复杂逻辑,比如根据 userAgent 判断设备类型、过滤掉来自爬虫的日志。下面是一段组合配置。
[FILTER]
name modify
match frontend.logs
add env production
remove userAgent
rename message log_message
[FILTER]
name lua
match frontend.logs
script /etc/fluent-bit/lua/clean.lua
call clean_log
[OUTPUT]
name loki
match frontend.logs
host 127.0.0.1
port 3100
labels job=vue3-frontend
这里通过 modify 添加了 env 字段并去掉了体积较大且价值低的 userAgent,通过 lua 调用 clean_log 函数做进一步清洗。Output 使用 Loki 插件,适合存储高基数、低价值的日志数据。如果团队已经使用 Elasticsearch,把 OUTPUT 替换为 es 插件并配置索引即可。
clean_log.lua 可以只保留错误级别的日志,并把 message 字段截断到 2000 字符,避免超长堆栈占用存储。
function clean_log(tag, timestamp, record)
if record.level ~= 'error' then
return -1, 0, 0
end
if string.len(record.log_message or '') > 2000 then
record.log_message = string.sub(record.log_message, 1, 2000)
end
return 1, timestamp, record
end
四、工程化部署与性能优化
Fluent Bit 最好以独立进程或容器运行,而不是嵌进 Vue 3 的 Node.js 服务中。使用 Docker Compose 可以快速搭建日志接收端。下面是一个配合 Nginx 的 Compose 片段,Nginx 负责反向代理前端上报请求到 Fluent Bit,同时处理 CORS 头。
version: '3.8'
services:
fluent-bit:
image: cr.fluentbit.io/fluent/fluent-bit:latest
container_name: fluent-bit
volumes:
- ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
- ./parsers.conf:/fluent-bit/etc/parsers.conf
- ./lua:/etc/fluent-bit/lua
ports:
- "9880:9880"
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
Nginx 配置需要把 /api/logs 路径代理到 Fluent Bit 的 9880 端口,并加上跨域响应头。由于 Fluent Bit 的 HTTP 输入只接受 POST 请求,这里要限制方法。
server {
listen 80;
server_name logs.ipipp.com;
location /api/logs {
proxy_pass http://fluent-bit:9880;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
add_header Access-Control-Allow-Origin "https://www.ipipp.com";
add_header Access-Control-Allow-Methods "POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type";
if ($request_method = OPTIONS) {
return 204;
}
}
}
注意上面配置中的 logs.ipipp.com 应替换为你自己的域名。性能优化方面,可以通过调整 http 输入插件的 buffer_max_size、workers 数量以及输出插件的存储策略来提升吞吐。Fluent Bit 默认单 worker 就能处理每秒数千条日志,如果前端流量很大,可以设置 workers 为 2 或 4,但要保证配置文件中的插件支持多 worker。对于突发流量,建议把 storage.type 设置为 filesystem,让磁盘承担一部分缓冲。
监控 Fluent Bit 自身状态也很重要。可以增加一个 Prometheus 输出插件暴露指标,或者使用 Fluent Bit 内置的 metrics 接口。关注日志丢弃数量、缓冲区占用和处理延迟,能提前发现管道瓶颈。
五、常见问题与避坑
跨域是前端日志接入时最常踩的坑。Vue 3 开发环境下,如果直接通过 localhost:8080 向 Fluent Bit 的 localhost:9880 发送请求,浏览器会触发 CORS 校验。开发阶段可以在 vue.config.js 中配置 devServer.proxy,把 /api/logs 代理到 Fluent Bit。生产环境则需要在 Nginx 中显式返回 Access-Control-Allow-Origin 头,并且不能写成 *,因为携带 keepalive 的请求需要明确来源。
另一个常见问题是时间字段。前端上报的 timestamp 是 ISO 8601 字符串,Fluent Bit 默认不把它识别为日志时间,导致最终存储的时间是 Fluent Bit 接收时间。解决方法是像前面那样在 PARSER 中配置 Time_Key 和 Time_Format。如果时间格式带毫秒且有时区偏移,Time_Format 可以写为 %Y-%m-%dT%H:%M:%S.%L%z,确保解析正确。
还有日志体积问题。浏览器堆栈信息可能非常长,如果全部存储会浪费大量空间。在 SDK 端可以截断 stack 字段,在 Fluent Bit 端也可以通过 lua 过滤器或 modify 插件删除无用字段。建议只保留错误级别日志和关键性能数据,把 warn 和 info 级别在源头丢弃或降采样。
六、小结
把 Fluent Bit 引入 Vue 3 日志体系,核心价值在于用极低的资源消耗完成日志清洗和路由,避免前端直接对接后端存储带来的耦合。前端 SDK 只负责可靠地发送结构化数据,字段处理交给 Fluent Bit 的过滤器链。
本文给出的配置和代码可以直接用于中小规模项目。如果日志量增长到每天数亿条,还可以扩展 Fluent Bit 为多实例部署,并通过 Kafka 作为缓冲层。无论选择哪种后端,保持前端日志结构化和管道可观测都是关键。
Vue 3日志采集Fluent Bit前端工程化修改时间:2026-10-03 10:28:52