Skip to content

Commit e3f6fbe

Browse files
Merge branch 'master' into harmon.herring/artsec-137-ddapm
2 parents 5facd95 + 5580c61 commit e3f6fbe

File tree

314 files changed

+5842
-2136
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

314 files changed

+5842
-2136
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
name: add-apm-integrations
3+
description: Write a new library instrumentation end-to-end. Use when the user ask to add a new APM integration or a library instrumentation.
4+
context: fork
5+
allowed-tools:
6+
- Bash
7+
- Read
8+
- Write
9+
- Edit
10+
- Glob
11+
- Grep
12+
---
13+
14+
Write a new APM end-to-end integration for dd-trace-java, based on library instrumentations, following all project conventions.
15+
16+
## Step 1 – Read the authoritative docs and sync this skill (mandatory, always first)
17+
18+
Before writing any code, read all three files in full:
19+
20+
1. [`docs/how_instrumentations_work.md`](docs/how_instrumentations_work.md) — full reference (types, methods, advice, helpers, context stores, decorators)
21+
2. [`docs/add_new_instrumentation.md`](docs/add_new_instrumentation.md) — step-by-step walkthrough
22+
3. [`docs/how_to_test.md`](docs/how_to_test.md) — test types and how to run them
23+
24+
These files are the single source of truth. Reference them while implementing.
25+
26+
**After reading the docs, sync this skill with them:**
27+
28+
Compare the content of the three docs against the rules encoded in Steps 2–11 of this skill file. Look for:
29+
- Patterns, APIs, or conventions described in the docs but absent or incorrect here
30+
- Steps that are out of date relative to the current docs (e.g. renamed methods, new base classes)
31+
- Advice constraints or test requirements that have changed
32+
33+
For every discrepancy found, edit this file (`.claude/skills/apm-integrations/SKILL.md`) to correct it using the
34+
`Edit` tool before continuing. Keep changes targeted: fix what diverged, add what is missing, remove what is wrong.
35+
Do not touch content that already matches the docs.
36+
37+
## Step 2 – Clarify the task
38+
39+
If the user has not already provided all of the following, ask before proceeding:
40+
41+
- **Framework name** and **minimum supported version** (e.g. `okhttp-3.0`)
42+
- **Target class(es) and method(s)** to instrument (fully qualified class names preferred)
43+
- **Target system**: one of `Tracing`, `Profiling`, `AppSec`, `Iast`, `CiVisibility`, `Usm`, `ContextTracking`
44+
- **Whether this is a bootstrap instrumentation** (affects allowed imports)
45+
46+
## Step 3 – Find a reference instrumentation
47+
48+
Search `dd-java-agent/instrumentation/` for a structurally similar integration:
49+
- Same target system
50+
- Comparable type-matching strategy (single type, hierarchy, known types)
51+
52+
Read the reference integration's `InstrumenterModule`, Advice, Decorator, and test files to understand the established
53+
pattern before writing new code. Use it as a template.
54+
55+
## Step 4 – Set up the module
56+
57+
1. Create directory: `dd-java-agent/instrumentation/$framework/$framework-$minVersion/`
58+
2. Under it, create the standard Maven source layout:
59+
- `src/main/java/` — instrumentation code
60+
- `src/test/groovy/` — Spock tests
61+
3. Create `build.gradle` with:
62+
- `compileOnly` dependencies for the target framework
63+
- `testImplementation` dependencies for tests
64+
- `muzzle { pass { } }` directives (see Step 9)
65+
4. Register the new module in `settings.gradle.kts` in **alphabetical order**
66+
67+
## Step 5 – Write the InstrumenterModule
68+
69+
Conventions to enforce:
70+
71+
- Add `@AutoService(InstrumenterModule.class)` annotation — required for auto-discovery
72+
- Extend the correct `InstrumenterModule.*` subclass (never the bare abstract class)
73+
- Implement the **narrowest** `Instrumenter` interface possible:
74+
- Prefer `ForSingleType` > `ForKnownTypes` > `ForTypeHierarchy`
75+
- Add `classLoaderMatcher()` if a sentinel class identifies the framework on the classpath
76+
- Declare **all** helper class names in `helperClassNames()`:
77+
- Include inner classes (`Foo$Bar`), anonymous classes (`Foo$1`), and enum synthetic classes
78+
- Declare `contextStore()` entries if context stores are needed (key class → value class)
79+
- Keep method matchers as narrow as possible (name, parameter types, visibility)
80+
81+
## Step 6 – Write the Decorator
82+
83+
- Extend the most specific available base decorator:
84+
- `HttpClientDecorator`, `DatabaseClientDecorator`, `ServerDecorator`, `MessagingClientDecorator`, etc.
85+
- One `public static final DECORATE` instance
86+
- Define `UTF8BytesString` constants for the component name and operation name
87+
- Keep all tag/naming/error logic here — not in the Advice class
88+
- Override `spanType()`, `component()`, `spanKind()` as appropriate
89+
90+
## Step 7 – Write the Advice class (highest-risk step)
91+
92+
### Must do
93+
94+
- Advice methods **must** be `static`
95+
- Annotate enter: `@Advice.OnMethodEnter(suppress = Throwable.class)`
96+
- Annotate exit: `@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)`
97+
- **Exception**: do NOT use `suppress` when hooking a constructor
98+
- Use `@Advice.Local("...")` for values shared between enter and exit (span, scope)
99+
- Use the correct parameter annotations:
100+
- `@Advice.This` — the receiver object
101+
- `@Advice.Argument(N)` — a method argument by index
102+
- `@Advice.Return` — the return value (exit only)
103+
- `@Advice.Thrown` — the thrown exception (exit only)
104+
- `@Advice.Enter` — the return value of the enter method (exit only)
105+
- Use `CallDepthThreadLocalMap` to guard against recursive instrumentation of the same method
106+
107+
### Span lifecycle (in order)
108+
109+
Enter method:
110+
1. `AgentSpan span = startSpan(DECORATE.operationName(), ...)`
111+
2. `DECORATE.afterStart(span)` + set domain-specific tags
112+
3. `AgentScope scope = activateSpan(span)` — return or store via `@Advice.Local`
113+
114+
Exit method:
115+
4. `DECORATE.onError(span, throwable)` — only if throwable is non-null
116+
5. `DECORATE.beforeFinish(span)`
117+
6. `span.finish()`
118+
7. `scope.close()`
119+
120+
### Must NOT do
121+
122+
- **No logger fields** in the Advice class or the Instrumentation class (loggers only in helpers/decorators)
123+
- **No code in the Advice constructor** — it is never called
124+
- **Do not use lambdas in advice methods** — they create synthetic classes that will be missing from helper declarations
125+
- **No references** to other methods in the same Advice class or in the InstrumenterModule class
126+
- **No `InstrumentationContext.get()`** outside of Advice code
127+
- **No `inline=false`** in production code (only for debugging; must be removed before committing)
128+
- **No `java.util.logging.*`, `java.nio.*`, or `javax.management.*`** in bootstrap instrumentations
129+
130+
## Step 8 – Add SETTER/GETTER adapters (if applicable)
131+
132+
For context propagation to and from upstream services, like HTTP headers,
133+
implement `AgentPropagation.Setter` / `AgentPropagation.Getter` adapters that wrap the framework's specific header API.
134+
Place them in the helpers package, declare them in `helperClassNames()`.
135+
136+
## Step 9 – Write tests
137+
138+
Cover all mandatory test types:
139+
140+
### 1. Instrumentation test (mandatory)
141+
142+
- Spock spec extending `InstrumentationSpecification`
143+
- Place in `src/test/groovy/`
144+
- Verify: spans created, tags set, errors propagated, resource names correct
145+
- Use `TEST_WRITER.waitForTraces(N)` for assertions
146+
- Use `runUnderTrace("root") { ... }` for synchronous code
147+
148+
For tests that need a separate JVM, suffix the test class with `ForkedTest` and run via the `forkedTest` task.
149+
150+
### 2. Muzzle directives (mandatory)
151+
152+
In `build.gradle`, add `muzzle` blocks:
153+
```groovy
154+
muzzle {
155+
pass {
156+
group = "com.example"
157+
module = "framework"
158+
versions = "[$minVersion,)"
159+
assertInverse = true // ensures versions below $minVersion fail muzzle
160+
}
161+
}
162+
```
163+
164+
### 3. Latest dependency test (mandatory)
165+
166+
Use the `latestDepTestLibrary` helper in `build.gradle` to pin the latest available version. Run with:
167+
```bash
168+
./gradlew :dd-java-agent:instrumentation:$framework-$version:latestDepTest
169+
```
170+
171+
### 4. Smoke test (optional)
172+
173+
Add a smoke test in `dd-smoke-tests/` only if the framework warrants a full end-to-end demo-app test.
174+
175+
## Step 10 – Build and verify
176+
177+
Run these commands in order and fix any failures before proceeding:
178+
179+
```bash
180+
./gradlew :dd-java-agent:instrumentation:$framework-$version:muzzle
181+
./gradlew :dd-java-agent:instrumentation:$framework-$version:test
182+
./gradlew :dd-java-agent:instrumentation:$framework-$version:latestDepTest
183+
./gradlew spotlessCheck
184+
```
185+
186+
**If muzzle fails:** check for missing helper class names in `helperClassNames()`.
187+
188+
**If tests fail:** verify span lifecycle order (start → activate → error → finish → close), helper registration,
189+
and `contextStore()` map entries match actual usage.
190+
191+
**If spotlessCheck fails:** run `./gradlew spotlessApply` to auto-format, then re-check.
192+
193+
## Step 11 – Checklist before finishing
194+
195+
Output this checklist and confirm each item is satisfied:
196+
197+
- [ ] `settings.gradle.kts` entry added in alphabetical order
198+
- [ ] `build.gradle` has `compileOnly` deps and `muzzle` directives with `assertInverse = true`
199+
- [ ] `@AutoService(InstrumenterModule.class)` annotation present on the module class
200+
- [ ] `helperClassNames()` lists ALL referenced helpers (including inner, anonymous, and enum synthetic classes)
201+
- [ ] Advice methods are `static` with `@Advice.OnMethodEnter` / `@Advice.OnMethodExit` annotations
202+
- [ ] `suppress = Throwable.class` on enter/exit (unless the hooked method is a constructor)
203+
- [ ] No logger field in the Advice class or InstrumenterModule class
204+
- [ ] No `inline=false` left in production code
205+
- [ ] No `java.util.logging.*` / `java.nio.*` / `javax.management.*` in bootstrap path
206+
- [ ] Span lifecycle order is correct: startSpan → afterStart → activateSpan (enter); onError → beforeFinish → finish → close (exit)
207+
- [ ] Muzzle passes
208+
- [ ] Instrumentation tests pass
209+
- [ ] `latestDepTest` passes
210+
- [ ] `spotlessCheck` passes
211+
212+
## Step 12 – Retrospective: update this skill with what was learned
213+
214+
After the instrumentation is complete (or abandoned), review the full session and improve this skill for future use.
215+
216+
**Collect lessons from four sources:**
217+
218+
1. **Build/test failures** — did any Gradle task fail with an error that this skill did not anticipate or gave wrong
219+
guidance for? (e.g. a muzzle failure that wasn't caused by missing helpers, a test pattern that didn't work)
220+
2. **Docs vs. skill gaps** — did Step 1's sync miss anything? Did you consult the docs for something not captured here?
221+
3. **Reference instrumentation insights** — did the reference integration use a pattern, API, or convention not
222+
reflected in any step of this skill?
223+
4. **User corrections** — did the user correct an output, override a decision, or point out a mistake?
224+
225+
**For each lesson identified**, edit this file (`.claude/skills/apm-integrations/SKILL.md`) using the `Edit` tool:
226+
- Wrong rule → fix it in place
227+
- Missing rule → add it to the most relevant step
228+
- Wrong failure guidance → update the relevant "If X fails" section in Step 10
229+
- Misleading or obsolete content → remove it
230+
231+
Keep each change minimal and targeted. Do not rewrite sections that worked correctly.
232+
After editing, confirm to the user which improvements were made to the skill.

