/** * Returns the name that should be used for the span surrounding the request. Defaults to whatever {@link * ServerSpanNamingAndTaggingStrategy#getInitialSpanName(Object)} returns, with a fallback * of {@link HttpRequestTracingUtils#getFallbackSpanNameForHttpRequest(String, String)} if the naming strategy * returned null or blank string. * * @param nettyRequest The Netty {@link HttpRequest}. * @param riposteRequestInfo The Riposte {@link RequestInfo} (if this is null, then the tag strategy and adapter * will be ignored, and we'll fallback to basic behavior using the {@code nettyRequest}). * @param namingStrategy The {@link ServerSpanNamingAndTaggingStrategy} being used. * @return The name that should be used for the span surrounding the request. */ protected @NotNull String getSpanName( @NotNull HttpRequest nettyRequest, @Nullable RequestInfo<?> riposteRequestInfo, @NotNull ServerSpanNamingAndTaggingStrategy<Span> namingStrategy ) { // Immediately go to the fallback span name if the Riposte RequestInfo is null. if (riposteRequestInfo == null) { return getFallbackSpanName(nettyRequest); } // We have a Riposte RequestInfo. Try the naming strategy first. String spanNameFromStrategy = namingStrategy.getInitialSpanName(riposteRequestInfo); if (StringUtils.isNotBlank(spanNameFromStrategy)) { return spanNameFromStrategy; } // The naming strategy didn't have anything for us. Fall back to something reasonable. return getFallbackSpanName(nettyRequest); }
@Test public void doChannelRead_does_not_propagate_exception_if_startTrace_throws_an_error() { // given DTraceStartHandler handlerSpy = spy(handler); doThrow(new RuntimeException("intentional exception")).when(handlerSpy).startTrace(any(), any()); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, httpRequest); // then verify(handlerSpy).startTrace(any(), any()); assertThat(result, is(PipelineContinuationBehavior.CONTINUE)); }
@Override protected boolean argsAreEligibleForLinkingAndUnlinkingDistributedTracingInfo( HandlerMethodToExecute methodToExecute, ChannelHandlerContext ctx, Object msgOrEvt, Throwable cause ) { return shouldHandleDoChannelReadMessage(msgOrEvt); }
@UseDataProvider("getSpanNameScenarioDataProvider") @Test public void getSpanName_works_as_expected(GetSpanNameScenario scenario) { // given DTraceStartHandler handlerSpy = spy(handler); initialSpanNameFromStrategy.set(scenario.strategySpanName); doReturn(scenario.fallbackSpanName).when(handlerSpy).getFallbackSpanName(any()); if (scenario.riposteRequestInfoIsNull) { requestInfoMock = null; } String expectedResult = (scenario.expectStrategyResult) ? scenario.strategySpanName : scenario.fallbackSpanName; // when String result = handlerSpy.getSpanName( httpRequest, requestInfoMock, distributedTracingConfig.getServerSpanNamingAndTaggingStrategy() ); // then Assertions.assertThat(result).isEqualTo(expectedResult); if (!scenario.riposteRequestInfoIsNull) { strategyInitialSpanNameArgs.get().verifyArgs(requestInfoMock, tagAndNamingAdapterMock); } if (!scenario.expectStrategyResult) { verify(handlerSpy).getFallbackSpanName(httpRequest); } }
doReturn(fallbackSpanName).when(handlerSpy).getFallbackSpanName(any()); handlerSpy.startTrace(httpRequest, ctxMock); long nanosAfter = System.nanoTime();
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { // We may get multiple messages for a single HTTP request since riposte handles each individual HTTP message // object (including individual payload content messages). // We only do the processing for HttpRequest which is the first message in a request chain. if (shouldHandleDoChannelReadMessage(msg)) { try { startTrace((HttpRequest) msg, ctx); } catch (Throwable t) { logger.error( "An unexpected error occurred while starting the distributed tracing overall request span. This " + "exception will be swallowed to avoid breaking the Netty pipeline, but it should be " + "investigated as it shouldn't ever happen.", t ); } } if (msg instanceof LastHttpContent) { HttpProcessingState httpProcessingState = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get(); // Add the "we received the last bytes of the request on the wire" annotation to the span if possible // and desired. Span requestSpan = handlerUtils.getOverallRequestSpan(httpProcessingState); if (requestSpan != null && spanNamingAndTaggingStrategy.shouldAddWireReceiveFinishAnnotation()) { requestSpan.addTimestampedAnnotationForCurrentTime( spanNamingAndTaggingStrategy.wireReceiveFinishAnnotationName() ); } } return PipelineContinuationBehavior.CONTINUE; }
@Test public void constructor_throws_IllegalArgumentException_if_distributedTracingConfig_is_null() { // when Throwable ex = catchThrowable( () -> new DTraceStartHandler(userIdHeaderKeys, null) ); // then Assertions.assertThat(ex) .isInstanceOf(IllegalArgumentException.class) .hasMessage("distributedTracingConfig cannot be null"); }
handler.startTrace(httpRequest, ctxMock); long nanosAfter = System.nanoTime();
PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, new DefaultLastHttpContent());
newSpan = tracer.startRequestWithChildSpan( parentSpan, getFallbackSpanName(nettyRequest) ); getFallbackSpanName(nettyRequest), HttpRequestTracingUtils.getUserIdFromRequestWithHeaders(requestWrapper, userIdHeaderKeys) ); getSpanName(nettyRequest, riposteRequestInfo, spanNamingAndTaggingStrategy) );
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { // We may get multiple messages for a single HTTP request since riposte handles each individual HTTP message // object (including individual payload content messages). // We only do the processing for HttpRequest which is the first message in a request chain. if (shouldHandleDoChannelReadMessage(msg)) { try { startTrace((HttpRequest) msg, ctx); } catch (Throwable t) { logger.error( "An unexpected error occurred while starting the distributed tracing overall request span. This " + "exception will be swallowed to avoid breaking the Netty pipeline, but it should be " + "investigated as it shouldn't ever happen.", t ); } } if (msg instanceof LastHttpContent) { HttpProcessingState httpProcessingState = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get(); // Add the "we received the last bytes of the request on the wire" annotation to the span if possible // and desired. Span requestSpan = handlerUtils.getOverallRequestSpan(httpProcessingState); if (requestSpan != null && spanNamingAndTaggingStrategy.shouldAddWireReceiveFinishAnnotation()) { requestSpan.addTimestampedAnnotationForCurrentTime( spanNamingAndTaggingStrategy.wireReceiveFinishAnnotationName() ); } } return PipelineContinuationBehavior.CONTINUE; }
p.addLast(DTRACE_START_HANDLER_NAME, new DTraceStartHandler(userIdHeaderKeys, distributedTracingConfig));
newSpan = tracer.startRequestWithChildSpan( parentSpan, getFallbackSpanName(nettyRequest) ); getFallbackSpanName(nettyRequest), HttpRequestTracingUtils.getUserIdFromRequestWithHeaders(requestWrapper, userIdHeaderKeys) ); getSpanName(nettyRequest, riposteRequestInfo, spanNamingAndTaggingStrategy) );
@Test public void doChannelRead_calls_startTrace_if_msg_is_HttpRequest_and_then_returns_CONTINUE() throws Exception { // given DTraceStartHandler handlerSpy = spy(handler); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, httpRequest); // then verify(handlerSpy).startTrace(httpRequest, ctxMock); assertThat(result, is(PipelineContinuationBehavior.CONTINUE)); }
/** * Returns the name that should be used for the span surrounding the request. Defaults to whatever {@link * ServerSpanNamingAndTaggingStrategy#getInitialSpanName(Object)} returns, with a fallback * of {@link HttpRequestTracingUtils#getFallbackSpanNameForHttpRequest(String, String)} if the naming strategy * returned null or blank string. * * @param nettyRequest The Netty {@link HttpRequest}. * @param riposteRequestInfo The Riposte {@link RequestInfo} (if this is null, then the tag strategy and adapter * will be ignored, and we'll fallback to basic behavior using the {@code nettyRequest}). * @param namingStrategy The {@link ServerSpanNamingAndTaggingStrategy} being used. * @return The name that should be used for the span surrounding the request. */ protected @NotNull String getSpanName( @NotNull HttpRequest nettyRequest, @Nullable RequestInfo<?> riposteRequestInfo, @NotNull ServerSpanNamingAndTaggingStrategy<Span> namingStrategy ) { // Immediately go to the fallback span name if the Riposte RequestInfo is null. if (riposteRequestInfo == null) { return getFallbackSpanName(nettyRequest); } // We have a Riposte RequestInfo. Try the naming strategy first. String spanNameFromStrategy = namingStrategy.getInitialSpanName(riposteRequestInfo); if (StringUtils.isNotBlank(spanNameFromStrategy)) { return spanNameFromStrategy; } // The naming strategy didn't have anything for us. Fall back to something reasonable. return getFallbackSpanName(nettyRequest); }
p.addLast(DTRACE_START_HANDLER_NAME, new DTraceStartHandler(userIdHeaderKeys, distributedTracingConfig));
@Override protected boolean argsAreEligibleForLinkingAndUnlinkingDistributedTracingInfo( HandlerMethodToExecute methodToExecute, ChannelHandlerContext ctx, Object msgOrEvt, Throwable cause ) { return shouldHandleDoChannelReadMessage(msgOrEvt); }
@Test public void doChannelRead_does_not_call_startTrace_if_msg_is_not_HttpRequest_but_it_does_return_CONTINUE() throws Exception { // given DTraceStartHandler handlerSpy = spy(handler); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, new Object()); // then verify(handlerSpy, times(0)).startTrace(any(), any()); assertThat(result, is(PipelineContinuationBehavior.CONTINUE)); }
@Test public void getFallbackSpanName_returns_UNKNOWN_HTTP_METHOD_if_unexpected_exception_occurs() { // given HttpRequest nettyHttpRequestMock = mock(HttpRequest.class); doThrow(new RuntimeException("intentional exception")).when(nettyHttpRequestMock).method(); // when String result = handler.getFallbackSpanName(nettyHttpRequestMock); // then Assertions.assertThat(result).isEqualTo("UNKNOWN_HTTP_METHOD"); verify(nettyHttpRequestMock).method(); } }
); handler = new DTraceStartHandler(userIdHeaderKeys, distributedTracingConfig);