Kafka + Elasticsearch 构建搜索型审计日志系统实战(含 Kibana 可视化)

🔍 Kafka + Elasticsearch 构建搜索型审计日志系统实战(含 Kibana 可视化)

🧱 背景:审计日志不仅要记录,还要可查、可搜、可视化

在实际中后台系统中,日志并非只是写入数据库做"留痕",我们还希望支持:

  • ✅ 精确/模糊搜索日志内容(如按action, entity, keyword等过滤)
  • ✅ 快速响应:即使在中大规模日志数据中,也可支持按action / entity / keyword等维度的全文查询,结合 Kibana 实现图形化展示。
  • ✅ 可视化浏览:图形界面展示最新日志流

因此,我们在已有 Kafka + MongoDB 的基础上,引入 Elasticsearch 作为全文索引引擎,并结合 Kibana 实现可视化界面。


✨ 方案结构一览

  • Kafka 仍然作为日志的消息中间件
  • MongoDB 用于保存原始日志数据(持久化)
  • Elasticsearch 用于构建索引,支持全文检索
  • Kibana 作为日志搜索和可视化界面
sequenceDiagram User->>SpringBoot: POST /api/generate SpringBoot->>KafkaProducer: Send AuditLogEvent KafkaProducer->>KafkaBroker: Publish to audit-log-topic KafkaConsumer->>KafkaBroker: Poll audit-log-topic KafkaConsumer->>MongoDB: Save to audit_logs KafkaConsumer->>Elasticsearch: Index log document

✅ 新增能力

  • 🔍 新增接口:POST /api/logs/search

    支持按 actionentity、关键词(payload 全文)过滤日志

    支持分页:page + size 控制结果数量

  • 📦 支持 Elasticsearch 客户端写入

  • 📈 整合 Kibana:在浏览器中可视化查看日志内容


🔧 搜索接口示例

http 复制代码
POST /api/logs/search
Content-Type: application/json

请求示例:

json 复制代码
{
  "action": "GENERATE",
  "entity": "user",
  "keyword": "Product",
  "page": 0,
  "size": 10
}

🔎 查询字段说明

字段 类型 说明
action 字符串 日志行为,如 GENERATEDELETE
entity 字符串 实体名称,如 UserProduct
keyword 字符串 payload 中的关键词全文搜索
page 数字 页码,从 0 开始
size 数字 每页返回数量

💻 Elasticsearch 配置简述

我们使用官方 Java 客户端 elasticsearch-java

xml 复制代码
<dependency>
  <groupId>co.elastic.clients</groupId>
  <artifactId>elasticsearch-java</artifactId>
  <version>8.12.2</version>
</dependency>

初始化方式:

java 复制代码
@Configuration
public class ElasticsearchConfig {

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        RestClient restClient = RestClient.builder(
                new HttpHost("localhost", 9200, "http")
        ).build();

        RestClientTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper()
        );

        return new ElasticsearchClient(transport);
    }
}
📺 Elasticsearch 安装和Kibana 可视化

在项目中,我们通过 Docker Compose 启动了 Kibana:docker-compose up -d

yml 复制代码
services:
    elasticsearch:
      image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
      container_name: elasticsearch
      environment:
        - discovery.type=single-node
        - xpack.security.enabled=false
        - bootstrap.memory_lock=true
        - ES_JAVA_OPTS=-Xms512m -Xmx512m
      ports:
        - "9200:9200"
      volumes:
        - es-data:/usr/share/elasticsearch/data

    kibana:
      image: docker.elastic.co/kibana/kibana:8.13.0
      container_name: kibana
      ports:
        - "5601:5601"
      environment:
        - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      depends_on:
        - elasticsearch
volumes:
  es-data:

你可以通过浏览器访问:

👉 http://localhost:5601

在 Kibana 中搜索 audit-logs 索引,即可查看结构化日志内容。

🧪 核心代码与实现解读

以下是 POST /api/logs/search 的 Controller 实现,支持根据多个条件组合查询 Elasticsearch 中的日志数据:

java 复制代码
@RestController
@RequestMapping("/api/logs")
@RequiredArgsConstructor
public class AuditLogSearchController {

    private final ElasticsearchClient elasticsearchClient;

    @PostMapping("/search")
    public List<Map<String, Object>> search(@RequestBody AuditLogSearchRequest req) throws IOException {
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder();

        // 精确匹配 action 字段
        if (StringUtils.hasText(req.getAction())) {
            boolBuilder.must(mq -> mq.match(m -> m.field("action").query(req.getAction())));
        }

        // 精确匹配 entity 字段
        if (StringUtils.hasText(req.getEntity())) {
            boolBuilder.must(mq -> mq.match(m -> m.field("entity").query(req.getEntity())));
        }

        // 对 payload 做全文关键词匹配
        if (StringUtils.hasText(req.getKeyword())) {
            boolBuilder.should(mq -> mq.match(m -> m.field("payload").query(req.getKeyword())));
        }

        Query query = Query.of(q -> q.bool(boolBuilder.build()));

        SearchRequest searchRequest = SearchRequest.of(s -> s
                .index("audit-logs")
                .from(req.getPage() * req.getSize())
                .size(req.getSize())
                .query(query)
        );

        SearchResponse<Map<String, Object>> response =
                elasticsearchClient.search(searchRequest, Map.class);

        return response.hits().hits().stream()
                .map(Hit::source)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
}

📝 说明:

