|
| 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