Skip to content

Commit d2a99ac

Browse files
Merge branch 'master' into daniel.mohedano/bazel-telemetry-provider
2 parents 88626ed + 9d73760 commit d2a99ac

30 files changed

Lines changed: 942 additions & 245 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
/dd-trace-api/src/main/java/datadog/trace/api/aiguard/ @DataDog/asm-java
8989
/dd-trace-api/src/main/java/datadog/trace/api/EventTracker.java @DataDog/asm-java
9090
/internal-api/src/main/java/datadog/trace/api/gateway/ @DataDog/asm-java
91+
/internal-api/src/main/java/datadog/trace/api/http/ @DataDog/asm-java
9192
**/appsec/ @DataDog/asm-java
9293
**/*CallSite*.java @DataDog/asm-java
9394
**/*CallSite*.groovy @DataDog/asm-java

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/java/net/HostNameResolver.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public final class HostNameResolver {
1717
try {
1818
final ClassLoader cl = HostNameResolver.class.getClassLoader();
1919
final MethodHandles methodHandles = new MethodHandles(cl);
20+
// forces the JPMS opener for this class to get executed. More efficient than getLocalHost
21+
InetAddress.getLoopbackAddress();
2022

2123
final Class<?> holderClass =
2224
Class.forName("java.net.InetAddress$InetAddressHolder", false, cl);
@@ -59,6 +61,9 @@ private static String fromCache(InetAddress remoteAddress, String ip) {
5961
}
6062

6163
public static String hostName(InetAddress address, String ip) {
64+
if (address == null) {
65+
return null;
66+
}
6267
final String alreadyResolved = getAlreadyResolvedHostName(address);
6368
if (alreadyResolved != null) {
6469
return alreadyResolved;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package datadog.trace.bootstrap.instrumentation.java.net;
2+
3+
import java.util.concurrent.atomic.AtomicBoolean;
4+
5+
public class JpmsInetAddressHelper {
6+
public static final AtomicBoolean OPENED = new AtomicBoolean(false);
7+
8+
private JpmsInetAddressHelper() {}
9+
}

dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
0 java.lang.VirtualThread
6060
0 java.net.http.*
6161
0 java.net.HttpURLConnection
62+
0 java.net.InetAddress
6263
0 java.net.Socket
6364
0 java.net.URL
6465
0 java.nio.DirectByteBuffer

dd-java-agent/instrumentation/aws-java/aws-java-sns-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/sns/SnsInterceptor.java

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package datadog.trace.instrumentation.aws.v2.sns;
22

33
import static datadog.context.propagation.Propagators.defaultPropagator;
4-
import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND;
5-
import static datadog.trace.api.datastreams.DataStreamsTags.create;
64
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.traceConfig;
7-
import static datadog.trace.instrumentation.aws.v2.sns.TextMapInjectAdapter.SETTER;
85

96
import datadog.context.Context;
107
import datadog.trace.api.Config;
@@ -28,6 +25,9 @@
2825

2926
public class SnsInterceptor implements ExecutionInterceptor {
3027

28+
// SQS subscriber limit; SNS inherits it when SQS is used as a subscriber
29+
private static final int MAX_MESSAGE_ATTRIBUTES = 10;
30+
3131
public static final ExecutionAttribute<Context> CONTEXT_ATTRIBUTE =
3232
InstanceStore.of(ExecutionAttribute.class)
3333
.putIfAbsent("DatadogContext", () -> new ExecutionAttribute<>("DatadogContext"));
@@ -38,10 +38,12 @@ private SdkBytes getMessageAttributeValueToInject(
3838
StringBuilder jsonBuilder = new StringBuilder();
3939
jsonBuilder.append('{');
4040
if (traceConfig().isDataStreamsEnabled()) {
41-
DataStreamsContext dsmContext = DataStreamsContext.fromTags(getTags(snsTopicName));
41+
DataStreamsTags tags =
42+
DataStreamsTags.create("sns", DataStreamsTags.Direction.OUTBOUND, snsTopicName);
43+
DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags);
4244
context = context.with(dsmContext);
4345
}
44-
defaultPropagator().inject(context, jsonBuilder, SETTER);
46+
defaultPropagator().inject(context, jsonBuilder, TextMapInjectAdapter.SETTER);
4547
jsonBuilder.setLength(jsonBuilder.length() - 1); // Remove the last comma
4648
jsonBuilder.append('}');
4749
return SdkBytes.fromString(jsonBuilder.toString(), StandardCharsets.UTF_8);
@@ -57,9 +59,7 @@ public SdkRequest modifyRequest(ModifyRequest context, ExecutionAttributes execu
5759
// Injecting the trace context into SNS messageAttributes.
5860
if (context.request() instanceof PublishRequest) {
5961
PublishRequest request = (PublishRequest) context.request();
60-
// 10 messageAttributes is a limit from SQS, which is often used as a subscriber, therefore
61-
// the limit still applies here
62-
if (request.messageAttributes().size() < 10) {
62+
if (request.messageAttributes().size() < MAX_MESSAGE_ATTRIBUTES) {
6363
// Get topic name for DSM
6464
String snsTopicArn = request.topicArn();
6565
if (null == snsTopicArn) {
@@ -70,17 +70,11 @@ public SdkRequest modifyRequest(ModifyRequest context, ExecutionAttributes execu
7070
}
7171

7272
String snsTopicName = snsTopicArn.substring(snsTopicArn.lastIndexOf(':') + 1);
73-
Map<String, MessageAttributeValue> modifiedMessageAttributes =
74-
new HashMap<>(request.messageAttributes());
75-
modifiedMessageAttributes.put(
76-
"_datadog", // Use Binary since SNS subscription filter policies fail silently with JSON
77-
// strings https://github.com/DataDog/datadog-lambda-js/pull/269
78-
MessageAttributeValue.builder()
79-
.dataType("Binary")
80-
.binaryValue(
81-
this.getMessageAttributeValueToInject(executionAttributes, snsTopicName))
82-
.build());
83-
return request.toBuilder().messageAttributes(modifiedMessageAttributes).build();
73+
Map<String, MessageAttributeValue> messageAttributes =
74+
withDatadogAttribute(
75+
request.messageAttributes(),
76+
this.getMessageAttributeValueToInject(executionAttributes, snsTopicName));
77+
return request.toBuilder().messageAttributes(messageAttributes).build();
8478
}
8579
return request;
8680
} else if (context.request() instanceof PublishBatchRequest) {
@@ -89,24 +83,28 @@ public SdkRequest modifyRequest(ModifyRequest context, ExecutionAttributes execu
8983
String snsTopicArn = request.topicArn();
9084
String snsTopicName = snsTopicArn.substring(snsTopicArn.lastIndexOf(':') + 1);
9185
ArrayList<PublishBatchRequestEntry> entries = new ArrayList<>();
92-
final SdkBytes sdkBytes =
93-
this.getMessageAttributeValueToInject(executionAttributes, snsTopicName);
86+
SdkBytes value = this.getMessageAttributeValueToInject(executionAttributes, snsTopicName);
9487
for (PublishBatchRequestEntry entry : request.publishBatchRequestEntries()) {
95-
if (entry.messageAttributes().size() < 10) {
96-
Map<String, MessageAttributeValue> modifiedMessageAttributes =
97-
new HashMap<>(entry.messageAttributes());
98-
modifiedMessageAttributes.put(
99-
"_datadog",
100-
MessageAttributeValue.builder().dataType("Binary").binaryValue(sdkBytes).build());
101-
entries.add(entry.toBuilder().messageAttributes(modifiedMessageAttributes).build());
88+
if (entry.messageAttributes().size() < MAX_MESSAGE_ATTRIBUTES) {
89+
Map<String, MessageAttributeValue> messageAttributes =
90+
withDatadogAttribute(entry.messageAttributes(), value);
91+
entry = entry.toBuilder().messageAttributes(messageAttributes).build();
10292
}
93+
entries.add(entry);
10394
}
10495
return request.toBuilder().publishBatchRequestEntries(entries).build();
10596
}
10697
return context.request();
10798
}
10899

109-
private DataStreamsTags getTags(String snsTopicName) {
110-
return create("sns", OUTBOUND, snsTopicName);
100+
private static Map<String, MessageAttributeValue> withDatadogAttribute(
101+
Map<String, MessageAttributeValue> attributes, SdkBytes value) {
102+
// copy since the original map may be unmodifiable
103+
Map<String, MessageAttributeValue> modified = new HashMap<>(attributes);
104+
// Use Binary since SNS subscription filter policies fail silently with JSON strings
105+
// https://github.com/DataDog/datadog-lambda-js/pull/269
106+
modified.put(
107+
"_datadog", MessageAttributeValue.builder().dataType("Binary").binaryValue(value).build());
108+
return modified;
111109
}
112110
}

dd-java-agent/instrumentation/aws-java/aws-java-sns-2.0/src/test/groovy/SnsClientTest.groovy

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
1313
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
1414
import software.amazon.awssdk.regions.Region
1515
import software.amazon.awssdk.services.sns.SnsClient
16-
import software.amazon.awssdk.services.sns.model.MessageAttributeValue
1716
import software.amazon.awssdk.services.sns.model.PublishResponse
1817
import software.amazon.awssdk.services.sqs.SqsClient
1918
import software.amazon.awssdk.services.sqs.model.QueueAttributeName
@@ -41,15 +40,15 @@ abstract class SnsClientTest extends VersionedNamingTestBase {
4140
LOCALSTACK.start()
4241
def endPoint = "http://" + LOCALSTACK.getHost() + ":" + LOCALSTACK.getMappedPort(4566)
4342
snsClient = SnsClient.builder()
44-
.endpointOverride(URI.create(endPoint))
45-
.region(Region.of("us-east-1"))
46-
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test")))
47-
.build()
43+
.endpointOverride(URI.create(endPoint))
44+
.region(Region.of("us-east-1"))
45+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test")))
46+
.build()
4847
sqsClient = SqsClient.builder()
49-
.endpointOverride(URI.create(endPoint))
50-
.region(Region.of("us-east-1"))
51-
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test")))
52-
.build()
48+
.endpointOverride(URI.create(endPoint))
49+
.region(Region.of("us-east-1"))
50+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test")))
51+
.build()
5352
testQueueURL = sqsClient.createQueue { it.queueName("testqueue") }.queueUrl()
5453
testQueueARN = sqsClient.getQueueAttributes {it.queueUrl(testQueueURL).attributeNames(QueueAttributeName.QUEUE_ARN)}.attributes().get(QueueAttributeName.QUEUE_ARN)
5554
testTopicARN = snsClient.createTopic { it.name("testtopic") }.topicArn()
@@ -82,30 +81,6 @@ abstract class SnsClientTest extends VersionedNamingTestBase {
8281
abstract String expectedOperation(String awsService, String awsOperation)
8382
abstract String expectedService(String awsService, String awsOperation)
8483

85-
def "trace details propagated when message attributes are readonly"() {
86-
when:
87-
TEST_WRITER.clear()
88-
89-
def headers = new HashMap<String, MessageAttributeValue>()
90-
headers.put("mykey", MessageAttributeValue.builder().stringValue("myvalue").dataType("String").build())
91-
def readonlyHeaders = Collections.unmodifiableMap(headers)
92-
snsClient.publish(b -> b.message("sometext").topicArn(testTopicARN).messageAttributes(readonlyHeaders))
93-
94-
def message = sqsClient.receiveMessage {
95-
it.queueUrl(testQueueURL).waitTimeSeconds(3)
96-
}.messages().get(0)
97-
98-
def messageBody = new JsonSlurper().parseText(message.body())
99-
100-
then:
101-
// injected value is here
102-
String injectedValue = messageBody["MessageAttributes"]["_datadog"]["Value"]
103-
injectedValue.length() > 0
104-
105-
// original header value is still present
106-
messageBody["MessageAttributes"]["mykey"] != null
107-
}
108-
10984
def "trace details propagated via SNS system message attributes"() {
11085
when:
11186
TEST_WRITER.clear()
@@ -214,7 +189,7 @@ abstract class SnsClientTest extends VersionedNamingTestBase {
214189
TEST_WRITER.clear()
215190
snsClient.publish { req ->
216191
req.message("test message")
217-
.topicArn(testTopicARN)
192+
.topicArn(testTopicARN)
218193
}
219194

220195
def message = sqsClient.receiveMessage { it.queueUrl(testQueueURL).waitTimeSeconds(3) }.messages().get(0)
@@ -339,4 +314,3 @@ class SnsClientV1DataStreamsForkedTest extends SnsClientTest {
339314
1
340315
}
341316
}
342-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package datadog.trace.instrumentation.aws.v2.sns;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotSame;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import datadog.context.Context;
9+
import java.util.Arrays;
10+
import java.util.Collections;
11+
import java.util.HashMap;
12+
import java.util.LinkedHashMap;
13+
import java.util.Map;
14+
import java.util.stream.Collectors;
15+
import org.junit.jupiter.api.Test;
16+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
17+
import software.amazon.awssdk.services.sns.model.MessageAttributeValue;
18+
import software.amazon.awssdk.services.sns.model.PublishBatchRequest;
19+
import software.amazon.awssdk.services.sns.model.PublishBatchRequestEntry;
20+
import software.amazon.awssdk.services.sns.model.PublishRequest;
21+
22+
public class SnsInterceptorTest {
23+
24+
@Test
25+
void publishBatchPreservesEntriesAndOnlyInjectsBelowTheMessageAttributeLimit() {
26+
PublishBatchRequest batchRequest =
27+
PublishBatchRequest.builder()
28+
.topicArn("arn:aws:sns:us-east-1:123456789012:test-topic")
29+
.publishBatchRequestEntries(
30+
PublishBatchRequestEntry.builder()
31+
.id("at-limit")
32+
.message("first")
33+
.messageAttributes(stringAttributes(10))
34+
.build(),
35+
PublishBatchRequestEntry.builder()
36+
.id("under-limit")
37+
.message("second")
38+
.messageAttributes(stringAttributes(9))
39+
.build())
40+
.build();
41+
42+
PublishBatchRequest modified =
43+
(PublishBatchRequest)
44+
new SnsInterceptor().modifyRequest(() -> batchRequest, executionAttributes());
45+
46+
assertEquals(
47+
Arrays.asList("at-limit", "under-limit"),
48+
modified.publishBatchRequestEntries().stream()
49+
.map(PublishBatchRequestEntry::id)
50+
.collect(Collectors.toList()));
51+
assertFalse(
52+
modified.publishBatchRequestEntries().get(0).messageAttributes().containsKey("_datadog"));
53+
assertTrue(
54+
modified.publishBatchRequestEntries().get(1).messageAttributes().containsKey("_datadog"));
55+
}
56+
57+
@Test
58+
void publishPreservesReadonlyAttributesWhileAddingDatadogContext() {
59+
Map<String, MessageAttributeValue> headers = new HashMap<>();
60+
headers.put(
61+
"mykey", MessageAttributeValue.builder().dataType("String").stringValue("myvalue").build());
62+
Map<String, MessageAttributeValue> readonlyHeaders = Collections.unmodifiableMap(headers);
63+
64+
PublishRequest request =
65+
PublishRequest.builder()
66+
.topicArn("arn:aws:sns:us-east-1:123456789012:test-topic")
67+
.message("sometext")
68+
.messageAttributes(readonlyHeaders)
69+
.build();
70+
71+
PublishRequest modified =
72+
(PublishRequest) new SnsInterceptor().modifyRequest(() -> request, executionAttributes());
73+
74+
assertNotSame(readonlyHeaders, modified.messageAttributes());
75+
assertEquals("myvalue", modified.messageAttributes().get("mykey").stringValue());
76+
assertTrue(modified.messageAttributes().containsKey("_datadog"));
77+
assertFalse(readonlyHeaders.containsKey("_datadog"));
78+
}
79+
80+
private static ExecutionAttributes executionAttributes() {
81+
ExecutionAttributes executionAttributes = new ExecutionAttributes();
82+
executionAttributes.putAttribute(SnsInterceptor.CONTEXT_ATTRIBUTE, Context.root());
83+
return executionAttributes;
84+
}
85+
86+
private static Map<String, MessageAttributeValue> stringAttributes(int count) {
87+
Map<String, MessageAttributeValue> attributes = new LinkedHashMap<>();
88+
for (int index = 1; index <= count; index++) {
89+
attributes.put(
90+
"key" + index,
91+
MessageAttributeValue.builder().dataType("String").stringValue("value" + index).build());
92+
}
93+
return attributes;
94+
}
95+
}

dd-java-agent/instrumentation/commons-fileupload-1.5/src/main/java/datadog/trace/instrumentation/commons/fileupload/FileItemContentReader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package datadog.trace.instrumentation.commons.fileupload;
22

3+
import datadog.trace.api.http.MultipartContentDecoder;
34
import java.io.IOException;
45
import java.io.InputStream;
5-
import java.nio.charset.StandardCharsets;
66
import java.util.ArrayList;
77
import java.util.List;
88
import org.apache.commons.fileupload.FileItem;
@@ -35,7 +35,7 @@ public static String readContent(FileItem fileItem) {
3535
&& (n = is.read(buf, total, MAX_CONTENT_BYTES - total)) != -1) {
3636
total += n;
3737
}
38-
return new String(buf, 0, total, StandardCharsets.ISO_8859_1);
38+
return MultipartContentDecoder.decodeBytes(buf, total, fileItem.getContentType());
3939
} catch (IOException ignored) {
4040
return "";
4141
}

dd-java-agent/instrumentation/commons-fileupload-1.5/src/test/groovy/FileItemContentReaderTest.groovy

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ class FileItemContentReaderTest extends Specification {
3939
FileItemContentReader.readContent(item) == ''
4040
}
4141

42+
void 'readContent uses Content-Type from file item for charset decoding'() {
43+
given:
44+
def text = 'héllo wörld'
45+
def item = fileItemFromBytes(text.getBytes('UTF-8'), 'file.txt', 'text/plain; charset=UTF-8')
46+
47+
expect:
48+
FileItemContentReader.readContent(item) == text
49+
}
50+
4251
void 'readContents returns content for each non-form file with a name'() {
4352
given:
4453
def items = [fileItem('content-a', 'file-a.txt'), fileItem('content-b', 'file-b.txt'),]
@@ -101,10 +110,19 @@ class FileItemContentReaderTest extends Specification {
101110
}
102111

103112
private FileItem fileItem(String content, String name) {
113+
fileItem(content, name, null)
114+
}
115+
116+
private FileItem fileItem(String content, String name, String contentType) {
117+
fileItemFromBytes((content ?: '').getBytes('ISO-8859-1'), name, contentType)
118+
}
119+
120+
private FileItem fileItemFromBytes(byte[] bytes, String name, String contentType) {
104121
FileItem item = Stub(FileItem)
105122
item.isFormField() >> false
106123
item.getName() >> name
107-
item.getInputStream() >> new ByteArrayInputStream((content ?: '').getBytes('ISO-8859-1'))
124+
item.getContentType() >> contentType
125+
item.getInputStream() >> new ByteArrayInputStream(bytes)
108126
return item
109127
}
110128
}

0 commit comments

Comments
 (0)