/** * Convenience constructor that uses {@link WingtipsToZipkinSpanConverterDefaultImpl} for converting Wingtips spans * to Zipkin spans, {@link AsyncReporter} for the Zipkin reporter, and {@link URLConnectionSender} as the underlying * {@link Sender} used by the {@link AsyncReporter}. If you need more flexibility in the span converter or * reporter/sender, then use the {@link * WingtipsToZipkinLifecycleListener#WingtipsToZipkinLifecycleListener(String, WingtipsToZipkinSpanConverter, Reporter)} * constructor instead. * * @param serviceName The name of this service. This is used to build the Zipkin {@link Endpoint}, which tells * Zipkin which service generated the spans we're going to send it. * @param postZipkinSpansBaseUrl The base URL of the Zipkin server. This should include the scheme, host, and port * (if non-standard for the scheme). e.g. {@code http://localhost:9411}, or * {@code https://zipkinserver.doesnotexist.com/}. This assumes you want to send spans to Zipkin over HTTP - if * you want to use something else then you'll need to specify your own reporter/sender using the {@link * WingtipsToZipkinLifecycleListener#WingtipsToZipkinLifecycleListener(String, WingtipsToZipkinSpanConverter, Reporter)} * constructor instead. */ public WingtipsToZipkinLifecycleListener(String serviceName, String postZipkinSpansBaseUrl) { this(serviceName, new WingtipsToZipkinSpanConverterDefaultImpl(), generateBasicZipkinReporter(postZipkinSpansBaseUrl) ); }
if (isAllowedNumChars(originalId, allow128Bit)) { if (isLowerHex(originalId)) { else if (isHex(originalId, true)) { Long originalIdAsRawLong = attemptToConvertToLong(originalId); if (originalIdAsRawLong != null) { String sanitizedId = TraceAndSpanIdGenerator.longToUnsignedLowerHexString(originalIdAsRawLong); if (allow128Bit && attemptToConvertToUuid(originalId) != null) { String sanitizedId = originalId.replace("-", "").toLowerCase(); logger.info(SANITIZED_ID_LOG_MSG, originalId, sanitizedId);
@UseDataProvider("idSanitizationScenarios") @Test public void convertWingtipsSpanToZipkinSpan_sanitizes_spanId_as_expected_when_sanitization_is_enabled( IdSanitizationScenario scenario ) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(true); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withSpanId(scenario.originalId) .withSpanStartTimeEpochMicros(Math.abs(random.nextLong())) .withDurationNanos(Math.abs(random.nextLong())) .build(); // when zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint); // then assertThat(zipkinSpan.id()).isEqualTo(scenario.expectedSanitizedResultForSpanIdOrParentSpanId); assertThat(zipkinSpan.tags().get("invalid.span_id")).isEqualTo(scenario.originalId); }
long durationMicros = TimeUnit.NANOSECONDS.toMicros(wingtipsSpan.getDurationNanos()); String spanId = sanitizeIdIfNecessary(wingtipsSpan.getSpanId(), false); String traceId = sanitizeIdIfNecessary(wingtipsSpan.getTraceId(), true); String parentId = sanitizeIdIfNecessary(wingtipsSpan.getParentSpanId(), false); .duration(durationMicros) .localEndpoint(zipkinEndpoint) .kind(determineZipkinKind(wingtipsSpan));
protected boolean isLowerHex(String id) { return isHex(id, false); }
@DataProvider(value = { "SERVER", "CLIENT", "LOCAL_ONLY", "UNKNOWN", "NULL" }) @Test public void determineZipkinKind_returns_expected_Zipkin_Kind_for_wingtips_SpanPurpose( WingtipsSpanPurposeToZipkinKindScenario scenario ) { // given Span wingtipsSpan = Span.newBuilder("foo", scenario.wingtipsSpanPurpose).build(); // It's technically impossible under normal circumstances to have a null SpanPurpose on a wingtips span // since it will be auto-converted to UNKNOWN, but that's the only way we can trigger the default/unhandled // case in the method, so we'll use reflection to force it. if (scenario.wingtipsSpanPurpose == null) { Whitebox.setInternalState(wingtipsSpan, "spanPurpose", null); } // when zipkin2.Span.Kind result = impl.determineZipkinKind(wingtipsSpan); // then assertThat(result).isEqualTo(scenario.expectedZipkinKind); }
@UseDataProvider("idSanitizationScenarios") @Test public void convertWingtipsSpanToZipkinSpan_sanitizes_parentSpanId_as_expected_when_sanitization_is_enabled( IdSanitizationScenario scenario ) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(true); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withParentSpanId(scenario.originalId) .withSpanStartTimeEpochMicros(Math.abs(random.nextLong())) .withDurationNanos(Math.abs(random.nextLong())) .build(); // when zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint); // then assertThat(zipkinSpan.parentId()).isEqualTo(scenario.expectedSanitizedResultForSpanIdOrParentSpanId); assertThat(zipkinSpan.tags().get("invalid.parent_id")).isEqualTo(scenario.originalId); }
long durationMicros = TimeUnit.NANOSECONDS.toMicros(wingtipsSpan.getDurationNanos()); String spanId = sanitizeIdIfNecessary(wingtipsSpan.getSpanId(), false); String traceId = sanitizeIdIfNecessary(wingtipsSpan.getTraceId(), true); String parentId = sanitizeIdIfNecessary(wingtipsSpan.getParentSpanId(), false); .duration(durationMicros) .localEndpoint(zipkinEndpoint) .kind(determineZipkinKind(wingtipsSpan));
protected boolean isLowerHex(String id) { return isHex(id, false); }
if (isAllowedNumChars(originalId, allow128Bit)) { if (isLowerHex(originalId)) { else if (isHex(originalId, true)) { Long originalIdAsRawLong = attemptToConvertToLong(originalId); if (originalIdAsRawLong != null) { String sanitizedId = TraceAndSpanIdGenerator.longToUnsignedLowerHexString(originalIdAsRawLong); if (allow128Bit && attemptToConvertToUuid(originalId) != null) { String sanitizedId = originalId.replace("-", "").toLowerCase(); logger.info(SANITIZED_ID_LOG_MSG, originalId, sanitizedId);
@UseDataProvider("idSanitizationScenarios") @Test public void convertWingtipsSpanToZipkinSpan_sanitizes_traceId_as_expected_when_sanitization_is_enabled( IdSanitizationScenario scenario ) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(true); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(scenario.originalId) .withSpanStartTimeEpochMicros(Math.abs(random.nextLong())) .withDurationNanos(Math.abs(random.nextLong())) .build(); String expectedZipkinInvalidIdTagValue = (scenario.expectedSanitizedResultForTraceId.equals(scenario.originalId)) ? null : scenario.originalId; // when zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint); // then assertThat(zipkinSpan.traceId()).isEqualTo(scenario.expectedSanitizedResultForTraceId); assertThat(zipkinSpan.tags().get("invalid.trace_id")).isEqualTo(expectedZipkinInvalidIdTagValue); }
/** * Convenience constructor that uses {@link WingtipsToZipkinSpanConverterDefaultImpl} for converting Wingtips spans * to Zipkin spans, {@link AsyncReporter} for the Zipkin reporter, and {@link URLConnectionSender} as the underlying * {@link Sender} used by the {@link AsyncReporter}. If you need more flexibility in the span converter or * reporter/sender, then use the {@link * WingtipsToZipkinLifecycleListener#WingtipsToZipkinLifecycleListener(String, WingtipsToZipkinSpanConverter, Reporter)} * constructor instead. * * @param serviceName The name of this service. This is used to build the Zipkin {@link Endpoint}, which tells * Zipkin which service generated the spans we're going to send it. * @param postZipkinSpansBaseUrl The base URL of the Zipkin server. This should include the scheme, host, and port * (if non-standard for the scheme). e.g. {@code http://localhost:9411}, or * {@code https://zipkinserver.doesnotexist.com/}. This assumes you want to send spans to Zipkin over HTTP - if * you want to use something else then you'll need to specify your own reporter/sender using the {@link * WingtipsToZipkinLifecycleListener#WingtipsToZipkinLifecycleListener(String, WingtipsToZipkinSpanConverter, Reporter)} * constructor instead. */ public WingtipsToZipkinLifecycleListener(String serviceName, String postZipkinSpansBaseUrl) { this(serviceName, new WingtipsToZipkinSpanConverterDefaultImpl(), generateBasicZipkinReporter(postZipkinSpansBaseUrl) ); }
@DataProvider(value = { "true", "false" }) @Test public void isHex_works_as_expected(boolean allowUppercase) { for (char c = Character.MIN_VALUE; c < Character.MAX_VALUE; c++) { // given boolean isHexDigit = (c >= '0') && (c <= '9'); boolean isHexLowercase = (c >= 'a') && (c <= 'f'); boolean isHexUppercase = (c >= 'A') && (c <= 'F'); boolean expectedResult = isHexDigit || isHexLowercase || (allowUppercase && isHexUppercase); // when boolean result = impl.isHex(String.valueOf(c), allowUppercase); // then assertThat(result) .withFailMessage("Did not get expected result for char with int value " + (int)c + ". Expected result: " + expectedResult) .isEqualTo(expectedResult); } } }
@Test public void convertWingtipsSpanToZipkinSpan_sanitizes_multiple_IDs_as_expected_when_sanitization_is_enabled() { impl = new WingtipsToZipkinSpanConverterDefaultImpl(true); zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint);
@Test public void default_constructor_sets_fields_as_expected() { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(); // expect assertThat(impl.enableIdSanitization).isFalse(); }
@UseDataProvider("idSanitizationScenarios") @Test public void convertWingtipsSpanToZipkinSpan_does_not_sanitize_ids_if_enableIdSanitization_is_false( IdSanitizationScenario scenario ) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(false); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(scenario.originalId) .withSpanId(scenario.originalId) .withSpanStartTimeEpochMicros(Math.abs(random.nextLong())) .withDurationNanos(Math.abs(random.nextLong())) .build(); String expectedExceptionMessageSuffix = (scenario.originalId.length() > 16) ? "id.length > 16" : "should be lower-hex encoded with no prefix"; // when Throwable ex = catchThrowable(() -> impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint)); // then assertThat(ex) .isInstanceOf(IllegalArgumentException.class) .hasMessageEndingWith(expectedExceptionMessageSuffix); }
@DataProvider(value = { "true", "false" }) @Test public void constructor_with_args_sets_fields_as_expected(boolean enableSanitization) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(enableSanitization); // expect assertThat(impl.enableIdSanitization).isEqualTo(enableSanitization); }
/** * Initialize configuration. * Add Zipkin listener if our {@link WingtipsZipkinProperties} indicates it has the necessary properties specified. */ private void init() { if (wingtipsZipkinProperties.shouldApplyWingtipsToZipkinLifecycleListener()) { Reporter<zipkin2.Span> zipkinSpanReporter = (zipkinReporterOverride != null) ? zipkinReporterOverride : WingtipsToZipkinLifecycleListener.generateBasicZipkinReporter(wingtipsZipkinProperties.getBaseUrl()); WingtipsToZipkinSpanConverter zipkinSpanConverter = (zipkinSpanConverterOverride != null) ? zipkinSpanConverterOverride : new WingtipsToZipkinSpanConverterDefaultImpl(); WingtipsToZipkinLifecycleListener listenerToRegister = new WingtipsToZipkinLifecycleListener( wingtipsZipkinProperties.getServiceName(), zipkinSpanConverter, zipkinSpanReporter ); Tracer.getInstance().addSpanLifecycleListener(listenerToRegister); } }