diff --git a/AGENTS.md b/AGENTS.md index 7640dfc32f1..f0b4dd87691 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,11 @@ # AGENTS.md -Read [CONTRIBUTING.md](CONTRIBUTING.md) first. It is the source of truth for repository layout, -build and test commands, style expectations, and scope. +Start with [docs/knowledge/README.md](docs/knowledge/README.md). It is the entry point for +coding patterns, build conventions, testing guidance, and API stability rules. Load documents +based on the scope of your work — the README contains a table mapping topics to load conditions. -Additional guidance for agents: +For project scope, PR process, and CLA requirements, see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Additional guidance * Prefer small, localized changes over broad refactors. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cb55cf8a37..867319b972a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,66 +1,33 @@ # Contributing -Welcome to OpenTelemetry Java repository! +Welcome to the OpenTelemetry Java repository! -Before you start - see OpenTelemetry general +Before you start, see the OpenTelemetry general [contributing](https://github.com/open-telemetry/community/blob/main/guides/contributor/README.md) requirements and recommendations. -If you want to add new features or change behavior, please make sure your changes follow the -[OpenTelemetry Specification](https://github.com/open-telemetry/opentelemetry-specification). -Otherwise, file an issue or submit a pull request (PR) to the specification repo first. +## CLA and license -Make sure to review the projects [license](LICENSE) and sign the -[CNCF CLA](https://identity.linuxfoundation.org/projects/cncf). A signed CLA will be enforced by an -automatic check once you submit a PR, but you can also sign it after opening your PR. +Review the project [license](LICENSE) and sign the +[CNCF CLA](https://identity.linuxfoundation.org/projects/cncf). A signed CLA will be enforced by +an automatic check once you submit a PR, but you can also sign it after opening your PR. -## Requirements +## Build and coding patterns -Java 21 or higher is required to build the projects in this repository. The built artifacts can be -used on Java 8 or higher. +See [docs/knowledge/README.md](docs/knowledge/README.md) for coding patterns, build conventions, +testing guidance, and API stability rules. Build requirements and common commands are in +[docs/knowledge/build.md](docs/knowledge/build.md). -## Building opentelemetry-java - -Continuous integration builds the project, runs the tests, and runs multiple types of static -analysis. - -1. Note: Currently, to run the full suite of tests, you'll need to be running a docker daemon. The - tests that require docker are disabled if docker is not present. If you wish to run them, you - must run a local docker daemon. - -2. Clone the repository - - `git clone https://github.com/open-telemetry/opentelemetry-java.git` - -3. Run the following commands to build, run tests and most static analysis, and check formatting: - - `./gradlew build` - -4. If you are a Windows user, use the alternate command mentioned below to run tests and check - formatting: - - `gradlew.bat` - -## Checks - -Before submitting a PR, you should make sure the style checks and unit tests pass. You can run these -with the `check` task. - -```bash -$ ./gradlew check -``` - -Note: this gradle task will potentially generate changes to files in -the `docs/apidiffs/current_vs_latest` -directory. Please make sure to include any changes to these files in your pull request (i.e. -add those files to your commits in the PR). - -## PR Review +## PR review After you submit a PR, it will be reviewed by the project maintainers and approvers. Not all maintainers need to review a particular PR, but merging to the base branch is authorized to restricted members (administrators). +Contributors without an official project role are encouraged to review PRs. This is a +useful signal for maintainers and is a requirement if you are interested in joining the project +in an official capacity. + ### Draft PRs Draft PRs are welcome, especially when exploring new ideas or experimenting with a hypothesis. @@ -69,214 +36,29 @@ requested directly. In order to help keep the PR backlog maintainable, drafts ol will be closed by the project maintainers. This should not be interpreted as a rejection. Closed PRs may be reopened by the author when time or interest allows. -## Project Scope +## Project scope -`opentelemetry-java` is one of several repositories which comprise the OpenTelemetry Java ecosystem, -and contains the core components upon which instrumentation and extensions are built. In order to -prevent sprawl and maintain a high level of quality, we limit this repository's scope to components -which implement concepts defined in -the [opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification), -with a few exceptions / comments: +`opentelemetry-java` contains the core components upon which instrumentation and extensions are +built. Its scope is limited to components which implement concepts defined in the +[OpenTelemetry Specification](https://github.com/open-telemetry/opentelemetry-specification). New +features or behavior changes should follow the specification — if the specification doesn't cover +your change, file an issue or submit a PR there first. Exceptions to strict spec alignment include: -* The [API incubator](./api/incubator) and [SDK incubator](./sdk-extensions/incubator) - contain prototypes which have been discussed in the specification - or [oteps](https://github.com/open-telemetry/oteps) and have a reasonable chance of becoming part - of the specification, subject to maintainers' discretion. -* Components like the [Kotlin Extension](./extensions/kotlin) are included which are required for - the API / SDK to function in key areas of the Java ecosystem. Inclusion is subject to maintainers' - discretion. +* The [API incubator](./api/incubator) and [SDK incubator](./sdk-extensions/incubator) contain + prototypes discussed in the specification or + [oteps](https://github.com/open-telemetry/oteps) with a reasonable chance of becoming part of + the specification, subject to maintainers' discretion. +* Components like the [Kotlin Extension](./extensions/kotlin) are included when required for the + API / SDK to function in key areas of the Java ecosystem, subject to maintainers' discretion. * As a general rule, components which implement semantic conventions belong elsewhere. -Other repositories in the OpenTelemetry Java ecosystem include: - -* [opentelemetry-java-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation) - contains instrumentation. -* [opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) - contains extensions, prototypes, and instrumentation, including vendor specific components. -* [opentelemetry-java-examples](https://github.com/open-telemetry/opentelemetry-java-examples) contains - working code snippets demonstrating various concepts. - -## Style guideline - -We follow the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Our -build will fail if source code is not formatted according to that style. To fix any style failures -the above [checks](#checks) show, automatically apply the formatting with: - -```bash -$ ./gradlew spotlessApply -``` - -To verify code style manually run the following command, which -uses [google-java-format](https://github.com/google/google-java-format) library: - -`./gradlew spotlessCheck` - -### Best practices that we follow - -* This project uses [semantic versioning](https://semver.org/). Except for major versions, a user - should be able to update their dependency version on this project and have nothing break. This - means we do not make breaking changes to the API (e.g., remove a public method) or to the ABI ( - e.g., change return type from void to non-void). -* Avoid exposing publicly any class/method/variable that don't need to be public. -* By default, all arguments/members are treated as non-null. Every argument/member that can - be `null` must be annotated with `@Nullable`. -* The project aims to provide a consistent experience across all the public APIs. It is important to - ensure consistency (same look and feel) across different public packages. -* Use `final` for public classes everywhere it is possible, this ensures that these classes cannot - be extended when the API does not intend to offer that functionality. -* In general, we use the following ordering of class members: - * Static fields (final before non-final) - * Instance fields (final before non-final) - * Constructors - * In static utility classes (where all members are static), the private constructor - (used to prevent construction) should be ordered after methods instead of before methods. - * Methods - * If methods call each other, it's nice if the calling method is ordered (somewhere) above the - method that it calls. So, for one example, a private method would be ordered (somewhere) below - the non-private methods that use it. - * Nested classes -* Adding `toString()` overrides on classes is encouraged, but we only use `toString()` to provide - debugging assistance. The implementations of all `toString()` methods should be considered to be - unstable unless explicitly documented otherwise. -* Avoid synchronizing using a class's intrinsic lock. Instead, synchronize on a dedicated lock object. E.g: - ```java - private final Object lock = new Object(); - - public void doSomething() { - synchronized (lock) { ... } - } - ``` -* Don't - use [gradle test fixtures](https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures) ( - i.e. `java-test-fixtures` plugin) to reuse code for internal testing. The test fixtures plugin has - side effects where test dependencies are added to the `pom.xml` and publishes an - extra `*-test-fixtures.jar` artifact which is unnecessary for internal testing. Instead, create a - new `*:testing-internal` module and omit the `otel.java-conventions`. For example, - see [/exporters/otlp/testing-internal](./exporters/otlp/testing-internal). - -If you notice any practice being applied in the project consistently that isn't listed here, please -consider a pull request to add it. - -### Pre-commit hook - -To completely delegate code style formatting to the machine, you can -add [git pre-commit hook](https://git-scm.com/docs/githooks). We provide an example script -in `buildscripts/pre-commit` file. Just copy or symlink it into `.git/hooks` folder. - -### Editorconfig - -As additional convenience for IntelliJ Idea users, we provide `.editorconfig` file. Idea will -automatically use it to adjust its code formatting settings. It does not support all required rules, -so you still have to run `spotlessApply` from time to time. - -### Javadoc - -* All public classes and their public and protected methods MUST have javadoc. It MUST be complete ( - all params documented etc.) Everything else - (package-protected classes, private) MAY have javadoc, at the code writer's whim. It does not have - to be complete, and reviewers are not allowed to require or disallow it. -* Each API element should have a `@since` tag specifying the minor version when it was released (or - the next minor version). -* There MUST be NO javadoc errors. - -See [section 7.3.1](https://google.github.io/styleguide/javaguide.html#s7.3.1-javadoc-exception-self-explanatory) -in the guide for exceptions to the Javadoc requirement. - -* Reviewers may request documentation for any element that doesn't require Javadoc, though the style - of documentation is up to the author. -* Try to do the least amount of change when modifying existing documentation. Don't change the style - unless you have a good reason. -* We do not use `@author` tags in our javadoc. -* Our javadoc is available via [ - javadoc.io}(https://javadoc.io/doc/io.opentelemetry/opentelemetry-api) - -### SDK Configuration Documentation - -All changes to the SDK configuration options or autoconfigure module should be documented on -[opentelemetry.io](https://opentelemetry.io/docs/languages/java/configuration/). - -### AutoValue - -* Use [AutoValue](https://github.com/google/auto/tree/master/value), when possible, for any new - value classes. Remember to add package-private constructors to all AutoValue classes to prevent - classes in other packages from extending them. - -### Unit Tests - -* Unit tests target Java 8, so language features such as lambda and streams can be used in tests. - -## Specific tasks - -### Updating the Snapshot build number - -The overall version number for opentelemetry-java is determined from git tags, and not fixed in any -file. - -This means it will not update, even if you `git pull` from the repo tip. It will still produce a set -of libraries with the old version number. - -To update it, you must fetch the tags, via `git fetch --all --tags` - which should work, even if you -have forked the repo, as long as the trunk repo is set as an upstream remote. - -### Composing builds - -Beware that this section is only meant for developers of opentelemetry-java, or closely related -projects. The steps described here could change at any time and what you do for one version (commit) -may break with the next one already. - -Gradle provides a feature -called ["composite builds"](https://docs.gradle.org/current/userguide/composite_builds.html) -that allows to replace some normally externally provided dependencies with a project that is built -(included) in the same Gradle invocation. This can be useful to quickly test a new feature or bug -fix you are developing in opentelemetry-java with the examples or the app or instrumentation library -where you need the feature or run into the bug. Unfortunately, opentelemetry-java does not work out -of the box with this feature because Gradle is unable to map the project names to the customized -artifact coordinates (see e.g. [gradle/gradle#18291](https://github.com/gradle/gradle/issues/18291) -and related issues. However, gradle supports manually declaring the mapping between ("substitution -of") -artifact coordinates and project names. To ease this tedious task, opentelemetry-java provides a -gradle task `:generateBuildSubstitutions` that generates a code snippet with these substitutions in -kts (Kotlin Script) format. - -Example usage could be as follows: - -1. Run `./gradlew generateBuildSubstitutions` -2. Two files named `build/substitutions.gradle.kts` are generated in the bom and bom-alpha project's - directory, containing substitutions for the stable and alpha projects respectively. -3. Copy & paste the content of these files to a new `settings.gradle.kts` or the one where you want - to include the opentelemetry build into, so that it contains something like the following: - - ```kotlin - includeBuild("PATH/TO/OPENTELEMETRY-JAVA/ROOT/DIRECTORY") { - // Copy & paste following block from the generated substitutions.gradle.kts, *not* from here! - dependencySubstitution { - substitute(module("io.opentelemetry:opentelemetry-api")).using(project(":api:all")) - substitute(module("io.opentelemetry:opentelemetry-sdk")).using(project(":sdk:all")) - // ... - } - } - ``` - - Please confirm whether the local opentelemetry-java version is consistent with the - opentelemetry-java version declared in the project that relies on opentelemetry-java. - If it is inconsistent, `dependencySubstitution` may not take effect. - - See [the Gradle documentation](https://docs.gradle.org/current/userguide/composite_builds.html#included_build_declaring_substitutions) - for more information. -4. If you now build your project, it will use the included build to supply the opentelemetry-java - artifacts, ignoring any version declarations. Use the prefix `:DIRECTORY:` to refer to - tasks/projects within the included build, where DIRECTORY is the name of the directory in the - included build (only the part after the last `/`). -5. Here are some issues and solutions ([discussions/6551](https://github.com/open-telemetry/opentelemetry-java/discussions/6551)) - you may encounter that may be helpful to you. - -### Updating the OTLP protobufs +For an overview of the other repositories in the OpenTelemetry Java ecosystem, see +[opentelemetry.io/docs/languages/java/intro/#repositories](https://opentelemetry.io/docs/languages/java/intro/#repositories). -OTLP protobuf Java bindings are published via -the [opentelemetry-proto-java](https://github.com/open-telemetry/opentelemetry-proto-java) -repository. This project does not use the java bindings, but does use the `.proto` files that are -published in the binding jar by that project. +## User-facing documentation -To update the OTLP protobuf version, -first [release a new version of the java bindings](https://github.com/open-telemetry/opentelemetry-proto-java/blob/main/RELEASING.md) -then simply update the dependency version that this project has on that jar. +End-user documentation for the Java SDK lives at +[opentelemetry.io/docs/languages/java/](https://opentelemetry.io/docs/languages/java/), with +source in [github.com/open-telemetry/opentelemetry.io](https://github.com/open-telemetry/opentelemetry.io). +If your change affects user-visible behavior — configuration options, new features, changed +defaults — please update or open an issue against the documentation there. diff --git a/docs/jmh.md b/docs/jmh.md deleted file mode 100644 index 6a399072c18..00000000000 --- a/docs/jmh.md +++ /dev/null @@ -1,21 +0,0 @@ - -# how to jmh - -[jmh](https://github.com/openjdk/jmh) is a tool for running benchmarks and reporting results. - -opentelemetry-java has a lot of micro benchmarks. They live inside -`jmh` directories in the appropriate module. - -The benchmarks are run with a gradle plugin. - -To run an entire suite for a module, you can run the jmh gradle task. -As an example, here's how you can run the benchmarks for all of -the sdk trace module. - -``` -`./gradlew :sdk:trace:jmh` -``` - -If you just want to run a single benchmark and not the entire suite: - -`./gradlew -PjmhIncludeSingleClass=BatchSpanProcessorBenchmark :sdk:trace:jmh` diff --git a/docs/knowledge/README.md b/docs/knowledge/README.md new file mode 100644 index 00000000000..0d5cbb0ef4c --- /dev/null +++ b/docs/knowledge/README.md @@ -0,0 +1,28 @@ +# Knowledge Index + +Repository guidance for coding and review, written to be useful for both humans and machines. + +**For humans**: these documents use plain language and examples rather than rigid rules. Where +something is a strong convention, it's described as such. Where something is a matter of +judgment, it's described that way too. + +**For machines**: load only files relevant to the current scope — the "Load when" column below +is the signal. + +## Topics + +| File | Load when | +| --- | --- | +| [build.md](build.md) | Always — build requirements and common tasks | +| [general-patterns.md](general-patterns.md) | Always — style, nullability, visibility, AutoValue, locking, logging | +| [api-stability.md](api-stability.md) | Public API additions, removals, renames, or deprecations; stable vs alpha compatibility | +| [gradle-conventions.md](gradle-conventions.md) | `build.gradle.kts` or `settings.gradle.kts` changes; new modules | +| [testing-patterns.md](testing-patterns.md) | Test files in scope — assertions, test utilities, test suites | +| [other-tasks.md](other-tasks.md) | Dev environment setup, benchmarks, composite builds, OTLP protobuf updates | + +## Conventions + +- File names are kebab-cased and topic-oriented. Most follow a `-.md` pattern + (e.g. `api-stability.md`, `testing-patterns.md`). +- Sections within each document are ordered alphabetically, with the exception of any + introductory content placed directly under the document title. diff --git a/docs/knowledge/api-stability.md b/docs/knowledge/api-stability.md new file mode 100644 index 00000000000..f8c3f8bcd10 --- /dev/null +++ b/docs/knowledge/api-stability.md @@ -0,0 +1,73 @@ +# API Stability and Breaking Changes + +See [VERSIONING.md](../../VERSIONING.md) for the full versioning and compatibility policy. + +## Breaking changes in alpha modules + +Breaking changes are allowed in alpha modules but should still be approached carefully: + +- Prefer going through a deprecation cycle to ease migration, unless the deprecation would be too + cumbersome or the old API is genuinely unusable. +- Bundle breaking changes together where possible to reduce churn for consumers. +- Deprecations in alpha modules are typically introduced and removed within one release cycle + (approximately one month). + +## Breaking changes in stable modules + +Breaking changes are not allowed in stable modules outside of a major version bump. The build +enforces this via japicmp — see [japicmp](#japicmp) below. + +## Deprecating API + +Applies to both stable and alpha modules. Use plain `@Deprecated` — do **not** use +`forRemoval = true` or `since = "..."` (Java 8 compatibility requirement). + +```java +/** + * @deprecated Use {@link #newMethod()} instead. + */ +@Deprecated +public ReturnType oldMethod() { + return newMethod(); // delegate to replacement +} +``` + +Rules: +- Include a `@deprecated` Javadoc tag naming the replacement. +- The deprecated method must delegate to its replacement, not the other way around — this ensures + overriders of the old method still get called. +- Deprecated items in stable modules cannot be removed until the next major version. +- Deprecated items in alpha modules should indicate when they will be removed (e.g. + `// to be removed in 1.X.0`). It is also common to log a warning when a deprecated-for-removal + feature is used, to increase visibility for consumers. +- Add the `deprecation` label to the PR. + +## japicmp + +`otel.japicmp-conventions` runs the `jApiCmp` task as part of `check` for every published module. +It compares the locally-built jar against the latest release to detect breaking changes, and writes +a human-readable diff to `docs/apidiffs/current_vs_latest/.txt`. + +- Breaking changes in stable modules fail the build. +- AutoValue classes are exempt from the abstract-method-added check — the generated implementation + handles those automatically. +- The diff files are committed to the repo. Include any changes to them in your PR. +- Run `./gradlew jApiCmp` to regenerate diffs after API changes. + +## Stable vs alpha modules + +Artifacts without an `-alpha` version suffix are **stable**. Artifacts with `-alpha` have no +compatibility guarantees and may break on every release. The `internal` package is always exempt +from compatibility guarantees regardless of artifact stability. + +To mark a module as alpha, add a `gradle.properties` file at the module root containing: + +```properties +otel.release=alpha +``` + +See [`exporters/prometheus/gradle.properties`](../../exporters/prometheus/gradle.properties) for +an example. + +See [VERSIONING.md](../../VERSIONING.md) for the full compatibility policy, including what +constitutes a source-incompatible vs binary-incompatible change. diff --git a/docs/knowledge/build.md b/docs/knowledge/build.md new file mode 100644 index 00000000000..767b5041fa9 --- /dev/null +++ b/docs/knowledge/build.md @@ -0,0 +1,36 @@ +# Build + +Java 21 or higher is required to build. The built artifacts target Java 8 or higher. + +## Common tasks + +```bash +# Fix formatting violations +./gradlew spotlessApply + +# Run checks only (tests + static analysis + formatting verification) +./gradlew check + +# Build, run tests, static analysis, and check formatting +./gradlew build +``` + +All tasks can be scoped to a single module by prefixing with the module path: + +```bash +./gradlew :sdk:metrics:spotlessApply +./gradlew :sdk:metrics:check +./gradlew :sdk:metrics:build +``` + +`./gradlew build` and `./gradlew check` both depend on the `jApiCmp` task, which compares the +locally-built jars against the latest release and writes diffs to `docs/apidiffs/current_vs_latest/`. +Include any changes to those files in your PR. See [api-stability.md](api-stability.md#japicmp) +for details. + +If your branch is not up to date with `main`, `jApiCmp` may produce a diff that reflects changes +already merged to `main` rather than your own changes. Rebase or merge `main` before treating +the diff as meaningful. + +For dev environment setup, composite builds, OTLP protobuf updates, and other tasks, see +[other-tasks.md](other-tasks.md). diff --git a/docs/knowledge/general-patterns.md b/docs/knowledge/general-patterns.md new file mode 100644 index 00000000000..92f6ff0688f --- /dev/null +++ b/docs/knowledge/general-patterns.md @@ -0,0 +1,135 @@ +# General Patterns + +## @Nullable + +All arguments and members are treated as non-null by default. Annotate with `@Nullable` (from +`javax.annotation`) only when `null` is actually possible. + +- **Fields**: annotate only if the field can hold `null` after construction. +- **Parameters**: annotate only if `null` is actually passed by callers. +- **Return types**: annotate only if the method actually returns `null`. A non-null implementation + of a `@Nullable`-declared interface method should omit the annotation — it is more precise. + +## API consistency + +The project aims to provide a consistent experience across all public APIs. When designing new +API, look for prior art in the project before introducing new patterns — prefer the style already +established in the same package, then the same module, then the broader project. + +## AutoValue + +Use [AutoValue](https://github.com/google/auto/tree/master/value) for new value classes. + +- Add `annotationProcessor("com.google.auto.value:auto-value")` in `build.gradle.kts`. +- `auto-value-annotations` is already available via `otel.java-conventions`. +- Add a package-private constructor to all AutoValue classes to prevent external extension. +- `japicmp` allows new abstract methods on AutoValue classes — the `AllowNewAbstractMethodOnAutovalueClasses` rule handles this. + +## Class member ordering + +In general, order class members as follows: + +1. Static fields (final before non-final) +2. Instance fields (final before non-final) +3. Constructors + - In static utility classes, the private constructor goes after methods, not before. +4. Methods + - If methods call each other, the calling method should appear above the method it calls. +5. Nested classes + +## Dedicated lock objects + +Do not synchronize using a class's intrinsic lock (`synchronized(this)` or `synchronized` method). +Use a dedicated lock object: + +```java +private final Object lock = new Object(); + +public void doSomething() { + synchronized (lock) { ... } +} +``` + +## Formatting + +Formatting is enforced by three tools. Run `./gradlew spotlessApply` before committing — it fixes +most violations automatically. + +- **Spotless** — formatting rules vary by file type (see `buildSrc/src/main/kotlin/otel.spotless-conventions.gradle.kts`): + Java uses google-java-format; Kotlin uses ktlint. Also enforces the Apache license header + (template in `buildscripts/spotless.license.java`) and misc file hygiene (trailing whitespace, + final newline, etc.). +- **Checkstyle** — enforces naming conventions, import ordering, Javadoc structure, and other + rules not covered by formatting alone. Config is in `buildscripts/checkstyle.xml`. +- **EditorConfig** (`.editorconfig`) — configures IntelliJ to match project style automatically. + It doesn't cover all rules, so `spotlessApply` is still required. + +## Internal code + +Prefer package-private over putting code in an `internal` package. Use `internal` only when the +code must be `public` for technical reasons (e.g. accessed across packages within the same module) +but should not be part of the public API. + +Public classes in `internal` packages are excluded from semver guarantees and Javadoc, but they +must carry one of the two standard disclaimers (enforced by the `OtelInternalJavadoc` Error Prone +check in `custom-checks/`): + +```java +// Standard internal disclaimer: +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ + +// For incubating internal code that may be promoted to public API: +/** + * This class is internal and experimental. Its APIs are unstable and can change at any time. + * Its APIs (or a version of them) may be promoted to the public stable API in the future, but + * no guarantees are made. + */ +``` + +Internal code must not be used across module boundaries — module `foo` must not call internal +code from module `bar`. Cross-module internal usage is a known issue being tracked and cleaned up +in open-telemetry/opentelemetry-java#6970. + +## Javadoc + +All public classes, and their public and protected methods, must have complete Javadoc including +all parameters — this is enforced for public APIs. Package-private and private members may have +Javadoc at the author's discretion. + +- No `@author` tags. +- New public API elements require a `@since` annotation. This is added automatically during the + [release process](../../RELEASING.md) — do not include it in your PR. +- See [section 7.3.1](https://google.github.io/styleguide/javaguide.html#s7.3.1-javadoc-exception-self-explanatory) + for self-explanatory exceptions. + +Published Javadoc is available at https://javadoc.io/doc/io.opentelemetry. + +## Language version compatibility + +Production code targets Java 8. Test code also targets Java 8. Do not use Java 9+ APIs unless +the module explicitly sets a higher minimum. See [VERSIONING.md](../../VERSIONING.md) for the full +language version compatibility policy, including Android and Kotlin minimum versions. + +## Logging + +Use `java.util.logging` (JUL) in production source sets. Do not use SLF4J or other logging +frameworks in `src/main/`. Tests bridge JUL to SLF4J via `JulBridgeInitializer` (configured +automatically by `otel.java-conventions`). + +```java +private static final Logger LOGGER = Logger.getLogger(MyClass.class.getName()); +``` + +## toString() + +Adding `toString()` overrides is encouraged for debugging assistance. All `toString()` +implementations should be considered unstable unless explicitly documented otherwise — do not +rely on their output programmatically. + +## Visibility + +Use the most restrictive access modifier that still allows the code to function correctly. Use +`final` on classes unless extension is explicitly intended. diff --git a/docs/knowledge/gradle-conventions.md b/docs/knowledge/gradle-conventions.md new file mode 100644 index 00000000000..cb0a86d19ec --- /dev/null +++ b/docs/knowledge/gradle-conventions.md @@ -0,0 +1,78 @@ +# Gradle Conventions + +## AutoValue in build files + +See [general-patterns.md](general-patterns.md#autovalue) for the AutoValue usage pattern. +`auto-value-annotations` is provided globally by `otel.java-conventions`. Only add the +annotation processor if the module uses AutoValue in production code: + +```kotlin +dependencies { + annotationProcessor("com.google.auto.value:auto-value") // production + testAnnotationProcessor("com.google.auto.value:auto-value") // tests only +} +``` + +## Convention plugins + +Every module applies a base set of convention plugins from `buildSrc/src/main/kotlin/`. + +| Plugin | Purpose | Who applies it | +| --- | --- | --- | +| `otel.java-conventions` | Base Java toolchain, Checkstyle, Spotless, Error Prone, test config | All modules | +| `otel.publish-conventions` | Maven publishing, POM generation | Published (non-internal) modules | +| `otel.animalsniffer-conventions` | Android API level compatibility checking | Modules targeting Android | +| `otel.jmh-conventions` | JMH benchmark support | Modules with benchmarks | +| `otel.japicmp-conventions` | API diff generation against latest release | Published modules (applied by `otel.publish-conventions`) | +| `otel.protobuf-conventions` | Protobuf code generation | Protobuf modules only | + +A typical published module: + +```kotlin +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") +} +``` + +A testing-internal module (shared test utilities, not published): + +```kotlin +plugins { + id("otel.java-conventions") + // no otel.publish-conventions +} +``` + +## Dependency resolution + +All external dependency versions are managed by `:dependencyManagement` (a BOM). Do not +specify versions directly in `build.gradle.kts` — add new entries to the BOM if a version +needs to be pinned. + +`otel.java-conventions` configures `failOnVersionConflict()` and `preferProjectModules()` +globally. Do not override these without a strong reason. + +## Module naming + +Every module must declare its Java module name: + +```kotlin +otelJava.moduleName.set("io.opentelemetry.sdk.metrics") +``` + +When the archive name cannot be derived correctly from the project name, set it explicitly: + +```kotlin +base.archivesName.set("opentelemetry-api") +``` + +## `settings.gradle.kts` ordering + +New `include(...)` entries must be in **alphabetical order** within their surrounding group. The +file is large — find the right lexicographic position before inserting. + +## Shared test utilities and test suites + +See [testing-patterns.md](testing-patterns.md) for shared test utility patterns and test suite +registration. diff --git a/docs/knowledge/other-tasks.md b/docs/knowledge/other-tasks.md new file mode 100644 index 00000000000..7a5b540120d --- /dev/null +++ b/docs/knowledge/other-tasks.md @@ -0,0 +1,85 @@ +# Other Tasks + +## Benchmarks (JMH) + +Microbenchmarks use [JMH](https://github.com/openjdk/jmh) via the `otel.jmh-conventions` plugin. +Benchmark sources live in `jmh/` directories within their respective modules. + +```bash +# Run all benchmarks for a module +./gradlew :sdk:trace:jmh + +# Run a single benchmark class +./gradlew -PjmhIncludeSingleClass=BatchSpanProcessorBenchmark :sdk:trace:jmh +``` + +## Composite builds + +> **No compatibility guarantees**: this process can change at any time. Steps that work for one +> commit may break with the next. + +To test local changes to this repo against another project (e.g. instrumentation or an app), +use Gradle [composite builds](https://docs.gradle.org/current/userguide/composite_builds.html). + +This repo does not work with composite builds out of the box because Gradle cannot automatically +map project names to the customized Maven artifact coordinates (see +[gradle/gradle#18291](https://github.com/gradle/gradle/issues/18291)). A helper task generates +the required substitution mappings. + +1. Run `./gradlew generateBuildSubstitutions` — generates `build/substitutions.gradle.kts` in + the `bom/` and `bom-alpha/` directories, containing substitutions for stable and alpha + projects respectively. +2. In the consuming project's `settings.gradle.kts`, add an `includeBuild` block and paste in + the generated `dependencySubstitution` content: + + ```kotlin + includeBuild("PATH/TO/OPENTELEMETRY-JAVA/ROOT/DIRECTORY") { + // paste from generated substitutions.gradle.kts + dependencySubstitution { + substitute(module("io.opentelemetry:opentelemetry-api")).using(project(":api:all")) + substitute(module("io.opentelemetry:opentelemetry-sdk")).using(project(":sdk:all")) + // ... + } + } + ``` + +3. Confirm the local version aligns with the version declared in the consuming project — + if they differ, `dependencySubstitution` may not take effect. +4. Use the prefix `:DIRECTORY:` to invoke tasks within the included build, where `DIRECTORY` + is the last path component of the included build's root directory. + +See [discussions/6551](https://github.com/open-telemetry/opentelemetry-java/discussions/6551) for +known issues and solutions. + +## Dev environment setup + +### EditorConfig + +An `.editorconfig` file is provided and automatically applied by IntelliJ to match project style. +It doesn't cover all formatting rules — `./gradlew spotlessApply` is still required — but it +reduces friction for common cases. + +### Pre-commit hook + +A pre-commit hook that runs `spotlessApply` automatically is provided at `buildscripts/pre-commit`. +Copy or symlink it into `.git/hooks/` to delegate formatting to the machine: + +```bash +cp buildscripts/pre-commit .git/hooks/pre-commit +# or +ln -s ../../buildscripts/pre-commit .git/hooks/pre-commit +``` + +## Updating OTLP protobufs + +OTLP protobuf Java bindings are published via +[opentelemetry-proto-java](https://github.com/open-telemetry/opentelemetry-proto-java). This +project uses the `.proto` files published in that binding jar, not the generated Java bindings +themselves. + +When a new version of the upstream proto definitions is released, the process is: + +1. [Release a new version of the java bindings](https://github.com/open-telemetry/opentelemetry-proto-java/blob/main/RELEASING.md) + in the `opentelemetry-proto-java` repo (which we maintain). +2. Renovate will automatically open a PR to update `io.opentelemetry.proto:opentelemetry-proto` + in this repo once the new version is published. diff --git a/docs/knowledge/testing-patterns.md b/docs/knowledge/testing-patterns.md new file mode 100644 index 00000000000..905ccde7315 --- /dev/null +++ b/docs/knowledge/testing-patterns.md @@ -0,0 +1,134 @@ +# Testing Patterns + +## Assertion library + +Use [AssertJ](https://assertj.github.io/doc/) for all assertions. + +```java +import static org.assertj.core.api.Assertions.assertThat; + +assertThat(result).isEqualTo(expected); +assertThat(list).hasSize(3).contains("foo"); +assertThatThrownBy(() -> doThing()).isInstanceOf(IllegalArgumentException.class); +``` + +For SDK signal data (spans, metrics, logs), use the OTel-specific assertj extensions from +`sdk:testing`: + +```java +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; +``` + +These provide fluent assertions on `SpanData`, `MetricData`, `LogRecordData`, etc. + +## Clocks and time + +Avoid `System.currentTimeMillis()` or `Instant.now()` in tests. Use `TestClock` from +`sdk:testing` for deterministic time control: + +```java +TestClock clock = TestClock.create(); +clock.advance(Duration.ofSeconds(1)); +``` + +## Equals and hashCode + +Use [EqualsVerifier](https://jqno.nl/equalsverifier/) to verify `equals`/`hashCode` contracts: + +```java +EqualsVerifier.forClass(MyValueClass.class).verify(); +``` + +Available in all test suites via `otel.java-conventions`. + +## Log capture + +Use [LogUnit](https://github.com/netmikey/logunit) to assert on log output: + +```java +@RegisterExtension +LogCapturer logs = LogCapturer.create().captureForType(MyClass.class); + +@Test +void logsWarning() { + // trigger code under test + logs.assertContains("expected warning text"); +} +``` + +`logunit-jul` is included in all test suites via `otel.java-conventions`. + +## Mocking + +Mockito is available in all test suites via `otel.java-conventions`. Use +`@ExtendWith(MockitoExtension.class)` with `@Mock` fields, or `Mockito.mock(...)` inline. + +```java +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class MyTest { + @Mock MetricReader reader; +} +``` + +The byte-buddy agent is pre-attached by `otel.java-conventions` to suppress dynamic-agent +warnings on Java 21+. No extra configuration needed. + +## Shared test utilities + +Do **not** use the `java-test-fixtures` plugin. It adds test dependencies to published POMs and +creates a separate `*-test-fixtures.jar` artifact. Shared test utilities belong in a +`*:testing-internal` module. + +The root `:testing-internal` module (`/testing-internal/`) contains utilities available across +all modules. It is included automatically by `otel.java-conventions` as a test dependency. + +For module-group-specific utilities, create a sibling `testing-internal` module: + +``` +sdk/ + metrics/ + src/test/java/... ← module-specific tests + testing/ ← published SDK testing utilities (sdk:testing) + src/main/java/... +``` + +`sdk:testing` is published and intended for use by external consumers testing OTel integrations. +Keep it stable — treat it as public API. + +## Test framework + +JUnit 5 (Jupiter). `otel.java-conventions` configures `useJUnitJupiter()` for all test suites. + +- Use `@Test`, `@BeforeEach`, `@AfterEach`, `@ParameterizedTest` from `org.junit.jupiter.api`. +- Use `@RegisterExtension` for JUnit 5 extensions (not `@Rule`). +- `@ExtendWith(MockitoExtension.class)` for Mockito injection. + +## Test suites + +This repo uses Gradle's `JvmTestSuite` API (not manual `Test` task registration). + +```kotlin +testing { + suites { + register("testIncubating") { + dependencies { + implementation(project(":api:incubator")) + } + } + } +} + +tasks { + check { + dependsOn(testing.suites) // wires all suites into check + } +} +``` + +Rules: +- All registered test suites must be wired into `check` — omitting this causes them to be + silently skipped in CI. +- Each suite has its own source set under `src//java/`. +- Suite-specific JVM args go in a `targets { all { testTask.configure { ... } } }` block.