在这里插入图片描述

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Prometheus这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

Prometheus - 日志与指标联动:结合 Loki 实现日志 + 指标一体化监控 🚀

在现代云原生和微服务架构中,可观测性(Observability)已成为保障系统稳定性和快速故障排查的关键能力。传统的监控体系往往将指标(Metrics)、日志(Logs)和追踪(Traces)三者割裂处理,导致运维和开发人员在排查问题时需要在多个工具之间来回切换,效率低下。而随着 Grafana Labs 推出的 Loki 日志聚合系统,配合早已广为人知的 Prometheus 指标监控系统,我们终于可以实现“日志 + 指标”的一体化监控体验。

本文将深入探讨如何将 Prometheus 与 Loki 结合使用,构建统一的可观测性平台,并通过 Java 应用示例展示如何在实际项目中实现日志与指标的联动分析。我们将从基础概念讲起,逐步搭建本地环境,编写代码,配置告警,并最终实现高效的根因分析(Root Cause Analysis)。


为什么需要日志与指标联动?🔍

在微服务架构中,一个用户请求可能穿越数十个服务。当系统出现异常(如响应延迟、错误率飙升)时,仅靠指标数据往往只能告诉我们“发生了什么”,但无法解释“为什么发生”。

  • 指标(Metrics):如 CPU 使用率、HTTP 请求延迟、错误计数等,是数值型的时间序列数据,适合做趋势分析、阈值告警和性能监控。
  • 日志(Logs):如应用打印的 ERROR 级别日志、堆栈跟踪、业务上下文信息等,是非结构化或半结构化的文本数据,适合做事件回溯和上下文分析。

💡 举个例子:Prometheus 发现某个服务的 http_requests_total{status="500"} 指标突然激增。此时,如果我们能立即跳转到对应时间点的错误日志(如 java.lang.NullPointerException),就能快速定位问题根源,而无需手动翻查日志文件或登录服务器。

这种“从指标下钻到日志”的能力,正是日志与指标联动的核心价值。


技术栈简介:Prometheus + Loki + Grafana 🛠️

Prometheus:指标监控的事实标准

Prometheus 是 CNCF(Cloud Native Computing Foundation)毕业项目,采用拉取(Pull)模型采集指标,支持强大的 PromQL 查询语言,广泛用于 Kubernetes 环境和微服务监控。

🔗 官方网站:https://prometheus.io

Loki:轻量级、高性价比的日志聚合系统

Loki 由 Grafana Labs 开发,设计理念是“只索引元数据,不索引日志内容”。它将日志按标签(Labels)组织,与 Prometheus 的标签体系高度兼容,因此天然支持与 Prometheus 联动。

Loki 的优势包括:

  • 存储成本低(日志内容以压缩块形式存储)
  • 查询速度快(通过标签过滤)
  • 与 Prometheus 标签对齐,便于关联

🔗 官方文档:https://grafana.com/docs/loki/latest/

Grafana:统一的可视化平台

Grafana 支持同时接入 Prometheus 和 Loki 数据源,提供“Explore”模式,允许用户在一个界面中同时查看指标和日志,并通过时间轴联动。


架构概览:一体化监控的数据流 📊

让我们先看一个典型的日志 + 指标一体化监控架构:

1. 指标暴露

2. 日志输出

3. 发送日志

4. 指标查询

5. 日志查询

6. 联动分析

Java Application

Prometheus

Promtail

Loki

Grafana

运维/开发人员

流程说明:

  1. Java 应用通过 Micrometer 暴露 Prometheus 指标端点(如 /actuator/prometheus)。
  2. 应用将日志写入文件(如 application.log)。
  3. Promtail(Loki 的日志收集代理)监听日志文件,提取标签(如 job, instance, level),并将日志发送给 Loki。
  4. Prometheus 定期从应用拉取指标。
  5. Grafana 同时连接 Prometheus 和 Loki,提供统一查询界面。
  6. 用户在 Grafana 中发现异常指标后,可一键跳转到对应时间点的日志。

✅ 关键点:Promtail 提取的标签必须与 Prometheus 中的指标标签一致(如 job="user-service"),这样才能实现精准关联。


环境搭建:本地运行 Prometheus + Loki + Grafana 🐳

我们将使用 Docker Compose 快速搭建本地环境。创建 docker-compose.yml 文件:

version: '3'

services:
  prometheus:
    image: prom/prometheus:v2.47.0
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--web.enable-lifecycle'

  loki:
    image: grafana/loki:2.9.0
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml

  promtail:
    image: grafana/promtail:2.9.0
    volumes:
      - ./logs:/var/log
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:10.1.0
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  grafana_data: {}

配置 Prometheus (prometheus.yml)

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'java-app'
    static_configs:
      - targets: ['host.docker.internal:8080']  # Mac/Windows 用 host.docker.internal,Linux 用 172.17.0.1
        labels:
          job: user-service

⚠️ 注意:host.docker.internal 是 Docker Desktop 提供的特殊 DNS 名称,用于从容器访问宿主机。在 Linux 上可能需要使用 network_mode: host 或指定宿主机 IP。

