protected boolean isLowerHex(String id) { return isHex(id, false); }
/** * 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) ); }
protected Map<String,String> createMultipleTagMap() { Map<String,String> multipleValues = createSingleTagMap(); multipleValues.put("secondTag", "secondValue"); return multipleValues; }
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);
@Override public void spanCompleted(Span span) { try { zipkin2.Span zipkinSpan = zipkinSpanConverter.convertWingtipsSpanToZipkinSpan(span, zipkinEndpoint); zipkinSpanReporter.report(zipkinSpan); } catch(Throwable ex) { long currentBadSpanCount = spanHandlingErrorCounter.incrementAndGet(); // Only log once every MIN_SPAN_HANDLING_ERROR_LOG_INTERVAL_MILLIS time interval to prevent log spam from a malicious (or broken) caller. long currentTimeMillis = System.currentTimeMillis(); long timeSinceLastLogMsgMillis = currentTimeMillis - lastSpanHandlingErrorLogTimeEpochMillis; if (timeSinceLastLogMsgMillis >= MIN_SPAN_HANDLING_ERROR_LOG_INTERVAL_MILLIS) { // We're not synchronizing the read and write to lastSpanHandlingErrorLogTimeEpochMillis, and that's ok. If we get a few extra // log messages due to a race condition it's not the end of the world - we're still satisfying the goal of not allowing a // malicious caller to endlessly spam the logs. lastSpanHandlingErrorLogTimeEpochMillis = currentTimeMillis; zipkinConversionOrReportingErrorLogger.warn( "There have been {} spans that were not Zipkin compatible, or that experienced an error during span handling. Latest example: " + "wingtips_span_with_error=\"{}\", conversion_or_handling_error=\"{}\"", currentBadSpanCount, span.toKeyValueString(), ex.toString()); } } } }
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));
@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); }
protected List<TimestampedAnnotation> createMultipleTimestampedAnnotationList() { List<TimestampedAnnotation> multipleAnnotationList = createSingleTimestampedAnnotationList(); multipleAnnotationList.add(TimestampedAnnotation.forEpochMicros(67890, "annotationTwoValue")); return multipleAnnotationList; }
@DataProvider @SuppressWarnings("unused") public static List<List<IdSanitizationScenario>> idSanitizationScenarios() { return Arrays.stream(IdSanitizationScenario.values()) .map(Collections::singletonList) .collect(Collectors.toList()); }
@Test @SuppressWarnings("UnnecessaryLocalVariable") public void convertWingtipsSpanToZipkinSpan_works_as_expected_for_128_bit_trace_id() { // given String high64Bits = "463ac35c9f6413ad"; String low64Bits = "48485a3953bb6124"; String traceId128Bits = high64Bits + low64Bits; long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong()); Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(traceId128Bits) .withSpanStartTimeEpochMicros(startTimeEpochMicros) .withDurationNanos(durationNanos) .build(); // when zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint); // then assertThat(zipkinSpan.traceId()).isEqualTo(traceId128Bits); }
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);
/** * 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) ); }
@Override public void spanCompleted(Span span) { try { zipkin2.Span zipkinSpan = zipkinSpanConverter.convertWingtipsSpanToZipkinSpan(span, zipkinEndpoint); zipkinSpanReporter.report(zipkinSpan); } catch(Throwable ex) { long currentBadSpanCount = spanHandlingErrorCounter.incrementAndGet(); // Only log once every MIN_SPAN_HANDLING_ERROR_LOG_INTERVAL_MILLIS time interval to prevent log spam from a malicious (or broken) caller. long currentTimeMillis = System.currentTimeMillis(); long timeSinceLastLogMsgMillis = currentTimeMillis - lastSpanHandlingErrorLogTimeEpochMillis; if (timeSinceLastLogMsgMillis >= MIN_SPAN_HANDLING_ERROR_LOG_INTERVAL_MILLIS) { // We're not synchronizing the read and write to lastSpanHandlingErrorLogTimeEpochMillis, and that's ok. If we get a few extra // log messages due to a race condition it's not the end of the world - we're still satisfying the goal of not allowing a // malicious caller to endlessly spam the logs. lastSpanHandlingErrorLogTimeEpochMillis = currentTimeMillis; zipkinConversionOrReportingErrorLogger.warn( "There have been {} spans that were not Zipkin compatible, or that experienced an error during span handling. Latest example: " + "wingtips_span_with_error=\"{}\", conversion_or_handling_error=\"{}\"", currentBadSpanCount, span.toKeyValueString(), ex.toString()); } } } }
protected boolean isLowerHex(String id) { return isHex(id, false); }
@Test public void default_constructor_sets_fields_as_expected() { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(); // expect assertThat(impl.enableIdSanitization).isFalse(); }
@Test public void spanCompleted_does_not_propagate_exceptions_generated_by_span_converter() { // given doThrow(new RuntimeException("kaboom")).when(spanConverterMock).convertWingtipsSpanToZipkinSpan(any(Span.class), any(Endpoint.class)); // when Throwable ex = catchThrowable(() -> listener.spanCompleted(spanMock)); // then verify(spanConverterMock).convertWingtipsSpanToZipkinSpan(spanMock, listener.zipkinEndpoint); verifyZeroInteractions(spanReporterMock); assertThat(ex).isNull(); }
@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); } } }
@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); }
@Test public void spanCompleted_converts_to_zipkin_span_and_passes_it_to_zipkinSpanReporter() { // given zipkin2.Span zipkinSpan = zipkin2.Span.newBuilder().traceId("42").id("4242").name("foo").build(); doReturn(zipkinSpan).when(spanConverterMock).convertWingtipsSpanToZipkinSpan(any(Span.class), any(Endpoint.class)); // when listener.spanCompleted(spanMock); // then verify(spanConverterMock).convertWingtipsSpanToZipkinSpan(spanMock, listener.zipkinEndpoint); verify(spanReporterMock).report(zipkinSpan); }
/** * 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); } }