.claude/skills/migrate-groovy-to-java/SKILL.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ When converting Groovy code to Java code, make sure that:
3232
- When translating Spock `Mock(...)` usage, use `libs.bundles.mockito` instead of writing manual recording/stub implementations
3333

3434
TableTest usage
35-
Dependency, if missing add:
36-
- Groovy: testImplementation libs.tabletest
37-
- Kotlin: testImplementation(libs.tabletest)
38-
3935
Import: `import org.tabletest.junit.TableTest;`
4036

4137
JDK 8 rules:

.claude/skills/migrate-junit-source-to-tabletest/SKILL.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ Process (do in this order):
1111
4) Write each modified file once in full using Write (no incremental per-test edits).
1212
5) Run module tests once and verify "BUILD SUCCESSFUL". If failed, inspect JUnit XML report.
1313

14-
Dependency:
15-
- If missing, add:
16-
- Groovy: testImplementation libs.tabletest
17-
- Kotlin: testImplementation(libs.tabletest)
18-
1914
Import: `import org.tabletest.junit.TableTest;`
2015

2116
JDK 8 rules:
@@ -34,6 +29,7 @@ Table formatting rules (mandatory):
3429
- Use '|' as delimiter.
3530
- Align columns with spaces so pipes line up vertically.
3631
- Prefer single quotes for strings requiring quotes (e.g., 'a|b', '[]', '{}', ' ').
32+
- Use value sets (`{a, b, c}`) instead of matrix-style repetition when only one dimension varies across otherwise-identical rows.
3733

