好的,没问题,我们开始吧!
Vue 应用中的可观测性集成:实现前后端日志、追踪与指标的统一收集
大家好,今天我们来聊聊 Vue 应用中可观测性的集成。 在微服务架构日益普及的今天,可观测性对于快速定位和解决问题至关重要。一个良好的可观测性方案能够帮助我们理解系统的运行状态,诊断性能瓶颈,并最终提升用户体验。 本次分享将围绕日志、追踪和指标这三个核心要素,探讨如何在 Vue 应用中实现前后端数据的统一收集。
一、可观测性的三大支柱:日志、追踪和指标
在深入代码之前,我们先简单回顾一下可观测性的三大支柱:
-
日志(Logs): 离散的事件记录,包含时间戳、消息内容以及其他相关信息。日志可以帮助我们了解发生了什么。例如,用户登录失败的日志,API 请求错误的日志等。
-
追踪(Traces): 记录请求在整个系统中的调用链路。追踪可以帮助我们了解请求是如何传播的,哪些服务参与了处理,以及每个服务的耗时。这对于诊断分布式系统中的性能问题非常有帮助。
-
指标(Metrics): 定量的数据,通常以时间序列的形式存在。指标可以帮助我们了解系统的状态,例如 CPU 使用率、内存占用、请求响应时间等。
这三者相互补充,共同构建了完整的可观测性体系。日志提供了详细的事件信息,追踪揭示了请求的生命周期,而指标则提供了宏观的系统状态。
二、前端可观测性集成
前端的可观测性主要关注用户行为、性能数据和错误信息。 Vue 应用在这方面有很大的潜力,我们可以利用 Vue 的生命周期钩子、错误处理机制以及第三方库来实现数据的收集。
1. 日志收集
前端日志通常包括用户操作日志、网络请求日志、错误日志等。我们可以通过以下方式实现:
- 自定义日志服务: 创建一个全局的日志服务,封装
console.log、console.warn、console.error等方法,并添加自定义的逻辑,例如发送日志到后端。
// src/services/logger.js
import axios from 'axios';
const LOG_LEVEL = {
DEBUG: 'debug',
INFO: 'info',
WARN: 'warn',
ERROR: 'error',
};
class Logger {
constructor(appName) {
this.appName = appName;
}
log(level, message, context = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level: level,
message: message,
appName: this.appName,
context: context,
};
console[level](message, context); // 仍然输出到控制台
// 可选:发送到后端
this.sendToServer(logEntry);
}
debug(message, context = {}) {
this.log(LOG_LEVEL.DEBUG, message, context);
}
info(message, context = {}) {
this.log(LOG_LEVEL.INFO, message, context);
}
warn(message, context = {}) {
this.log(LOG_LEVEL.WARN, message, context);
}
error(message, context = {}) {
this.log(LOG_LEVEL.ERROR, message, context);
}
sendToServer(logEntry) {
// 替换为你的后端日志接收地址
const logServerUrl = '/api/logs';
axios.post(logServerUrl, logEntry)
.catch(error => {
console.error('Failed to send log to server:', error);
});
}
}
const logger = new Logger('MyApp'); // 替换为你的应用名称
export default logger;
// 在 Vue 组件中使用
import logger from '@/services/logger';
export default {
mounted() {
logger.info('Component mounted', { component: 'MyComponent' });
},
methods: {
handleClick() {
logger.debug('Button clicked', { button: 'MyButton' });
},
},
};
- 全局错误处理: 利用 Vue 的
errorHandler选项,捕获全局的未处理异常,并记录到日志中。
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import logger from '@/services/logger';
Vue.config.productionTip = false
Vue.config.errorHandler = (err, vm, info) => {
// 处理错误
logger.error('Unhandled error', { error: err, component: vm.$options.name, info: info });
}
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
- 网络请求拦截器: 使用
axios或fetch的拦截器,记录请求的 URL、方法、状态码、响应时间等信息。
// axios 拦截器示例
import axios from 'axios';
import logger from '@/services/logger';
// 请求拦截器
axios.interceptors.request.use(
config => {
config.metadata = { startTime: new Date() };
logger.debug(`Request: ${config.method.toUpperCase()} ${config.url}`, { requestData: config.data || config.params });
return config;
},
error => {
logger.error('Request error', { error: error });
return Promise.reject(error);
}
);
// 响应拦截器
axios.interceptors.response.use(
response => {
const duration = new Date().getTime() - response.config.metadata.startTime.getTime();
logger.info(`Response: ${response.config.method.toUpperCase()} ${response.config.url} - ${response.status} (${duration}ms)`, { responseData: response.data });
return response;
},
error => {
const duration = new Date().getTime() - error.config.metadata.startTime.getTime();
logger.error(`Response error: ${error.config.method.toUpperCase()} ${error.config.url} - ${error.response?.status || 'Unknown'} (${duration}ms)`, { error: error });
return Promise.reject(error);
}
);
export default axios;
2. 追踪收集
前端追踪通常用于记录用户在页面上的行为路径,以及组件之间的交互。 我们可以使用以下方式实现:
- 手动埋点: 在关键的用户操作上添加埋点,记录用户行为和相关数据。
<template>
<button @click="handleClick">Click me</button>
</template>
<script>
import logger from '@/services/logger';
export default {
methods: {
handleClick() {
// 记录用户点击事件
logger.info('User clicked button', { buttonId: 'myButton' });
// 执行其他操作
}
}
}
</script>
-
自动埋点: 使用第三方库,例如
vue-gtm,自动收集页面浏览、点击事件等数据。 -
集成 OpenTelemetry (实验性): OpenTelemetry 正在逐渐成为可观测性领域的标准。 虽然前端的 OpenTelemetry 支持不如后端成熟,但仍然可以尝试集成。 可以使用
@opentelemetry/sdk-trace-web和@opentelemetry/exporter-jaeger等库来收集和导出追踪数据。 注意:这部分可能需要更多配置和自定义代码。
// main.js (OpenTelemetry 示例,需要更多配置)
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
// 配置 Jaeger Exporter
const jaegerExporter = new JaegerExporter({
endpoint: 'http://localhost:14268/api/traces', // 替换为你的 Jaeger 地址
});
// 配置 TracerProvider
const provider = new WebTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(jaegerExporter));
provider.register({
contextManager: new ZoneContextManager(),
});
// 注册 Instrumentation (例如 Fetch API)
registerInstrumentations({
instrumentations: [], // 可以添加 Fetch Instrumentation 等
});
console.log("opentelemetry initialized");
3. 指标收集
前端指标通常包括页面加载时间、资源加载时间、首屏渲染时间、错误率等。 我们可以使用以下方式实现:
- Performance API: 使用
Performance API收集页面加载时间、资源加载时间等性能数据。
// 收集页面加载时间
window.addEventListener('load', () => {
const timing = performance.timing;
const loadTime = timing.loadEventEnd - timing.navigationStart;
logger.info(`Page loaded in ${loadTime}ms`, { loadTime: loadTime });
// 或者使用 PerformanceObserver API 获取更详细的指标
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
logger.info(`Performance Entry: ${entry.name} - ${entry.duration}ms`, {entryName: entry.name, duration: entry.duration});
});
});
observer.observe({ entryTypes: ['paint', 'resource'] }); // 观察 FCP, LCP, 资源加载 等
});
- 第三方库: 使用第三方库,例如
web-vitals,收集 Core Web Vitals 指标。
// 安装 web-vitals: npm install web-vitals
import { getCLS, getFID, getLCP } from 'web-vitals';
import logger from '@/services/logger';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
// 或者发送到后端
getCLS((metric) => {
logger.info(`CLS: ${metric.value}`, {value: metric.value});
});
getFID((metric) => {
logger.info(`FID: ${metric.value}`, {value: metric.value});
});
getLCP((metric) => {
logger.info(`LCP: ${metric.value}`, {value: metric.value});
});
- 自定义指标: 根据业务需求,自定义指标,例如用户点击次数、页面滚动深度等。
三、后端可观测性集成(Node.js/Express 示例)
后端的可观测性主要关注服务器性能、API 响应时间、数据库查询时间等。 这里以 Node.js/Express 为例,介绍如何实现后端数据的收集。
1. 日志收集
后端日志通常包括请求日志、错误日志、业务日志等。 我们可以使用以下方式实现:
- 日志库: 使用成熟的日志库,例如
winston或morgan,提供灵活的日志格式、日志级别和日志输出方式。
// 安装 winston: npm install winston
const winston = require('winston');
const { combine, timestamp, json } = winston.format;
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
json()
),
transports: [
new winston.transports.Console(),
// 可选:输出到文件
// new winston.transports.File({ filename: 'combined.log' })
]
});
// 在 Express 中使用
const express = require('express');
const app = express();
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info(`Request: ${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms)`, {
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: duration,
ip: req.ip
});
});
next();
});
app.get('/', (req, res) => {
logger.info('Hello world requested');
res.send('Hello World!');
});
app.use((err, req, res, next) => {
logger.error('Unhandled error', { error: err, method: req.method, url: req.originalUrl });
res.status(500).send('Something broke!');
});
app.listen(3000, () => {
logger.info('Server listening on port 3000');
});
- 错误处理中间件: 创建一个全局的错误处理中间件,捕获未处理的异常,并记录到日志中。
2. 追踪收集
后端追踪通常用于记录请求在服务之间的调用链路。 我们可以使用以下方式实现:
- OpenTelemetry: 使用 OpenTelemetry SDK 收集和导出追踪数据。 需要安装相关的库,例如
@opentelemetry/sdk-node,@opentelemetry/auto-instrumentations-node,@opentelemetry/exporter-jaeger。
// 安装 OpenTelemetry 相关依赖: npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-jaeger
const { NodeTracerProvider } = require('@opentelemetry/sdk-node');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { registerInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');
// 可选:开启调试日志
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
// 配置 Jaeger Exporter
const jaegerExporter = new JaegerExporter({
endpoint: 'http://localhost:14268/api/traces', // 替换为你的 Jaeger 地址
});
// 配置 TracerProvider
const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(jaegerExporter));
provider.register();
// 注册 Instrumentation (自动收集 HTTP, Express, MySQL 等的追踪数据)
registerInstrumentations({
instrumentations: [], // 可以配置具体的 Instrumentation
});
console.log("opentelemetry initialized");
// 在 Express 中使用 (不需要显式地创建 Span, Instrumentation 会自动处理)
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
- 手动创建 Span: 在关键的业务逻辑中,手动创建 Span,记录操作的开始和结束时间。
const { trace } = require('@opentelemetry/api');
// 获取 Tracer
const tracer = trace.getTracer('my-application', '1.0.0');
// 创建 Span
const span = tracer.startSpan('my-operation');
// 执行业务逻辑
try {
// ...
span.setAttribute('key', 'value'); // 添加属性
span.addEvent('my-event'); // 添加事件
} catch (err) {
span.recordException(err); // 记录异常
} finally {
span.end(); // 结束 Span
}
3. 指标收集
后端指标通常包括 CPU 使用率、内存占用、请求响应时间、数据库连接数等。 我们可以使用以下方式实现:
- Prometheus: 使用 Prometheus 收集和存储指标数据。 需要安装相关的库,例如
prom-client。
// 安装 prom-client: npm install prom-client
const promClient = require('prom-client');
// 创建一个 Registry 来注册所有指标
const register = new promClient.Registry();
// 创建一个 HTTP 请求计数器
const httpRequestCounter = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status'],
registers: [register]
});
// 创建一个 HTTP 请求持续时间直方图
const httpRequestDurationMicroseconds = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status'],
buckets: [0.05, 0.1, 0.3, 0.5, 1, 3, 5, 10], // 定义 buckets
registers: [register]
});
// 在 Express 中使用
const express = require('express');
const app = express();
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const durationInSeconds = duration / 1000;
httpRequestCounter.inc({ method: req.method, route: req.originalUrl, status: res.statusCode });
httpRequestDurationMicroseconds.observe({ method: req.method, route: req.originalUrl, status: res.statusCode }, durationInSeconds);
});
next();
});
app.get('/', (req, res) => {
res.send('Hello World!');
});
// 提供 Prometheus 指标端点
app.get('/metrics', async (req, res) => {
try {
res.setHeader('Content-Type', register.contentType);
res.end(await register.metrics());
} catch (ex) {
res.status(500).end(ex);
}
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
- 系统指标: 使用
os模块收集 CPU 使用率、内存占用等系统指标。
四、前后端数据的统一收集和展示
前端和后端收集到的数据需要统一存储和展示,才能发挥最大的价值。 常用的方案包括:
-
ELK Stack (Elasticsearch, Logstash, Kibana): Logstash 负责收集和处理日志数据,Elasticsearch 负责存储数据,Kibana 负责数据可视化。
-
Jaeger: 用于存储和展示追踪数据。
-
Prometheus + Grafana: Prometheus 负责收集和存储指标数据,Grafana 负责数据可视化。
-
云服务: 使用云厂商提供的可观测性服务,例如 AWS CloudWatch, Azure Monitor, Google Cloud Operations Suite。
以下是一个使用 ELK Stack 收集前后端日志的示例:
- 前端: 将前端日志发送到后端 API 接口。
- 后端: 后端 API 接收到日志后,使用 Logstash 客户端将日志发送到 Logstash。
- Logstash: Logstash 接收到日志后,进行处理和转换,然后将数据发送到 Elasticsearch。
- Elasticsearch: Elasticsearch 存储日志数据。
- Kibana: Kibana 连接到 Elasticsearch,可以查询和可视化日志数据。
五、可观测性最佳实践
- 结构化日志: 使用 JSON 格式的结构化日志,方便查询和分析。
- 统一日志格式: 前后端使用统一的日志格式,方便关联和分析。
- 关联 ID: 为每个请求分配一个唯一的 ID,并在前后端传递,方便追踪请求的整个生命周期。
- 选择合适的工具: 根据实际需求选择合适的工具和方案。
- 持续监控和优化: 定期监控可观测性数据的质量,并根据实际情况进行优化。
六、Demo 代码
这里提供一个简单的 Demo 代码,演示如何使用 Vue + Node.js + OpenTelemetry 实现前后端追踪:
- 前端 (Vue):
// src/components/MyComponent.vue
<template>
<button @click="handleClick">Call API</button>
</template>
<script>
import axios from 'axios';
import logger from '@/services/logger';
export default {
methods: {
async handleClick() {
try {
const response = await axios.get('/api/hello');
logger.info('API response', { data: response.data });
} catch (error) {
logger.error('API error', { error: error });
}
}
}
}
</script>
- 后端 (Node.js/Express):
// index.js
const express = require('express');
const app = express();
const { trace } = require('@opentelemetry/api');
app.get('/api/hello', async (req, res) => {
const tracer = trace.getTracer('my-application', '1.0.0');
const span = tracer.startSpan('api-hello-handler');
try {
// 模拟一些延迟
await new Promise(resolve => setTimeout(resolve, 200));
res.json({ message: 'Hello from backend!' });
} finally {
span.end();
}
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
表格:常用可观测性工具
| 工具 | 类型 | 描述 |
|---|---|---|
| Elasticsearch | 存储 | 存储日志数据 |
| Logstash | 数据处理 | 收集、处理和转换日志数据 |
| Kibana | 可视化 | 可视化日志数据 |
| Jaeger | 追踪 | 存储和展示追踪数据 |
| Prometheus | 指标 | 收集和存储指标数据 |
| Grafana | 可视化 | 可视化指标数据 |
| OpenTelemetry | 标准 | 提供用于生成、收集和导出遥测数据的 API、SDK 和工具 |
| AWS CloudWatch | 云服务 | 亚马逊云提供的监控和可观测性服务 |
| Azure Monitor | 云服务 | 微软 Azure 提供的监控和可观测性服务 |
| Google Cloud Ops | 云服务 | 谷歌云提供的监控和可观测性服务 (以前称为 Stackdriver) |
日志、追踪和指标的统一收集方案
总的来说,实现 Vue 应用中可观测性的关键在于选择合适的工具,并结合实际业务需求进行定制。通过统一收集和展示日志、追踪和指标数据,可以更好地理解系统的运行状态,并快速定位和解决问题,最终提升用户体验。
持续改进的道路
可观测性不是一蹴而就的,需要持续的监控和优化。随着业务的发展,我们需要不断调整和完善可观测性方案,才能更好地应对新的挑战。
更多IT精英技术系列讲座,到智猿学院