  • 使用 BoolQuery 实现多个查询条件组合(must + should)
  • 支持分页:from + size
  • 返回的结果是 List<Map<String, Object>>,可直接序列化为 JSON 返回前端
  • 若只传 keyword,也能模糊搜索 payload 字段中的关键词

2️⃣ Kafka 消费端:写入 Elasticsearch

每次生成代码都会触发一个 Kafka 异步事件,在消费者中我们将该日志同时写入 MongoDB 和 Elasticsearch:

java 复制代码
@KafkaListener(topics = "audit-log-topic", groupId = "audit-consumer-group")
public void consume(AuditLogEvent event, Acknowledgment ack) throws IOException {
    // MongoDB 落库逻辑
    
    // 写入 Elasticsearch
    try {
        // ✅ 使用 Spring 注入的 ObjectMapper 序列化事件
        String json = objectMapper.writeValueAsString(event);

        // ✅ 再反序列化为 Map 结构(ES 文档支持 Map 类型)
        Map<String, Object> jsonMap = objectMapper.readValue(json, new TypeReference<>() {});

        IndexRequest<Map<String, Object>> request = IndexRequest.of(builder ->
                builder.index("audit-logs").document(jsonMap)
        );

        IndexResponse response = elasticsearchClient.index(request);
        log.info("✅ Indexed to Elasticsearch with ID: {}", response.id());

        // ✅ 确认提交 Kafka offset,避免重复消费
        ack.acknowledge();
    } catch (Exception e) {
        log.error("❌ Elasticsearch indexing failed", e);
    }
}

📝 说明:

  • Elasticsearch 客户端使用官方的 elasticsearch-java
  • Kafka 消费者使用 manual_immediate ack 模式,确保写入成功再确认 offset
  • elasticsearchClient.index(...) 是同步阻塞操作,因此考虑优化的话,建议配合线程池异步处理

📝 遇到的坑

❌ ObjectMapper 无法序列化 LocalDateTime

Elasticsearch 客户端默认使用 Jackson 进行 JSON 序列化,如果你项目中手动声明了一个新的 ObjectMapper,却没有注册 JavaTimeModule ,或在序列化时没有使用 Spring 注入的 ObjectMapper 实例,就会导致如下错误:

bash 复制代码
InvalidDefinitionException: Java 8 date/time type `LocalDateTime` not supported

✅ 解决方案详见 👉 Spring Boot 中 ObjectMapper 配置踩坑实录:LocalDateTime 无法序列化的终极解决方案


🎯 成果总结

目前我们已完成:

  • ✅ Kafka 日志异步发送
  • ✅ MongoDB + Elasticsearch 双通道持久化
  • ✅ 支持基于action / entity / keyword 的全文搜索
  • ✅ 提供 RESTful 搜索接口
  • ✅ Kibana 图形化日志浏览界面

📦 项目源码

项目名称:rapid-crud-generator

这是一个从 JSON Schema 自动生成后端 API 与前端管理页面的全栈工具,已集成:

  • 异步日志系统(Kafka)
  • MongoDB 存储
  • Elasticsearch 搜索
  • Kibana 可视化
  • Prometheus + Grafana 指标监控
  • Swagger 接口管理
  • 一键 zip 下载

👉 GitHub 地址:github.com/xmyLydia/ra...

欢迎 Star、Fork、留言交流!

相关推荐
MaYuKang24 分钟前
「ES数据迁移可视化工具(Python实现)」支持7.x索引数据互传
大数据·数据库·python·mysql·elasticsearch
ShAn DiAn3 小时前
实时步数统计系统 kafka + spark +redis
大数据·redis·分布式·spark·kafka
Elasticsearch3 小时前
使用 AutoGen 与 Elasticsearch
elasticsearch
苏小夕夕4 小时前
kafka安装、spark安装
大数据·spark·kafka
知初~4 小时前
java—12 kafka
分布式·中间件·kafka
小马爱打代码5 小时前
Kafka 命令行样例大全
kafka
sinat_262292118 小时前
Java面试实战:音视频场景下的微服务架构与缓存技术剖析
java·spring boot·redis·微服务·kafka·分布式系统·面试技巧
怒放吧德德15 小时前
MySQL篇:MySQL如何实时同步到ES
mysql·elasticsearch·面试