Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d19d27e
Add RumInjectorHealthMetrics
sarahchen6 Jul 28, 2025
e01d1a9
Add telemetry collector and methods to RumInjector
sarahchen6 Jul 28, 2025
8c71ef6
Initialize health metrics and telemetry collector
sarahchen6 Jul 28, 2025
6c01e10
Get injectionsucceed count
sarahchen6 Jul 28, 2025
ceb3e11
Add comments
sarahchen6 Jul 28, 2025
92f0c54
Reorganize classes
sarahchen6 Jul 31, 2025
c5c860f
Connect rum injector, telemetry collector, and statsdclient
sarahchen6 Jul 31, 2025
3d4ac53
Add tests
sarahchen6 Aug 1, 2025
3fd498d
Get and test metrics for injection failures and skips
sarahchen6 Aug 1, 2025
14c338b
Add Content Security Policy and HTTP response size telemetry
sarahchen6 Aug 1, 2025
c5b5389
Add injection duration telemetry
sarahchen6 Aug 1, 2025
feb40be
Fix some things
sarahchen6 Aug 2, 2025
23e0455
Fix content-length retrieval and add test for injection timing
sarahchen6 Aug 6, 2025
07db174
Add injection initialization success telemetry
sarahchen6 Aug 7, 2025
a5352ff
Fix CoreTracer compilation with InstrumenterConfig
sarahchen6 Aug 8, 2025
2a1f676
Add tags to all metrics
sarahchen6 Aug 8, 2025
b32874c
Update InjectingPipeOutputStreamTest
sarahchen6 Aug 8, 2025
7a5cd68
Tweaks
sarahchen6 Aug 8, 2025
412a3f8
Address jacoco coverage and injectingpipeoutstream interface updates
sarahchen6 Aug 9, 2025
b3fcde4
Add content-length detection for InjectingPipeWriter and improve tests
sarahchen6 Aug 11, 2025
4b2f6b3
Address review comments
sarahchen6 Aug 11, 2025
3e462d4
Fix header retrieval
sarahchen6 Aug 12, 2025
b1ab871
Add lots of improvements from review comments
sarahchen6 Aug 12, 2025
93bd0a4
Fix constructors and address review comment
sarahchen6 Aug 13, 2025
5a24d7d
Merge branch 'master' into sarahchen6/implement-rum-injector-telemetry
sarahchen6 Aug 13, 2025
b20d6c4
Clarify bytes written and address review comment
sarahchen6 Aug 13, 2025
f7607ba
Use dynamic servlet version retrieval
sarahchen6 Aug 18, 2025
e6afc52
Change injection timing logic
sarahchen6 Aug 18, 2025
6f2dc97
Clean up
sarahchen6 Aug 19, 2025
32372c1
Use dynamic servlet version retrieval for tagging as well
sarahchen6 Aug 19, 2025
821dc87
Address merge conflicts
sarahchen6 Aug 19, 2025
150ab05
Add a telemetry check to HttpServerTest
sarahchen6 Aug 19, 2025
e9095b5
Clean up
sarahchen6 Aug 19, 2025
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
Expand Up @@ -112,7 +112,7 @@ class InjectingPipeOutputStreamTest extends DDSpecification {
piped.close()

then:
counter.value == 12
counter.value == testBytes.length
downstream.toByteArray() == testBytes
}

Expand All @@ -130,29 +130,26 @@ class InjectingPipeOutputStreamTest extends DDSpecification {
piped.close()

then:
counter.value == 4
counter.value == testBytes.length
downstream.toByteArray() == testBytes
}

def 'should count bytes correctly with multiple writes'() {
setup:
def part1 = "test".getBytes("UTF-8")
def part2 = " ".getBytes("UTF-8")
def part3 = "content".getBytes("UTF-8")
def testBytes = "test content".getBytes("UTF-8")
def testBytes = "test content"
def downstream = new ByteArrayOutputStream()
def counter = new Counter()
def piped = new InjectingPipeOutputStream(downstream, MARKER_BYTES, CONTEXT_BYTES, null, { long bytes -> counter.incr(bytes) }, null)

when:
piped.write(part1)
piped.write(part2)
piped.write(part3)
piped.write(testBytes[0..4].getBytes("UTF-8"))
piped.write(testBytes[5..5].getBytes("UTF-8"))
piped.write(testBytes[6..-1].getBytes("UTF-8"))
piped.close()

then:
counter.value == 12
downstream.toByteArray() == testBytes
counter.value == testBytes.length()
downstream.toByteArray() == testBytes.getBytes("UTF-8")
}

