Vue应用中的可观测性(Observability)集成:实现前后端日志、追踪与指标的统一收集

好的,没问题,我们开始吧!

Vue 应用中的可观测性集成:实现前后端日志、追踪与指标的统一收集

大家好,今天我们来聊聊 Vue 应用中可观测性的集成。 在微服务架构日益普及的今天,可观测性对于快速定位和解决问题至关重要。一个良好的可观测性方案能够帮助我们理解系统的运行状态,诊断性能瓶颈,并最终提升用户体验。 本次分享将围绕日志、追踪和指标这三个核心要素,探讨如何在 Vue 应用中实现前后端数据的统一收集。

一、可观测性的三大支柱:日志、追踪和指标

在深入代码之前,我们先简单回顾一下可观测性的三大支柱:

  • 日志(Logs): 离散的事件记录,包含时间戳、消息内容以及其他相关信息。日志可以帮助我们了解发生了什么。例如,用户登录失败的日志,API 请求错误的日志等。

  • 追踪(Traces): 记录请求在整个系统中的调用链路。追踪可以帮助我们了解请求是如何传播的,哪些服务参与了处理,以及每个服务的耗时。这对于诊断分布式系统中的性能问题非常有帮助。

  • 指标(Metrics): 定量的数据,通常以时间序列的形式存在。指标可以帮助我们了解系统的状态,例如 CPU 使用率、内存占用、请求响应时间等。

这三者相互补充,共同构建了完整的可观测性体系。日志提供了详细的事件信息,追踪揭示了请求的生命周期,而指标则提供了宏观的系统状态。

二、前端可观测性集成

前端的可观测性主要关注用户行为、性能数据和错误信息。 Vue 应用在这方面有很大的潜力,我们可以利用 Vue 的生命周期钩子、错误处理机制以及第三方库来实现数据的收集。

1. 日志收集

前端日志通常包括用户操作日志、网络请求日志、错误日志等。我们可以通过以下方式实现:

  • 自定义日志服务: 创建一个全局的日志服务,封装 console.logconsole.warnconsole.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')
  • 网络请求拦截器: 使用 axiosfetch 的拦截器,记录请求的 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. 日志收集

后端日志通常包括请求日志、错误日志、业务日志等。 我们可以使用以下方式实现:

  • 日志库: 使用成熟的日志库,例如 winstonmorgan,提供灵活的日志格式、日志级别和日志输出方式。
// 安装 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 收集前后端日志的示例:

  1. 前端: 将前端日志发送到后端 API 接口。
  2. 后端: 后端 API 接收到日志后,使用 Logstash 客户端将日志发送到 Logstash。
  3. Logstash: Logstash 接收到日志后,进行处理和转换,然后将数据发送到 Elasticsearch。
  4. Elasticsearch: Elasticsearch 存储日志数据。
  5. 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精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注