feat(servlet): content length (#726)

This commit is contained in:
Frank Spitulski 2020-07-21 20:24:32 -07:00 committed by GitHub
parent 3cc735742f
commit d6e39f89e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 238 additions and 25 deletions

View File

@ -21,6 +21,7 @@ import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerTrace
import io.opentelemetry.auto.instrumentation.api.MoreAttributes;
import io.opentelemetry.context.propagation.HttpTextFormat.Getter;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.attributes.SemanticAttributes;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
@ -107,4 +108,8 @@ public abstract class ServletHttpServerTracer
span.setAttribute(MoreAttributes.USER_NAME, principal.getName());
}
}
public void setContentLength(Span span, int length) {
SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.set(span, String.valueOf(length));
}
}

View File

@ -105,7 +105,7 @@ class DropwizardTest extends HttpServerTest<DropwizardTestSupport> {
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName "$method ${endpoint == PATH_PARAM ? "/path/{id}/param" : endpoint.resolvePath(address).path}"
spanKind SERVER
@ -122,6 +122,8 @@ class DropwizardTest extends HttpServerTest<DropwizardTestSupport> {
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
"servlet.context" ""
"servlet.path" ""
if (endpoint.errored) {

View File

@ -23,8 +23,10 @@ import io.opentelemetry.auto.test.asserts.TraceAssert
import io.opentelemetry.auto.test.base.HttpServerTest
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.trace.attributes.SemanticAttributes
import java.util.concurrent.TimeoutException
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static io.opentelemetry.trace.Span.Kind.INTERNAL
@ -97,7 +99,7 @@ class FinatraServerTest extends HttpServerTest<HttpServer> {
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.resolvePath(address).path
spanKind SERVER
@ -114,6 +116,8 @@ class FinatraServerTest extends HttpServerTest<HttpServer> {
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
if (endpoint.query) {
"$MoreAttributes.HTTP_QUERY" endpoint.query
}

View File

@ -59,6 +59,10 @@ public final class JettyHandlerInstrumentation extends Instrumenter.Default {
"io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer",
"io.opentelemetry.auto.instrumentation.servlet.v3_0.Servlet3HttpServerTracer",
"io.opentelemetry.auto.instrumentation.servlet.v3_0.TagSettingAsyncListener",
"io.opentelemetry.auto.instrumentation.servlet.v3_0.TagSettingAsyncListener",
"io.opentelemetry.auto.instrumentation.servlet.v3_0.CountingHttpServletResponse",
"io.opentelemetry.auto.instrumentation.servlet.v3_0.CountingHttpServletResponse$CountingServletOutputStream",
"io.opentelemetry.auto.instrumentation.servlet.v3_0.CountingHttpServletResponse$CountingPrintWriter",
packageName + ".JettyHttpServerTracer",
};
}

View File

@ -18,6 +18,7 @@ import io.opentelemetry.auto.instrumentation.api.MoreAttributes
import io.opentelemetry.auto.test.asserts.TraceAssert
import io.opentelemetry.auto.test.base.HttpServerTest
import io.opentelemetry.trace.attributes.SemanticAttributes
import javax.servlet.DispatcherType
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
@ -115,7 +116,7 @@ class JettyHandlerTest extends HttpServerTest<Server> {
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName "TestHandler.handle"
spanKind SERVER
@ -132,6 +133,8 @@ class JettyHandlerTest extends HttpServerTest<Server> {
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
"servlet.path" ''
if (endpoint.errored) {
"error.msg" { it == null || it == EXCEPTION.body }

View File

@ -114,6 +114,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/$jspFileName"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/$jspFileName"
}
@ -174,6 +175,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/getQuery.jsp?$queryString"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/getQuery.jsp"
}
@ -231,6 +233,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/post.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "POST"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/post.jsp"
}
@ -285,6 +288,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/$jspFileName"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 500
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/$jspFileName"
"error.type" { String tagExceptionType ->
@ -358,6 +362,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/includes/includeHtml.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/includes/includeHtml.jsp"
}
@ -411,6 +416,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/includes/includeMulti.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/includes/includeMulti.jsp"
}
@ -502,6 +508,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/$jspFileName"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 500
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/$jspFileName"
errorAttributes(JasperException, String)
@ -557,6 +564,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/$staticFile"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/$staticFile"
}

View File

@ -112,6 +112,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/$forwardFromFileName"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/$forwardFromFileName"
}
@ -190,6 +191,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/forwards/forwardToHtml.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/forwards/forwardToHtml.jsp"
}
@ -243,6 +245,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/forwards/forwardToIncludeMulti.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/forwards/forwardToIncludeMulti.jsp"
}
@ -356,6 +359,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/forwards/forwardToJspForward.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/forwards/forwardToJspForward.jsp"
}
@ -449,6 +453,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/forwards/forwardToCompileError.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 500
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/forwards/forwardToCompileError.jsp"
errorAttributes(JasperException, String)
@ -515,6 +520,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner {
"${SemanticAttributes.HTTP_URL.key()}" "http://localhost:$port/$jspWebappContext/forwards/forwardToNonExistent.jsp"
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 404
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" String
"servlet.context" "/$jspWebappContext"
"servlet.path" "/forwards/forwardToNonExistent.jsp"
}

