Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
@@ -0,0 +1,252 @@
package datadog.smoketest

import okhttp3.Request
import java.util.concurrent.atomic.AtomicInteger
import java.util.regex.Pattern

class HttpEndpointTaggingSmokeTest extends AbstractServerSmokeTest {

@Override
ProcessBuilder createProcessBuilder() {
String springBootShadowJar = System.getProperty("datadog.smoketest.springboot.shadowJar.path")

List<String> command = new ArrayList<>()
command.add(javaPath())
command.addAll(defaultJavaProperties)
command.addAll((String[]) [
"-Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()}:includeResource,DDAgentWriter",
"-Ddd.trace.resource.renaming.enabled=true",
"-jar",
springBootShadowJar,
"--server.port=${httpPort}"
])
ProcessBuilder processBuilder = new ProcessBuilder(command)
processBuilder.directory(new File(buildDirectory))
}

@Override
File createTemporaryFile() {
return File.createTempFile("http-endpoint-tagging-trace", "out")
}

@Override
protected Set<String> expectedTraces() {
return [
Pattern.quote("[servlet.request:GET /greeting[spring.handler:IastWebController.greeting]]")
]
}

@Override
protected Set<String> assertTraceCounts(Set<String> expected, Map<String, AtomicInteger> traceCounts) {
List<Pattern> remaining = expected.collect { Pattern.compile(it) }.toList()
for (def i = remaining.size() - 1; i >= 0; i--) {
for (Map.Entry<String, AtomicInteger> entry : traceCounts.entrySet()) {
if (entry.getValue() > 0 && remaining.get(i).matcher(entry.getKey()).matches()) {
remaining.remove(i)
break
}
}
}
return remaining.collect { it.pattern() }.toSet()
}

def "test basic HTTP endpoint tagging"() {
setup:
String url = "http://localhost:${httpPort}/greeting"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
def responseBodyStr = response.body().string()
responseBodyStr != null
responseBodyStr.contains("Sup Dawg")
response.code() == 200
waitForTraceCount(1)
}

def "test URL parameterization for numeric IDs"() {
setup:
String url = "http://localhost:${httpPort}/users/123"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
// May return 404 since endpoint doesn't exist, but span should still be created
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test URL parameterization for hex patterns"() {
setup:
String url = "http://localhost:${httpPort}/session/abc123def456"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
// May return 404 since endpoint doesn't exist, but span should still be created
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test int_id pattern parameterization"() {
setup:
String url = "http://localhost:${httpPort}/api/versions/12.34.56"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test hex_id pattern parameterization"() {
setup:
String url = "http://localhost:${httpPort}/api/tokens/550e8400-e29b-41d4-a716-446655440000"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test str pattern parameterization for long strings"() {
setup:
String url = "http://localhost:${httpPort}/files/very-long-filename-that-exceeds-twenty-characters.pdf"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test str pattern parameterization for special characters"() {
setup:
String url = "http://localhost:${httpPort}/search/query%20with%20spaces"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test mixed URL patterns with multiple segments"() {
setup:
String url = "http://localhost:${httpPort}/api/users/123/orders/abc456def/items/789"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test URL with query parameters"() {
setup:
String url = "http://localhost:${httpPort}/api/users/123?filter=active&limit=10"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test URL with trailing slash"() {
setup:
String url = "http://localhost:${httpPort}/api/users/456/"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test static paths are preserved"() {
setup:
String url = "http://localhost:${httpPort}/health"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test root path handling"() {
setup:
String url = "http://localhost:${httpPort}/"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test pattern precedence - int pattern wins over int_id"() {
setup:
String url = "http://localhost:${httpPort}/api/items/12345"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test pattern precedence - hex pattern wins over hex_id"() {
setup:
String url = "http://localhost:${httpPort}/api/hashes/abc123def"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}

def "test edge case - short segments not parameterized"() {
setup:
String url = "http://localhost:${httpPort}/api/x/y"
def request = new Request.Builder().url(url).get().build()

when:
def response = client.newCall(request).execute()

then:
response.code() in [200, 404]
waitForTraceCount(1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ public final class ConfigDefaults {
"datadog.trace.*:org.apache.commons.*:org.mockito.*";
static final boolean DEFAULT_CIVISIBILITY_GIT_UPLOAD_ENABLED = true;
static final boolean DEFAULT_CIVISIBILITY_GIT_UNSHALLOW_ENABLED = true;

// HTTP Endpoint Tagging feature flags
static final boolean DEFAULT_RESOURCE_RENAMING_ENABLED =
false; // Default enablement of resource renaming
static final boolean DEFAULT_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT =
false; // Manual disablement of resource renaming
static final long DEFAULT_CIVISIBILITY_GIT_COMMAND_TIMEOUT_MILLIS = 30_000;
static final long DEFAULT_CIVISIBILITY_BACKEND_API_TIMEOUT_MILLIS = 30_000;
static final long DEFAULT_CIVISIBILITY_GIT_UPLOAD_TIMEOUT_MILLIS = 60_000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public final class TracerConfig {
"trace.http.resource.remove-trailing-slash";
public static final String TRACE_HTTP_SERVER_PATH_RESOURCE_NAME_MAPPING =
"trace.http.server.path-resource-name-mapping";

// HTTP Endpoint Tagging feature flags
public static final String TRACE_RESOURCE_RENAMING_ENABLED = "trace.resource.renaming.enabled";
public static final String TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT =
"trace.resource.renaming.always-simplified-endpoint";
public static final String TRACE_HTTP_CLIENT_PATH_RESOURCE_NAME_MAPPING =
"trace.http.client.path-resource-name-mapping";
// Use TRACE_HTTP_SERVER_ERROR_STATUSES instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static datadog.communication.ddagent.DDAgentFeaturesDiscovery.V6_METRICS_ENDPOINT;
import static datadog.trace.api.DDTags.BASE_SERVICE;
import static datadog.trace.api.Functions.UTF8_ENCODE;
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT;
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER;
Expand Down Expand Up @@ -307,6 +309,8 @@ private boolean spanKindEligible(CoreSpan<?> span) {

private boolean publish(CoreSpan<?> span, boolean isTopLevel) {
final CharSequence spanKind = span.getTag(SPAN_KIND, "");
final CharSequence httpMethod = span.getTag(HTTP_METHOD, "");
Comment thread
sezen-datadog marked this conversation as resolved.
Outdated
final CharSequence httpEndpoint = span.getTag(HTTP_ENDPOINT, "");
MetricKey newKey =
new MetricKey(
span.getResourceName(),
Expand All @@ -318,7 +322,9 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel) {
span.getParentId() == 0,
SPAN_KINDS.computeIfAbsent(
spanKind, UTF8BytesString::create), // save repeated utf8 conversions
getPeerTags(span, spanKind.toString()));
getPeerTags(span, spanKind.toString()),
httpMethod,
httpEndpoint);
boolean isNewKey = false;
MetricKey key = keys.putIfAbsent(newKey, newKey);
if (null == key) {
Expand Down
Loading
Loading