Spring Colud Sleuth를 번역합니다.
2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...
spring-cloud-sleuth-zipkin
가 classpath에 있으면 Zipkin과 호환되는 trace를 생성하고 수집한다. 기본적으로 HTTP를 통해 로컬호스트(9411 포트)의 Zipkin 서버로 데이터를 전송한다. spring.zipkin.baseUrl
를 설정하면 이를 변경할 수 있다.spring-rabbit
을 사용하는 경우 HTTP 대신 RabbitMQ 브로커에 trace를 전달한다.spring-kafka
를 사용하며 spring.zipkin.sender.type: kafka
라는 설정을 추가하면 HTTP 대신 kafka 브로커에 trace를 전달한다.
spring-cloud-sleuth-stream
는 스팩 아웃되었으므로 사용해선 안된다.
Slf4j MDC는 자동 설정되며 logback의 경우 이전에 보여준 예제처럼 log 내 trace와 span을 즉시 확인 할 수 있다. 만약 다른 로깅 시스템을 사용하는 경우 자체 포맷터를 정의해야만 동일한 결과(출력)를 얻을 수 있을 것이다.
기본값은 다음과 같다 :
logging.pattern.level
를%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]
으로 설정한다. (이는 logback 사용자를 위한 스프링 부트가 제공하는 기능이다.)Slf4j를 사용하지 않는다면 위 패턴은 자동으로 적용되지 않는다.
❗️ NOTE
Spring Cloud Sleuth 2.0.0 버전부터 Brave를 추적 라이브러리로 사용한다. 편의를 위해 Brave document의 일부를 포함시킨다.
❗️ NOTE
대부분의 경우 Sleuth가 제공하는 Brave의Tracer
나SpanCustomizer
빈을 사용하면 된다. 아래의 document에는 Brave의 정의와 작동 방법에 대한 개요가 포함되어있다.
Brave는 분산 작업에 대한 지연 시간 정보를 포착하고 Zipkin에 보고하는데 사용되는 라이브러리이다. 대부분 사용자들은 Brave를 직접 사용하기보다 라이브러리나 프레임워크를 사용한다. (Most users do not use Brave directly. They use libraries or frameworks rather than employ Brave on their behalf.)
이 모듈은 잠재적인 분산 작업의 지연 시간을 모델링하는 span을 만들고 합치는 tracer를 포함한다. 또한 네트워트 경계를 넘어(예를 들어 HTTP 헤더를 사용한다.) trace의 context를 전파하는 라이브러리도 포함하고 있다.
가장 중요한 것은 Zipkin에 보고하기 위한 brave.Tracer
를 구성하는 것이다.
다음 예제는 추적 데이터(span)를 HTTP를 사용하여(Kafka와 반대됨) Zipkin에 전송하기 위한 설정을 보여준다.
class MyClass {
private final Tracer tracer;
// 트레이서는 자동주입된다.
MyClass(Tracer tracer) {
this.tracer = tracer;
}
void doSth() {
Span span = tracer.newTrace().name("encode").start();
// ...
}
}
이름은 구체적이고 명확해야한다. 하지만 span이 50자보다 긴 이름을 가지는 경우 50자로 잘리게되며 긴 이름은 지연 시간 문제를 일으키거나 예외를 발생시키기도 한다.
tracer는 잠재적인 분산 작업의 지연 시간을 모델링하는 span을 만들고 합친다. "sampling"을 사용함으로써 일련의 과정 중에 발생하는 오버헤드나 Zipkin으로 전송되는 데이터를 감소시킬 수 있다.
tracer가 반환한 span은 작업이 끝나면 Zipkin에 데이터를 전달하나 샘플링되지 않았으면 아무일도 일어나지 않는다. span이 시작되면 관심있는 이벤트에 어노테이션을 추가하거나 세부 정보나 조회를 위한 키가 포함된 태그를 추가할 수 있다.
span의 컨텍스트는 분산 작업을 표현하는 트리에 해당 span을 올바르게 배치하기위한 trace 식별자를 포함한다.
프로세스를 벗어나지 않는 코드를 추적 할 때는 span 영역 내부에서 실행하라
@Autowired Tracer tracer;
// 기존의 trace 내에서 새로운 trace나 span을 시작한다.
ScopedSpan span = tracer.startScopedSpan("encode");
try {
// span이 "scope" 내에 있으므로 logger와 같은 다운스트림 코드가 trace Id를 알 수 있다.
return encoder.encode();
} catch (RuntimeException | Error e) {
span.error(e); // 예외를 핸들링하지 않으면 작업이 실패했음을 알 수 없을 수도 있다.
throw e;
} finally {
span.finish(); // span은 항상 종료된다.
}
더 많은 기능이나 세밀한 제어가 필요한 경우, 다음의 Span
유형을 사용한다.
@Autowired Tracer tracer;
// 기존의 trace 내에서 새로운 trace나 span을 시작한다.
Span span = tracer.nextSpan().name("encode").start();
// logger와 같은 다운스트림 코드가 trace Id에 접근할 수 있도록 span을 "scope"에 넣는다.
try (SpanInScope ws = tracer.withSpanInScope(span)) {
return encoder.encode();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // scope는 span에 독립적이다. span은 항상 종료된다.
}
위의 두 예는 완료시 동일한 span이 보고된다.
위 예에서 span은 새로운 root span이거나 기존 trace의 다음 하위 span이다.
span에는 조회를 위한 키나 세부 정보로 사용가능한 tag를 추가할 수 있다. 예를 들어 다음과 같이 런타임 버전에 대한 정보를 태그로 추가할 수 있다.
span.tag("clnt/finagle.version", "6.36.0");
제 3자(3rd party)에 사용자지정 span에 대한 기능을 노출하는 경우에는 brave.Span
보다는 brave.SpanCustomizer
를 사용하기를 추천한다. brave.SpanCustomizer
는 이해하고 테스트하기에 더 간단하며 span의 lifecycle hook으로 사용자를 유도하지 않는다. ??? 머라는겨 (The former is simpler to understand and test and does not tempt users with span lifecycle hooks.)
interface MyTraceCallback {
void request(Request request, SpanCustomizer customizer);
}
brave.Span
은 brave.SpanCustomizer
를 구현하기때문에 다음처럼 이를 사용자에게 전달할 수 있다.
for (MyTraceCallback callback : userCallbacks) {
callback.request(request, span);
}
종종 추적이 진행 중인지 아닌지를 알 수 없을 것이다. 또한 '사용자가 null을 확인하는 것'을 원하지 않을 수도있다. (Sometimes, you do not know if a trace is in progress or not, and you do not want users to do null checks. brave.CurrentSpanCustomizer handles this problem by adding data to any span that’s in progress or drops, as shown in the following example:)
brave.CurrentSpanCustomizer
는 진행 중이거나 삭제된 모든 span에 데이터를 추가함으로써 이런 문제를 처리한다.
Ex.
// 사용자 코드에서 null일 가능성없이 주입할 수 있다.
@Autowired SpanCustomizer span;
void userCode() {
span.annotate("tx.started");
...
}
❗️ NOTE
RPC, Remote procedure call
네트워크로부터 떨어져 있는 컴퓨터에서 코드를 실행하는 방식. 프로그래머는 함수가 실행 프로그램의 로컬 위치에 있든 원격 위치에 있든 동일한 코드를 이용할 수 있다.네트워크 서버 프로그램에서 작업 코드와 메인 코드를 독립 시킴으로써 경우에 따라서 Code1(네트워크 연결)와 Code2(코드의 실행을 담당하는 부분)를 실행시키고자 할 때 사용하는 개발도구이다.
RPC 추적은 종종 인터셉터에 의해 자동으로 수행된다. 뒤편에서 인터셉터는 RPC 작업에서 해당 작업의 역할과 관련된 tag와 이벤트를 추가한다.
다음 예는 client span을 추가하는 방법을 보여준다.
@Autowired Tracing tracing;
@Autowired Tracer tracer;
// 요청을 보내기전에 작업을 설명하는 메타데이터를 추가한다.
span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
span.tag("myrpc.version", "1.0.0");
span.remoteServiceName("backend");
span.remoteIpAndPort("172.3.4.1", 8108);
// in-band내에 전파될 수 있도록 trace 컨텍스트를 요청에 추가한다.
tracing.propagation().injector(Request::addHeader)
.inject(span.context(), request);
// 요청이 예약되면 span이 시작된다.
span.start();
// 에러가 있으면 이를 span의 tag로 추가한다.
span.tag("error", error.getCode());
// 혹은 예외를 발생시킨다.
span.error(exception);
// 응답이 완료되면 span을 종료한다.
span.finish();
One-Way tracing
요청은 있으나 응답이 없는 비동기 작업을 모델링해야하는 경우가 있다. 일반적인 경우에는 span.finish()
을 사용하여 응답을 받았음을 나타낸다. 하지만 단방향 추적에서는 응답이 없으므로 span.flush()
를 사용한다.
다음 예는 클라이언트가 단방향 작업을 모델링하는 방법을 보여준다.
@Autowired Tracing tracing;
@Autowired Tracer tracer;
// 클라이언트 요청을 나타내는 새로운 span을 시작한다.
oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
// in-band에 전파하기 위해 trace 컨텍스트를 요청에 추가한다.
tracing.propagation().injector(Request::addHeader)
.inject(oneWaySend.context(), request);
// fire off the request asynchronously, totally dropping any response
request.execute();
// 클라이언트 측을 시작하고 finish 대신 flush를 사용한다.
oneWaySend.start().flush();
다음은 서버가 단방향 작업을 처리하는 방법을 보여준다.
@Autowired Tracing tracing;
@Autowired Tracer tracer;
// 들어오는 요청에서 컨텍스트를 가져온다.
extractor = tracing.propagation().extractor(Request::getHeader);
// 이 컨텍스트를 이름을 지정하고 태그를 추가할 수 있는 span으로 변환한다.
oneWayReceive = nextSpan(tracer, extractor.extract(request))
.name("process-request")
.kind(SERVER)
... add tags etc.
// 서버 측을 시작하고 finish대신 flush를 사용한다.
oneWayReceive.start().flush();
// 이 span이 완료되었으므로 더 이상 수정되서는 안되나 후처리를 위해 자식을 만들 수 있다.
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();