마이크로미터 트레이싱 공식 레퍼런스를 한글로 번역한 문서입니다.
전체 목차는 여기에 있습니다.
Micrometer Tracing에는 micrometer-tracing-test
, micrometer-tracing-integration-test
모듈이 포함돼 있다.
단위 테스트의 경우, Tracer
의 테스트 전용 구현체인 SimpleTracer
를 제공한다.
통합 테스트의 경우, 테스트할 코드에 SampleTestRunner
를 연결해주면 된다. SampleTestRunner
는 다음과 같은 메커니즘을 제공한다:
- OpenZipkin Brave Tracer를 설정한다
- Tanzu Observability by Wavefront Reporter를 세팅한다
- OpenZipkin Zipkin Reporter를 세팅한다
- OpenTelemetry Tracer를 설정한다
- Tanzu Observability by Wavefront Exporter를 세팅한다
- OpenZipkin Zipkin Exporter를 세팅한다
- 사용자 코드와 실행 중인 인프라에 대해 위의 모든 조합을 실행한다.
목차
Installing
다음은 Gradle에서 필요한 의존성을 나타낸 예시다 (Micrometer Tracing BOM을 추가했다고 가정한다):
testImplementation 'io.micrometer:micrometer-tracing-test' // for unit tests
testImplementation 'io.micrometer:micrometer-tracing-integration-test' // for integration tests
다음은 Maven에서 필요한 의존성을 나타낸 예시다 (Micrometer Tracing BOM을 추가했다고 가정한다):
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId> <!-- For unit tests -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-integration-test</artifactId> <!-- For integration tests -->
<scope>test</scope>
</dependency>
Running Tracing Unit Tests
커스텀 핸들러의 단위 테스트 코드를 만들려면, 테스트 전용 Tracer
구현체인 SimpleTracer
를 사용하면 된다. 아래와 같이 TracingObservationHandler
를 커스텀했다고 가정해 보자:
static class MyTracingObservationHandler implements TracingObservationHandler<CustomContext> {
private final Tracer tracer;
MyTracingObservationHandler(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void onStart(CustomContext context) {
String databaseName = context.getDatabaseName();
Span.Builder builder = this.tracer.spanBuilder().kind(Span.Kind.CLIENT).remoteServiceName(databaseName);
getTracingContext(context).setSpan(builder.start());
}
@Override
public void onError(CustomContext context) {
getTracingContext(context).getSpan().error(context.getError());
}
@Override
public void onStop(CustomContext context) {
Span span = getRequiredSpan(context);
span.name(context.getContextualName() != null ? context.getContextualName() : context.getName());
tagSpan(context, span);
span.end();
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof CustomContext;
}
@Override
public Tracer getTracer() {
return this.tracer;
}
}
span이 제대로 생성되었는지 검증하려면, 다음과 같이 SimpleTracer
를 활용하면 된다:
class SomeComponentThatIsUsingMyTracingObservationHandlerTests {
ObservationRegistry registry = ObservationRegistry.create();
SomeComponent someComponent = new SomeComponent(registry);
SimpleTracer simpleTracer = new SimpleTracer();
MyTracingObservationHandler handler = new MyTracingObservationHandler(simpleTracer);
@BeforeEach
void setup() {
registry.observationConfig().observationHandler(handler);
}
@Test
void should_store_a_span() {
// this code will call actual Observation API
someComponent.doSthThatShouldCreateSpans();
TracerAssert.assertThat(simpleTracer)
.onlySpan()
.hasNameEqualTo("insert user")
.hasKindEqualTo(Span.Kind.CLIENT)
.hasRemoteServiceNameEqualTo("mongodb-database")
.hasTag("mongodb.command", "insert")
.hasTag("mongodb.collection", "user")
.hasTagWithKey("mongodb.cluster_id")
.assertThatThrowable()
.isInstanceOf(IllegalStateException.class)
.backToSpan()
.hasIpThatIsBlank()
.hasPortThatIsNotSet();
}
}
Running integration tests
다음은 작성한 코드로 통합 테스트를 진행하는 방법을 보여주는 예시다:
- 외부 시스템으로 보고하지 않고 저장한 span을 검증한다
- Tanzu Observability by Wavefront 인스턴스를 실행해본다 (생성자에 Wavefront 관련 설정을 전달한 경우에만. 그 외는 비활성화된다)
- Zipkin 인스턴스를 실행해본다 (Zipkin이 실행 중일 때에만. 그 외는 비활성화된다)
class ObservabilitySmokeTest extends SampleTestRunner {
ObservabilitySmokeTest() {
super(SampleRunnerConfig.builder().wavefrontApplicationName("my-app").wavefrontServiceName("my-service")
.wavefrontToken("...")
.wavefrontUrl("...")
.zipkinUrl("...") // defaults to localhost:9411
.build());
}
@Override
public BiConsumer<BuildingBlocks, Deque<ObservationHandler<? extends Observation.Context>>> customizeObservationHandlers() {
return (bb, handlers) -> {
ObservationHandler defaultHandler = handlers.removeLast();
handlers.addLast(new MyTracingObservationHandler(bb.getTracer()));
handlers.addLast(defaultHandler);
};
}
@Override
public SampleTestRunnerConsumer yourCode() {
return (bb, meterRegistry) -> {
// here you would be running your code
yourCode();
SpansAssert.assertThat(bb.getFinishedSpans())
.haveSameTraceId()
.hasNumberOfSpansEqualTo(8)
.hasNumberOfSpansWithNameEqualTo("handle", 4)
.forAllSpansWithNameEqualTo("handle", span -> span.hasTagWithKey("rsocket.request-type"))
.hasASpanWithNameIgnoreCase("request_stream")
.thenASpanWithNameEqualToIgnoreCase("request_stream")
.hasTag("rsocket.request-type", "REQUEST_STREAM")
.backToSpans()
.hasASpanWithNameIgnoreCase("request_channel")
.thenASpanWithNameEqualToIgnoreCase("request_channel")
.hasTag("rsocket.request-type", "REQUEST_CHANNEL")
.backToSpans()
.hasASpanWithNameIgnoreCase("request_fnf")
.thenASpanWithNameEqualToIgnoreCase("request_fnf")
.hasTag("rsocket.request-type", "REQUEST_FNF")
.backToSpans()
.hasASpanWithNameIgnoreCase("request_response")
.thenASpanWithNameEqualToIgnoreCase("request_response")
.hasTag("rsocket.request-type", "REQUEST_RESPONSE");
MeterRegistryAssert.assertThat(meterRegistry)
.hasTimerWithNameAndTags("rsocket.response", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_RESPONSE")))
.hasTimerWithNameAndTags("rsocket.fnf", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_FNF")))
.hasTimerWithNameAndTags("rsocket.request", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_RESPONSE")))
.hasTimerWithNameAndTags("rsocket.channel", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_CHANNEL")))
.hasTimerWithNameAndTags("rsocket.stream", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_STREAM")));
};
}
}
전체 목차는 여기에 있습니다.