配置 Promtail (promtail-config.yml)

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: user-service
    static_configs:
      - targets:
          - localhost
        labels:
          job: user-service
          __path__: /var/log/application.log

启动服务

mkdir logs
docker-compose up -d

现在你可以访问:

  • Grafana: http://localhost:3000 (用户名 admin,密码 admin)
  • Prometheus: http://localhost:9090
  • Loki: http://localhost:3100/metrics

Java 应用开发:暴露指标 + 输出结构化日志 ☕

接下来,我们创建一个 Spring Boot 应用,集成 Micrometer(用于指标)和 Logback(用于日志)。

1. 添加依赖(Maven)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
        <version>7.4</version>
    </dependency>
</dependencies>

2. 配置 application.yml

server:
  port: 8080

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  metrics:
    tags:
      application: user-service
      job: user-service  # 与 Prometheus/Promtail 标签对齐!

logging:
  file:
    name: ./logs/application.log
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

3. 配置 Logback 输出 JSON 日志(logback-spring.xml

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${LOG_FILE}</file>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp/>
                <logLevel/>
                <loggerName/>
                <message/>
                <mdc/> <!-- 支持 MDC 上下文 -->
                <stackTrace/>
            </providers>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
</configuration>

✅ JSON 日志格式便于 Loki 解析字段(如 level, logger),并支持后续的过滤和聚合。

4. 编写业务 Controller

@RestController
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @GetMapping("/users/{id}")
    public ResponseEntity<String> getUser(@PathVariable String id) {
        // 模拟业务逻辑
        if ("error".equals(id)) {
            logger.error("Invalid user ID: {}", id, new IllegalArgumentException("ID cannot be 'error'"));
            return ResponseEntity.status(500).body("Internal Error");
        }

        logger.info("Fetching user with ID: {}", id);
        return ResponseEntity.ok("User " + id);
    }

    @GetMapping("/slow")
    public String slowEndpoint() throws InterruptedException {
        logger.warn("Slow endpoint called");
        Thread.sleep(2000); // 模拟慢请求
        return "Done";
    }
}

5. 自定义指标:记录业务事件

@Service
public class UserService {

    private final Counter userFetchCounter;
    private final Timer requestTimer;

    public UserService(MeterRegistry meterRegistry) {
        this.userFetchCounter = Counter.builder("user.fetch.total")
            .description("Total number of user fetch attempts")
            .register(meterRegistry);

        this.requestTimer = Timer.builder("user.request.duration")
            .description("Duration of user requests")
            .register(meterRegistry);
    }

    public void fetchUser(String id) {
        userFetchCounter.increment();
        requestTimer.record(() -> {
            // 模拟业务耗时
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        });
    }
}

启动应用后,访问 http://localhost:8080/actuator/prometheus,你应该能看到类似:

# HELP user_fetch_total Total number of user fetch attempts
# TYPE user_fetch_total counter
user_fetch_total{application="user-service", job="user-service"} 3.0

同时,./logs/application.log 会生成 JSON 格式日志:

{"@timestamp":"2023-10-01T12:00:00.000Z","level":"ERROR","logger":"com.example.UserController","message":"Invalid user ID: error","stack_trace":"java.lang.IllegalArgumentException: ID cannot be 'error'\n\tat ..."}

Grafana 配置:关联 Prometheus 与 Loki 📈

1. 添加数据源

  • 打开 Grafana → Configuration → Data Sources
  • 添加 Prometheus:URL 填 http://prometheus:9090
  • 添加 Loki:URL 填 http://loki:3100

✅ 注意:在 Docker Compose 网络中,服务名(如 prometheus, loki)可直接解析。

2. 创建仪表盘:指标 + 日志联动

进入 Explore 模式:

步骤 1:查询指标

在 Prometheus 数据源中输入:

rate(http_server_requests_seconds_count{job="user-service", status=~"5.."}[5m])

这将显示 5xx 错误的请求速率。

步骤 2:联动日志

点击右上角 “Split” 按钮,添加一个 Loki 查询面板。

在 Loki 查询框中输入:

{job="user-service"} |= "ERROR"

现在,两个面板的时间轴是同步的!当你在指标图上选择一个异常时间段(如错误突增),日志面板会自动聚焦到该时间段的 ERROR 日志。

步骤 3:使用“Logs panel”增强可视化

你还可以在同一个仪表盘中添加 Logs panel,直接显示结构化日志,并支持按 levellogger 等字段过滤。


高级技巧:通过标签实现精准关联 🔗

要实现真正的“一键下钻”,关键在于 标签一致性

场景:从指标跳转到日志

假设你在 Prometheus 中看到:

http_server_requests_seconds_count{job="user-service", instance="192.168.1.10:8080", status="500"}

你希望点击该时间点,直接跳转到该实例的 ERROR 日志。

解决方案:在 Promtail 中注入相同标签

修改 promtail-config.yml

scrape_configs:
  - job_name: user-service
    static_configs:
      - targets: [localhost]
        labels:
          job: user-service
          instance: '192.168.1.10:8080'  # 必须与 Prometheus 中的 instance 一致!
          __path__: /var/log/application.log

💡 更优雅的方式:使用 relabel_configs 从日志文件路径或内容中动态提取标签。

在 Java 中通过 MDC 注入上下文

你还可以在日志中加入业务上下文(如 userId, traceId),并通过 Loki 的 | json 解析器提取为标签:

@GetMapping("/users/{id}")
public String getUser(@PathVariable String id) {
    MDC.put("userId", id);
    MDC.put("traceId", UUID.randomUUID().toString());
    try {
        // 业务逻辑
    } finally {
        MDC.clear();
    }
}

Loki 查询:

{job="user-service"} | json | userId = "123"

告警联动:从 Alertmanager 触发日志分析 🔔

Prometheus 的告警通常通过 Alertmanager 发送通知。我们可以让告警消息中包含 直接跳转到 Loki 日志的链接

1. 配置 Alertmanager 告警规则

prometheus.yml 中添加:

rule_files:
  - "alert.rules.yml"

创建 alert.rules.yml

groups:
- name: example
  rules:
  - alert: HighErrorRate
    expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m]) > 0.1
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "High error rate on {{ $labels.job }}"
      description: "Error rate is {{ $value }} errors/sec"
      dashboard: "http://localhost:3000/explore?orgId=1&left=%7B%22datasource%22:%22Loki%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%7Bjob%3D%5C%22{{ $labels.job }}%5C%22%7D%20%7C%3D%20%5C%22ERROR%5C%22%22%7D%5D%7D"

2. 配置 Alertmanager 通知模板

在 Alertmanager 配置中,使用 dashboard 注解生成超链接:

receivers:
- name: 'slack'
  slack_configs:
  - api_url: 'YOUR_SLACK_WEBHOOK'
    text: |
      {{ range .Alerts }}
      *Alert:* {{ .Annotations.summary }}
      *Description:* {{ .Annotations.description }}
      *View Logs:* <{{ .Annotations.dashboard }}|Click here>
      {{ end }}

这样,当告警触发时,运维人员可以直接点击链接跳转到 Grafana 的 Loki 查询页面,查看相关错误日志。


性能与成本优化 💰

Loki 的设计哲学是“低成本存储 + 高效查询”。以下是一些最佳实践:

1. 合理设计标签(Labels)

  • 不要将高基数字段(如 userId, requestId)作为标签,否则会导致 Loki 的索引爆炸。
  • 应该使用低基数、高区分度的标签,如 job, environment, level

2. 使用日志采样(Sampling)

对于 DEBUG 或 INFO 日志,可考虑在 Promtail 中配置采样:

scrape_configs:
  - job_name: user-service
    pipeline_stages:
      - match:
          selector: '{level="DEBUG"}'
          stages:
            - sample: 10  # 每 10 条采样 1 条

3. 压缩与保留策略

Loki 支持配置日志保留时间(如 7 天)和压缩算法(如 Snappy),可在 loki-config.yaml 中设置。


故障排查实战:模拟一次线上事故 🚨

让我们模拟一个典型场景:

  1. 用户报告“注册功能偶尔失败”。
  2. 运维打开 Grafana,发现 http_server_requests_seconds_count{status="500"} 有少量突增。
  3. 点击异常时间点,Loki 面板显示:
    {"level":"ERROR","message":"Failed to connect to database","stack_trace":"java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available..."}
    
  4. 结论:数据库连接池耗尽。
  5. 进一步检查指标:hikaricp_connections_active 接近 hikaricp_connections_max
  6. 解决方案:增加连接池大小或优化慢 SQL。

整个过程无需登录服务器,全部在 Grafana 中完成。


扩展:与 Jaeger/Tempo 集成实现 Tracing 🕵️

虽然本文聚焦日志 + 指标,但完整的可观测性三角还包括 分布式追踪(Tracing)

Grafana 也支持 Tempo(Grafana Labs 的追踪系统)或 Jaeger。通过在日志中注入 traceId,你可以在 Grafana 中实现:

  • 从指标 → 日志 → 追踪 的完整下钻链路
  • 在 Trace 查看器中直接看到关联的日志事件

例如,在 Java 中使用 OpenTelemetry:

Span.current().spanContext().getTraceId()

并将 traceId 放入 MDC,最终出现在日志中。


总结:一体化监控的价值 🌟

通过将 Prometheus 与 Loki 结合,我们实现了:

  • 统一查询界面:Grafana Explore 同时支持 PromQL 和 LogQL。
  • 高效根因分析:从指标异常一键跳转到上下文日志。
  • 降低运维成本:减少工具切换,提升 MTTR(平均修复时间)。
  • 标签驱动关联:利用一致的标签体系实现精准数据关联。

🚀 在云原生时代,可观测性不再是“锦上添花”,而是“生存必需”。Prometheus + Loki + Grafana 的组合,正成为越来越多团队构建现代化监控体系的首选。


参考资料 📚

现在,就去搭建你的日志 + 指标一体化监控平台吧!🎉


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