記事ディレクトリ
序文
最近、本番環境で例外を確認しているときに問題が発生しました。リクエストの入力パラメータのログ情報は見つかりましたが、本番環境では同時リクエストの数が比較的多いため、リクエストの入力パラメータとリクエストの対応関係が判断できません。後続の実行プロセスで出力されるログ、返される情報、および入力パラメータを考慮すると、リクエストの完全なリンク情報を分類することは不可能であり、位置決めの問題に大きな課題をもたらします。したがって、マイクロサービス アーキテクチャだけでなく、単一のアプリケーションにもスルース リンク トラッキングを導入することを強くお勧めします。
1. 問題のシミュレーション
ログの側面を定義し、环绕通知
レコード リクエストの入力パラメータを使用して、結果を返します。
/**
* Web层日志切面
*/
@Aspect
@Component
@Slf4j
public class WebLogAspect {
/**
* 定义切面
*/
@Pointcut("execution(public * com.laowan.limit.controller..*.*(..))")
public void webLog(){
}
@Around(value = "webLog()")
public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = StopWatch.createStarted();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String params = this.getRequestParams(request,joinPoint);
log.info("【请求信息】请求url:{},参数:{} ", request.getServletPath(), params);
//继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
//如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走.
Object proceed = joinPoint.proceed(joinPoint.getArgs());
stopWatch.stop();
long watchTime = stopWatch.getTime();
log.info("【响应结果】返回值={},耗时={} (毫秒)", proceed, watchTime);
return proceed;
}
/**
* 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知@AfterReturning不会再触发
* @param jp
* @param ex
*/
@AfterThrowing(pointcut = "webLog()", throwing = "ex")
public void aspectAfterThrowing(JoinPoint jp, Exception ex) {
String methodName = jp.getSignature().getName();
log.error("【后置异常通知】{}方法异常:{}" ,methodName, ex.getMessage());
}
/**
* 获取请求参数
* @param request
* @param joinPoint
*/
private String getRequestParams(HttpServletRequest request, JoinPoint joinPoint) throws JsonProcessingException {
StringBuilder params = new StringBuilder();
// 获取 request parameter 中的参数
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
params.append(entry.getKey()).append(" = ").append(Arrays.toString(entry.getValue())).append(";");
}
}
// 获取非 request parameter 中的参数
Object[] objects = joinPoint.getArgs();
for (Object arg : objects) {
if (arg == null) {
break;
}
String className = arg.getClass().getName().toLowerCase();
String contentType = request.getHeader("Content-Type");
if (contentType != null && contentType.contains("application/json")){
// json 参数
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
params.append(mapper.writeValueAsString(arg));
}
}
return params.toString();
}
}
単一リクエストのログ情報:
同時リクエストのログ情報:
問題の説明:
同時リクエストでは、リクエストログ間の対応関係はリクエスト実行スレッド [nio-8080-exec-*] を通じて間接的にのみ実現できますが、各実行スレッドは常に新しい HTTP リクエストを処理し、クラスタ内で環境 次に、スレッド名が重複する可能性があります。この場合、ログ情報に基づいてリクエストの完全な実行リンク情報を整理することは困難です。
2. 探偵リンク追跡の導入
リンク追跡の実装は数多くありますが、ここではそれを使用しますspring-cloud-starter-sleuth
。
Sleuth は単独で使用することも、Zipkin と統合して使用することもできます。ここではログのリンク情報を生成するだけなのでSleuthを単独で使用します。
公式サイト情報:Only Sleuth(ログ相関)
1. sleuth の Maven 依存関係を導入する
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--引入sleuth依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
……
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注:
ここでは、スプリング ブートのメジャー バージョンとスプリング クラウドのバージョン間の互換性関係が必要です。
公式Webサイトのコンポーネントバージョンの対応アドレス:Spring Cloud公式Webサイト統合コンポーネントバージョンの対応説明
2. 属性設定の追加
# 开启sleuth链路跟踪,默认为true,所以可以省略配置
spring.sleuth.enabled = true
# 配置服务名称
spring.application.name=Sleuth
3. ログバック設定
sleuth link tracking での logback のログ構成情報については、公式 Web サイトのデモを参照できます:
Logback 構成: logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- Example for logging into the build folder of your project -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
<!-- You can override this to have a custom pattern -->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${
CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file -->
<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${
LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${
LOG_FILE}.%d{
yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<!-- 注意:这里不用使用控制台格式${
CONSOLE_LOG_PATTERN}输出 -->
<pattern>${
FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="flatfile"/>
</root>
</configuration>
注:
1. テキスト ログ出力フラットファイルにカラー ログ出力を使用しないでください。使用しないと、生成されたログ ファイルに大量の情報が文字化けして表示されます。
2. ログを収集したい場合は、現場分析に便利なjson形式でログを出力するlogstashの利用を推奨します。
4. ログ情報
ログの形式は次のとおりです。[application name, traceId, spanId]
- アプリケーション名 — アプリケーションの名前。これは、application.properties の spring.application.name パラメーターによって構成されたプロパティです。
- traceId — リクエストに割り当てられた ID 番号。リクエスト リンクを識別するために使用されます。
- scanId — 基本的な作業単位を示します。リクエストには複数のステップを含めることができ、各ステップには独自の spanId があります。リクエストには TraceId を 1 つだけ含めることができますが、複数の SpanId を含めることができます。
5. @NewSpan アノテーションを使用して新しい Span を宣言します。
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@NewSpan
@Override
public void order(Order order) throws Exception {
Thread.sleep(200);
log.info("下单");
}
}
注: @NewSpan は同じクラスでは有効になりません。
3. Sleuth リンク トラッキングを導入する利点
1. ログ情報のtraceIdを通じて、単一のリクエストに関連するすべてのログ情報を簡単に見つけることができます理清整个请求的完整链路
。
2. ログ情報内のtraceId、spanId、ログ出力時刻情報により、ログ情報の取得が可能です得出整个请求以及各个阶段span的耗时情况
。
4. スルースのコンセプトの説明
Spring Cloud Sleuth は、Google のオープンソース プロジェクト Dapper の用語を使用します。
● スパン: 基本的な作業単位。たとえば、新しく作成されたスパンで RPC を送信することは、RPC に応答要求を送信することと同じです。スパンは 64 ビット ID によって一意に識別され、トレースは別の 64 ビット ID によって表されます。ビット ID であり、スパンには他のデータが含まれています。サマリー、タイムスタンプ イベント、キー値の注釈 (タグ)、スパン ID、プログレス ID (通常は IP アドレス) などの情報です。スパンは常に開始および停止しており、時間情報
はスパンを作成したら、将来のある時点でそれを停止する必要があります。
● トレース: 一連のスパンで構成されるツリー構造 たとえば、分散ビッグ データ プロジェクトを実行している場合は、トレースの作成が必要になる場合があります。
● 注釈: イベントの存在を時間内に記録するために使用され、いくつかのコア注釈はリクエストの開始と終了を定義するために使用されます。
○ cs - 送信されたクライアント - クライアントがリクエストを開始します。このアノテーションはスパンの開始を示します。
○ sr - サーバーが受信しました - サーバーはリクエストを受信し、処理を開始する準備ができています。sr から cs タイムスタンプを減算すると、次のようになります。ネットワーク遅延を取得します。
○ ss - サーバー送信 - アノテーションはリクエスト処理の完了(リクエストがクライアントに戻ったとき)を示します。ss から sr タイムスタンプを引いた場合、サーバーが要求した処理リクエスト時間を取得できます。 ○ cr - クライアント受信 - スパンの終わりを示します。
クライアントはサーバーから応答を正常に受信しました。 cr から cs タイムスタンプを引いた場合、クライアントがサーバーから応答を取得するのに必要なすべての時間を取得できます。
五、LogbackのMDCの特徴
Logback の MDC 機能、すなわちMapped Diagnostic Contexts (诊断上下文映射)
。
Logback の設計目標の 1 つは、複雑な分散アプリケーションを監査およびデバッグすることです。実際の分散システムのほとんどは、複数のクライアントからのリクエストを同時に処理する必要があります。各クライアントのログを区別し、特定のリクエスト ログがどのクライアントからのものかをすばやく特定するために、最も簡単な方法は、各クライアントの各ログ リクエストに一意のタグを付けることです。各リクエストを一意にマークするために、ユーザーはコンテキスト情報を MDC に入力します。
MDC クラスには、開発者が特定のログ コンポーネントで取得できる情報を診断コンテキストに配置できる静的メソッドのみが含まれています。MDC根据每个线程管理上下文信息
。通常、新しいクライアントのサービス リクエストを開始するとき、開発者はクライアント ID、クライアントの IP アドレス、リクエスト パラメータなどの関連するコンテキスト情報を挿入します。LOGBACK コンポーネント (適切に構成されている場合) は、この情報をすべてのログ エントリに自動的に含めます。
package org.slf4j;
public class MDC {
//Put a context value as identified by key
//into the current thread's context map.
public static void put(String key, String val);
//Get the context identified by the key parameter.
public static String get(String key);
//Remove the context identified by the key parameter.
public static void remove(String key);
//Clear all entries in the MDC.
public static void clear();
}
したがって、sleuth は、traceId および spanId 情報を生成し、その情報を Logback の診断マッピング コンテキスト (DMC) に渡す役割を果たし、Logback は特定のログ形式に従ってログ出力を実行します。
Sleuth (ログ相関) のみ
logback の MDC 機能
Spring-Cloud-Sleuth の紹介
Spring Cloud 公式サイト統合コンポーネント バージョン対応説明