各位观众,老司机又来开车了!今天咱们聊点儿实际的,如何在 Vue 项目里打造一个靠谱的日志管理系统。这玩意儿就像汽车的黑匣子,平时默默无闻,关键时刻能帮你找到问题的根源,避免背锅。
第一章:需求分析与架构设计
先别急着撸代码,咱们得搞清楚目标:
- 日志级别: 必须要有不同的日志级别,比如
DEBUG
、INFO
、WARN
、ERROR
,方便我们筛选重要信息。 - 本地存储: 日志要先存在本地,万一网络不好,数据也不会丢。
- 自动上报: 当日志达到一定数量或者满足特定条件时,自动上报到服务器。
- 可视化展示: 在服务器端,能清晰地看到各种日志信息,最好还能按级别、时间等进行过滤。
基于以上需求,我们可以设计一个简单的架构:
graph LR
A[Vue 前端] --> B(日志管理模块);
B --> C{LocalStorage};
B --> D[日志上报模块];
D --> E(服务器端);
E --> F{数据库};
E --> G[可视化展示];
第二章:前端日志管理模块
咱们先从前端开始,核心是创建一个日志管理模块,负责记录、存储和上报日志。
2.1 日志级别定义
先定义一个枚举类型的日志级别,方便管理:
// src/utils/logger.js
const LogLevel = {
DEBUG: 'debug',
INFO: 'info',
WARN: 'warn',
ERROR: 'error'
};
Object.freeze(LogLevel); // 防止被修改
export default LogLevel;
2.2 日志记录函数
接下来,创建一个 Logger
类,提供记录不同级别日志的方法:
// src/utils/logger.js
import LogLevel from './log-level';
class Logger {
constructor(options = {}) {
this.level = options.level || LogLevel.DEBUG; // 默认 DEBUG 级别
this.appName = options.appName || 'default-app'; // 应用名称
this.storageKey = `log-${this.appName}`; // localStorage key
this.logs = this.loadLogs(); // 从 localStorage 加载日志
this.maxLogs = options.maxLogs || 1000; // 最大日志数量
this.uploadThreshold = options.uploadThreshold || 50; // 上报阈值
this.uploadUrl = options.uploadUrl || '/api/logs'; // 上报接口
this.uploadInterval = options.uploadInterval || 60000; // 上报间隔
this.uploadTimer = null; // 上报定时器
this.startAutoUpload();
}
// 判断是否允许记录该级别的日志
isLevelEnabled(level) {
const levelMap = {
[LogLevel.DEBUG]: 1,
[LogLevel.INFO]: 2,
[LogLevel.WARN]: 3,
[LogLevel.ERROR]: 4
};
return levelMap[level] >= levelMap[this.level];
}
// 记录日志
log(level, message, ...args) {
if (!this.isLevelEnabled(level)) {
return;
}
const logEntry = {
level,
message,
timestamp: new Date().toISOString(),
args
};
this.logs.push(logEntry);
// 限制日志数量
if (this.logs.length > this.maxLogs) {
this.logs.shift(); // 移除最旧的日志
}
this.saveLogs(); // 保存到 localStorage
console.log(`[${level.toUpperCase()}] ${message}`, ...args); // 同时输出到控制台
if (this.logs.length >= this.uploadThreshold) {
this.uploadLogs();
}
}
debug(message, ...args) {
this.log(LogLevel.DEBUG, message, ...args);
}
info(message, ...args) {
this.log(LogLevel.INFO, message, ...args);
}
warn(message, ...args) {
this.log(LogLevel.WARN, message, ...args);
}
error(message, ...args) {
this.log(LogLevel.ERROR, message, ...args);
}
// 从 localStorage 加载日志
loadLogs() {
try {
const logs = localStorage.getItem(this.storageKey);
return logs ? JSON.parse(logs) : [];
} catch (error) {
console.error('Failed to load logs from localStorage:', error);
return [];
}
}
// 保存日志到 localStorage
saveLogs() {
try {
localStorage.setItem(this.storageKey, JSON.stringify(this.logs));
} catch (error) {
console.error('Failed to save logs to localStorage:', error);
}
}
// 上报日志
async uploadLogs() {
if (this.isUploading) {
return; // 避免重复上报
}
if (this.logs.length === 0) {
return; // 没有日志
}
this.isUploading = true;
try {
const logsToSend = [...this.logs]; // 复制一份,避免上报过程中日志被修改
this.logs = []; // 清空本地日志
const response = await fetch(this.uploadUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
appName: this.appName,
logs: logsToSend
})
});
if (!response.ok) {
// 上报失败,重新保存到 localStorage
this.logs = logsToSend.concat(this.loadLogs());
this.saveLogs();
throw new Error(`HTTP error! status: ${response.status}`);
}
this.saveLogs(); // 保存清空后的日志
console.log('Logs uploaded successfully!');
} catch (error) {
console.error('Failed to upload logs:', error);
// 上报失败,重新保存到 localStorage
this.logs = logsToSend.concat(this.loadLogs());
this.saveLogs();
} finally {
this.isUploading = false;
}
}
// 自动上报
startAutoUpload() {
if (this.uploadTimer) {
clearInterval(this.uploadTimer);
}
this.uploadTimer = setInterval(() => {
if (this.logs.length > 0) {
this.uploadLogs();
}
}, this.uploadInterval);
}
// 停止自动上报
stopAutoUpload() {
if (this.uploadTimer) {
clearInterval(this.uploadTimer);
this.uploadTimer = null;
}
}
}
export { Logger };
2.3 Vue 项目中使用
在 main.js
中初始化 Logger
实例,并挂载到 Vue 原型上:
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import { Logger } from './utils/logger';
import LogLevel from './utils/log-level';
Vue.config.productionTip = false;
const logger = new Logger({
level: LogLevel.DEBUG, // 设置日志级别
appName: 'my-vue-app', // 应用名称
uploadUrl: '/api/logs', // 上报接口地址
uploadThreshold: 10, // 达到10条日志就上报
uploadInterval: 30000 // 每隔30秒上报一次
});
Vue.prototype.$logger = logger;
new Vue({
render: h => h(App),
}).$mount('#app');
这样,在任何 Vue 组件中,都可以通过 this.$logger.debug('message')
、this.$logger.info('message')
等方法记录日志。
第三章:服务器端日志处理
接下来,我们需要在服务器端接收并存储前端上报的日志。这里以 Node.js + Express + MongoDB 为例:
3.1 安装依赖
npm install express body-parser mongoose --save
3.2 创建 Express 应用
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const app = express();
const port = 3000;
// 连接 MongoDB
mongoose.connect('mongodb://localhost:27017/vue-logs', { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));
// 定义日志 Schema
const LogSchema = new mongoose.Schema({
appName: String,
level: String,
message: String,
timestamp: String,
args: [mongoose.Schema.Types.Mixed]
});
const Log = mongoose.model('Log', LogSchema);
// 配置 body-parser
app.use(bodyParser.json());
// 定义接收日志的接口
app.post('/api/logs', async (req, res) => {
try {
const { appName, logs } = req.body;
if (!Array.isArray(logs)) {
return res.status(400).send('Invalid logs format.');
}
const logDocuments = logs.map(log => ({
...log,
appName: appName
}));
await Log.insertMany(logDocuments);
res.status(200).send('Logs received successfully.');
} catch (error) {
console.error('Failed to save logs:', error);
res.status(500).send('Failed to save logs.');
}
});
// 启动服务器
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
3.3 创建前端页面(可选)
如果想做个简单的可视化展示,可以写一个前端页面,从 MongoDB 读取日志并显示出来。这个就看个人需求了,可以用 Vue,React,或者原生JS来实现。
一个简单的Vue页面:
<template>
<div>
<h1>Logs</h1>
<div v-for="log in logs" :key="log._id">
<p><strong>{{ log.timestamp }}</strong> - {{ log.appName }} - {{ log.level }}: {{ log.message }}</p>
<p v-if="log.args && log.args.length">Arguments: {{ log.args }}</p>
<hr>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
logs: []
};
},
mounted() {
this.fetchLogs();
},
methods: {
async fetchLogs() {
try {
const response = await axios.get('/api/logs');
this.logs = response.data;
} catch (error) {
console.error('Failed to fetch logs:', error);
}
}
}
};
</script>
对应的后端接口:
app.get('/api/logs', async (req, res) => {
try {
const logs = await Log.find().sort({ timestamp: -1 }).limit(100); // 获取最新的100条日志
res.status(200).json(logs);
} catch (error) {
console.error('Failed to fetch logs:', error);
res.status(500).send('Failed to fetch logs.');
}
});
第四章:进阶功能与优化
- 日志采样: 如果日志量太大,可以考虑只采样一部分日志进行上报,比如只上报
WARN
和ERROR
级别的日志。 - 错误堆栈信息: 对于
ERROR
级别的日志,可以捕获错误堆栈信息,方便定位问题。 - 日志搜索与过滤: 在可视化展示界面,提供按时间、级别、关键字等条件搜索和过滤日志的功能。
- 告警机制: 当出现特定类型的错误日志时,自动发送告警邮件或短信。
- 性能优化: 批量上报日志,减少网络请求次数。使用更高效的存储方式,比如压缩日志文件。
总结
一个完善的日志管理系统,就像给你的 Vue 项目装了一个千里眼,能让你及时发现并解决问题。当然,这只是一个简单的示例,实际项目中还需要根据具体需求进行调整和优化。希望今天的分享对大家有所帮助!记住,好的日志系统能让你少加班,多摸鱼!