Skip to content

Commit 6ae02a1

Browse files
committed
feat(ai): Add skill to create instrumentations
1 parent 7be2605 commit 6ae02a1

1 file changed

Lines changed: 231 additions & 0 deletions

File tree

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
---
2+
name: apm-integrations
3+
description: Write a new library instrumentation end-to-end.
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` — full reference (types, methods, advice, helpers, context stores, decorators)
21+
2. `docs/add_new_instrumentation.md` — step-by-step walkthrough
22+
3. `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+
- **No references** to other methods in the same Advice class or in the InstrumenterModule class
125+
- **No `InstrumentationContext.get()`** outside of Advice code
126+
- **No `inline=false`** in production code (only for debugging; must be removed before committing)
127+
- **No `java.util.logging.*`, `java.nio.*`, or `javax.management.*`** in bootstrap instrumentations
128+
129+
## Step 8 – Add SETTER/GETTER adapters (if applicable)
130+
131+
For context propagation to and from upstream services, like HTTP headers,
132+
implement `AgentPropagation.Setter` / `AgentPropagation.Getter` adapters that wrap the framework's specific header API.
133+
Place them in the helpers package, declare them in `helperClassNames()`.
134+
135+
## Step 9 – Write tests
136+
137+
Cover all mandatory test types:
138+
139+
### 1. Instrumentation test (mandatory)
140+
141+
- Spock spec extending `InstrumentationSpecification`
142+
- Place in `src/test/groovy/`
143+
- Verify: spans created, tags set, errors propagated, resource names correct
144+
- Use `TEST_WRITER.waitForTraces(N)` for assertions
145+
- Use `runUnderTrace("root") { ... }` for synchronous code
146+
147+
For tests that need a separate JVM, suffix the test class with `ForkedTest` and run via the `forkedTest` task.
148+
149+
### 2. Muzzle directives (mandatory)
150+
151+
In `build.gradle`, add `muzzle` blocks:
152+
```groovy
153+
muzzle {
154+
pass {
155+
group = "com.example"
156+
module = "framework"
157+
versions = "[$minVersion,)"
158+
assertInverse = true // ensures versions below $minVersion fail muzzle
159+
}
160+
}
161+
```
162+
163+
### 3. Latest dependency test (mandatory)
164+
165+
Use the `latestDepTestLibrary` helper in `build.gradle` to pin the latest available version. Run with:
166+
```bash
167+
./gradlew :dd-java-agent:instrumentation:$framework-$version:latestDepTest
168+
```
169+
170+
### 4. Smoke test (optional)
171+
172+
Add a smoke test in `dd-smoke-tests/` only if the framework warrants a full end-to-end demo-app test.
173+
174+
## Step 10 – Build and verify
175+
176+
Run these commands in order and fix any failures before proceeding:
177+
178+
```bash
179+
./gradlew :dd-java-agent:instrumentation:$framework-$version:muzzle
180+
./gradlew :dd-java-agent:instrumentation:$framework-$version:test
181+
./gradlew :dd-java-agent:instrumentation:$framework-$version:latestDepTest
182+
./gradlew spotlessCheck
183+
```
184+
185+
**If muzzle fails:** check for missing helper class names in `helperClassNames()`.
186+
187+
**If tests fail:** verify span lifecycle order (start → activate → error → finish → close), helper registration,
188+
and `contextStore()` map entries match actual usage.
189+
190+
**If spotlessCheck fails:** run `./gradlew spotlessApply` to auto-format, then re-check.
191+
192+
## Step 11 – Checklist before finishing
193+
194+
Output this checklist and confirm each item is satisfied:
195+
196+
- [ ] `settings.gradle.kts` entry added in alphabetical order
197+
- [ ] `build.gradle` has `compileOnly` deps and `muzzle` directives with `assertInverse = true`
198+
- [ ] `@AutoService(InstrumenterModule.class)` annotation present on the module class
199+
- [ ] `helperClassNames()` lists ALL referenced helpers (including inner, anonymous, and enum synthetic classes)
200+
- [ ] Advice methods are `static` with `@Advice.OnMethodEnter` / `@Advice.OnMethodExit` annotations
201+
- [ ] `suppress = Throwable.class` on enter/exit (unless the hooked method is a constructor)
202+
- [ ] No logger field in the Advice class or InstrumenterModule class
203+
- [ ] No `inline=false` left in production code
204+
- [ ] No `java.util.logging.*` / `java.nio.*` / `javax.management.*` in bootstrap path
205+
- [ ] Span lifecycle order is correct: startSpan → afterStart → activateSpan (enter); onError → beforeFinish → finish → close (exit)
206+
- [ ] Muzzle passes
207+
- [ ] Instrumentation tests pass
208+
- [ ] `latestDepTest` passes
209+
- [ ] `spotlessCheck` passes
210+
211+
## Step 12 – Retrospective: update this skill with what was learned
212+
213+
After the instrumentation is complete (or abandoned), review the full session and improve this skill for future use.
214+
215+
**Collect lessons from four sources:**
216+
217+
1. **Build/test failures** — did any Gradle task fail with an error that this skill did not anticipate or gave wrong
218+
guidance for? (e.g. a muzzle failure that wasn't caused by missing helpers, a test pattern that didn't work)
219+
2. **Docs vs. skill gaps** — did Step 1's sync miss anything? Did you consult the docs for something not captured here?
220+
3. **Reference instrumentation insights** — did the reference integration use a pattern, API, or convention not
221+
reflected in any step of this skill?
222+
4. **User corrections** — did the user correct an output, override a decision, or point out a mistake?
223+
224+
**For each lesson identified**, edit this file (`.claude/skills/apm-integrations/SKILL.md`) using the `Edit` tool:
225+
- Wrong rule → fix it in place
226+
- Missing rule → add it to the most relevant step
227+
- Wrong failure guidance → update the relevant "If X fails" section in Step 10
228+
- Misleading or obsolete content → remove it
229+
230+
Keep each change minimal and targeted. Do not rewrite sections that worked correctly.
231+
After editing, confirm to the user which improvements were made to the skill.

0 commit comments

Comments
 (0)