Skip to content

Commit c13a440

Browse files
authored
Merge branch 'main' into final
2 parents c6dd71d + 6d8ac90 commit c13a440

866 files changed

Lines changed: 4252 additions & 3329 deletions

File tree

Some content is hidden

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

.github/agents/code-review-and-fix.agent.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ Auto-fix boundaries:
134134

135135
- Safe to fix:
136136
- import cleanup or direct style-guide conformance
137+
- normalization of existing `@SuppressWarnings` syntax or placement, but preserve any
138+
accurate explanatory comment attached to the suppression instead of deleting it as
139+
style noise
137140
- obvious assertion API migrations (e.g., AssertJ preference) and idiomatic
138141
simplifications listed in `testing-general-patterns.md` § AssertJ Idiomatic
139142
Simplifications (e.g., `assertThat(list.size()).isEqualTo(N)`
@@ -142,6 +145,8 @@ Auto-fix boundaries:
142145
lambdas where the lambda parameter is already an `AbstractAssert` (e.g.,
143146
`AbstractStringAssert`). Calls like `taskId.contains(jobName)` on the assert object are
144147
already valid AssertJ assertions; do not wrap them in `assertThat(...).isTrue()`
148+
- AssertJ `.as(...)` descriptions and `.withFailMessage(...)` in tests — remove them
149+
and prefer direct assertions whose failure output already exposes the unexpected values
145150
- deterministic semconv constant handling aligned with repository rules
146151
- missing test-task wiring patterns with clear canonical form
147152
- missing `testInstrumentation` cross-version references — when a javaagent module belongs
@@ -207,6 +212,16 @@ Auto-fix boundaries:
207212
to avoid allocating on every invocation. Only convert singletons used at
208213
registration/initialization time (e.g., `Instrumenter` builder chains, `Singletons`
209214
setup)
215+
- try-with-resources wrapping most of a test body for an `AutoCloseable` that only
216+
needs cleanup at test end — convert to `AutoCleanupExtension` with `deferCleanup(...)`.
217+
Add a `@RegisterExtension static final AutoCleanupExtension cleanup =
218+
AutoCleanupExtension.create();` field if one does not already exist, then replace
219+
the try-with-resources with `cleanup.deferCleanup(resource);` and un-indent the body.
220+
Keep try-with-resources for semantically scoped resources whose lifetime must end
221+
mid-test (e.g., `Scope` / `Context.makeCurrent()`, `MockedStatic`, short-lived
222+
readers/writers/streams/response bodies).
223+
Do not apply this conversion in non-JUnit helper methods, `@BeforeAll`, or shared
224+
setup code.
210225
- `hasAttributesSatisfying(...)` calls in test assertions — replace with
211226
`hasAttributesSatisfyingExactly(...)` because it is more precise (the non-exact
212227
variant silently ignores unexpected attributes)

.github/agents/knowledge/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ Load only files relevant to the current scope to reduce noise and avoid over-con
1313
| `general-rules.md` | Always — review checklist table and core rules enforced on every review |
1414
| `gradle-conventions.md` | `build.gradle.kts` or `settings.gradle.kts` changes, custom test task registration or wiring |
1515
| `javaagent-advice-patterns.md` | ByteBuddy `@Advice` class or advice-method changes |
16-
| `javaagent-module-patterns.md` | `InstrumentationModule`, `TypeInstrumentation`, `Singletons`, `VirtualField`, `CallDepth` |
16+
| `javaagent-module-patterns.md` | `InstrumentationModule`, `TypeInstrumentation`, `VirtualField`, `CallDepth` |
17+
| `javaagent-singletons-patterns.md` | `*Singletons` holder classes, singleton accessors, static-imported singleton callers |
1718
| `library-patterns.md` | Library instrumentation telemetry, builder, getter, or setter pattern changes |
1819
| `module-naming.md` | New or renamed modules or packages; settings includes |
19-
| `testing-general-patterns.md` | Test files in scope — assertion style, attribute assertion patterns, `satisfies()` lambda usage |
20+
| `testing-general-patterns.md` | Test files in scope — assertion style, resource cleanup patterns, attribute assertion patterns, `satisfies()` lambda usage |
2021
| `testing-experimental-flags.md` | `testExperimental` task or experimental span-attribute assertions |
2122
| `testing-semconv-stability.md` | Semconv opt-in modes, `emitOld*`/`emitStable*`, `maybeStable`, Semconv test tasks |
2223

.github/agents/knowledge/config-property-stability.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Defined in [VERSIONING.md](../../../VERSIONING.md):
5252
Examples (flat ↔ YAML):
5353

5454
- `otel.instrumentation.http.client.capture-request-headers``request_captured_headers`**stable**
55-
- `otel.instrumentation.common.experimental.db-sqlcommenter.enabled``sqlcommenter/development: { enabled: true }`**experimental**
55+
- `otel.instrumentation.common.db.experimental.sqlcommenter.enabled``sqlcommenter/development: { enabled: true }`**experimental**
5656
- `otel.instrumentation.http.client.emit-experimental-telemetry``emit_experimental_telemetry/development: true`**experimental**
5757

5858
## Deprecation Communication

.github/agents/knowledge/general-rules.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ When a "Knowledge File" is listed, load it from `knowledge/` before reviewing th
1717
| Style | Style guide | Always ||
1818
| Style | Uppercase field names should reflect semantic constants or immutable value constants such as `Duration` timeouts/intervals, not simply `static final` | Always ||
1919
| Naming | Getter naming (`get` / `is`) | Always ||
20-
| Naming | Boolean/collection option naming (`set*Enabled`, `setCapture*`, `setEmitExperimental*`) | `Experimental` or `TelemetryBuilder` setter changes | `library-patterns.md` |
2120
| Naming | Module/package naming | New or renamed modules/packages | `module-naming.md` |
2221
| Javaagent | Advice patterns | `@Advice` classes | `javaagent-advice-patterns.md` |
23-
| Javaagent | Module structure patterns | `InstrumentationModule`, `TypeInstrumentation`, `Singletons` | `javaagent-module-patterns.md` |
22+
| Javaagent | Module structure patterns | `InstrumentationModule`, `TypeInstrumentation` | `javaagent-module-patterns.md` |
23+
| Javaagent | Singletons patterns | `*Singletons` holder classes, singleton accessors, static-imported singleton callers | `javaagent-singletons-patterns.md` |
2424
| Javaagent | Incorrect `classLoaderMatcher()` | `classLoaderMatcher()` override that is redundant (muzzle already handles it) or missing when needed (muzzle cannot distinguish version range) | `javaagent-module-patterns.md` |
2525
| Semconv | Library vs javaagent semconv constant usage | Semconv constants/assertions ||
2626
| Semconv | Dual semconv testing | `SemconvStability`, `maybeStable`, semconv Gradle tasks | `testing-semconv-stability.md` |
27-
| Testing | General test patterns | Test files in scope | `testing-general-patterns.md` |
27+
| Testing | General test patterns | Test files in scope — assertion style, resource cleanup, attribute assertions | `testing-general-patterns.md` |
2828
| Testing | Experimental flag tests | `testExperimental`, experimental attribute assertions, `experimental` flags in JVM args or system properties | `testing-experimental-flags.md` |
2929
| Library | TelemetryBuilder/getter/setter patterns | Library instrumentation classes | `library-patterns.md` |
3030
| API | Deprecation and breaking-change policy | Public API changes | `api-deprecation-policy.md` |
@@ -73,6 +73,15 @@ Do not flag the following patterns (common false positives):
7373
The project disables javac's `-Xlint:deprecation` globally and uses a custom Error Prone
7474
check (`OtelDeprecatedApiUsage`) instead. Only add the annotation when it is actually
7575
required to fix an Error Prone error — not speculatively.
76+
- Preserve concise explanatory comments attached to existing `@SuppressWarnings` annotations
77+
when they explain why the suppression exists (for example `// using deprecated semconv`
78+
or `// using deprecated config property`). This repository has established precedent for
79+
such comments, and test code disables Error Prone's `SuppressWarningsWithoutExplanation`
80+
check only because enforcing it everywhere causes too many failures, not because these
81+
comments are undesirable.
82+
- When normalizing or moving an existing `@SuppressWarnings`, keep any accurate explanatory
83+
comment with it. Remove the comment only if it is incorrect, obsolete, or contradicted by
84+
the code after your change.
7685

7786
## [Naming] Getter Naming
7887

.github/agents/knowledge/gradle-conventions.md

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,37 @@ Verify `build.gradle.kts` applies the correct plugin for the module type:
5151
| `library/` | `otel.library-instrumentation` |
5252
| `testing/` | `otel.java-conventions` |
5353

54-
## Boolean Project Properties
54+
## Shared Gradle Project Properties
5555

56-
For simple boolean Gradle project properties in `build.gradle.kts`, prefer the repository's most
57-
common pattern:
56+
In `build.gradle.kts`, prefer the shared `otelProps` extension for repository-wide Gradle project
57+
properties that are already modeled there, including:
58+
59+
- `testLatestDeps`
60+
- `denyUnsafe`
61+
- `collectMetadata`
62+
- `testJavaVersion`
63+
- `testJavaVM`
64+
- `maxTestRetries`
65+
- `enableStrictContext`
66+
67+
Examples:
5868

5969
```kotlin
60-
val myFlag = findProperty("myFlag") == "true"
61-
if (myFlag) {
70+
if (otelProps.testLatestDeps) {
6271
// ...
6372
}
73+
74+
tasks.withType<Test>().configureEach {
75+
systemProperty("collectMetadata", otelProps.collectMetadata)
76+
}
6477
```
6578

79+
For module-local one-off properties that are not part of `otelProps`, using `findProperty(...)`
80+
directly is still fine.
81+
82+
`settings.gradle.kts` is the exception: it cannot use project extensions like `otelProps`, so
83+
direct `gradle.startParameter.projectProperties[...]` access is expected there.
84+
6685
## `testInstrumentation` Dependencies
6786

6887
The `testInstrumentation` configuration declares which other javaagent instrumentation modules
@@ -175,6 +194,9 @@ When a module has custom test tasks (e.g., `testStableSemconv`), system properti
175194
args that apply to **all** test tasks should be set once in a `withType<Test>().configureEach`
176195
block, not repeated on each individual task.
177196

197+
If a property or JVM arg is moved into `withType<Test>().configureEach`, remove any now-redundant
198+
copies from individual tasks unless a task intentionally overrides the shared value.
199+
178200
When there is only one test task, `tasks.test { ... }` is fine — do not convert it to
179201
`withType<Test>().configureEach` and do not flag it.
180202

@@ -186,7 +208,7 @@ should apply to all tasks. If so, move them to `withType<Test>().configureEach`.
186208
```kotlin
187209
tasks {
188210
withType<Test>().configureEach {
189-
systemProperty("collectMetadata", findProperty("collectMetadata"))
211+
systemProperty("collectMetadata", otelProps.collectMetadata)
190212
// ... other properties common to all test tasks
191213
}
192214

@@ -205,7 +227,7 @@ review**. Only verify correctness when they are already present.
205227

206228
| Property | Type | Value |
207229
| --- | --- | --- |
208-
| `collectMetadata` | System property | Pass-through of the `collectMetadata` Gradle project property; defaults to `"false"` |
230+
| `collectMetadata` | System property | Pass-through of `otelProps.collectMetadata`; defaults to `false` |
209231
| `metadataConfig` | System property | A single `key=value` string describing the non-default configuration active during this test run |
210232

211233
When already present, verify:

.github/agents/knowledge/javaagent-advice-patterns.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,95 @@ Because `none()` matches no methods, ByteBuddy never inlines this advice into an
172172
`suppress = Throwable.class` on such a method is entirely meaningless — **do not add it**,
173173
and **do not flag its absence** during review.
174174

175+
## AdviceScope Patterns
176+
177+
`AdviceScope` usage in this repository falls into **two justified state patterns**.
178+
Review new code against these patterns instead of treating every existing variation as equally
179+
canonical.
180+
181+
### Pattern 1 — Nullable `AdviceScope` for ordinary advice
182+
183+
Use this by default when enter advice may decide not to start instrumentation.
184+
185+
- `@Advice.OnMethodEnter` returns `@Nullable AdviceScope`
186+
- The factory method returns `null` when preconditions fail or instrumentation should not start
187+
- If an `AdviceScope` is created, its `Context` and `Scope` fields should be non-null
188+
- The `end()` method should close `scope` unconditionally; the null check belongs in exit advice,
189+
not inside `AdviceScope.end()`
190+
191+
In this document, use `start()` / `end()` as the canonical `AdviceScope` naming.
192+
For new code, prefer `start()` for the factory method and `end()` for the completion method.
193+
Do not introduce one-off names such as `create()` for ordinary `AdviceScope` factories.
194+
195+
Preferred shape:
196+
197+
```java
198+
public static class AdviceScope {
199+
private final Context context;
200+
private final Scope scope;
201+
202+
private AdviceScope(Context context) {
203+
this.context = context;
204+
this.scope = context.makeCurrent();
205+
}
206+
207+
@Nullable
208+
public static AdviceScope start(Request request) {
209+
Context parentContext = Context.current();
210+
if (!instrumenter().shouldStart(parentContext, request)) {
211+
return null;
212+
}
213+
Context context = instrumenter().start(parentContext, request);
214+
return new AdviceScope(context);
215+
}
216+
217+
public void end(@Nullable Throwable throwable) {
218+
scope.close();
219+
instrumenter().end(context, request, null, throwable);
220+
}
221+
}
222+
223+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
224+
public static void onExit(
225+
@Advice.Thrown @Nullable Throwable throwable,
226+
@Advice.Enter @Nullable AdviceScope adviceScope) {
227+
if (adviceScope != null) {
228+
adviceScope.end(throwable);
229+
}
230+
}
231+
```
232+
233+
### Pattern 2 — Non-null placeholder `AdviceScope` for bookkeeping
234+
235+
Use a non-null `AdviceScope` with nullable internals only when exit advice still needs state even
236+
if no tracing scope was started, for example:
237+
238+
- `CallDepth` tracking
239+
- wrapped arguments or listeners that must be carried from enter to exit
240+
- other bookkeeping that must survive even on the "no span started" path
241+
242+
In this pattern, `AdviceScope` is the carrier object for control-flow state, not just tracing
243+
state. Nullable inner fields and exit guards are justified here.
244+
245+
### Do not use the defensive hybrid as a standard pattern
246+
247+
Avoid the hybrid shape where:
248+
249+
- `@Advice.Enter` is `@Nullable AdviceScope`
250+
- `AdviceScope` still stores a nullable `Scope`
251+
- `AdviceScope.end()` has an extra `if (scope == null)` guard
252+
253+
That double-guards the same condition and makes simple advice harder to reason about.
254+
If the helper that creates the context can return `null`, prefer returning `null` from
255+
`AdviceScope.start()` and only creating `AdviceScope` when the context is present.
256+
257+
### Async completion is not a separate `AdviceScope` state pattern
258+
259+
Async instrumentations may end spans from listeners, callbacks, or wrapped handlers instead of
260+
directly in method exit. That affects **where** the scope/span is completed, but it does not
261+
justify a third core `AdviceScope` state model. Async advice should still use either Pattern 1 or
262+
Pattern 2 as appropriate.
263+
175264
## Never Throw Exceptions in Javaagent Code
176265

177266
Javaagent instrumentations must never throw exceptions. The goal is to be invisible to the
@@ -193,3 +282,5 @@ the instrumentation automatically rather than letting it fail at runtime.
193282
- **`@Advice.OnMethodExit` method named `onEnter`** (or vice versa) — the method name should match the annotation. A mismatch is a copy-paste bug that compiles but confuses readers and may mask intent errors.
194283
- **Advice referenced in `transform()` using anything other than `getClass().getName() + "$InnerAdvice"`** — see `javaagent-module-patterns.md` for the canonical pattern. Flag `this.getClass().getName() + "$InnerAdvice"` as a redundant qualifier, and flag both `InnerAdvice.class.getName()` and `OuterInstrumentation.class.getName() + "$InnerAdvice"` because any `.class` literal in a `transform()` method triggers unwanted class loading.
195284
- **`onThrowable = Throwable.class` on return-only exit advice** — if the exit method only processes `@Advice.Return` and has no `@Advice.Enter` state to clean up, `onThrowable` should be omitted. The return value is `null`/zero on the exceptional path, and dereferencing it causes a suppressed exception for no benefit. Keep `suppress = Throwable.class` but remove `onThrowable`.
285+
- **One-off `AdviceScope` method naming** — for new ordinary advice, prefer `start()` / `end()` for `AdviceScope` methods, and avoid introducing unique names such as `create()`.
286+
- **Defensive hybrid `AdviceScope` in simple advice** — if `@Advice.Enter` is already nullable, flag simple advice that also stores a nullable inner `Scope`/`Context` and re-checks it inside `AdviceScope.end()`. Prefer returning `null` from the factory and keeping the created `AdviceScope` fully initialized.

0 commit comments

Comments
 (0)