在本文中,您将了解如何收集 Spring Boot 应用程序日志并将其发送到Grafana Loki。我们将使用附加的Loki4j Logback。
Loki 是一个受 Prometheus 启发的水平可扩展、高可用的日志聚合系统。
本文展示如何配置应用程序和 Loki 之间的集成。但是,可以使用自动配置库来记录 HTTP 请求和响应,这将为您完成所有这些步骤。
克隆 GitHub 存储库。
使用 Loki4j Logback Appender
为了使用Appender Loki4j Logback,我们需要在 Maven 中包含一个依赖项pom.xml。该库的当前版本是1.4.1:
<dependency> <groupId>com.github.loki4j</groupId> <artifactId>loki-logback-appender</artifactId> <version>1.4.1</version> </dependency>
|
然后我们需要在 src/main/resources 目录下创建 logback-spring.xml 文件:
<?xml version="1.0" encoding="UTF-8"?> <configuration>
<springProperty name="name" source="spring.application.name" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern> %d{HH:mm:ss.SSS} %-5level %logger{36} %X{X-Request-ID} - %msg%n </pattern> </encoder> </appender>
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender"> <!-- (1) --> <http> <url>http://localhost:3100/loki/api/v1/push</url> </http> <format> <!-- (2) --> <label> <pattern>app=${name},host=${HOSTNAME},level=%level</pattern> <!-- (3) --> <readMarkers>true</readMarkers> </label> <message> <!-- (4) --> <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="CONSOLE" /> <appender-ref ref="LOKI" /> </root>
</configuration>
|
这样我们的Loki实例在http://localhost:3100 下可访问。
Loki并不对日志的内容进行索引--而只是对元数据标签进行索引。
- 有一些静态标签,如应用程序名称、日志级别或主机名。见上面logback-spring.xml第1行注释处
- 我们可以在format.label字段中设置它们(2)。
- 我们还将设置一些动态标签,因此我们启用Logback标记功能(3)。
- 最后,我们要设置日志格式模式(4)。为了简化,与LogQL(Loki查询语言)的潜在转换,我们将使用JSON符号。
除了静态标签,我们还可以发送动态数据:
例如,只针对当前请求的特定内容。假设我们有一个管理人的服务,我们想从请求中记录目标人的ID。
@RestController @RequestMapping("/persons") public class PersonController {
private final Logger LOG = LoggerFactory .getLogger(PersonController.class); private final List<Person> persons = new ArrayList<>();
@GetMapping public List<Person> findAll() { return persons; }
@GetMapping("/{id}") public Person findById(@PathVariable("id") Long id) { Person p = persons.stream().filter(it -> it.getId().equals(id)) .findFirst() .orElseThrow(); LabelMarker marker = LabelMarker.of("personId", () -> String.valueOf(p.getId())); [b]// (1)[/b] LOG.info(marker, "Person successfully found"); [b]// (2)[/b] return p; }
@GetMapping("/name/{firstName}/{lastName}") public List<Person> findByName( @PathVariable("firstName") String firstName, @PathVariable("lastName") String lastName) { return persons.stream() .filter(it -> it.getFirstName().equals(firstName) && it.getLastName().equals(lastName)) .toList(); }
@PostMapping public Person add(@RequestBody Person p) { p.setId((long) (persons.size() + 1)); LabelMarker marker = LabelMarker.of("personId", () -> String.valueOf(p.getId())); LOG.info(marker, "New person successfully added"); persons.add(p); return p; }
@DeleteMapping("/{id}") public void delete(@PathVariable("id") Long id) { Person p = persons.stream() .filter(it -> it.getId().equals(id)) .findFirst() .orElseThrow(); persons.remove(p); LabelMarker marker = LabelMarker.of("personId", () -> String.valueOf(id)); LOG.info(marker, "Person successfully removed"); }
@PutMapping public void update(@RequestBody Person p) { Person person = persons.stream() .filter(it -> it.getId().equals(p.getId())) .findFirst() .orElseThrow(); persons.set(persons.indexOf(person), p); LabelMarker marker = LabelMarker.of("personId", () -> String.valueOf(p.getId())); LOG.info(marker, "Person successfully updated"); }
}
|
正如我之前提到的,使用Loki4j我们可以使用Logback标记。
在经典的Logback中,标记主要用于过滤日志记录。
使用Loki,我们只需要定义 LabelMarker 对象,其中包含动态字段的键/值图,代码在(1)处;然后我们将该对象传递给当前的日志行(2)。
假设我们在单个日志行中有多个动态字段,我们必须以这种方式创建LabelMarker对象:
LabelMarker marker = LabelMarker.of(() -> Map.of("audit", "true", "X-Request-ID", MDC.get("X-Request-ID"), "X-Correlation-ID", MDC.get("X-Correlation-ID")));
|
用Spring Boot应用程序运行Loki
在本地机器上运行Loki的最简单方法是使用Docker容器。除了Loki实例,我们还将运行Grafana来显示和搜索日志。下面是包含所有必要服务的docker-compose.yml。你可以用docker compose up命令来运行它们。
version: "3"
networks: loki:
services: loki: image: grafana/loki:2.8.2 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
|
为了利用Spring Boot的Docker Compose支持,我们需要将docker-compose.yml放在应用根目录下。然后,我们必须在Maven的pom.xml中包含spring-boot-docker-compose依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-docker-compose</artifactId> <optional>true</optional> </dependency>
|
运行:
$ mvn spring-boot:run
使用Spring Boot Loki Starter库
如果你不想自己配置上面这些,你可以使用我的Spring Boot库,它提供了自动配置功能。此外,它还自动记录所有传入的HTTP请求和传出的HTTP响应。如果默认设置已经足够了,你只需要把单一的Spring Boot启动器作为一个依赖项:
<dependency> <groupId>com.github.piomin</groupId> <artifactId>logstash-logging-spring-boot-starter</artifactId> <version>2.0.2</version> </dependency>
|
该库用几个默认标签记录每个请求和响应,包括例如requestId或correlationId。