Skip to content

Commit 64e46eb

Browse files
Add Claude skill to create instrumentations (#10774)
feat(ai): Add skill to create instrumentations feat(ai): Add lambda rule for advice feat(ai): Use markdown link format feat(ai): Improve skill name and trigger Co-authored-by: bruce.bujon <bruce.bujon@datadoghq.com>
1 parent 99d47ca commit 64e46eb

File tree

1 file changed

+232
-0
lines changed
  • .claude/skills/add-apm-integrations

1 file changed

+232
-0
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.

0 commit comments

Comments
 (0)