1. 概述

Grafana Labs 开发了名为 Loki 的开源日志聚合系统,灵感来源于 Prometheus。它的目标是存储和索引来自不同应用和系统的日志数据,以便于高效查询和分析。

本文将指导您如何为一个Spring Boot应用设置使用Grafana Loki的日志管理。Loki负责收集和聚合应用日志,而Grafana负责展示它们。

2. 启动Loki和Grafana服务

首先,我们将启动Loki和Grafana服务,以便收集和监控日志。我们将使用Docker容器来简化配置和运行过程。

首先,在一个docker-compose文件中配置Loki和Grafana服务:

version: "3"
networks:
  loki:
services:
  loki:
    image: grafana/loki:2.9.0
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - loki
  grafana:
    environment:
      - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    entrypoint:
      - sh
      - -euc
      - |
        mkdir -p /etc/grafana/provisioning/datasources
        cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
        apiVersion: 1
        datasources:
        - name: Loki
          type: loki
          access: proxy
          orgId: 1
          url: http://loki:3100
          basicAuth: false
          isDefault: true
          version: 1
          editable: false
        EOF
        /run.sh
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    networks:
      - loki

接下来,使用docker-compose命令启动服务:

docker-compose up

最后,确认两个服务是否已启动:

docker ps

211c526ea384        grafana/loki:2.9.0       "/usr/bin/loki -conf…"   4 days ago          Up 56 seconds       0.0.0.0:3100->3100/tcp   surajmishra_loki_1
a1b3b4a4995f        grafana/grafana:latest   "sh -euc 'mkdir -p /…"   4 days ago          Up 56 seconds       0.0.0.0:3000->3000/tcp   surajmishra_grafana_1

3. 与Spring Boot集成Loki

启动Grafana和Loki服务后,我们需要配置应用,使其将日志发送到Loki。我们将使用loki-logback-appender,它负责将日志发送到Loki聚合器进行存储和索引。

首先,在pom.xml文件中添加loki-logback-appender

<dependency>
    <groupId>com.github.loki4j</groupId>
    <artifactId>loki-logback-appender</artifactId>
    <version>1.4.1</version>
</dependency>

其次,在src/main/resources目录下创建一个logback-spring.xml文件,控制Spring Boot应用的日志行为,如日志格式、Loki服务的端点等:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>        
   <appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
        <http>
            <url>http://localhost:3100/loki/api/v1/push</url>
        </http>
        <format>
            <label>
                <pattern>app=${name},host=${HOSTNAME},level=%level</pattern>
                <readMarkers>true</readMarkers>
            </label>
            <message>
                <pattern>
                    {
                    "level":"%level",
                    "class":"%logger{36}",
                    "thread":"%thread",
                    "message": "%message",
                    "requestId": "%X{X-Request-ID}"
                    }
                </pattern>
            </message>
         </format>
     </appender>
     
     <root level="INFO">
        <appender-ref ref="LOKI" />
     </root>
</configuration>

设置完成后,编写一个简单的服务,以INFO级别记录数据:

@Service
class DemoService{

    private final Logger LOG = LoggerFactory.getLogger(DemoService.class);

    public void log(){
        LOG.info("DemoService.log invoked");
    }
}

4. 测试验证

现在,通过启动Grafana和Loki容器,执行服务方法将日志推送到Loki。然后,我们将使用Loki的HTTP API(https://grafana.com/docs/loki/latest/reference/api/#query-loki-over-a-range-of-time)查询日志,确认是否成功推送。关于启动服务,请参考前面的部分。

首先,执行DemoService.log()方法,调用Logger.info(),这将使用loki-logback-appender发送一条消息,Loki会收集:

DemoService service = new DemoService();
service.log();

其次,构造一个REST请求,使用Loki HTTP API提供的端点。GET API接受代表查询时间范围的查询参数。我们将这些参数添加到请求对象中:

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

String query = "{level=\"INFO\"} |= `DemoService.log invoked`";

// Get time in UTC
LocalDateTime currentDateTime = LocalDateTime.now(ZoneOffset.UTC);
String current_time_utc = currentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));

LocalDateTime tenMinsAgo = currentDateTime.minusMinutes(10);
String start_time_utc = tenMinsAgo.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));

URI uri = UriComponentsBuilder.fromUriString(baseUrl)
  .queryParam("query", query)
  .queryParam("start", start_time_utc)
  .queryParam("end", current_time_utc)
  .build()
  .toUri();

接着,使用请求对象执行REST请求:

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers), String.class);

现在,我们需要处理响应并提取感兴趣的日志消息。我们将使用ObjectMapper来解析JSON响应并提取日志信息:

ObjectMapper objectMapper = new ObjectMapper();
List<String> messages = new ArrayList<>();
String responseBody = response.getBody();
JsonNode jsonNode = objectMapper.readTree(responseBody);
JsonNode result = jsonNode.get("data")
  .get("result")
  .get(0)
  .get("values");

result.iterator()
  .forEachRemaining(e -> {
      Iterator<JsonNode> elements = e.elements();
      elements.forEachRemaining(f -> messages.add(f.toString()));
  });

最后,检查响应中接收到的消息是否包含DemoService记录的信息:

assertThat(messages).anyMatch(e -> e.contains(expected));

5. 日志聚合与可视化

由于使用了loki-logback-appender进行配置,我们的服务日志已经被推送到Loki。可以通过访问部署Grafana的服务的http://localhost:3000在浏览器中查看。

为了查看Loki中存储和索引的日志,我们需要使用Grafana。Grafana的数据源提供了连接到Loki的可配置参数,包括Loki端点、认证机制等。

首先,配置日志被推送至的Loki端点:

Loki数据源连接页面

成功配置数据源后,转到数据探索页面来查询我们的日志:

Loki探索数据菜单

我们可以编写查询语句将应用日志导入Grafana进行可视化。在我们的示例服务中,我们推送的是INFO级别的日志,因此需要添加过滤条件并运行查询:

在Grafana中添加和过滤查询

一旦运行查询,我们将看到所有匹配搜索的INFO日志:

查询结果

6. 总结

本文介绍了如何为Spring Boot应用设置Grafana Loki的日志管理。我们还进行了单元测试和可视化验证,使用简单逻辑记录INFO日志,并在Grafana中设置了Loki数据源。

如往常一样,示例代码可在GitHub上获取。