feat(servlet): content length (#726)
This commit is contained in:
parent
3cc735742f
commit
d6e39f89e6
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue