|
1 | 1 | # OpenTelemetry Java Instrumentation |
2 | 2 |
|
3 | | -## Scope |
| 3 | +First-pass PR review rules. A deep review with full knowledge files runs |
| 4 | +separately later in the PR lifecycle. **Prefer silence over uncertainty.** Only |
| 5 | +flag substantive issues on changed lines. Skip stylistic preferences not listed |
| 6 | +below. Do not nitpick. |
4 | 7 |
|
5 | | -Repository-wide defaults for Copilot behavior. |
6 | | -Keep scoped language or file-type rules in `.github/instructions/*.instructions.md`. |
| 8 | +Use category tags like `[Style]`, `[Naming]`, `[Testing]`, `[General]`. |
7 | 9 |
|
8 | | -## Repository Layout |
| 10 | +## [Style] Style Guide |
9 | 11 |
|
10 | | -- Workspace-level overview: `.github/README.md` |
11 | | -- Scoped instructions and `applyTo` patterns: `.github/instructions/README.md` |
12 | | -- Agent review and implementation knowledge index: `.github/agents/knowledge/README.md` |
| 12 | +Follow `docs/contributing/style-guide.md`. |
13 | 13 |
|
14 | | -## Core References |
| 14 | +- **Visibility**: principle of least access. Use the most restrictive modifier |
| 15 | + that still works. Static fields should be `private` unless they are |
| 16 | + constant-like with a `SCREAMING_SNAKE_CASE` name. |
| 17 | +- **`final` on classes**: declare public API classes `final` where possible. Do |
| 18 | + **not** add `final` in `javaagent/src/main/`, in `.internal` packages, or in |
| 19 | + test code (paths under `src/test/` or modules whose name starts/ends with |
| 20 | + `testing` or `tests`). |
| 21 | +- **`final` on parameters and local variables**: never declare them `final`. |
| 22 | +- **Null comparisons**: use `value == null` / `value != null`, not |
| 23 | + `null == value` / `null != value`. Applies to Java, Kotlin, and Scala. |
| 24 | +- **`equals` operand order**: prefer `value.equals(CONSTANT)` over |
| 25 | + `CONSTANT.equals(value)`. Do not flip operand order solely as a defensive |
| 26 | + null-safety cleanup; only flip when `value` can actually be null. |
| 27 | +- **Class organization**: static fields → static initializer → instance fields |
| 28 | + → constructors → methods → nested classes. Place calling methods above the |
| 29 | + methods they call. |
| 30 | +- **Static factory entry points**: place them below fields and immediately |
| 31 | + above constructors — treat factories and constructors as one construction |
| 32 | + section. |
| 33 | +- **Static utility classes**: place the private no-arg constructor after all |
| 34 | + methods. |
| 35 | +- **Uppercase field names**: use `SCREAMING_SNAKE_CASE` only for constant-like |
| 36 | + values — literals, immutable value constants (e.g. `Duration` timeouts), |
| 37 | + semantic keys/handles (`AttributeKey`, `ContextKey`, `VirtualField`, |
| 38 | + `MethodHandle`, `Pattern`), and canonical singletons (`INSTANCE`, `EMPTY`, |
| 39 | + `NOOP`). Use lower camel case for runtime collaborators (loggers, |
| 40 | + instrumenters, helpers, caches), even when `static final`. |
| 41 | +- **Avoid throwaway forwarding locals** that mirror an existing constant, |
| 42 | + argument, or SDK field into both an SDK call and span attributes; pass the |
| 43 | + original value directly unless real derivation justifies a local. |
| 44 | +- **`Optional`**: do not use in public API signatures or on the hot path. |
| 45 | +- **Semconv constants**: in `library/src/main/`, copy incubating semconv |
| 46 | + constants locally as `private static final` with a `// copied from <Class>` |
| 47 | + comment; do not depend on the semconv incubating artifact. In |
| 48 | + `javaagent/src/main/` and tests, use semconv constants directly. |
| 49 | +- **`@Nullable` in tests**: do not add it to test code. |
15 | 50 |
|
16 | | -- Style guide and core conventions: `docs/contributing/style-guide.md` |
17 | | -- Review and implementation knowledge by topic: `.github/agents/knowledge/README.md` |
| 51 | +## [Style] `@SuppressWarnings` Scoping |
18 | 52 |
|
19 | | -## Knowledge Loading |
| 53 | +Place `@SuppressWarnings` on the single member that needs it. Use class-level |
| 54 | +only when two or more members would need the same suppression. |
20 | 55 |
|
21 | | -For coding, fix, and refactoring tasks, consult `.github/agents/knowledge/README.md` |
22 | | -before making substantial changes. |
| 56 | +## [Naming] Catch Variable Names |
23 | 57 |
|
24 | | -Use the knowledge index to load only the article(s) relevant to the current task. |
25 | | -Do not load the entire knowledge folder by default. |
| 58 | +In **catch clauses only** (not method/lambda parameters or fields): |
26 | 59 |
|
27 | | -## Gradle Execution Rules |
| 60 | +- Used exception → `e` (or `error` for a specific `*Error` subtype). |
| 61 | +- Used exception in nested catch where outer already uses `e` → `f`. |
| 62 | +- Used `Throwable` → `t`. |
| 63 | +- Intentionally unused → `ignored` (or `ignore` if `ignored` would shadow an |
| 64 | + outer catch). |
28 | 65 |
|
29 | | -Never use `--rerun-tasks`. Use `--rerun` when needed. |
| 66 | +## [Naming] Public API Getters |
30 | 67 |
|
31 | | -Builds and tests in this repository can take several minutes. |
32 | | -Run Gradle commands with timeout `0` (no timeout), and wait for completion. |
33 | | -Do not treat slow output as a hang by default. |
| 68 | +Public API getters use `get*` (or `is*` for booleans). |
34 | 69 |
|
35 | | -Never pipe Gradle output through `tail`, `head`, `grep`, or any other command. |
36 | | -Piping masks the Gradle exit code — the shell reports the exit code of the last |
37 | | -pipe segment, not Gradle, so a failing build silently appears to succeed. |
| 70 | +## [Style] No Redundant Null Guards on Attribute Puts |
| 71 | + |
| 72 | +`AttributesBuilder.put`, `Span.setAttribute`, `SpanBuilder.setAttribute`, and |
| 73 | +`LogRecordBuilder.setAttribute` are no-ops when the value is `null`. Do not wrap |
| 74 | +calls in `if (value != null)` when the value can be passed straight through: |
| 75 | + |
| 76 | +```java |
| 77 | +// BAD |
| 78 | +String v = getSomething(); |
| 79 | +if (v != null) { |
| 80 | + attributes.put(SOME_KEY, v); |
| 81 | +} |
| 82 | +// GOOD |
| 83 | +attributes.put(SOME_KEY, getSomething()); |
| 84 | +``` |
| 85 | + |
| 86 | +Do **not** flag when the guard protects a dereference or derived computation |
| 87 | +(e.g. `view.getClass().getName()`). When in doubt, stay silent. |
| 88 | + |
| 89 | +## [Testing] General Patterns |
| 90 | + |
| 91 | +- Use AssertJ (`assertThat(...)`) for assertions in new test code. Do not |
| 92 | + use JUnit `Assert.*` or Hamcrest `assertThat`. |
| 93 | +- Do not add AssertJ `.as(...)` descriptions or `.withFailMessage(...)` in |
| 94 | + tests. Direct assertions whose failure output already shows the unexpected |
| 95 | + values are preferred. |
| 96 | +- Test methods do not need `throws Exception` clauses unless actually required. |
| 97 | +- Prefer the nearest common parent in `catch` (including `Exception` / |
| 98 | + `Throwable`) over multi-catch. |
| 99 | +- Use `e` / `f` / `t` / `ignored` per the naming rules above. |
| 100 | + |
| 101 | +## [Testing] AssertJ Idiomatic Simplifications |
| 102 | + |
| 103 | +Prefer built-in AssertJ collection/string/map assertions over manual extraction: |
| 104 | + |
| 105 | +| Anti-pattern | Idiomatic | |
| 106 | +| --- | --- | |
| 107 | +| `assertThat(list.size()).isEqualTo(N)` | `assertThat(list).hasSize(N)` | |
| 108 | +| `assertThat(list.isEmpty()).isTrue()` / `.hasSize(0)` | `assertThat(list).isEmpty()` | |
| 109 | +| `assertThat(list.contains(x)).isTrue()` | `assertThat(list).contains(x)` | |
| 110 | +| per-index `get(i)` checks of every element | `assertThat(list).containsExactly(a, b, ...)` | |
| 111 | + |
| 112 | +`containsExactly` already verifies size, so a separate `hasSize` is redundant. |
| 113 | +Same shape applies to `String.length()`, `Map.size()`, and `array.length` → |
| 114 | +`assertThat(...).hasSize(N)`. |
| 115 | + |
| 116 | +## [Testing] Span Attribute Assertions |
| 117 | + |
| 118 | +- Prefer `hasAttributesSatisfyingExactly(...)` over `hasAttributesSatisfying(...)` |
| 119 | + — the non-exact variant **silently ignores unexpected attributes**. Also |
| 120 | + prefer it over `hasAttributes(...)` for consistency. |
| 121 | +- For zero-attribute span assertions, use `hasTotalAttributeCount(0)`. |
| 122 | +- `hasTotalAttributeCount(...)` paired with `hasAttributesSatisfyingExactly(...)` |
| 123 | + is redundant — the exact variant already validates the count. Remove the |
| 124 | + count call. |
| 125 | +- Metric points are different: there is no `hasTotalAttributeCount(...)` on |
| 126 | + metric points, so use `point.hasAttributes(Attributes.empty())` for empty |
| 127 | + metric-point checks. |
| 128 | +- Do not introduce redundant `(long)` casts in `equalTo(longKey(...), value)` |
| 129 | + when `value` is already an `int` — the `equalTo(AttributeKey<Long>, int)` |
| 130 | + overload exists. |
| 131 | + |
| 132 | +## [Testing] `satisfies()` Lambda Parameters |
| 133 | + |
| 134 | +Inside a `satisfies(AttributeKey, lambda)` attribute-assertion the lambda |
| 135 | +parameter is an `AbstractAssert` (e.g. `AbstractStringAssert<?>`), not the raw |
| 136 | +value. Fluent calls like `taskId.contains(jobName)` are already proper |
| 137 | +assertions — do **not** wrap them in `assertThat(value.contains(x)).isTrue()`, |
| 138 | +which degrades the failure message. |
| 139 | + |
| 140 | +Name the outer parameter `val` in Java (or `value` in Scala, where `val` is |
| 141 | +reserved). Use `v` only for a nested inner-lambda parameter. |
| 142 | + |
| 143 | +This guidance applies only to attribute-assertion `satisfies(...)`; for |
| 144 | +`span.satisfies(...)`, `point.satisfies(...)`, etc. use a descriptive name |
| 145 | +(`spanData`, `pointData`, `result`). |
| 146 | + |
| 147 | +## [Javaagent] Singleton Accessor Naming |
| 148 | + |
| 149 | +In `*Singletons`, `*SpanNaming`, and similar holder classes, zero-arg accessor |
| 150 | +methods that **directly return a stored singleton field** must match the field |
| 151 | +name with no `get` prefix: |
| 152 | + |
| 153 | +```java |
| 154 | +private static final Instrumenter<Request, Response> instrumenter = ...; |
| 155 | + |
| 156 | +public static Instrumenter<Request, Response> instrumenter() { |
| 157 | + return instrumenter; |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +- Methods that take arguments or compute a value are not singleton accessors — |
| 162 | + keep their normal names (including `get*` when appropriate). Do not flag |
| 163 | + `getAddressAndPort(client)` on this basis. |
| 164 | +- Uppercase constant-like fields (e.g. `VirtualField`, `ContextKey`) may be |
| 165 | + exposed as `public static final` directly with no accessor. |
| 166 | +- Caller sites should static-import the accessor / constant and call it |
| 167 | + unqualified. |
| 168 | + |
| 169 | +## [General] Engineering Correctness |
| 170 | + |
| 171 | +Flag real defects on changed lines: logic errors, concurrency hazards, resource |
| 172 | +leaks, copy/paste mistakes, incorrect comments, unsafe error handling, dead |
| 173 | +code, security regressions. Skip stylistic preferences not listed above. |
0 commit comments