3834
Conversions:
3935
A) @CsvSource
@@ -42,7 +38,8 @@ A) @CsvSource
4238
- If delimiter is ',' (default): replace ',' with '|' in rows.
4339

4440
B) @ValueSource
45-
- Convert to @TableTest with header from parameter name.
41+
- Keep single-parameter tests on `@ValueSource` (and `@NullSource` when null cases are needed).
42+
- Otherwise convert to @TableTest with header from parameter name.
4643
- Each value becomes one row.
4744
- Add "scenario" column using common sense for name.
4845

@@ -60,6 +57,11 @@ C) @MethodSource (convert only if values are representable as strings)
6057
- '' = empty string.
6158
- For String params that start with '[' or '{', quote to avoid collection parsing (prefer '[]'/'{}').
6259

60+
D) @TypeConverter
61+
- Use `@TypeConverter` for symbolic constants used by migrated table rows (e.g. `Long.MAX_VALUE`, `DDSpanId.MAX`).
62+
- Prefer explicit one-case-one-return mappings.
63+
- Prefer shared converter utilities (e.g. in `utils/test-utils`) when reuse across modules is likely.
64+
6365
Scenario handling:
6466
- If MethodSource includes a leading description string OR @ParameterizedTest(name=...) uses {0}, convert that to a scenario column and remove that parameter from method signature.
6567