def 'should be resilient to exceptions when onBytesWritten callback is null'() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,63 +121,66 @@ class InjectingPipeWriterTest extends DDSpecification {
def downstream = new StringWriter()
def counter = new Counter()
def piped = new InjectingPipeWriter(downstream, MARKER_CHARS, CONTEXT_CHARS, null, { long bytes -> counter.incr(bytes) }, null)
def testBytes = "test content"

when:
piped.write("test content".toCharArray())
piped.write(testBytes.toCharArray())
piped.close()

then:
counter.value == 12
downstream.toString() == "test content"
counter.value == testBytes.length()
downstream.toString() == testBytes
}

def 'should count bytes correctly when writing characters individually'() {
setup:
def downstream = new StringWriter()
def counter = new Counter()
def piped = new InjectingPipeWriter(downstream, MARKER_CHARS, CONTEXT_CHARS, null, { long bytes -> counter.incr(bytes) }, null)
def testBytes = "test"

when:
def content = "test"
for (int i = 0; i < content.length(); i++) {
piped.write((int) content.charAt(i))
for (int i = 0; i < testBytes.length(); i++) {
piped.write((int) testBytes.charAt(i))
}
piped.close()

then:
counter.value == 4
downstream.toString() == "test"
counter.value == testBytes.length()
downstream.toString() == testBytes
}

def 'should count bytes correctly with multiple writes'() {
setup:
def downstream = new StringWriter()
def counter = new Counter()
def piped = new InjectingPipeWriter(downstream, MARKER_CHARS, CONTEXT_CHARS, null, { long bytes -> counter.incr(bytes) }, null)
def testBytes = "test content"

when:
piped.write("test".toCharArray())
piped.write(" ".toCharArray())
piped.write("content".toCharArray())
piped.write(testBytes[0..4].toCharArray())
piped.write(testBytes[5..5].toCharArray())
piped.write(testBytes[6..-1].toCharArray())
piped.close()

then:
counter.value == 12
downstream.toString() == "test content"
counter.value == testBytes.length()
downstream.toString() == testBytes
}

def 'should be resilient to exceptions when onBytesWritten callback is null'() {
setup:
def downstream = new StringWriter()
def piped = new InjectingPipeWriter(downstream, MARKER_CHARS, CONTEXT_CHARS)
def testBytes = "test content"

when:
piped.write("test content".toCharArray())
piped.write(testBytes.toCharArray())
piped.close()

then:
noExceptionThrown()
downstream.toString() == "test content"
downstream.toString() == testBytes
}

def 'should call timing callback when injection happens'() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {

void setup() {
mockRequest.getServletContext() >> mockServletContext
mockServletContext.getEffectiveMajorVersion() >> 3
mockServletContext.getEffectiveMajorVersion() >> Integer.parseInt(SERVLET_VERSION)
wrapper = new RumHttpServletResponseWrapper(mockRequest, mockResponse)
RumInjector.setTelemetryCollector(mockTelemetryCollector)
}
Expand Down Expand Up @@ -172,14 +172,15 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {
}
def wrappedStream = new WrappedServletOutputStream(
downstream, marker, contentToInject, null, onBytesWritten, null)
def testBytes = "test content"

when:
wrappedStream.write("test".getBytes("UTF-8"))
wrappedStream.write("content".getBytes("UTF-8"))
wrappedStream.write(testBytes[0..5].getBytes("UTF-8"))
wrappedStream.write(testBytes[6..-1].getBytes("UTF-8"))
wrappedStream.close()

then:
1 * mockTelemetryCollector.onInjectionResponseSize(SERVLET_VERSION, 11)
1 * mockTelemetryCollector.onInjectionResponseSize(SERVLET_VERSION, testBytes.length())
}

void 'response sizes are reported by the InjectingPipeOutputStream callback'() {
Expand All @@ -190,14 +191,15 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {
def onBytesWritten = Mock(LongConsumer)
def stream = new InjectingPipeOutputStream(
downstream, marker, contentToInject, null, onBytesWritten, null)
def testBytes = "test content"

when:
stream.write("test".getBytes("UTF-8"))
stream.write("content".getBytes("UTF-8"))
stream.write(testBytes[0..5].getBytes("UTF-8"))
stream.write(testBytes[6..-1].getBytes("UTF-8"))
stream.close()

then:
1 * onBytesWritten.accept(11)
1 * onBytesWritten.accept(testBytes.length())
}

void 'response sizes are reported by the InjectingPipeWriter callback'() {
Expand All @@ -208,14 +210,15 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {
def onBytesWritten = Mock(LongConsumer)
def writer = new InjectingPipeWriter(
downstream, marker, contentToInject, null, onBytesWritten, null)
def testBytes = "test content"

when:
writer.write("test".toCharArray())
writer.write("content".toCharArray())
writer.write(testBytes[0..5].toCharArray())
writer.write(testBytes[6..-1].toCharArray())
writer.close()

then:
1 * onBytesWritten.accept(11)
1 * onBytesWritten.accept(testBytes.length())
}

void 'injection timing is reported by the InjectingPipeOutputStream callback'() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {

void setup() {
mockRequest.getServletContext() >> mockServletContext
mockServletContext.getEffectiveMajorVersion() >> 5
mockServletContext.getEffectiveMajorVersion() >> Integer.parseInt(SERVLET_VERSION)
wrapper = new RumHttpServletResponseWrapper(mockRequest, mockResponse)
RumInjector.setTelemetryCollector(mockTelemetryCollector)
}
Expand Down Expand Up @@ -172,14 +172,15 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {
}
def wrappedStream = new WrappedServletOutputStream(
downstream, marker, contentToInject, null, onBytesWritten, null)
def testBytes = "test content"

when:
wrappedStream.write("test".getBytes("UTF-8"))
wrappedStream.write("content".getBytes("UTF-8"))
wrappedStream.write(testBytes[0..5].getBytes("UTF-8"))
wrappedStream.write(testBytes[6..-1].getBytes("UTF-8"))
wrappedStream.close()

then:
1 * mockTelemetryCollector.onInjectionResponseSize(SERVLET_VERSION, 11)
1 * mockTelemetryCollector.onInjectionResponseSize(SERVLET_VERSION, testBytes.length())
}

void 'response sizes are reported by the InjectingPipeOutputStream callback'() {
Expand All @@ -190,14 +191,15 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {
def onBytesWritten = Mock(LongConsumer)
def stream = new InjectingPipeOutputStream(
downstream, marker, contentToInject, null, onBytesWritten, null)
def testBytes = "test content"

when:
stream.write("test".getBytes("UTF-8"))
stream.write("content".getBytes("UTF-8"))
stream.write(testBytes[0..5].getBytes("UTF-8"))
stream.write(testBytes[6..-1].getBytes("UTF-8"))
stream.close()

then:
1 * onBytesWritten.accept(11)
1 * onBytesWritten.accept(testBytes.length())
}

void 'response sizes are reported by the InjectingPipeWriter callback'() {
Expand All @@ -208,14 +210,15 @@ class RumHttpServletResponseWrapperTest extends AgentTestRunner {
def onBytesWritten = Mock(LongConsumer)
def writer = new InjectingPipeWriter(
downstream, marker, contentToInject, null, onBytesWritten, null)
def testBytes = "test content"

when:
writer.write("test".toCharArray())
writer.write("content".toCharArray())
writer.write(testBytes[0..5].toCharArray())
writer.write(testBytes[6..-1].toCharArray())
writer.close()

then:
1 * onBytesWritten.accept(11)
1 * onBytesWritten.accept(testBytes.length())
}

void 'injection timing is reported by the InjectingPipeOutputStream callback'() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2236,14 +2236,28 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
def "test rum injection in head for mime #mime"() {
setup:
assumeTrue(testRumInjection())
def telemetryCollector = RumInjector.getTelemetryCollector()
def request = new Request.Builder().url(server.address().resolve("gimme-$mime").toURL())
.get().build()

when:
def response = client.newCall(request).execute()
def responseBody = response.body().string()
def finalSummary = telemetryCollector.summary()

then:
assert response.code() == 200
assert response.body().string().contains(new String(RumInjector.get().getSnippetBytes("UTF-8"), "UTF-8")) == expected
assert responseBody.contains(new String(RumInjector.get().getSnippetBytes("UTF-8"), "UTF-8")) == expected
assert response.header("x-datadog-rum-injected") == (expected ? "1" : null)

// Check a few telemetry metrics
if (expected) {
assert finalSummary.contains("injectionSucceed=")
assert responseBody.length() > 0
} else {
assert finalSummary.contains("injectionSkipped=")
}

where:
mime | expected
"html" | true
Expand Down
13 changes: 8 additions & 5 deletions dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -705,11 +705,14 @@ private CoreTracer(
config.isHealthMetricsEnabled()
? new MonitoringImpl(this.statsDClient, 10, SECONDS)
: Monitoring.DISABLED;
healthMetrics =
config.isHealthMetricsEnabled()
? new TracerHealthMetrics(this.statsDClient)
: HealthMetrics.NO_OP;
healthMetrics.start();

this.healthMetrics =
healthMetrics != null
? healthMetrics
: (config.isHealthMetricsEnabled()
? new TracerHealthMetrics(this.statsDClient)
: HealthMetrics.NO_OP);
this.healthMetrics.start();

// Start RUM injector telemetry
if (InstrumenterConfig.get().isRumEnabled()) {
Expand Down
Loading