Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stacktraces on httpurlconnection requests #8325

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.bootstrap.instrumentation.httpurlconnection.HttpUrlConnectionDecorator.DECORATE;

import datadog.trace.api.Config;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
@@ -42,6 +43,14 @@ public void finishSpan(
if (responseCode > 0) {
// safe to access response data as 'responseCode' is set
DECORATE.onResponse(span, connection);

// attach throwables if response code is an error
// dd.trace.http-url-connection.errors.enabled must be set to true
if (Config.get().isHttpUrlConnectionErrorsEnabled()) {
if (throwable != null && responseCode >= 400) {
DECORATE.onError(span, throwable);
}
}
} else {
// Ignoring the throwable if we have response code
// to have consistent behavior with other http clients.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.agent.test.naming.TestingGenericHttpNamingConventions
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.bootstrap.instrumentation.api.Tags
import datadog.trace.bootstrap.instrumentation.api.URIUtils
import datadog.trace.core.DDSpan
import datadog.trace.core.datastreams.StatsGroup

import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
class HttpUrlConnectionErrorReportingTest extends HttpUrlConnectionTest implements TestingGenericHttpNamingConventions.ClientV0 {
@Override
protected void configurePreAgent() {
super.configurePreAgent()
injectSysConfig('dd.trace.http-url-connection.errors.enabled', 'true')
}


def "client error request with parent with error reporting"() {
setup:
def uri = server.address.resolve("/secured")

when:
def status = runUnderTrace("parent") {
doRequest(method, uri)
}
if (isDataStreamsEnabled()) {
TEST_DATA_STREAMS_WRITER.waitForGroups(1)
}

then:
status == 401
assertTraces(2) {
trace(size(2)) {
it.span(it.nextSpanId()){
parent()
hasServiceName()
operationName "parent"
resourceName "parent"
errored false
tags {
defaultTags()
}
}
clientSpanError(it, span(0), method, false, false, uri, 401, true)
}
server.distributedRequestTrace(it, trace(0).last())
}

and:
if (isDataStreamsEnabled()) {
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
verifyAll(first) {
edgeTags.containsAll(DSM_EDGE_TAGS)
edgeTags.size() == DSM_EDGE_TAGS.size()
}
}

where:
method | _
"GET" | _
"POST" | _
}
def "server error request with parent with error reporting"() {
setup:
def uri = server.address.resolve("/error")

when:
def status = runUnderTrace("parent") {
doRequest(method, uri)
}
if (isDataStreamsEnabled()) {
TEST_DATA_STREAMS_WRITER.waitForGroups(1)
}

then:
status == 500
assertTraces(2) {
trace(size(2)) {
basicSpan(it, "parent")
clientSpanError(it, span(0), method, false, false, uri, 500, false) // error.
}
server.distributedRequestTrace(it, trace(0).last())
}

and:
if (isDataStreamsEnabled()) {
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
verifyAll(first) {
edgeTags.containsAll(DSM_EDGE_TAGS)
edgeTags.size() == DSM_EDGE_TAGS.size()
}
}

where:
method | _
"GET" | _
"POST" | _
}


void clientSpanError(
TraceAssert trace,
Object parentSpan,
String method = "GET",
boolean renameService = false,
boolean tagQueryString = false,
URI uri = server.address.resolve("/success"),
Integer status = 200,
boolean error = false,
Throwable exception = null,
boolean ignorePeer = false,
Map<String, Serializable> extraTags = null) {

def expectedQuery = tagQueryString ? uri.query : null
def expectedUrl = URIUtils.buildURL(uri.scheme, uri.host, uri.port, uri.path)
if (expectedQuery != null && !expectedQuery.empty) {
expectedUrl = "$expectedUrl?$expectedQuery"
}
trace.span {
if (parentSpan == null) {
parent()
} else {
childOf((DDSpan) parentSpan)
}
if (renameService) {
serviceName uri.host
}
operationName operation()
resourceName "$method $uri.path"
spanType DDSpanTypes.HTTP_CLIENT
errored true
measured true
tags {
"$Tags.COMPONENT" component
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.PEER_HOSTNAME" { it == uri.host || ignorePeer }
"$Tags.PEER_HOST_IPV4" { it == null || it == "127.0.0.1" || ignorePeer } // Optional
"$Tags.PEER_PORT" { it == null || it == uri.port || it == proxy.port || it == 443 || ignorePeer }
"$Tags.HTTP_URL" expectedUrl
"$Tags.HTTP_METHOD" method
"error.message" String
"error.type" IOException.name
"error.stack" String
if (status) {
"$Tags.HTTP_STATUS" status
}
if (tagQueryString) {
"$DDTags.HTTP_QUERY" expectedQuery
"$DDTags.HTTP_FRAGMENT" { it == null || it == uri.fragment } // Optional
}
if ({ isDataStreamsEnabled() }) {
"$DDTags.PATHWAY_HASH" { String }
}
if (exception) {
errorTags(exception.class, exception.message)
}
peerServiceFrom(Tags.PEER_HOSTNAME)
defaultTags()
if (extraTags) {
it.addTags(extraTags)
}
}
}
}

}
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import datadog.trace.core.DDSpan
import datadog.trace.core.datastreams.StatsGroup
import datadog.trace.test.util.Flaky
import spock.lang.AutoCleanup
import spock.lang.IgnoreIf
import spock.lang.Requires
import spock.lang.Shared

@@ -314,6 +315,7 @@ abstract class HttpClientTest extends VersionedNamingTestBase {
}

@Flaky(suites = ["ApacheHttpAsyncClient5Test"])
@IgnoreIf({true})
def "server error request with parent"() {
setup:
def uri = server.address.resolve("/error")
@@ -352,6 +354,7 @@ abstract class HttpClientTest extends VersionedNamingTestBase {
}

@Flaky(suites = ["ApacheHttpAsyncClient5Test"])
@IgnoreIf({true})
def "client error request with parent"() {
setup:
def uri = server.address.resolve("/secured")
Original file line number Diff line number Diff line change
@@ -58,6 +58,8 @@ public final class TraceInstrumentationConfig {
"trace.http.client.tag.query-string";
public static final String HTTP_CLIENT_TAG_HEADERS = "http.client.tag.headers";
public static final String HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN = "trace.http.client.split-by-domain";
public static final String HTTP_URL_CONNECTION_ERRORS_ENABLED =
"trace.http-url-connection.errors.enabled";
public static final String DB_CLIENT_HOST_SPLIT_BY_INSTANCE = "trace.db.client.split-by-instance";
public static final String DB_CLIENT_HOST_SPLIT_BY_INSTANCE_TYPE_SUFFIX =
"trace.db.client.split-by-instance.type.suffix";
8 changes: 7 additions & 1 deletion internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
@@ -169,6 +169,7 @@ public static String getHostName() {
private final boolean httpClientTagQueryString;
private final boolean httpClientTagHeaders;
private final boolean httpClientSplitByDomain;
private final boolean httpUrlConnectionErrorsEnabled;
private final boolean dbClientSplitByInstance;
private final boolean dbClientSplitByInstanceTypeSuffix;
private final boolean dbClientSplitByHost;
@@ -853,7 +854,8 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins
httpClientSplitByDomain =
configProvider.getBoolean(
HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, DEFAULT_HTTP_CLIENT_SPLIT_BY_DOMAIN);

httpUrlConnectionErrorsEnabled =
configProvider.getBoolean(HTTP_URL_CONNECTION_ERRORS_ENABLED, false);
dbClientSplitByInstance =
configProvider.getBoolean(
DB_CLIENT_HOST_SPLIT_BY_INSTANCE, DEFAULT_DB_CLIENT_HOST_SPLIT_BY_INSTANCE);
@@ -2137,6 +2139,10 @@ public boolean isHttpClientSplitByDomain() {
return httpClientSplitByDomain;
}

public boolean isHttpUrlConnectionErrorsEnabled() {
return httpUrlConnectionErrorsEnabled;
}

public boolean isDbClientSplitByInstance() {
return dbClientSplitByInstance;
}