.github/workflows/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ _Recovery:_ Manually verify the guideline compliance.
3030

3131
### check-pull-request-labels [🔗](check-pull-request-labels.yaml)
3232

33-
_Trigger:_ When creating or updating a pull request.
33+
_Trigger:_ When creating or updating a pull request, or when new commits are pushed to it.
34+
35+
_Actions:_
3436

35-
_Action:_ Check the pull request did not introduce unexpected label.
37+
* Detect AI-generated pull requests then apply the `tag: ai generated` label.
38+
* Check the pull request did not introduce unexpected labels.
3639

3740
_Recovery:_ Update the pull request or add a comment to trigger the action again.
3841

.github/workflows/analyze-changes.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
${{ runner.os }}-gradle-
3131
3232
- name: Initialize CodeQL
33-
uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5
33+
uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
3434
with:
3535
languages: 'java'
3636
build-mode: 'manual'
@@ -43,7 +43,7 @@ jobs:
4343
./gradlew clean :dd-java-agent:shadowJar --build-cache --parallel --stacktrace --no-daemon --max-workers=4
4444
4545
- name: Perform CodeQL Analysis and upload results to GitHub Security tab
46-
uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5
46+
uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
4747

4848
trivy:
4949
name: Analyze changes with Trivy
@@ -89,7 +89,7 @@ jobs:
8989
ls -laR "./workspace/.trivy"
9090
9191
- name: Run Trivy security scanner
92-
uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # v0.34.2
92+
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
9393
with:
9494
scan-type: rootfs
9595
scan-ref: './workspace/.trivy/'
@@ -102,7 +102,7 @@ jobs:
102102
TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db
103103

104104
- name: Upload Trivy scan results to GitHub Security tab
105-
uses: github/codeql-action/upload-sarif@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5
105+
uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
106106
if: always()
107107
with:
108108
sarif_file: 'trivy-results.sarif'

0 commit comments

Comments
 (0)