Spring BootとSkywalkingの統合: トレーシング機能の追加

Skywalkingとは

Skywalkingは、特にマイクロサービス、クラウドネイティブ、およびコンテナ化されたアプリケーションを対象としたオープンソースの観測プラットフォームです。このプラットフォームは、分散システムのトレーシング、モニタリング、診断機能を提供します。

JDKバージョンとの対応関係

SkyWalking 8.xはJDK 1.8以上、9.xはJDK 11以上のバージョンが必要です。

Skywalkingのダウンロード

公式サイトからAPMとAgentsをダウンロードできます:https://skywalking.apache.org/downloads/

古いバージョンのAPMや特定のJavaバージョン向けのAgentsは以下のURLから入手可能です:

本記事では、APM 8.9.1とAgents 8.9.0を使用します。

データストア

Skywalkingは複数のデータストアオプションをサポートしています:

  1. H2(デフォルト:再起動時にデータが失われる)
  2. Elasticsearch(最も一般的な選択肢)
  3. MySQL
  4. TiDB

OAPの設定ファイル(config/application.yml)でストレージを選択します。以下はElasticsearchの設定例です:

storage:
  selector: ${SW_STORAGE:elasticsearch}
  elasticsearch:
    clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
    user: ${SW_ES_USER:""}
    password: ${SW_ES_PASSWORD:""}
    indexShardsNumber: ${SW_STORAGE_ES_INDEX_SHARDS_NUMBER:1}
    indexReplicasNumber: ${SW_STORAGE_ES_INDEX_REPLICAS_NUMBER:1}
  

Skywalkingの起動

APMディレクトリ内のbinフォルダに移動し、startup.batを管理者権限で実行します。これにより、次の2つのプロセスが開始されます:

  • Skywalking-Collector: gRPC/HTTP経由でクライアントからの情報を収集します。デフォルトポートはgRPC 11800, HTTP 12800。
  • Skywalking-Webapp: 管理画面。デフォルトポートは8080。

ブラウザでhttp://localhost:8080/にアクセスすると管理画面が表示されます。

プローブの配置

Agents 8.9.0をプロジェクトに配置します。

IDEAでのプローブの配置

VM optionsに次のパラメータを追加します:

-javaagent:D:\path\to\skywalking-agent.jar
-Dskywalking.agent.service_name=your-service-name
-Dskywalking.collector.backend_service=127.0.0.1:11800
  

コマンドラインでの起動

java -javaagent:D:\path\to\skywalking-agent.jar -Dskywalking.agent.service_name=your-service-name -Dskywalking.collector.backend_service=localhost:11800 -jar your-app.jar
  

Linux環境でのシェルスクリプトによる起動

#!/bin/bash
AGENT_PATH="/path/to/skywalking-agent"
JAR_PATH="/path/to/your-app.jar"
SERVICE_NAME="your-service-name"
COLLECTOR_BACKEND_SERVICE="localhost:11800"

JAVA_AGENT="-javaagent:$AGENT_PATH/skywalking-agent.jar -Dskywalking.agent.service_name=$SERVICE_NAME -Dskywalking.collector.backend_service=$COLLECTOR_BACKEND_SERVICE"

java $JAVA_AGENT -jar $JAR_PATH
  

Spring Bootの起動

IDEAから探針を設定してSpring Bootを起動すると、コンソールに接続ログが出力され、SkywalkingのUIでも確認できます。

ログの構成

まず、Maven依存関係を追加します:

<dependency>
  <groupId>org.apache.skywalking</groupId>
  <artifactId>apm-toolkit-logback-1.x</artifactId>
  <version>9.0.0</version>
</dependency>
  

次に、resourcesディレクトリ下にlogback-spring.xmlを作成します:

<configuration debug="false">
  <property name="LOG_HOME" value="D:/logs/"></property>

  <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
        <pattern>%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} [%X{tid}] %clr([%-10.10thread]){faint} %clr(%-5level) %clr(%-50.50logger{50}:%-3L){cyan} %clr(-){faint} %msg%n</pattern>
      </layout>
    </encoder>
  </appender>

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

入出力パラメータの取得

Agent設定での実現

Agentの設定ファイル(agent.config)で次の設定を有効にします:

plugin.tomcat.collect_http_params=true
plugin.springmvc.collect_http_params=true
plugin.httpclient.collect_http_params=true
  

ただし、この方法ではGETリクエストのパラメータのみ取得可能で、POSTリクエストのパラメータは取得できません。

TraceとFilterを使った実現

Maven依存関係を追加します:

<dependency>
  <groupId>org.apache.skywalking</groupId>
  <artifactId>apm-toolkit-trace</artifactId>
  <version>9.0.0</version>
</dependency>
  

次に、HttpFilterとContentCachingRequestWrapperを使用して、リクエストとレスポンスの内容をログに出力するフィルターを作成します:

package com.example.springbootfull.quartztest.Filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ApmHttpInfo extends HttpFilter {
  private static final Set<String> IGNORED_HEADERS = new HashSet<>();

  static {
    Set<String> ignoredHeaders = new HashSet<>();
    ignoredHeaders.addAll(
        java.util.Arrays.asList(
            "Content-Type",
            "User-Agent",
            "Accept",
            "Cache-Control",
            "Postman-Token",
            "Host",
            "Accept-Encoding",
            "Connection",
            "Content-Length"
        ).stream()
          .map(String::toUpperCase)
          .collect(Collectors.toList())
    );
    IGNORED_HEADERS = ignoredHeaders;
  }

  @Override
  public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
    ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
    ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

    try {
      filterChain.doFilter(requestWrapper, responseWrapper);
    } finally {
      try {
        StringBuilder sb = new StringBuilder("curl")
          .append(" -X ").append(request.getMethod())
          .append(" ").append(request.getRequestURL().toString());
        if (StringUtils.hasLength(request.getQueryString())) {
          sb.append("?").append(request.getQueryString());
        }

        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
          String headerName = headerNames.nextElement();
          if (!IGNORED_HEADERS.contains(headerName.toUpperCase())) {
            sb.append(" -H '").append(headerName).append(": ").append(request.getHeader(headerName)).append("'");
          }
        }

        String body = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        if (StringUtils.hasLength(body)) {
          sb.append(" -d '").append(body).append("'");
        }
        ActiveSpan.tag("input", sb.toString());

        String responseBody = new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        ActiveSpan.tag("output", responseBody);
      } catch (Exception e) {
        log.warn("fail to build http log", e);
      } finally {
        responseWrapper.copyBodyToResponse();
      }
    }
  }
}
  

これにより、GETとPOSTリクエストの入出力パラメータをログに出力できます。

タグ: SpringBoot Skywalking APM Tracing Elasticsearch

8月20日 02:32 投稿