View File

@ -82,7 +82,7 @@ class GlassFishServerTest extends HttpServerTest<GlassFish> {
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName entryPointName()
spanKind SERVER
@ -99,6 +99,8 @@ class GlassFishServerTest extends HttpServerTest<GlassFish> {
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
"servlet.context" "/$context"
"servlet.path" endpoint.path
if (endpoint.errored) {

View File

@ -18,6 +18,7 @@ import io.opentelemetry.auto.instrumentation.api.MoreAttributes
import io.opentelemetry.auto.test.asserts.TraceAssert
import io.opentelemetry.auto.test.base.HttpServerTest
import io.opentelemetry.sdk.trace.data.SpanData
import javax.servlet.http.HttpServletRequest
import io.opentelemetry.trace.attributes.SemanticAttributes
import org.eclipse.jetty.server.Server
@ -97,7 +98,7 @@ class JettyServlet2Test extends HttpServerTest<Server> {
}
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName 'HttpServlet.service'
spanKind SERVER

View File

@ -50,6 +50,10 @@ public final class AsyncContextInstrumentation extends Instrumenter.Default {
return new String[] {
"io.opentelemetry.instrumentation.servlet.HttpServletRequestGetter",
"io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer",
packageName + ".CountingHttpServletResponse",
packageName + ".CountingHttpServletResponse$CountingServletOutputStream",
packageName + ".CountingHttpServletResponse$CountingPrintWriter",
packageName + ".TagSettingAsyncListener",
packageName + ".Servlet3HttpServerTracer"
};
}

View File

@ -0,0 +1,145 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.servlet.v3_0;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/** HttpServletResponseWrapper since servlet 2.3, not applicable to 2.2 */
public class CountingHttpServletResponse extends HttpServletResponseWrapper {
private CountingServletOutputStream outputStream = null;
private CountingPrintWriter printWriter = null;
private int errorLength = 0;
/**
* Constructs a response adaptor wrapping the given response.
*
* @throws IllegalArgumentException if the response is null
*/
public CountingHttpServletResponse(HttpServletResponse response) {
super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (outputStream == null) {
outputStream = new CountingServletOutputStream(super.getOutputStream());
}
return outputStream;
}
@Override
public PrintWriter getWriter() throws IOException {
if (printWriter == null) {
printWriter = new CountingPrintWriter(super.getWriter());
}
return printWriter;
}
public int getContentLength() {
int contentLength = errorLength;
if (outputStream != null) {
contentLength += outputStream.counter;
}
if (printWriter != null) {
contentLength += printWriter.counter;
}
return contentLength;
}
/** sendError bypasses the servlet response writers and writes directly to the response */
@Override
public void sendError(int sc, String msg) throws IOException {
super.sendError(sc, msg);
if (msg != null) {
errorLength += msg.length();
}
}
static class CountingServletOutputStream extends ServletOutputStream {
private final ServletOutputStream delegate;
private int counter = 0;
public CountingServletOutputStream(ServletOutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
delegate.write(b);
counter++;
}
@Override
public void write(byte[] b) throws IOException {
delegate.write(b);
counter += b.length;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
delegate.write(b, off, len);
counter += len;
}
@Override
public void flush() throws IOException {
delegate.flush();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
static class CountingPrintWriter extends PrintWriter {
private int counter = 0;
/**
* write(String s) and write(char[] buf) are not overridden because they delegate to another
* write function which would result in their write being counted twice.
*/
public CountingPrintWriter(Writer out) {
super(out);
}
@Override
public void write(int c) {
super.write(c);
counter++;
}
@Override
public void write(char[] buf, int off, int len) {
super.write(buf, off, len);
counter += len;
}
@Override
public void write(String s, int off, int len) {
super.write(s, off, len);
counter += len;
}
}
}

View File

@ -36,10 +36,9 @@ public class Servlet3Advice {
public static void onEnter(
@Advice.Origin final Method method,
@Advice.Argument(0) final ServletRequest request,
@Advice.Argument(1) final ServletResponse response,
@Advice.Argument(value = 1, readOnly = false) ServletResponse response,
@Advice.Local("otelSpan") Span span,
@Advice.Local("otelScope") Scope scope) {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
return;
}
@ -57,6 +56,7 @@ public class Servlet3Advice {
span = TRACER.startSpan(httpServletRequest, httpServletRequest, method);
scope = TRACER.startScope(span, httpServletRequest);
response = new CountingHttpServletResponse((HttpServletResponse) response);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
@ -77,8 +77,8 @@ public class Servlet3Advice {
}
TRACER.setPrincipal(span, (HttpServletRequest) request);
if (throwable != null) {
contentLengthHelper(span, response);
TRACER.endExceptionally(span, throwable, ((HttpServletResponse) response).getStatus());
return;
}
@ -97,7 +97,14 @@ public class Servlet3Advice {
// Check again in case the request finished before adding the listener.
if (!request.isAsyncStarted() && responseHandled.compareAndSet(false, true)) {
contentLengthHelper(span, response);
TRACER.end(span, ((HttpServletResponse) response).getStatus());
}
}
public static void contentLengthHelper(Span span, ServletResponse response) {
if (response instanceof CountingHttpServletResponse) {
TRACER.setContentLength(span, ((CountingHttpServletResponse) response).getContentLength());
}
}
}

View File

@ -54,6 +54,10 @@ public final class Servlet3Instrumentation extends Instrumenter.Default {
return new String[] {
"io.opentelemetry.instrumentation.servlet.HttpServletRequestGetter",
"io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer",
packageName + ".CountingHttpServletResponse",
packageName + ".CountingHttpServletResponse$CountingServletOutputStream",
packageName + ".CountingHttpServletResponse$CountingPrintWriter",
packageName + ".Servlet3Advice",
packageName + ".Servlet3HttpServerTracer",
packageName + ".TagSettingAsyncListener"
};

View File

@ -20,6 +20,7 @@ import io.opentelemetry.trace.Span;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class TagSettingAsyncListener implements AsyncListener {
@ -37,6 +38,7 @@ public class TagSettingAsyncListener implements AsyncListener {
@Override
public void onComplete(final AsyncEvent event) {
if (responseHandled.compareAndSet(false, true)) {
contentLengthHelper(span, event);
servletHttpServerTracer.end(
span, ((HttpServletResponse) event.getSuppliedResponse()).getStatus());
}
@ -45,6 +47,7 @@ public class TagSettingAsyncListener implements AsyncListener {
@Override
public void onTimeout(final AsyncEvent event) {
if (responseHandled.compareAndSet(false, true)) {
contentLengthHelper(span, event);
servletHttpServerTracer.onTimeout(span, event.getAsyncContext().getTimeout());
}
}
@ -52,6 +55,7 @@ public class TagSettingAsyncListener implements AsyncListener {
@Override
public void onError(final AsyncEvent event) {
if (responseHandled.compareAndSet(false, true)) {
contentLengthHelper(span, event);
servletHttpServerTracer.endExceptionally(
span,
event.getThrowable(),
@ -64,4 +68,12 @@ public class TagSettingAsyncListener implements AsyncListener {
public void onStartAsync(final AsyncEvent event) {
event.getAsyncContext().addListener(this);
}
public static void contentLengthHelper(Span span, AsyncEvent event) {
final ServletResponse response = event.getSuppliedResponse();
if (response instanceof CountingHttpServletResponse) {
servletHttpServerTracer.setContentLength(
span, ((CountingHttpServletResponse) response).getContentLength());
}
}
}

View File

@ -71,7 +71,7 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName entryPointName()
spanKind Span.Kind.SERVER // can't use static import because of SERVER type parameter
@ -88,6 +88,9 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
// Optional
if (context) {
"servlet.context" "/$context"
}

View File

@ -164,7 +164,7 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
basicSpan(it, 0, "TEST_SPAN")
}
trace(1, 2) {
serverSpan(it, 0, null, null, method, ERROR)
serverSpan(it, 0, null, null, method, response.body().contentLength(), ERROR)
controllerSpan(it, 1, span(0))
}

View File

@ -23,7 +23,6 @@ import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.trace.attributes.SemanticAttributes
import okhttp3.FormBody
import okhttp3.RequestBody
import org.apache.catalina.core.ApplicationFilterChain
import org.springframework.boot.SpringApplication
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.web.servlet.view.RedirectView
@ -99,7 +98,7 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
basicSpan(it, 0, "TEST_SPAN")
}
trace(1, 1) {
serverSpan(it, 0, null, null, "POST", LOGIN)
serverSpan(it, 0, null, null, "POST", response.body()?.contentLength(), LOGIN)
}
}
@ -146,7 +145,7 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName endpoint == LOGIN ? "ApplicationFilterChain.doFilter" : endpoint == PATH_PARAM ? "/path/{id}/param" : endpoint.resolvePath(address).path
spanKind SERVER
@ -163,6 +162,8 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
"servlet.path" endpoint.path
"servlet.context" ""
if (endpoint.errored) {

View File

@ -91,7 +91,7 @@ class ServletFilterTest extends HttpServerTest<ConfigurableApplicationContext> {
}
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName endpoint == PATH_PARAM ? "/path/{id}/param" : endpoint.resolvePath(address).path
spanKind SERVER
@ -108,6 +108,8 @@ class ServletFilterTest extends HttpServerTest<ConfigurableApplicationContext> {
"${SemanticAttributes.HTTP_URL.key()}" { it == "${endpoint.resolve(address)}" || it == "${endpoint.resolveWithoutFragment(address)}" }
"${SemanticAttributes.HTTP_METHOD.key()}" method
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" endpoint.status
// exception bodies are not yet recorded
"${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" { "${responseContentLength ?: 0}" || endpoint == EXCEPTION }
"servlet.path" endpoint.path
"servlet.context" ""
if (endpoint.errored) {

View File

@ -253,7 +253,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
response.body().string() == SUCCESS.body
and:
assertTheTraces(1, traceId, parentId)
assertTheTraces(1, traceId, parentId, "GET", SUCCESS, null, response)
where:
method = "GET"
@ -272,7 +272,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
response.body().string() == endpoint.body
and:
assertTheTraces(1, null, null, method, endpoint)
assertTheTraces(1, null, null, method, endpoint, null, response)
where:
method = "GET"
@ -292,7 +292,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
response.body().contentLength() < 1 || redirectHasBody()
and:
assertTheTraces(1, null, null, method, REDIRECT)
assertTheTraces(1, null, null, method, REDIRECT, null, response)
where:
method = "GET"
@ -309,7 +309,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
response.body().string() == ERROR.body
and:
assertTheTraces(1, null, null, method, ERROR)
assertTheTraces(1, null, null, method, ERROR, null, response)
where:
method = "GET"
@ -329,7 +329,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
}
and:
assertTheTraces(1, null, null, method, EXCEPTION, EXCEPTION.body)
assertTheTraces(1, null, null, method, EXCEPTION, EXCEPTION.body, response)
where:
method = "GET"
@ -346,7 +346,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
response.code() == NOT_FOUND.status
and:
assertTheTraces(1, null, null, method, NOT_FOUND)
assertTheTraces(1, null, null, method, NOT_FOUND, null, response)
where:
method = "GET"
@ -364,7 +364,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
response.body().string() == PATH_PARAM.body
and:
assertTheTraces(1, null, null, method, PATH_PARAM)
assertTheTraces(1, null, null, method, PATH_PARAM, null, response)
where:
method = "GET"
@ -373,7 +373,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
//FIXME: add tests for POST with large/chunked data
void assertTheTraces(int size, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS, String errorMessage = null) {
void assertTheTraces(int size, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS, String errorMessage = null, Response response = null) {
def spanCount = 1 // server span
if (hasHandlerSpan()) {
spanCount++
@ -387,7 +387,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
spanCount++
}
if (hasErrorPageSpans(endpoint)) {
spanCount ++
spanCount++
}
}
assertTraces(size * 2) {
@ -397,7 +397,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
}
trace(it * 2 + 1, spanCount) {
def spanIndex = 0
serverSpan(it, spanIndex++, traceID, parentID, method, endpoint)
serverSpan(it, spanIndex++, traceID, parentID, method, response?.body()?.contentLength(), endpoint)
if (hasHandlerSpan()) {
handlerSpan(it, spanIndex++, span(0), method, endpoint)
}
@ -453,7 +453,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
}
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName expectedServerSpanName(method, endpoint)
spanKind Span.Kind.SERVER // can't use static import because of SERVER type parameter