Skip to content

Commit 98576a9

Browse files
Merge branch 'master' into alexeyk/update-spotless-8.9.0
2 parents 9015897 + 362845a commit 98576a9

11 files changed

Lines changed: 1035 additions & 946 deletions

File tree

.github/workflows/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ _Trigger:_ When creating or updating a pull request, or when new commits are pus
3434

3535
_Actions:_
3636

37+
* Clean up the tool labels (`Bits AI`, `campaigner-automated-change`) by removing them from both the pull request and the repository.
3738
* Detect AI-generated pull requests then apply the `tag: ai generated` label.
3839
* Check the pull request did not introduce unexpected labels.
3940

.github/workflows/check-pull-request-labels.yaml

Lines changed: 74 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ jobs:
2222
with:
2323
scope: DataDog/dd-trace-java
2424
policy: self.check-pull-request-labels
25-
- name: Flag AI-generated pull requests
26-
id: flag_ai_generated
25+
- name: Clean up tool labels
26+
id: clean_up_labels
2727
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 9.0.0
2828
with:
2929
github-token: ${{ steps.generate-token.outputs.token }}
@@ -35,101 +35,102 @@ jobs:
3535
const prNumber = context.payload.pull_request.number
3636
const owner = context.repo.owner
3737
const repo = context.repo.repo
38-
const aiGeneratedLabel = 'tag: ai generated'
39-
let isAiGenerated = false
40-
let labelsStale = false
41-
42-
/*
43-
* Check for 'Bits AI' label and remove it.
44-
*/
45-
const bitsAiLabel = 'Bits AI'
38+
// Labels applied by external tooling that are never allowed on a pull request
39+
const toolLabels = [
40+
'Bits AI', // Applied by the Bits AI tooling
41+
'campaigner-automated-change' // Applied by the Campaigner tooling
42+
]
4643
const prLabels = context.payload.pull_request.labels.map(l => l.name)
47-
if (prLabels.includes(bitsAiLabel)) {
48-
isAiGenerated = true
44+
const cleanedLabels = toolLabels.filter(label => prLabels.includes(label))
45+
for (const label of cleanedLabels) {
4946
// Remove label from the PR
5047
try {
5148
await github.rest.issues.removeLabel({
5249
owner, repo,
5350
issue_number: prNumber,
54-
name: bitsAiLabel
51+
name: label
5552
})
5653
} catch (e) {
57-
core.warning(`Could not remove '${bitsAiLabel}' label from PR: ${e.message}`)
54+
core.warning(`Could not remove '${label}' label from PR: ${e.message}`)
5855
}
59-
labelsStale = true
60-
// Delete label from the repository
56+
// Delete label from the repository as the tooling applying it also recreates it
6157
try {
62-
await github.rest.issues.deleteLabel({ owner, repo, name: bitsAiLabel })
58+
await github.rest.issues.deleteLabel({ owner, repo, name: label })
6359
} catch (e) {
64-
core.warning(`Could not delete '${bitsAiLabel}' label from repo: ${e.message}`)
60+
core.warning(`Could not delete '${label}' label from repo: ${e.message}`)
6561
}
6662
}
63+
core.setOutput('cleaned_labels', JSON.stringify(cleanedLabels))
6764
68-
/*
69-
* Inspect commits for AI authorship signals.
70-
*/
65+
- name: Flag AI-generated pull requests
66+
id: flag_ai_generated
67+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 9.0.0
68+
env:
69+
CLEANED_LABELS: ${{ steps.clean_up_labels.outputs.cleaned_labels }}
70+
with:
71+
github-token: ${{ steps.generate-token.outputs.token }}
72+
script: |
73+
// Skip draft pull requests
74+
if (context.payload.pull_request.draft) {
75+
return
76+
}
77+
const prNumber = context.payload.pull_request.number
78+
const owner = context.repo.owner
79+
const repo = context.repo.repo
80+
// Skip if the PR is already labeled as AI-generated
81+
const aiGeneratedLabel = 'tag: ai generated'
7182
if (context.payload.pull_request.labels.some(l => l.name === aiGeneratedLabel)) {
7283
core.info(`PR #${prNumber} is already labeled as AI-generated, skipping commit scan.`)
73-
core.setOutput('labels_stale', String(labelsStale))
7484
return
7585
}
76-
const aiRegex = /\b(anthropic|chatgpt|codex|copilot|cursor|openai)\b/i
77-
const commits = await github.paginate(github.rest.pulls.listCommits, {
78-
owner, repo,
79-
pull_number: prNumber,
80-
per_page: 100
81-
})
82-
for (const { commit } of commits) {
83-
const authorName = commit.author?.name ?? ''
84-
const authorEmail = commit.author?.email ?? ''
85-
const committerName = commit.committer?.name ?? ''
86-
const committerEmail = commit.committer?.email ?? ''
87-
// Extract Co-authored-by trailer lines from commit message
88-
const coAuthors = (commit.message ?? '').split('\n')
89-
.filter(line => /^co-authored-by:/i.test(line.trim()))
90-
const fieldsToCheck = [authorName, authorEmail]
91-
// Skip GitHub's generic noreply for committer
92-
if (committerEmail !== 'noreply@github.com') {
93-
fieldsToCheck.push(committerName, committerEmail)
94-
}
95-
fieldsToCheck.push(...coAuthors)
96-
if (fieldsToCheck.some(field => aiRegex.test(field))) {
97-
isAiGenerated = true
98-
break
86+
// The cleaned up 'Bits AI' label flags an AI-generated pull request
87+
let isAiGenerated = JSON.parse(process.env.CLEANED_LABELS || '[]').includes('Bits AI')
88+
// Inspect commits for AI authorship signals
89+
if (!isAiGenerated) {
90+
const aiRegex = /\b(anthropic|chatgpt|codex|copilot|cursor|openai)\b/i
91+
const commits = await github.paginate(github.rest.pulls.listCommits, {
92+
owner, repo,
93+
pull_number: prNumber,
94+
per_page: 100
95+
})
96+
for (const { commit } of commits) {
97+
const authorName = commit.author?.name ?? ''
98+
const authorEmail = commit.author?.email ?? ''
99+
const committerName = commit.committer?.name ?? ''
100+
const committerEmail = commit.committer?.email ?? ''
101+
// Extract Co-authored-by trailer lines from commit message
102+
const coAuthors = (commit.message ?? '').split('\n')
103+
.filter(line => /^co-authored-by:/i.test(line.trim()))
104+
const fieldsToCheck = [authorName, authorEmail]
105+
// Skip GitHub's generic noreply for committer
106+
if (committerEmail !== 'noreply@github.com') {
107+
fieldsToCheck.push(committerName, committerEmail)
108+
}
109+
fieldsToCheck.push(...coAuthors)
110+
if (fieldsToCheck.some(field => aiRegex.test(field))) {
111+
isAiGenerated = true
112+
break
113+
}
99114
}
100115
}
101-
102-
/*
103-
* Add 'tag: ai generated' label if AI-generated.
104-
*/
116+
// Add 'tag: ai generated' label if AI-generated
105117
if (isAiGenerated) {
106-
// Re-fetch labels only if they were modified above (Bits AI removal)
107-
let currentLabels
108-
if (labelsStale) {
109-
const { data: currentPr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber })
110-
currentLabels = currentPr.labels.map(l => l.name)
111-
} else {
112-
currentLabels = context.payload.pull_request.labels.map(l => l.name)
113-
}
114-
if (!currentLabels.includes(aiGeneratedLabel)) {
115-
try {
116-
await github.rest.issues.addLabels({
117-
owner, repo,
118-
issue_number: prNumber,
119-
labels: [aiGeneratedLabel]
120-
})
121-
core.info(`Added '${aiGeneratedLabel}' label to PR #${prNumber}`)
122-
} catch (e) {
123-
core.setFailed(`Could not add '${aiGeneratedLabel}' label to PR #${prNumber}: ${e.message}`)
124-
}
118+
try {
119+
await github.rest.issues.addLabels({
120+
owner, repo,
121+
issue_number: prNumber,
122+
labels: [aiGeneratedLabel]
123+
})
124+
core.info(`Added '${aiGeneratedLabel}' label to PR #${prNumber}`)
125+
} catch (e) {
126+
core.setFailed(`Could not add '${aiGeneratedLabel}' label to PR #${prNumber}: ${e.message}`)
125127
}
126128
}
127-
core.setOutput('labels_stale', String(labelsStale))
128129
129130
- name: Check pull request labels
130131
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 9.0.0
131132
env:
132-
LABELS_STALE: ${{ steps.flag_ai_generated.outputs.labels_stale }}
133+
CLEANED_LABELS: ${{ steps.clean_up_labels.outputs.cleaned_labels }}
133134
with:
134135
github-token: ${{ steps.generate-token.outputs.token }}
135136
script: |
@@ -143,19 +144,12 @@ jobs:
143144
'comp:',
144145
'inst:',
145146
'tag:',
146-
'mergequeue-status:',
147-
'team:',
148147
'performance:', // To refactor to 'ci: ' in the future
149148
'run-tests:' // Unused since GitLab migration
150149
]
151-
// Exact-match labels that don't fit a category prefix (e.g. labels applied
152-
// by external automation tooling).
153-
const exactAllowlist = [
154-
'campaigner-automated-change'
155-
]
156-
// Re-fetch labels only if the previous step modified them (ex: "Bits AI" removal)
150+
// Re-fetch labels only if the clean up step removed some of them
157151
let prLabels
158-
if (process.env.LABELS_STALE === 'true') {
152+
if (JSON.parse(process.env.CLEANED_LABELS || '[]').length > 0) {
159153
const { data: currentPr } = await github.rest.pulls.get({
160154
owner: context.repo.owner,
161155
repo: context.repo.repo,
@@ -168,10 +162,7 @@ jobs:
168162
// Look for invalid labels
169163
const invalidLabels = prLabels
170164
.map(label => label.name)
171-
.filter(label =>
172-
!exactAllowlist.includes(label) &&
173-
validCategories.every(prefix => !label.startsWith(prefix))
174-
)
165+
.filter(label => validCategories.every(prefix => !label.startsWith(prefix)))
175166
const hasInvalidLabels = invalidLabels.length > 0
176167
// Get existing comments to check for blocking comment
177168
const comments = await github.rest.issues.listComments({

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ private void addFilteredCaptureExpressions(
725725
}
726726

727727
private void processCaptureExpressions(CapturedContext context, LogStatus logStatus) {
728-
if (captureExpressions == null) {
728+
if (captureExpressions == null || !logStatus.shouldSend()) {
729729
return;
730730
}
731731
for (CaptureExpression captureExpression : captureExpressions) {

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3020,6 +3020,31 @@ public void captureExpressionsWithNullCondition() throws IOException, URISyntaxE
30203020
assertEquals("Cannot dereference field: fld", evaluationErrors.get(0).getMessage());
30213021
}
30223022

3023+
@Test
3024+
public void captureExpressionsWithRejectingCondition() throws IOException, URISyntaxException {
3025+
final String CLASS_NAME = "CapturedSnapshot08";
3026+
LogProbe probe =
3027+
createProbeBuilder(PROBE_ID, CLASS_NAME, "doit", null)
3028+
.evaluateAt(MethodLocation.EXIT)
3029+
.captureSnapshot(false)
3030+
.when(new ProbeCondition(DSL.when(DSL.eq(DSL.value(1), DSL.value(2))), "1 == 2"))
3031+
.template("plain log", Collections.emptyList())
3032+
.captureExpressions(
3033+
Collections.singletonList(
3034+
new LogProbe.CaptureExpression(
3035+
"unknown_symbol",
3036+
new ValueScript(ref("doesNotExist"), "doesNotExist"),
3037+
null)))
3038+
.build();
3039+
TestSnapshotListener listener = installProbes(probe);
3040+
Class<?> testClass = compileAndLoadClass(CLASS_NAME);
3041+
for (int i = 0; i < 5; i++) {
3042+
int result = Reflect.onClass(testClass).call("main", "1").get();
3043+
assertEquals(3, result);
3044+
}
3045+
assertEquals(0, listener.snapshots.size());
3046+
}
3047+
30233048
@Test
30243049
public void captureExpressionsPrimitives() throws IOException, URISyntaxException {
30253050
final String CLASS_NAME = "CapturedSnapshot08";

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
import static java.lang.String.format;
66
import static java.lang.Thread.currentThread;
77
import static java.util.Collections.emptyList;
8+
import static java.util.Collections.singletonList;
89
import static org.junit.jupiter.api.Assertions.assertEquals;
910
import static org.junit.jupiter.api.Assertions.assertNull;
1011
import static org.junit.jupiter.api.Assertions.assertTrue;
1112
import static org.mockito.Mockito.mock;
1213

1314
import com.datadog.debugger.agent.DebuggerAgentHelper;
15+
import com.datadog.debugger.el.DSL;
16+
import com.datadog.debugger.el.ProbeCondition;
17+
import com.datadog.debugger.el.ValueScript;
1418
import com.datadog.debugger.probe.LogProbe.Builder;
1519
import com.datadog.debugger.probe.LogProbe.LogStatus;
1620
import com.datadog.debugger.sink.DebuggerSink;
@@ -19,6 +23,7 @@
1923
import datadog.context.ContextScope;
2024
import datadog.trace.api.Config;
2125
import datadog.trace.api.IdGenerationStrategy;
26+
import datadog.trace.api.sampling.ConstantSampler;
2227
import datadog.trace.bootstrap.debugger.CapturedContext;
2328
import datadog.trace.bootstrap.debugger.EvaluationError;
2429
import datadog.trace.bootstrap.debugger.MethodLocation;
@@ -342,6 +347,49 @@ public void fillSnapshot_shouldSend_evalErrors() {
342347
"errorExit", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage());
343348
}
344349

350+
@Test
351+
public void captureExpressionsInActiveDebugSession() {
352+
DebuggerAgentHelper.injectSink(new DebuggerSink(getConfig(), mock(ProbeStatusSink.class)));
353+
TracerAPI tracer =
354+
CoreTracer.builder().idGenerationStrategy(IdGenerationStrategy.fromName("random")).build();
355+
AgentTracer.registerIfAbsent(tracer);
356+
AgentSpan span = tracer.startSpan("log probe capture expression testing", "test span");
357+
try (ContextScope scope = tracer.activateManualSpan(span)) {
358+
span.setTag(Tags.PROPAGATED_DEBUG, DEBUG_SESSION_ID + ":1");
359+
// the probe sampler always rejects: the active session decision must still win
360+
ProbeRateLimiter.setSamplerSupplier(rate -> new ConstantSampler(false));
361+
LogProbe logProbe =
362+
createLog("log line")
363+
.probeId(ProbeId.newId())
364+
.evaluateAt(MethodLocation.EXIT)
365+
.tags(format("session_id:%s", DEBUG_SESSION_ID))
366+
.when(new ProbeCondition(DSL.when(DSL.eq(DSL.value(1), DSL.value(1))), "1 == 1"))
367+
.captureExpressions(
368+
singletonList(
369+
new LogProbe.CaptureExpression(
370+
"greeting", new ValueScript(DSL.value("hello"), "'hello'"), null)))
371+
.build();
372+
logProbe.initSamplers();
373+
CapturedContext entryContext = capturedContext(span, logProbe);
374+
CapturedContext exitContext = capturedContext(span, logProbe);
375+
logProbe.evaluate(entryContext, new LogStatus(logProbe), MethodLocation.ENTRY, false);
376+
logProbe.evaluate(exitContext, new LogStatus(logProbe), MethodLocation.EXIT, false);
377+
Snapshot snapshot = new Snapshot(currentThread(), logProbe, 3);
378+
assertTrue(logProbe.fillSnapshot(entryContext, exitContext, emptyList(), snapshot));
379+
assertEquals(
380+
"hello",
381+
snapshot
382+
.getCaptures()
383+
.getReturn()
384+
.getCaptureExpressions()
385+
.get("greeting")
386+
.getValue()
387+
.toString());
388+
} finally {
389+
ProbeRateLimiter.setSamplerSupplier(null);
390+
}
391+
}
392+
345393
private Builder createLog(String template) {
346394
return LogProbe.builder()
347395
.language(LANGUAGE)

dd-smoke-tests/springboot-tomcat/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description = 'SpringBoot Tomcat Smoke Tests.'
99

1010
def serverName = 'tomcat'
1111
def serverModule = 'tomcat-9'
12-
def serverVersion = '9.0.117'
12+
def serverVersion = '9.0.120'
1313
def serverExtension = 'zip'
1414

1515
repositories {

dd-smoke-tests/springboot-tomcat/gradle.lockfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,5 @@ org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeCla
115115
org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath
116116
org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath
117117
org.xmlresolver:xmlresolver:5.3.3=spotbugs
118-
tomcat:tomcat-9:9.0.117=serverFile
118+
tomcat:tomcat-9:9.0.120=serverFile
119119
empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor

0 commit comments

Comments
 (0)