스프링 클라우드 슬루스 공식 레퍼런스를 한글로 번역한 문서입니다.
전체 목차는 여기에 있습니다.
이번 섹션에선 Spring Cloud Sleuth를 사용할 때 자주 묻곤 하는 “어떻게 해야 하나요…?” 류의 질문들에 답하는 섹션이다. 모든 질문에 답해주진 않지만, 꽤 많은 내용을 다루고 있다.
현재 직면한 문제를 이 문서에서 다루지 않았다면, stackoverflow.com에서도 찾아봐라. 이미 다른 사람이 답변해 두었을 수도 있다. 스택 오버플로는 질문을 새로 올리기에도 아주 좋은 곳이다 (spring-cloud-sleuth
태그를 달아달라).
이 섹션에 내용을 추가하는 것 또한 환영한다. “how-to” 가이드를 추가하고 싶다면 풀 리퀘스트를 만들면 된다.
목차
- 5.1. Sleuth를 Brave와 함께 사용하려면 어떻게 설정해야 하나요?
- 5.2. Sleuth를 Brave, Zipkin과 함께 사용하면서 HTTP로 통신하려면 어떻게 설정해야 하나요?
- 5.3. Sleuth를 Brave, Zipkin과 함께 사용하면서 메시지를 통해 통신하려면 어떻게 설정해야 하나요?
- 5.4. Span이 외부 시스템에 전달되지 않을 땐 어떻게 하나요?
- 5.5. RestTemplate, WebClient 등이 계측되지 않을 땐 어떻게 하나요?
- 5.6. HTTP 서버 응답에 헤더는 어떻게 추가하나요?
- 5.7. HTTP 클라이언트 Span을 커스텀하려면 어떻게 해야 하나요?
- 5.8. HTTP 서버 Span을 커스텀하려면 어떻게 해야 하나요?
- 5.9. 로그에 애플리케이션 이름을 추가하려면 어떻게 해야 하나요?
- 5.10. 컨텍스트 전파 메커니즘Context Propagation Mechanism은 어떻게 변경하나요?
- 5.11. 트레이서Tracer를 직접 구현하려면 어떻게 해야 하나요?
5.1. Sleuth를 Brave와 함께 사용하려면 어떻게 설정해야 하나요?
클래스패스에 Sleuth starter를 추가해라.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-sleuth"
}
5.2. Sleuth를 Brave, Zipkin과 함께 사용하면서 HTTP로 통신하려면 어떻게 설정해야 하나요?
클래스패스에 Sleuth starter와 Zipkin을 추가해라.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-sleuth"
implementation "org.springframework.cloud:spring-cloud-sleuth-zipkin"
}
5.3. Sleuth를 Brave, Zipkin과 함께 사용하면서 메시지를 통해 통신하려면 어떻게 설정해야 하나요?
HTTP 대신 RabbitMQ나 Kafka, ActiveMQ를 사용하려면 spring-rabbit
, spring-kafka
, org.apache.activemq:activemq-client
의존성 중 필요한 의존성을 추가해라. 디폴트 목적지destination 이름은 Zipkin
이다.
카프카를 사용한다면, 반드시 카프카 의존성을 추가해줘야 한다.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-sleuth"
implementation "org.springframework.cloud:spring-cloud-sleuth-zipkin"
implementation "org.springframework.kafka:spring-kafka"
}
추가로, spring.zipkin.sender.type
프로퍼티도 맞는 값으로 설정해 줘야 한다:
spring.zipkin.sender.type: kafka
Sleuth를 RabbitMQ와 연동하려면 spring-cloud-starter-sleuth
, spring-cloud-sleuth-zipkin
, spring-rabbit
의존성을 추가해라.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-sleuth"
implementation "org.springframework.cloud:spring-cloud-sleuth-zipkin"
implementation "org.springframework.amqp:spring-rabbit"
}
Sleuth를 ActiveMQ와 연동하려면 spring-cloud-starter-sleuth
, spring-cloud-sleuth-zipkin
, activemq-client
의존성을 추가해라.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-client</artifactId>
</dependency>
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-sleuth"
implementation "org.springframework.cloud:spring-cloud-sleuth-zipkin"
implementation "org.apache.activemq:activemq-client"
}
또한, spring.zipkin.sender.type
프로퍼티도 그에 맞게 설정해줘야 한다:
spring.zipkin.sender.type: activemq
5.4. Span이 외부 시스템에 전달되지 않을 땐 어떻게 하나요?
Zipkin 등의 외부 시스템에 span이 리포트되지 않는다면, 보통은 아래와 같은 문제일 가능성이 크다:
- Span이 샘플링되지 않고 있는 경우
- 외부 시스템에 리포트할 때 필요한 의존성을 추가하는 것을 잊은 경우 (e.g.
spring-cloud-sleuth-zipkin
) - 외부 시스템의 커넥션 정보를 잘못 설정한 경우
5.4.1. Span이 샘플링되지 않고 있는 경우
span이 샘플링되고 있는지 여부를 체크하려면, 플래그 값만 확인해주면 된다. 아래 예시를 살펴보자:
2020-10-21 12:01:16.285 INFO [backend,0b6aaf642574edd3,0b6aaf642574edd3,true] 289589 --- [nio-9000-exec-1] Example : Hello world!
위에 출력된 [backend,0b6aaf642574edd3,0b6aaf642574edd3,true]
안에 있는 boolean 값이 true
이면, 이 span은 샘플링하고 있으며 리포트된다는 걸 의미한다.
5.4.2. 의존성이 추가되지 않은 경우
Sleuth 3.0.0 이전엔 spring-cloud-starter-zipkin
의존성에 spring-cloud-starter-sleuth
와 spring-cloud-sleuth-zipkin
이 포함돼 있었다. 3.0.0에서는 spring-cloud-starter-zipkin
이 제거됐으므로, spring-cloud-sleuth-zipkin
으로 변경해줘야 한다.
5.4.3. 커넥션 설정 오류
원격 시스템의 주소를 제대로 설정했는지 (e.g. spring.zipkin.baseUrl
), 브로커를 이용해 통신한다면 브로커 커넥션 정보를 제대로 설정했는지 다시 한 번 확인해봐라.
5.5. RestTemplate, WebClient 등이 계측되지 않을 땐 어떻게 하나요?
트레이싱tracing 컨텍스트가 전파되지 않는다면, 다음과 같은 이유가 있을 수 있다:
- 주어진 라이브러리 계측instrumentation을 지원하지 않는 경우
- 라이브러리 계측instrumentation은 지원하지만 설정이 잘못된 경우
계측instrumentation을 지원하지 않는 라이브러리를 사용하고 있다면, 이슈를 등록해 계측instrumentation 기능 추가를 요청해달라.
설정이 잘못된 경우라면, 통신에 사용 중인 클라이언트가 스프링 빈이 맞는지 확인해봐라. new
연산자를 통해 클라이언트를 직접 생성한다면 계측instrumentation 기능이 동작하지 않는다.
다음은 계측instrumentation 기능이 동작하는 예시다:
@Configuration(proxyBeanMethods = false)
class MyConfiguration {
@Bean RestTemplate myRestTemplate() {
return new RestTemplate();
}
}
@Service
class MyService {
private final RestTemplate restTemplate;
MyService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
String makeACall() {
return this.restTemplate.getForObject("http://example.com", String.class);
}
}
다음은 계측instrumentation 기능이 동작하지 않는 예시다:
@Service
class MyService {
String makeACall() {
// This will not work because RestTemplate is not a bean
return new RestTemplate().getForObject("http://example.com", String.class);
}
}
5.6. HTTP 서버 응답에 헤더는 어떻게 추가하나요?
서버 응답을 세팅할 필터를 등록해라.
@Configuration(proxyBeanMethods = false)
class MyConfig {
// Example of a servlet Filter for non-reactive applications
@Bean
Filter traceIdInResponseFilter(Tracer tracer) {
return (request, response, chain) -> {
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
HttpServletResponse resp = (HttpServletResponse) response;
// putting trace id value in [mytraceid] response header
resp.addHeader("mytraceid", currentSpan.context().traceId());
}
chain.doFilter(request, response);
};
}
// Example of a reactive WebFilter for reactive applications
@Bean
WebFilter traceIdInResponseFilter(Tracer tracer) {
return (exchange, chain) -> {
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
// putting trace id value in [mytraceid] response header
exchange.getResponse().getHeaders().add("mytraceid", currentSpan.context().traceId());
}
return chain.filter(exchange);
};
}
}
5.7. HTTP 클라이언트 Span을 커스텀하려면 어떻게 해야 하나요?
요청하는 쪽을 커스텀하려면 HttpRequestParser
타입 빈을 HttpClientRequestParser.NAME
이란 이름으로 등록해라. 응답하는 쪽을 커스텀하려면 HttpResponseParser
타입 빈을 HttpClientRequestParser.NAME
이라는 이름으로 등록해라.
@Configuration(proxyBeanMethods = false)
public static class ClientParserConfiguration {
// example for Feign
@Bean(name = HttpClientRequestParser.NAME)
HttpRequestParser myHttpClientRequestParser() {
return (request, context, span) -> {
// Span customization
span.name(request.method());
span.tag("ClientRequest", "Tag");
Object unwrap = request.unwrap();
if (unwrap instanceof feign.Request) {
feign.Request req = (feign.Request) unwrap;
// Span customization
span.tag("ClientRequestFeign", req.httpMethod().name());
}
};
}
// example for Feign
@Bean(name = HttpClientResponseParser.NAME)
HttpResponseParser myHttpClientResponseParser() {
return (response, context, span) -> {
// Span customization
span.tag("ClientResponse", "Tag");
Object unwrap = response.unwrap();
if (unwrap instanceof feign.Response) {
feign.Response resp = (feign.Response) unwrap;
// Span customization
span.tag("ClientResponseFeign", String.valueOf(resp.status()));
}
};
}
}
이 parser가 동작하려면 span을 샘플링해야 한다. 즉, Zipkin 등으로 span을 내보낼 수 있어야 한다.
5.8. HTTP 서버 Span을 커스텀하려면 어떻게 해야 하나요?
요청하는 쪽을 커스텀하려면 HttpRequestParser
타입 빈을 HttpServerRequestParser.NAME
이란 이름으로 등록해라. 응답하는 쪽을 커스텀하려면 HttpResponseParser
타입 빈을 HttpServerResponseParser.NAME
이라는 이름으로 등록해라.
@Configuration(proxyBeanMethods = false)
public static class ServerParserConfiguration {
@Bean(name = HttpServerRequestParser.NAME)
HttpRequestParser myHttpRequestParser() {
return (request, context, span) -> {
// Span customization
span.tag("ServerRequest", "Tag");
Object unwrap = request.unwrap();
if (unwrap instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest) unwrap;
// Span customization
span.tag("ServerRequestServlet", req.getMethod());
}
};
}
@Bean(name = HttpServerResponseParser.NAME)
HttpResponseParser myHttpResponseParser() {
return (response, context, span) -> {
// Span customization
span.tag("ServerResponse", "Tag");
Object unwrap = response.unwrap();
if (unwrap instanceof HttpServletResponse) {
HttpServletResponse resp = (HttpServletResponse) unwrap;
// Span customization
span.tag("ServerResponseServlet", String.valueOf(resp.getStatus()));
}
};
}
@Bean
Filter traceIdInResponseFilter(Tracer tracer) {
return (request, response, chain) -> {
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
HttpServletResponse resp = (HttpServletResponse) response;
resp.addHeader("mytraceid", currentSpan.context().traceId());
}
chain.doFilter(request, response);
};
}
}
이 parser가 동작하려면 span을 샘플링해야 한다. 즉, Zipkin 등으로 span을 내보낼 수 있어야 한다.
5.9. 로그에 애플리케이션 이름을 추가하려면 어떻게 해야 하나요?
디폴트 로그 포맷을 변경하지 않았다는 가정 하에, application.yml
이 아닌 bootstrap.yml
에 spring.application.name
프로퍼티를 설정해라.
Spring Cloud의 새로운 설정 부트스트랩 기능에서는 더 이상 부트스트랩 컨텍스트가 존재하지 않으므로, 이 작업은 더 이상 필요하지 않다.
5.10. 컨텍스트 전파 메커니즘Context Propagation Mechanism은 어떻게 변경하나요?
기본으로 제공하는 메커니즘을 통해 헤더를 전파propagation하려면 spring.sleuth.propagation.type
프로퍼티를 변경해주면 된다. 값을 여러 개 지정하면 더 다양한 트레이싱propagation 헤더를 전파한다.
Brave의 경우 전파propagation 타입으로 AWS
, B3
, W3C
를 지원한다.
커스텀 전파propagation 메커니즘을 사용하려면, spring.sleuth.propagation.type
프로퍼티를 CUSTOM
으로 설정하고 자체 빈을 구현해라 (Brave의 경우 Propagation.Factory
). 샘플 코드는 아래에서 확인할 수 있다:
@Component
class CustomPropagator extends Propagation.Factory implements Propagation<String> {
@Override
public List<String> keys() {
return Arrays.asList("myCustomTraceId", "myCustomSpanId");
}
@Override
public <R> TraceContext.Injector<R> injector(Setter<R, String> setter) {
return (traceContext, request) -> {
setter.put(request, "myCustomTraceId", traceContext.traceIdString());
setter.put(request, "myCustomSpanId", traceContext.spanIdString());
};
}
@Override
public <R> TraceContext.Extractor<R> extractor(Getter<R, String> getter) {
return request -> TraceContextOrSamplingFlags.create(TraceContext.newBuilder()
.traceId(HexCodec.lowerHexToUnsignedLong(getter.get(request, "myCustomTraceId")))
.spanId(HexCodec.lowerHexToUnsignedLong(getter.get(request, "myCustomSpanId"))).build());
}
@Override
public Propagation<String> get() {
return this;
}
}
5.11. 트레이서Tracer를 직접 구현하려면 어떻게 해야 하나요?
Spring Cloud Sleuth API에는 트레이서tracer가 구현해야 하는 모든 인터페이스가 포함돼 있다. Spring Cloud Sleuth 프로젝트는 OpenZipkin Brave 구현체와 함께 제공된다. 두 트레이서tracer가 Sleuth API에 어떻게 연결되는지는 org.springframework.cloud.sleuth.brave.bridge
모듈을 보면 확인할 수 있다.
Next :Spring Cloud Sleuth customization
스프링 클라우드 슬루스의 다양한 통합 기능들과 이를 커스텀 하는 방법
전체 목차는 여기에 있습니다.