MST

星途 面试题库

面试题:Node.js日志管理之专家难度:日志的高性能持久化与分布式追踪整合

在高并发的Node.js微服务架构中,一方面要保证日志的高性能持久化存储(如使用Elasticsearch),另一方面要整合分布式追踪系统(如Jaeger),使日志能够关联到具体的分布式链路。请设计一套完整的解决方案,包括日志采集、传输、存储以及与分布式追踪系统的集成思路和关键代码示例。
19.3万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

日志采集

  1. 使用winstonwinston是Node.js中广泛使用的日志记录库,可用于灵活配置日志格式和输出目标。
    • 安装:npm install winston
    • 示例代码:
const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transport.Console()
    ]
});

module.exports = logger;
  1. 在业务代码中记录日志:在每个需要记录日志的地方,引入上述logger实例进行日志记录。
const logger = require('./logger');

app.get('/api', (req, res) => {
    logger.info('Received request to /api');
    // 业务逻辑
    res.send('Response');
});
  1. 结合express中间件(如果是Web服务):可以在express应用中使用中间件记录请求和响应相关的日志。
const express = require('express');
const logger = require('./logger');
const app = express();

app.use((req, res, next) => {
    logger.info(`Received ${req.method} request to ${req.url}`);
    next();
});

app.get('/api', (req, res) => {
    res.send('Response');
});

app.use((err, req, res, next) => {
    logger.error(err.message);
    res.status(500).send('Internal Server Error');
});

日志传输

  1. 使用winston-elasticsearch:这是winston的一个传输器,可将日志发送到Elasticsearch。
    • 安装:npm install winston-elasticsearch
    • 配置:修改winston配置,添加Elasticsearch传输器。
const winston = require('winston');
const winstonElasticsearch = require('winston-elasticsearch');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winstonElasticsearch({
            level: 'info',
            clientOpts: {
                node: 'http://localhost:9200'
            }
        })
    ]
});

module.exports = logger;
  1. 消息队列(可选):为了进一步提高性能和可靠性,可以在日志采集和传输之间引入消息队列,如KafkaRabbitMQ。在Node.js中可以使用相应的客户端库(如kafkajs for Kafka)。
    • Kafka示例
      • 安装:npm install kafkajs
      • 生产者代码:
const { Kafka } = require('kafkajs');

const kafka = new Kafka({
    clientId: 'logger - producer',
    brokers: ['localhost:9092']
});

const producer = kafka.producer();

const run = async () => {
    await producer.connect();
    const message = { value: JSON.stringify({ message: 'Log message' }) };
    await producer.send({
        topic: 'log - topic',
        messages: [message]
    });
    await producer.disconnect();
};

run().catch(console.error);
  • 消费者代码
const { Kafka } = require('kafkajs');

const kafka = new Kafka({
    clientId: 'logger - consumer',
    brokers: ['localhost:9092']
});

const consumer = kafka.consumer({ groupId: 'log - group' });

const run = async () => {
    await consumer.connect();
    await consumer.subscribe({ topic: 'log - topic', fromBeginning: true });
    await consumer.run({
        eachMessage: async ({ topic, partition, message }) => {
            const log = JSON.parse(message.value.toString());
            // 这里可以将日志发送到Elasticsearch
        }
    });
};

run().catch(console.error);

日志存储(Elasticsearch)

  1. 索引管理:在Elasticsearch中创建合适的索引来存储日志。可以使用elasticsearch官方Node.js客户端库进行索引创建和管理。
    • 安装:npm install @elastic/elasticsearch
    • 创建索引示例:
const { Client } = require('@elastic/elasticsearch');

const client = new Client({ node: 'http://localhost:9200' });

const createIndex = async () => {
    try {
        const indexName = 'log - index';
        const indexSettings = {
            settings: {
                number_of_shards: 1,
                number_of_replicas: 0
            },
            mappings: {
                properties: {
                    timestamp: { type: 'date' },
                    level: { type:'string' },
                    message: { type:'string' }
                }
            }
        };
        await client.indices.create({ index: indexName, body: indexSettings });
        console.log(`Index ${indexName} created`);
    } catch (error) {
        console.error('Error creating index:', error);
    }
};

createIndex();
  1. 数据写入:通过winston - elasticsearch传输器或消息队列消费者将日志数据写入Elasticsearch。

与分布式追踪系统(Jaeger)集成

  1. 安装jaeger - client - nodejs:这是Jaeger的Node.js客户端库。
    • 安装:npm install jaeger - client - nodejs
  2. 初始化Tracer:在应用入口处初始化Jaeger Tracer。
const initTracer = require('jaeger - client - nodejs').initTracer;

const tracer = initTracer({
    serviceName: 'your - service - name',
    sampler: {
        type: 'const',
        param: 1
    },
    reporter: {
        logSpans: true
    }
});

module.exports = tracer;
  1. 在业务代码中使用Tracer:在处理请求等关键业务逻辑处使用Tracer来创建跨度(Span)。
const tracer = require('./tracer');

app.get('/api', (req, res) => {
    const span = tracer.startSpan('handle - api - request');
    try {
        // 业务逻辑
        span.finish();
        res.send('Response');
    } catch (error) {
        span.recordException(error);
        span.finish();
        res.status(500).send('Internal Server Error');
    }
});
  1. 关联日志和追踪:在日志记录中添加追踪相关信息,如Trace ID和Span ID。
const winston = require('winston');
const tracer = require('./tracer');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transport.Console()
    ]
});

app.get('/api', (req, res) => {
    const span = tracer.startSpan('handle - api - request');
    const traceId = span.context().toTraceId();
    const spanId = span.context().toSpanId();
    logger.info({
        message: 'Received request to /api',
        traceId,
        spanId
    });
    try {
        // 业务逻辑
        span.finish();
        res.send('Response');
    } catch (error) {
        span.recordException(error);
        span.finish();
        res.status(500).send('Internal Server Error');
    }
});

在Jaeger UI中,可以通过Trace ID来查看整个分布式链路,并且在Elasticsearch中可以通过Trace ID和Span ID来关联相关的日志,实现日志与分布式追踪系统的整合。