使用 Loki 登入 Spring Boot
一、簡介
Grafana Labs 開發了 Loki,這是一個受Prometheus啟發的開源日誌聚合系統。其目的是儲存和索引日誌數據,以便於對不同應用程式和系統產生的日誌進行高效查詢和分析。
在本文中,我們將使用 Grafana Loki 為 Spring Boot 應用程式設定日誌記錄。 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-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資料夾下建立一個logging-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。之後,我們將使用HTTP API查詢 Loki 以確認日誌是否確實已推送。有關啟動 Grafana 和 Loki 容器的信息,請參閱前面的部分。
首先,讓我們執行DemoService.log()方法,該方法將呼叫Logger.info() 。這使用loki-logback-appender發送一條訊息,Loki 將收集該訊息:
DemoService service = new DemoService();
 service.log();
其次,我們將建立一個呼叫 Loki HTTP API 提供的 REST 端點的請求。此 GET API 接受表示query 、 start時間和end時間的查詢參數。我們將新增這些參數作為請求物件的一部分:
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();
接下來,我們使用 request 物件來執行 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 服務。我們可以透過在瀏覽器中造訪http://localhost:3000 (部署 Grafana 服務的位置)來視覺化它。
要查看 Loki 中儲存和索引的日誌,我們需要使用 Grafana。 Grafana 資料來源為 Loki 提供可設定的連線參數,我們需要在其中輸入 Loki 端點、驗證機制等。
首先,讓我們設定日誌已推送到的 Loki 端點:
   
成功配置資料來源後,讓我們繼續探索用於查詢日誌的資料頁面:
   
我們可以編寫查詢以將應用程式日誌取得到 Grafana 中以進行視覺化。在我們的演示服務中,我們正在推送INFO日誌,因此我們需要將其新增至我們的過濾器並執行查詢:
   
執行查詢後,我們將看到與我們的搜尋相符的所有INFO日誌:
   
六,結論
在本文中,我們使用 Grafana Loki 為 Spring Boot 應用程式設定日誌記錄。我們還透過單元測試和視覺化驗證了我們的設置,使用簡單的邏輯記錄INFO日誌並在 Grafana 中設置 Loki 資料來源。
與往常一樣,範例程式碼可以在 GitHub 上取得。