From a815f2780142fbf04b2bbe3b6e96d21647ff64bd Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 19 Sep 2025 16:39:06 +0200 Subject: [PATCH 01/45] Add files via upload Signed-off-by: Hilmar Falkenberg --- .github/workflows/sync-fork.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/sync-fork.yaml diff --git a/.github/workflows/sync-fork.yaml b/.github/workflows/sync-fork.yaml new file mode 100644 index 00000000000..de520f2bc0e --- /dev/null +++ b/.github/workflows/sync-fork.yaml @@ -0,0 +1,16 @@ +name: sync-fork +on: + schedule: + - cron: '4 2 * * 4,2' + workflow_dispatch: +jobs: + sync-fork: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - run: gh repo sync $REPOSITORY -b $BRANCH_NAME + env: + GITHUB_TOKEN: ${{ secrets.SYNC_FORK_TOKEN }} + REPOSITORY: ${{ github.repository }} + BRANCH_NAME: ${{ github.ref_name }} From c98438f83ddb64c7f048d300e418be643d3b640f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 28 Apr 2026 11:27:54 +0200 Subject: [PATCH 02/45] REUSE status Signed-off-by: Hilmar Falkenberg --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b4d88aa43ea..0485c9013db 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![FOSSA Security Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-java.svg?type=shield&issueType=security)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-java?ref=badge_shield&issueType=security) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/open-telemetry/opentelemetry-java/badge)](https://scorecard.dev/viewer/?uri=github.com/open-telemetry/opentelemetry-java) [![Reproducible Builds](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/io/opentelemetry/java/badge.json)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/io/opentelemetry/java/README.md) +[![REUSE status](https://api.reuse.software/badge/github.com/apeirora/opentelemetry-java)](https://api.reuse.software/info/github.com/apeirora/opentelemetry-java) `opentelemetry-java` is the home of the Java implementation of the OpenTelemetry API for recording telemetry, and SDK for managing telemetry recorded by the API. From 262b0138ea1d5dff159143d5062272f34dadf005 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 28 Apr 2026 11:37:29 +0200 Subject: [PATCH 03/45] Ensure GHA work properly on fork or are disabled! Signed-off-by: Hilmar Falkenberg --- .github/workflows/backport.yml | 1 + .github/workflows/benchmark-tags.yml | 1 + .github/workflows/build-daily.yml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index a28f8898c4b..dd7d8f4954e 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -11,6 +11,7 @@ permissions: jobs: backport: + if: github.repository == 'open-telemetry/opentelemetry-java' permissions: contents: write # for git push to PR branch runs-on: ubuntu-latest diff --git a/.github/workflows/benchmark-tags.yml b/.github/workflows/benchmark-tags.yml index fd029f3caf7..95b44af6c65 100644 --- a/.github/workflows/benchmark-tags.yml +++ b/.github/workflows/benchmark-tags.yml @@ -8,6 +8,7 @@ permissions: jobs: sdk-benchmark: + if: github.repository == 'open-telemetry/opentelemetry-java' permissions: contents: write # for git push to benchmarks branch name: Benchmark SDK diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index 8bc79e530fb..ec56fab0d91 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -53,5 +53,5 @@ jobs: success: >- ${{ needs.link-check.result == 'success' && - needs.publish-snapshots.result == 'success' + (needs.publish-snapshots.result == 'success' || needs.publish-snapshots.result == 'skipped') }} From 862a5f2cf7d00361b8573aecbee142f307ef1a55 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 19 May 2026 11:09:13 +0200 Subject: [PATCH 04/45] reuse download --all Signed-off-by: Hilmar Falkenberg --- LICENSES/Apache-2.0.txt | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 LICENSES/Apache-2.0.txt diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 00000000000..137069b8238 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. From d06ec75e682a2b63e9679b8a4fb9b5863086f946 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 19 May 2026 18:04:59 +0200 Subject: [PATCH 05/45] =?UTF-8?q?Add=20REUSE=20compliance:=20LICENSES/,=20?= =?UTF-8?q?.reuse/dep5,=20and=20fix=20invalid=20SPDX=20ex=E2=80=A6=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add REUSE compliance: LICENSES/, .reuse/dep5, and fix invalid SPDX expressions - Add LICENSES/Apache-2.0.txt (copy of LICENSE) as required by REUSE spec - Add .reuse/dep5 to provide copyright/license info for files without inline headers - Fix invalid SPDX expressions in OtelImplJavadocTest and OtelInternalJavadocTest by wrapping embedded SPDX-License-Identifier strings in REUSE-IgnoreStart/End comments to prevent the REUSE linter from treating test fixture content as actual license declarations * reuse convert-dep5 Signed-off-by: Hilmar Falkenberg --------- Signed-off-by: Hilmar Falkenberg --- REUSE.toml | 16 ++++++++++++++++ .../gradle/customchecks/OtelImplJavadocTest.java | 6 ++++++ .../customchecks/OtelInternalJavadocTest.java | 4 ++++ 3 files changed, 26 insertions(+) create mode 100644 REUSE.toml diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 00000000000..6cad12eb65c --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,16 @@ +version = 1 +SPDX-PackageName = "OpenTelemetry Java" +SPDX-PackageSupplier = "opentelemetry-java@googlegroups.com" +SPDX-PackageDownloadLocation = "https://github.com/open-telemetry/opentelemetry-java" + +[[annotations]] +path = "gradle/wrapper/gradle-wrapper.jar" +precedence = "aggregate" +SPDX-FileCopyrightText = "Copyright Gradle, Inc." +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = "**" +precedence = "aggregate" +SPDX-FileCopyrightText = "Copyright The OpenTelemetry Authors" +SPDX-License-Identifier = "Apache-2.0" diff --git a/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelImplJavadocTest.java b/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelImplJavadocTest.java index ad294d61b2c..9b9126a1506 100644 --- a/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelImplJavadocTest.java +++ b/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelImplJavadocTest.java @@ -15,10 +15,12 @@ void positiveCases() { CompilationTestHelper.newInstance(OtelImplJavadoc.class, OtelImplJavadocTest.class) .addSourceLines( "impl/ImplJavadocPositiveCases.java", + // REUSE-IgnoreStart "/*", " * Copyright The OpenTelemetry Authors", " * SPDX-License-Identifier: Apache-2.0", " */", + // REUSE-IgnoreEnd "package io.opentelemetry.gradle.customchecks.impl;", "// BUG: Diagnostic contains: missing the required javadoc disclaimer", "public class ImplJavadocPositiveCases {", @@ -36,10 +38,12 @@ void negativeCases() { CompilationTestHelper.newInstance(OtelImplJavadoc.class, OtelImplJavadocTest.class) .addSourceLines( "impl/ImplJavadocNegativeCases.java", + // REUSE-IgnoreStart "/*", " * Copyright The OpenTelemetry Authors", " * SPDX-License-Identifier: Apache-2.0", " */", + // REUSE-IgnoreEnd "package io.opentelemetry.gradle.customchecks.impl;", "/**", " * This class is not intended for use by application developers. Its API is stable and", @@ -62,10 +66,12 @@ void nonImplPackageIgnored() { CompilationTestHelper.newInstance(OtelImplJavadoc.class, OtelImplJavadocTest.class) .addSourceLines( "other/NonImplPackageCases.java", + // REUSE-IgnoreStart "/*", " * Copyright The OpenTelemetry Authors", " * SPDX-License-Identifier: Apache-2.0", " */", + // REUSE-IgnoreEnd "package io.opentelemetry.gradle.customchecks.other;", "public class NonImplPackageCases {", " public static class One {}", diff --git a/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelInternalJavadocTest.java b/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelInternalJavadocTest.java index 34fbc46b6b6..777b4dd8c06 100644 --- a/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelInternalJavadocTest.java +++ b/custom-checks/src/test/java/io/opentelemetry/gradle/customchecks/OtelInternalJavadocTest.java @@ -15,10 +15,12 @@ void positiveCases() { CompilationTestHelper.newInstance(OtelInternalJavadoc.class, OtelInternalJavadocTest.class) .addSourceLines( "internal/InternalJavadocPositiveCases.java", + // REUSE-IgnoreStart "/*", " * Copyright The OpenTelemetry Authors", " * SPDX-License-Identifier: Apache-2.0", " */", + // REUSE-IgnoreEnd "package io.opentelemetry.gradle.customchecks.internal;", "// BUG: Diagnostic contains: doesn't end with any of the applicable javadoc disclaimers", "public class InternalJavadocPositiveCases {", @@ -36,10 +38,12 @@ void negativeCases() { CompilationTestHelper.newInstance(OtelInternalJavadoc.class, OtelInternalJavadocTest.class) .addSourceLines( "internal/InternalJavadocNegativeCases.java", + // REUSE-IgnoreStart "/*", " * Copyright The OpenTelemetry Authors", " * SPDX-License-Identifier: Apache-2.0", " */", + // REUSE-IgnoreEnd "package io.opentelemetry.gradle.customchecks.internal;", "/**", " * This class is internal and is hence not for public use. Its APIs are unstable and can change at", From 4b0cbb94a9b4511cb98c2f42eeb36139eabf55d2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 4 Jun 2026 10:40:37 +0200 Subject: [PATCH 06/45] using APEIRORA_BOT Signed-off-by: Hilmar Falkenberg --- .github/workflows/detect-api-changes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/detect-api-changes.yml b/.github/workflows/detect-api-changes.yml index e1ff5ddf1a2..bb26fdf11be 100644 --- a/.github/workflows/detect-api-changes.yml +++ b/.github/workflows/detect-api-changes.yml @@ -21,8 +21,8 @@ jobs: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: otelbot-token with: - app-id: ${{ vars.OTELBOT_APP_ID }} - private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} + app-id: ${{ vars.APEIRORA_BOT_APP_ID }} + private-key: ${{ secrets.APEIRORA_BOT_PRIVATE_KEY }} - name: Check for API changes and update PR env: From a4d20d14e7e601ead1ab1787fb1118c180d74ecc Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 9 Jun 2026 15:53:52 +0200 Subject: [PATCH 07/45] revert to upstream Signed-off-by: Hilmar Falkenberg --- .github/workflows/detect-api-changes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/detect-api-changes.yml b/.github/workflows/detect-api-changes.yml index bb26fdf11be..e1ff5ddf1a2 100644 --- a/.github/workflows/detect-api-changes.yml +++ b/.github/workflows/detect-api-changes.yml @@ -21,8 +21,8 @@ jobs: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: otelbot-token with: - app-id: ${{ vars.APEIRORA_BOT_APP_ID }} - private-key: ${{ secrets.APEIRORA_BOT_PRIVATE_KEY }} + app-id: ${{ vars.OTELBOT_APP_ID }} + private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - name: Check for API changes and update PR env: From 320259461feccbff9f8ef901b8118cb2a71de7ae Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 14:47:59 +0200 Subject: [PATCH 08/45] add .dockerignore Signed-off-by: Hilmar Falkenberg --- .dockerignore | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..5c250631c59 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +# Gradle-Build +**/build/ +**/.gradle/ +.gradle/ + +# cached Gradle-Wrapper +gradle/wrapper/gradle-wrapper.jar.sha256 + +# IDE-Files +.idea/ +.vscode/ +*.iml +.classpath +.project +.settings/ + +# OS artefacts +.DS_Store +Thumbs.db + +# DO NOT ignore: +# .git/ → required by jApiCmp for Versions-Checks +# docs/apidiffs/ → required by jApiCmp-Diff-Check +# gradlew → required by Build From 964c5b954d571d0345e55e249fd8538b4c8a8538 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 12:12:25 +0200 Subject: [PATCH 09/45] group = "eu.apeirora.opentelemetry" Signed-off-by: Hilmar Falkenberg --- api/build.gradle.kts | 2 +- bom-alpha/build.gradle.kts | 2 +- bom/build.gradle.kts | 2 +- build.gradle.kts | 4 ++-- buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts | 2 +- exporters/build.gradle.kts | 2 +- exporters/otlp/build.gradle.kts | 2 +- exporters/sender/build.gradle.kts | 2 +- extensions/build.gradle.kts | 2 +- sdk-extensions/build.gradle.kts | 2 +- sdk/build.gradle.kts | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index facf4cf9506..6a2df27ce7e 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // Workaround https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.api" + group = "eu.apeirora.opentelemetry.api" val proj = this plugins.withId("java") { configure { diff --git a/bom-alpha/build.gradle.kts b/bom-alpha/build.gradle.kts index df363b28fd1..69017589d79 100644 --- a/bom-alpha/build.gradle.kts +++ b/bom-alpha/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } description = "OpenTelemetry Bill of Materials (Alpha)" -group = "io.opentelemetry" +group = "eu.apeirora.opentelemetry" base.archivesName.set("opentelemetry-bom-alpha") otelBom.projectFilter.set { it.findProperty("otel.release") == "alpha" } diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts index 50d8dc950a1..e92d92eae3f 100644 --- a/bom/build.gradle.kts +++ b/bom/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } description = "OpenTelemetry Bill of Materials" -group = "io.opentelemetry" +group = "eu.apeirora.opentelemetry" base.archivesName.set("opentelemetry-bom") otelBom.projectFilter.set { !it.hasProperty("otel.release") } diff --git a/build.gradle.kts b/build.gradle.kts index b2953d90939..143c7b99897 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { apply(from = "version.gradle.kts") nexusPublishing { - packageGroup.set("io.opentelemetry") + packageGroup.set("eu.apeirora.opentelemetry") repositories { // see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration @@ -39,7 +39,7 @@ tasks.register("generateBuildSubstitutions") { } subprojects { - group = "io.opentelemetry" + group = "eu.apeirora.opentelemetry" } tasks { diff --git a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts index 0a6a100fe15..ef2b6c7f6d0 100644 --- a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts @@ -8,7 +8,7 @@ plugins { publishing { publications { register("mavenPublication") { - groupId = "io.opentelemetry" + groupId = "eu.apeirora.opentelemetry" afterEvaluate { // not available until evaluated. artifactId = base.archivesName.get() diff --git a/exporters/build.gradle.kts b/exporters/build.gradle.kts index 9e409da647e..99f78b274bd 100644 --- a/exporters/build.gradle.kts +++ b/exporters/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.exporters" + group = "eu.apeirora.opentelemetry.exporters" val proj = this plugins.withId("java") { configure { diff --git a/exporters/otlp/build.gradle.kts b/exporters/otlp/build.gradle.kts index 7769556c39f..ce4d16f34a5 100644 --- a/exporters/otlp/build.gradle.kts +++ b/exporters/otlp/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // Workaround https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.exporter.otlp" + group = "eu.apeirora.opentelemetry.exporter.otlp" val proj = this plugins.withId("java") { configure { diff --git a/exporters/sender/build.gradle.kts b/exporters/sender/build.gradle.kts index a212101567e..cd8e4a7460d 100644 --- a/exporters/sender/build.gradle.kts +++ b/exporters/sender/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // Workaround https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.exporter.httpsender" + group = "eu.apeirora.opentelemetry.exporter.httpsender" val proj = this plugins.withId("java") { configure { diff --git a/extensions/build.gradle.kts b/extensions/build.gradle.kts index f0744dfe9ef..0ad8e49d0c7 100644 --- a/extensions/build.gradle.kts +++ b/extensions/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // Workaround https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.extensions" + group = "eu.apeirora.opentelemetry.extensions" val proj = this plugins.withId("java") { configure { diff --git a/sdk-extensions/build.gradle.kts b/sdk-extensions/build.gradle.kts index bcb76a95ae0..9a5be668b6a 100644 --- a/sdk-extensions/build.gradle.kts +++ b/sdk-extensions/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // Workaround https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.sdk.extensions" + group = "eu.apeirora.opentelemetry.sdk.extensions" val proj = this plugins.withId("java") { configure { diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index 72ea1f8e323..6d26bf381dc 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -1,6 +1,6 @@ subprojects { // Workaround https://github.com/gradle/gradle/issues/847 - group = "io.opentelemetry.sdk" + group = "eu.apeirora.opentelemetry.sdk" val proj = this plugins.withId("java") { configure { From 3e22c6405099a1f9d7899541be4682839bbb0329 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 13:03:44 +0200 Subject: [PATCH 10/45] =?UTF-8?q?Remap=20the=20Maven=20group=20to=20the=20?= =?UTF-8?q?Java=20package=20root:=20eu.apeirora.opentelemetry=20=E2=86=92?= =?UTF-8?q?=20io.opentelemetry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hilmar Falkenberg --- .../kotlin/io/opentelemetry/gradle/OtelVersionClassPlugin.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelVersionClassPlugin.kt b/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelVersionClassPlugin.kt index fabcd943840..f5b40158d6a 100644 --- a/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelVersionClassPlugin.kt +++ b/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelVersionClassPlugin.kt @@ -26,8 +26,11 @@ class OtelVersionClassPlugin : Plugin { val outDir = project.layout.buildDirectory.dir("generated/sources/version/java/main") + // Remap the Maven group to the Java package root: eu.apeirora.opentelemetry → io.opentelemetry + val javaGroup = "${project.group}".replace("eu.apeirora.opentelemetry", "io.opentelemetry") + project.tasks.register("generateOtelVersionClass", GenerateOtelVersionClassTask::class.java) { - projectGroup.set("${project.group}") + projectGroup.set(javaGroup) projectName.set(project.name) projectVersion.set("${project.version}") outputDirectory.set(outDir) From 038baf65ae695d94a57304d1472ce6af3f8a8b52 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 15:45:29 +0200 Subject: [PATCH 11/45] build: add GitHub Packages repository to publish conventions Signed-off-by: Hilmar Falkenberg --- .../kotlin/otel.publish-conventions.gradle.kts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts index ef2b6c7f6d0..b15448aab3e 100644 --- a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts @@ -66,6 +66,22 @@ publishing { } } } + + // Publish to GitHub Packages when GITHUB_TOKEN is available (e.g. in CI snapshot workflow). + // Local builds and the Sonatype release workflow are unaffected because they do not set GITHUB_TOKEN. + val githubToken = System.getenv("GITHUB_TOKEN") + if (githubToken != null) { + repositories { + maven { + name = "githubPackages" + url = uri("https://maven.pkg.github.com/apeirora/opentelemetry-java") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: "apeirora-bot" + password = githubToken + } + } + } + } } if (System.getenv("CI") != null) { From a1ce4053018b294c6c25fe27e38edbaa7bac6363 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 15:50:29 +0200 Subject: [PATCH 12/45] build: restrict GitHub Packages publishing to SNAPSHOT versions only Signed-off-by: Hilmar Falkenberg --- .../src/main/kotlin/otel.publish-conventions.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts index b15448aab3e..e219f5afcab 100644 --- a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts @@ -67,10 +67,11 @@ publishing { } } - // Publish to GitHub Packages when GITHUB_TOKEN is available (e.g. in CI snapshot workflow). - // Local builds and the Sonatype release workflow are unaffected because they do not set GITHUB_TOKEN. + // Publish to GitHub Packages only for SNAPSHOT builds. + // Activated when GITHUB_TOKEN is set and the version ends with -SNAPSHOT. + // Local builds and the Sonatype release workflow are unaffected. val githubToken = System.getenv("GITHUB_TOKEN") - if (githubToken != null) { + if (githubToken != null && project.version.toString().endsWith("-SNAPSHOT")) { repositories { maven { name = "githubPackages" From 4a833ab522146646e33e3904b4daeb7994e91f30 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 15:51:26 +0200 Subject: [PATCH 13/45] ci: add snapshot publish workflow for GitHub Packages Signed-off-by: Hilmar Falkenberg --- .github/workflows/publish-snapshot.yml | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/publish-snapshot.yml diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml new file mode 100644 index 00000000000..96414c46e72 --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,34 @@ +name: Publish Snapshot + +on: + push: + branches: + - main + - auditing + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Publish SNAPSHOT to GitHub Packages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: sapmachine + java-version: 25 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + + - name: Publish SNAPSHOT artifacts + run: ./gradlew assemble publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} + DISABLE_REMOTE_BUILD_CACHE: true From 8e8e45ce78b7716b023bfd45e9ed339feeb7e65a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 15:52:43 +0200 Subject: [PATCH 14/45] docs: add GitHub Packages SNAPSHOT consumer guide Signed-off-by: Hilmar Falkenberg --- docs/github-packages-snapshot.md | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/github-packages-snapshot.md diff --git a/docs/github-packages-snapshot.md b/docs/github-packages-snapshot.md new file mode 100644 index 00000000000..0be7c521c16 --- /dev/null +++ b/docs/github-packages-snapshot.md @@ -0,0 +1,96 @@ +# Consuming SNAPSHOT Artifacts from GitHub Packages + +SNAPSHOT builds of this project are published to GitHub Packages on every push +to `main` and `auditing`. The group ID is `eu.apeirora.opentelemetry` and the +version is `1.64.0-SNAPSHOT` (or whatever `version.gradle.kts` currently sets). + +## Prerequisites + +You need a GitHub personal access token (PAT) with the `read:packages` scope. +Create one at https://github.com/settings/tokens. + +## Maven + +**`~/.m2/settings.xml`** — add a server entry: + +```xml + + + + github-apeirora + YOUR_GITHUB_USERNAME + YOUR_GITHUB_TOKEN + + + +``` + +**`pom.xml`** — add the repository and declare the dependency: + +```xml + + + github-apeirora + https://maven.pkg.github.com/apeirora/opentelemetry-java + true + false + + + + + + eu.apeirora.opentelemetry + opentelemetry-api + 1.64.0-SNAPSHOT + + +``` + +Replace `opentelemetry-api` with the artifact you need. All published artifact +IDs follow the pattern `opentelemetry-`. + +## Gradle (Kotlin DSL) + +**`~/.gradle/gradle.properties`** — store credentials outside the project: + +```properties +gpr.user=YOUR_GITHUB_USERNAME +gpr.key=YOUR_GITHUB_TOKEN +``` + +**`build.gradle.kts`** — add the repository: + +```kotlin +repositories { + maven { + name = "GitHubApeirora" + url = uri("https://maven.pkg.github.com/apeirora/opentelemetry-java") + credentials { + username = providers.gradleProperty("gpr.user") + .orElse(providers.environmentVariable("GITHUB_ACTOR")).get() + password = providers.gradleProperty("gpr.key") + .orElse(providers.environmentVariable("GITHUB_TOKEN")).get() + } + } +} + +dependencies { + implementation("eu.apeirora.opentelemetry:opentelemetry-api:1.64.0-SNAPSHOT") +} +``` + +## Using in CI (GitHub Actions) + +When your consumer project itself runs on GitHub Actions, `GITHUB_TOKEN` is +automatically available — no PAT needed: + +```yaml +- name: Build + run: mvn verify # or ./gradlew build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} +``` + +For Gradle, the repository block above already reads `GITHUB_TOKEN` from the +environment, so no further configuration is required. From 80b726132b444c820a5a0c1e3929376e36e2298c Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 15:53:59 +0200 Subject: [PATCH 15/45] docs: clarify Maven CI settings.xml token wiring for GitHub Packages Signed-off-by: Hilmar Falkenberg --- docs/github-packages-snapshot.md | 38 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/github-packages-snapshot.md b/docs/github-packages-snapshot.md index 0be7c521c16..15301d440be 100644 --- a/docs/github-packages-snapshot.md +++ b/docs/github-packages-snapshot.md @@ -82,15 +82,45 @@ dependencies { ## Using in CI (GitHub Actions) When your consumer project itself runs on GitHub Actions, `GITHUB_TOKEN` is -automatically available — no PAT needed: +automatically available — no PAT needed. + +### Gradle + +The repository block above already reads `GITHUB_TOKEN` and `GITHUB_ACTOR` from +the environment, so no extra configuration is required: ```yaml - name: Build - run: mvn verify # or ./gradlew build + run: ./gradlew build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} ``` -For Gradle, the repository block above already reads `GITHUB_TOKEN` from the -environment, so no further configuration is required. +### Maven + +Maven does not substitute environment variables in `settings.xml` unless you use +`${env.VAR}` syntax. Create a CI-specific settings file (e.g., +`.github/maven-settings.xml`) in your consumer project: + +```xml + + + + github-apeirora + ${env.GITHUB_ACTOR} + ${env.GITHUB_TOKEN} + + + +``` + +Then reference it in your workflow step: + +```yaml +- name: Build + run: mvn verify --settings .github/maven-settings.xml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} +``` From 34ebefc500a893e7e3a3253026d7c31b5478ef20 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 16:39:57 +0200 Subject: [PATCH 16/45] docs: update CLAUDE.md with language rules and publishing architecture notes Signed-off-by: Hilmar Falkenberg --- CLAUDE.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..680f8d3b7b6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,28 @@ +# opentelemetry-java — Development Notes + +## Language + +- AI conversation: always respond in English +- Code, code comments, documentation: always write in US English + +## Before creating a git commit + +Run `./gradlew jApiCmp` as the last step before committing **when Kotlin sources changed**. +This updates the API diff files in `docs/apidiffs/current_vs_latest/` and must be included in +the commit. CI enforces that these files are up to date and will fail if they are stale. +YAML, Markdown, and workflow-only changes do not require running `jApiCmp`. + +## Publishing Architecture + +`otel.publish-conventions.gradle.kts` (in `buildSrc/`) defines the Maven publication for all +subprojects via the `maven-publish` convention plugin. + +Sonatype/Maven Central is wired via `nexusPublishing` in the root `build.gradle.kts` — not via +a `repositories {}` block in the convention plugin. Additional publish targets (e.g. GitHub +Packages) must be added inside `publishing { }` in the convention plugin. + +The signing block activates when the `CI` env var is set. Omit `CI` in a workflow to suppress +signing without removing GPG secrets. + +Version is always `1.64.0-SNAPSHOT` while `snapshot = true` in `version.gradle.kts`. + From b1d657324a4ae542fd2b26cec0e9308e11ee5a21 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 17:05:37 +0200 Subject: [PATCH 17/45] build: gate signing on GPG_PRIVATE_KEY instead of CI env var Signed-off-by: Hilmar Falkenberg --- buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts index e219f5afcab..ea18c7c2d7f 100644 --- a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts @@ -85,7 +85,9 @@ publishing { } } -if (System.getenv("CI") != null) { +// Sign only when GPG credentials are explicitly provided. +// GitHub Actions sets CI=true unconditionally, so gating on CI would break snapshot-only workflows. +if (System.getenv("GPG_PRIVATE_KEY") != null) { signing { useInMemoryPgpKeys(System.getenv("GPG_PRIVATE_KEY"), System.getenv("GPG_PASSWORD")) sign(publishing.publications["mavenPublication"]) From 98d9423db558f6ea02ca7e140b59f04bd791f93b Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 17:13:53 +0200 Subject: [PATCH 18/45] ci: use targeted publish task to avoid Sonatype credential errors Signed-off-by: Hilmar Falkenberg --- .github/workflows/publish-snapshot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index 96414c46e72..0c8a34c1d02 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -27,7 +27,7 @@ jobs: uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 - name: Publish SNAPSHOT artifacts - run: ./gradlew assemble publish + run: ./gradlew assemble publishAllPublicationsToGithubPackagesRepository env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} From 0fc5d2a134bf02029d80ecdb640e573b4821c2b1 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 28 Apr 2026 10:43:35 +0200 Subject: [PATCH 19/45] Add initial audit logging pipeline: - API - SDK - OTLP exporter - autoconfigure Signed-off-by: Hilmar Falkenberg --- api/audit/build.gradle.kts | 13 + api/audit/gradle.properties | 1 + .../io/opentelemetry/api/audit/ActorType.java | 23 ++ .../api/audit/AuditDeliveryException.java | 25 ++ .../opentelemetry/api/audit/AuditLogger.java | 25 ++ .../api/audit/AuditLoggerBuilder.java | 28 ++ .../api/audit/AuditProvider.java | 48 ++++ .../opentelemetry/api/audit/AuditReceipt.java | 87 ++++++ .../api/audit/AuditRecordBuilder.java | 163 +++++++++++ .../api/audit/DefaultAuditLogger.java | 140 ++++++++++ .../api/audit/DefaultAuditProvider.java | 41 +++ .../api/audit/GlobalAuditProvider.java | 49 ++++ .../io/opentelemetry/api/audit/Outcome.java | 23 ++ .../opentelemetry/api/audit/package-info.java | 10 + exporters/otlp/audit/build.gradle.kts | 18 ++ exporters/otlp/audit/gradle.properties | 1 + .../http/audit/AuditLogRecordDataAdapter.java | 147 ++++++++++ .../audit/OtlpHttpAuditRecordExporter.java | 153 +++++++++++ .../OtlpHttpAuditRecordExporterBuilder.java | 99 +++++++ .../otlp/http/audit/package-info.java | 10 + ...nfigurableAuditRecordExporterProvider.java | 32 +++ .../autoconfigure/spi/audit/package-info.java | 16 ++ .../AuditExporterConfiguration.java | 67 +++++ .../AuditProviderConfiguration.java | 55 ++++ sdk/all/build.gradle.kts | 1 + sdk/audit/build.gradle.kts | 18 ++ sdk/audit/gradle.properties | 1 + .../sdk/audit/AuditExportResult.java | 66 +++++ .../sdk/audit/AuditRecordData.java | 112 ++++++++ .../sdk/audit/AuditRecordExporter.java | 56 ++++ .../sdk/audit/AuditRecordProcessor.java | 79 ++++++ .../sdk/audit/MultiAuditRecordProcessor.java | 50 ++++ .../sdk/audit/NoopAuditRecordProcessor.java | 34 +++ .../sdk/audit/ReadWriteAuditRecord.java | 63 +++++ .../sdk/audit/SdkAuditLogger.java | 28 ++ .../sdk/audit/SdkAuditLoggerBuilder.java | 42 +++ .../sdk/audit/SdkAuditProvider.java | 179 ++++++++++++ .../sdk/audit/SdkAuditProviderBuilder.java | 61 ++++ .../sdk/audit/SdkAuditRecordBuilder.java | 260 ++++++++++++++++++ .../sdk/audit/SdkAuditRecordData.java | 75 +++++ .../sdk/audit/SdkReadWriteAuditRecord.java | 211 ++++++++++++++ .../export/BatchAuditRecordProcessor.java | 257 +++++++++++++++++ .../BatchAuditRecordProcessorBuilder.java | 99 +++++++ .../export/InMemoryAuditRecordExporter.java | 90 ++++++ .../export/SimpleAuditRecordProcessor.java | 117 ++++++++ .../opentelemetry/sdk/audit/package-info.java | 10 + settings.gradle.kts | 3 + 47 files changed, 3186 insertions(+) create mode 100644 api/audit/build.gradle.kts create mode 100644 api/audit/gradle.properties create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/ActorType.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/AuditLogger.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/AuditProvider.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/Outcome.java create mode 100644 api/audit/src/main/java/io/opentelemetry/api/audit/package-info.java create mode 100644 exporters/otlp/audit/build.gradle.kts create mode 100644 exporters/otlp/audit/gradle.properties create mode 100644 exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java create mode 100644 exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java create mode 100644 exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java create mode 100644 exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/package-info.java create mode 100644 sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java create mode 100644 sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/package-info.java create mode 100644 sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java create mode 100644 sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java create mode 100644 sdk/audit/build.gradle.kts create mode 100644 sdk/audit/gradle.properties create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordExporter.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorBuilder.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/InMemoryAuditRecordExporter.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java create mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/package-info.java diff --git a/api/audit/build.gradle.kts b/api/audit/build.gradle.kts new file mode 100644 index 00000000000..8042641c88d --- /dev/null +++ b/api/audit/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") + id("otel.animalsniffer-conventions") +} + +description = "OpenTelemetry Audit Logging API" +otelJava.moduleName.set("io.opentelemetry.api.audit") + +dependencies { + api(project(":api:all")) + api(project(":context")) +} diff --git a/api/audit/gradle.properties b/api/audit/gradle.properties new file mode 100644 index 00000000000..4476ae57e31 --- /dev/null +++ b/api/audit/gradle.properties @@ -0,0 +1 @@ +otel.release=alpha diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/ActorType.java b/api/audit/src/main/java/io/opentelemetry/api/audit/ActorType.java new file mode 100644 index 00000000000..c6458f170d0 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/ActorType.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +/** + * Classifies the kind of entity that performed an auditable action. + * + * @see AuditRecordBuilder#setActorType(ActorType) + */ +public enum ActorType { + + /** A human user, identified by a user account. */ + USER, + + /** An automated service, daemon, or service account. */ + SERVICE, + + /** The operating system or a privileged system component. */ + SYSTEM +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java new file mode 100644 index 00000000000..8b30f2c9009 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +/** + * Hard-error thrown by {@link AuditRecordBuilder#emit()} when the audit sink cannot be reached and + * all retries are exhausted. This is an unchecked exception so that audit-logging call sites remain + * clean, but callers SHOULD catch it and escalate the failure through their incident-management + * process. + */ +public final class AuditDeliveryException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public AuditDeliveryException(String message) { + super(message); + } + + public AuditDeliveryException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLogger.java b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLogger.java new file mode 100644 index 00000000000..e345e56e7f9 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLogger.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * The entry point for emitting audit records. + * + *

Obtain an {@link AuditRecordBuilder} via {@link #auditRecordBuilder()}, populate all required + * fields, and call {@link AuditRecordBuilder#emit()} to deliver the record to the audit sink. + * + *

Unlike {@link io.opentelemetry.api.logs.Logger}, this interface does not expose an + * {@code isEnabled} check: audit records are ALWAYS emitted. Dropping audit records is prohibited + * by the audit logging specification. + */ +@ThreadSafe +public interface AuditLogger { + + /** Returns an {@link AuditRecordBuilder} for constructing and emitting an audit record. */ + AuditRecordBuilder auditRecordBuilder(); +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java new file mode 100644 index 00000000000..b2ea7437c76 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +/** + * Builder for creating named {@link AuditLogger} instances. + * + *

The {@code name} (set on the owning {@link AuditProvider}) is stored as a diagnostic label on + * the logger. Unlike {@link io.opentelemetry.api.logs.LoggerBuilder}, the name is NOT mapped to an + * OTLP {@code InstrumentationScope}. + */ +public interface AuditLoggerBuilder { + + /** + * Sets the schema URL to be recorded on emitted {@link AuditRecordBuilder}s for semantic + * convention versioning. + */ + AuditLoggerBuilder setSchemaUrl(String schemaUrl); + + /** Sets the version of the component or library that is emitting audit records. */ + AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion); + + /** Returns the configured {@link AuditLogger}. */ + AuditLogger build(); +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditProvider.java b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditProvider.java new file mode 100644 index 00000000000..125174064da --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * The entry point of the Audit Logging API. Provides named {@link AuditLogger} instances. + * + *

The provider is expected to be accessed from a central place. Use {@link + * GlobalAuditProvider#get()} to obtain the globally registered instance, or create an {@link + * AuditProvider} directly via the SDK. + * + *

When no SDK is installed, {@link #noop()} returns an {@link AuditProvider} whose loggers emit + * no-op receipts without error. + */ +@ThreadSafe +public interface AuditProvider { + + /** + * Gets or creates a named {@link AuditLogger} instance. + * + * @param name A string identifying the component or subsystem emitting audit records (for example + * {@code "com.example.auth"}). MUST NOT be empty. + */ + default AuditLogger get(String name) { + return auditLoggerBuilder(name).build(); + } + + /** + * Creates an {@link AuditLoggerBuilder} for a named {@link AuditLogger}. + * + * @param name A string identifying the component or subsystem emitting audit records. MUST NOT be + * empty. + */ + AuditLoggerBuilder auditLoggerBuilder(String name); + + /** + * Returns a no-op {@link AuditProvider} whose loggers return no-op {@link AuditReceipt}s + * immediately without error. + */ + static AuditProvider noop() { + return DefaultAuditProvider.getInstance(); + } +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java new file mode 100644 index 00000000000..74ec7a4c3b6 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +import javax.annotation.concurrent.Immutable; + +/** + * Proof-of-delivery returned by {@link AuditLogger} once the audit sink has persisted the record. + * + *

The {@code recordId} echoes the caller's {@link AuditRecordBuilder#setRecordId(String)}. + * {@code integrityHash} is the SHA-256 of the record as written by the sink. {@code + * sinkTimestampEpochNanos} is the nanosecond UNIX epoch at which the sink persisted the record. + */ +@Immutable +public final class AuditReceipt { + + private final String recordId; + private final String integrityHash; + private final long sinkTimestampEpochNanos; + + private AuditReceipt(String recordId, String integrityHash, long sinkTimestampEpochNanos) { + this.recordId = recordId; + this.integrityHash = integrityHash; + this.sinkTimestampEpochNanos = sinkTimestampEpochNanos; + } + + /** Creates an {@link AuditReceipt} with the given fields. */ + public static AuditReceipt create( + String recordId, String integrityHash, long sinkTimestampEpochNanos) { + return new AuditReceipt(recordId, integrityHash, sinkTimestampEpochNanos); + } + + /** Returns the {@code RecordId} echoed from the corresponding {@link AuditRecordBuilder}. */ + public String recordId() { + return recordId; + } + + /** + * Returns the SHA-256 hex digest of the canonical serialization of the {@code AuditRecord} as + * persisted by the audit sink. + */ + public String integrityHash() { + return integrityHash; + } + + /** Returns the nanosecond UNIX epoch at which the audit sink persisted the record. */ + public long sinkTimestampEpochNanos() { + return sinkTimestampEpochNanos; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AuditReceipt)) { + return false; + } + AuditReceipt other = (AuditReceipt) obj; + return recordId.equals(other.recordId) + && integrityHash.equals(other.integrityHash) + && sinkTimestampEpochNanos == other.sinkTimestampEpochNanos; + } + + @Override + public int hashCode() { + int result = recordId.hashCode(); + result = 31 * result + integrityHash.hashCode(); + result = 31 * result + Long.hashCode(sinkTimestampEpochNanos); + return result; + } + + @Override + public String toString() { + return "AuditReceipt{" + + "recordId=" + + recordId + + ", integrityHash=" + + integrityHash + + ", sinkTimestampEpochNanos=" + + sinkTimestampEpochNanos + + "}"; + } +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java new file mode 100644 index 00000000000..9879ce78111 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java @@ -0,0 +1,163 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Value; +import java.time.Instant; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * Used to construct and emit {@link AuditReceipt}-returning audit records from an {@link + * AuditLogger}. + * + *

Obtain an {@link AuditLogger#auditRecordBuilder()}, set all required and desired optional + * fields, then call {@link #emit()} which blocks until the audit sink acknowledges the record and + * returns an {@link AuditReceipt} as proof-of-delivery. + * + *

Unlike {@link io.opentelemetry.api.logs.LogRecordBuilder}, {@code emit()} returns a non-void + * {@link AuditReceipt} and MUST NOT silently drop the record. An exception is raised if the sink + * cannot be reached within the configured timeout. + */ +public interface AuditRecordBuilder { + + // ── Required fields ────────────────────────────────────────────────────── + + /** + * Sets the caller-generated unique identifier for this record. If not set, the SDK MUST generate + * a UUID v4. The value MUST remain stable across retries of the same event. + */ + AuditRecordBuilder setRecordId(String recordId); + + /** + * Sets the epoch timestamp (event time) using the given value and unit. + * + *

This field is required. It represents the time at which the auditable action occurred. + */ + AuditRecordBuilder setTimestamp(long timestamp, TimeUnit unit); + + /** Sets the epoch timestamp (event time) using the given {@link Instant}. */ + AuditRecordBuilder setTimestamp(Instant instant); + + /** + * Sets the semantic name that uniquely identifies the type of audit event, e.g. {@code + * "user.login.success"}. MUST be non-empty and stable across releases. + */ + AuditRecordBuilder setEventName(String eventName); + + /** + * Sets the identity of the entity that performed the auditable action. + * + *

MAY be a structured value. If the actor cannot be determined, set to a sentinel such as + * {@code "anonymous"}. + */ + AuditRecordBuilder setActor(Value actor); + + /** Convenience overload of {@link #setActor(Value)} accepting a plain string. */ + default AuditRecordBuilder setActor(String actor) { + return setActor(Value.of(actor)); + } + + /** Sets the type of the actor. */ + AuditRecordBuilder setActorType(ActorType actorType); + + /** + * Sets the verb that describes what the actor did, e.g. {@code "LOGIN"}, {@code "READ"}, {@code + * "DELETE"}. MUST be non-empty and stable across releases. + */ + AuditRecordBuilder setAction(String action); + + /** Sets the result of the auditable action. */ + AuditRecordBuilder setOutcome(Outcome outcome); + + // ── Optional fields ─────────────────────────────────────────────────────── + + /** + * Sets the epoch observed-timestamp using the given value and unit. If not set, the SDK MUST set + * this to the wall-clock time at the moment {@link #emit()} is called. + */ + AuditRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit); + + /** Sets the epoch observed-timestamp using the given {@link Instant}. */ + AuditRecordBuilder setObservedTimestamp(Instant instant); + + /** Sets the schema version of the audit payload, e.g. {@code "1.0.0"}. */ + AuditRecordBuilder setSchemaVersion(String schemaVersion); + + /** + * Sets the object upon which the action was performed, e.g. a file path, database row, or + * structured resource descriptor. + */ + AuditRecordBuilder setTargetResource(Value targetResource); + + /** Sets the source network address of the auditable action, e.g. {@code "203.0.113.42"}. */ + AuditRecordBuilder setSourceIp(String sourceIp); + + /** Sets free-form additional information about the audit event. */ + AuditRecordBuilder setBody(Value body); + + /** Convenience overload of {@link #setBody(Value)} accepting a plain string. */ + default AuditRecordBuilder setBody(String body) { + return setBody(Value.of(body)); + } + + /** + * Sets an attribute on this record. If the record already contains a mapping for the key, the + * old value is replaced. + * + *

Providing a {@code null} value is a no-op and does not remove previously set values. + */ + AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value); + + /** + * Sets an asymmetric digital signature over the canonical serialization of this record and the + * algorithm used (e.g. {@code "ES256"}). MUST NOT be set together with {@link + * #setHmac(byte[], String)}. + */ + AuditRecordBuilder setSignature(byte[] signature, String algorithm); + + /** + * Sets the DER-encoded X.509 public-key certificate corresponding to the signing key. Only + * meaningful when {@link #setSignature(byte[], String)} is also set. + */ + AuditRecordBuilder setCertificate(byte[] certificate); + + /** + * Sets a symmetric HMAC over the canonical serialization of this record and the algorithm used + * (e.g. {@code "HMAC-SHA256"}). MUST NOT be set together with {@link + * #setSignature(byte[], String)}. + */ + AuditRecordBuilder setHmac(byte[] hmac, String algorithm); + + /** + * Sets the monotonically increasing sequence number for hash-chain continuity. When set, + * receivers can detect gaps that indicate lost or deleted records. + */ + AuditRecordBuilder setSequenceNo(long sequenceNo); + + /** + * Sets the {@code IntegrityHash} of the immediately preceding record in the same audit stream, + * enabling hash-chain validation. + */ + AuditRecordBuilder setPrevHash(String prevHash); + + // ── Terminal ────────────────────────────────────────────────────────────── + + /** + * Emits the audit record and blocks until the audit sink acknowledges receipt. + * + *

Returns an {@link AuditReceipt} containing the sink-assigned {@code RecordId}, {@code + * IntegrityHash}, and {@code SinkTimestamp}. + * + *

If the sink cannot be reached within the configured timeout and the retry budget is + * exhausted, this method MUST throw a runtime exception and MUST NOT return silently. + * + * @throws AuditDeliveryException if the audit sink cannot be reached and all retries are + * exhausted + */ + AuditReceipt emit(); +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java new file mode 100644 index 00000000000..162d4288323 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java @@ -0,0 +1,140 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Value; +import java.time.Instant; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +class DefaultAuditLogger implements AuditLogger { + + private static final AuditLogger INSTANCE = new DefaultAuditLogger(); + private static final AuditRecordBuilder NOOP_BUILDER = new NoopAuditRecordBuilder(); + private static final AuditReceipt NOOP_RECEIPT = AuditReceipt.create("", "", 0); + + private DefaultAuditLogger() {} + + static AuditLogger getInstance() { + return INSTANCE; + } + + @Override + public AuditRecordBuilder auditRecordBuilder() { + return NOOP_BUILDER; + } + + private static final class NoopAuditRecordBuilder implements AuditRecordBuilder { + + private NoopAuditRecordBuilder() {} + + @Override + public AuditRecordBuilder setRecordId(String recordId) { + return this; + } + + @Override + public AuditRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { + return this; + } + + @Override + public AuditRecordBuilder setTimestamp(Instant instant) { + return this; + } + + @Override + public AuditRecordBuilder setEventName(String eventName) { + return this; + } + + @Override + public AuditRecordBuilder setActor(Value actor) { + return this; + } + + @Override + public AuditRecordBuilder setActorType(ActorType actorType) { + return this; + } + + @Override + public AuditRecordBuilder setAction(String action) { + return this; + } + + @Override + public AuditRecordBuilder setOutcome(Outcome outcome) { + return this; + } + + @Override + public AuditRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) { + return this; + } + + @Override + public AuditRecordBuilder setObservedTimestamp(Instant instant) { + return this; + } + + @Override + public AuditRecordBuilder setSchemaVersion(String schemaVersion) { + return this; + } + + @Override + public AuditRecordBuilder setTargetResource(Value targetResource) { + return this; + } + + @Override + public AuditRecordBuilder setSourceIp(String sourceIp) { + return this; + } + + @Override + public AuditRecordBuilder setBody(Value body) { + return this; + } + + @Override + public AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value) { + return this; + } + + @Override + public AuditRecordBuilder setSignature(byte[] signature, String algorithm) { + return this; + } + + @Override + public AuditRecordBuilder setCertificate(byte[] certificate) { + return this; + } + + @Override + public AuditRecordBuilder setHmac(byte[] hmac, String algorithm) { + return this; + } + + @Override + public AuditRecordBuilder setSequenceNo(long sequenceNo) { + return this; + } + + @Override + public AuditRecordBuilder setPrevHash(String prevHash) { + return this; + } + + @Override + public AuditReceipt emit() { + return NOOP_RECEIPT; + } + } +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java b/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java new file mode 100644 index 00000000000..e42810c6627 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +class DefaultAuditProvider implements AuditProvider { + + private static final AuditProvider INSTANCE = new DefaultAuditProvider(); + private static final AuditLoggerBuilder NOOP_BUILDER = new NoopAuditLoggerBuilder(); + + private DefaultAuditProvider() {} + + static AuditProvider getInstance() { + return INSTANCE; + } + + @Override + public AuditLoggerBuilder auditLoggerBuilder(String name) { + return NOOP_BUILDER; + } + + private static class NoopAuditLoggerBuilder implements AuditLoggerBuilder { + + @Override + public AuditLoggerBuilder setSchemaUrl(String schemaUrl) { + return this; + } + + @Override + public AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion) { + return this; + } + + @Override + public AuditLogger build() { + return DefaultAuditLogger.getInstance(); + } + } +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java b/api/audit/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java new file mode 100644 index 00000000000..e5dbdf2fcac --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Global singleton holder for the process-wide {@link AuditProvider}. + * + *

In most applications there is only one {@link AuditProvider}. {@link #set(AuditProvider)} + * SHOULD be called once, early in the application lifecycle (for example, in the same place where + * the OpenTelemetry SDK is initialised). + * + *

If no provider is registered, {@link #get()} returns the no-op provider from {@link + * AuditProvider#noop()}. + */ +public final class GlobalAuditProvider { + + private static final AtomicReference globalProvider = + new AtomicReference<>(AuditProvider.noop()); + + private GlobalAuditProvider() {} + + /** Returns the globally registered {@link AuditProvider}, or the no-op instance if none set. */ + public static AuditProvider get() { + return globalProvider.get(); + } + + /** + * Sets the globally registered {@link AuditProvider}. + * + * @param auditProvider the provider to register; MUST NOT be null + * @throws IllegalArgumentException if {@code auditProvider} is null + */ + public static void set(AuditProvider auditProvider) { + if (auditProvider == null) { + throw new IllegalArgumentException("auditProvider must not be null"); + } + globalProvider.set(auditProvider); + } + + /** Resets the global provider to the no-op implementation. Intended for use in tests only. */ + public static void resetForTest() { + globalProvider.set(AuditProvider.noop()); + } +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/Outcome.java b/api/audit/src/main/java/io/opentelemetry/api/audit/Outcome.java new file mode 100644 index 00000000000..7f9e891d8f7 --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/Outcome.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +/** + * The result of an auditable action. + * + * @see AuditRecordBuilder#setOutcome(Outcome) + */ +public enum Outcome { + + /** The action completed successfully. */ + SUCCESS, + + /** The action was attempted but did not complete successfully. */ + FAILURE, + + /** The outcome could not be determined at the time of emission. */ + UNKNOWN +} diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/package-info.java b/api/audit/src/main/java/io/opentelemetry/api/audit/package-info.java new file mode 100644 index 00000000000..67bbe297c0b --- /dev/null +++ b/api/audit/src/main/java/io/opentelemetry/api/audit/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** OpenTelemetry Audit Logging API. */ +@ParametersAreNonnullByDefault +package io.opentelemetry.api.audit; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/exporters/otlp/audit/build.gradle.kts b/exporters/otlp/audit/build.gradle.kts new file mode 100644 index 00000000000..d03630a23c2 --- /dev/null +++ b/exporters/otlp/audit/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") + id("otel.animalsniffer-conventions") +} + +description = "OpenTelemetry OTLP Audit Exporter" +otelJava.moduleName.set("io.opentelemetry.exporter.otlp.audit") + +dependencies { + api(project(":sdk:audit")) + api(project(":sdk:logs")) + implementation(project(":exporters:otlp:common")) + implementation(project(":exporters:sender:okhttp")) + + testImplementation(project(":exporters:otlp:testing-internal")) + testImplementation("com.linecorp.armeria:armeria-junit5") +} diff --git a/exporters/otlp/audit/gradle.properties b/exporters/otlp/audit/gradle.properties new file mode 100644 index 00000000000..4476ae57e31 --- /dev/null +++ b/exporters/otlp/audit/gradle.properties @@ -0,0 +1 @@ +otel.release=alpha diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java new file mode 100644 index 00000000000..0be32c753ec --- /dev/null +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.http.audit; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import javax.annotation.Nullable; + +/** + * Adapts an {@link AuditRecordData} to the {@link LogRecordData} interface so that the existing + * OTLP log marshaling infrastructure can serialize audit records to the {@code + * ExportLogsServiceRequest} protobuf message. + * + *

Mappings per the Audit Logging specification: + * + *

    + *
  • {@code SeverityNumber} MUST remain unset ({@code null}). + *
  • {@code InstrumentationScope} MUST be empty. + *
  • Mandatory audit fields are stored as {@code Attributes} with well-known keys. + *
+ */ +final class AuditLogRecordDataAdapter implements LogRecordData { + + private static final String ATTR_RECORD_ID = "audit.record_id"; + private static final String ATTR_ACTOR = "audit.actor"; + private static final String ATTR_ACTOR_TYPE = "audit.actor_type"; + private static final String ATTR_ACTION = "audit.action"; + private static final String ATTR_OUTCOME = "audit.outcome"; + private static final String ATTR_TARGET_RESOURCE = "audit.target_resource"; + private static final String ATTR_SOURCE_IP = "audit.source_ip"; + private static final String ATTR_SCHEMA_VERSION = "audit.schema_version"; + private static final String ATTR_SEQUENCE_NO = "audit.sequence_no"; + private static final String ATTR_PREV_HASH = "audit.prev_hash"; + + private final AuditRecordData audit; + private final Attributes mergedAttributes; + + AuditLogRecordDataAdapter(AuditRecordData audit) { + this.audit = audit; + this.mergedAttributes = buildAttributes(audit); + } + + private static Attributes buildAttributes(AuditRecordData a) { + AttributesBuilder b = Attributes.builder(); + // Mandatory audit fields as attributes + b.put(AttributeKey.stringKey(ATTR_RECORD_ID), a.getRecordId()); + b.put(AttributeKey.stringKey(ATTR_ACTOR), a.getActor().asString()); + b.put(AttributeKey.stringKey(ATTR_ACTOR_TYPE), a.getActorType().name()); + b.put(AttributeKey.stringKey(ATTR_ACTION), a.getAction()); + b.put(AttributeKey.stringKey(ATTR_OUTCOME), a.getOutcome().name()); + // Optional audit fields + if (a.getTargetResource() != null) { + b.put(AttributeKey.stringKey(ATTR_TARGET_RESOURCE), a.getTargetResource().asString()); + } + if (a.getSourceIp() != null) { + b.put(AttributeKey.stringKey(ATTR_SOURCE_IP), a.getSourceIp()); + } + if (a.getSchemaVersion() != null) { + b.put(AttributeKey.stringKey(ATTR_SCHEMA_VERSION), a.getSchemaVersion()); + } + if (a.getSequenceNo() != 0) { + b.put(AttributeKey.longKey(ATTR_SEQUENCE_NO), a.getSequenceNo()); + } + if (a.getPrevHash() != null) { + b.put(AttributeKey.stringKey(ATTR_PREV_HASH), a.getPrevHash()); + } + // User-supplied attributes (merged last so they can override if needed) + a.getAttributes() + .forEach( + (key, value) -> { + @SuppressWarnings("unchecked") + AttributeKey castKey = (AttributeKey) key; + b.put(castKey, value); + }); + return b.build(); + } + + @Override + public Resource getResource() { + return audit.getResource(); + } + + /** Audit records do not use instrumentation scope; always returns empty. */ + @Override + public InstrumentationScopeInfo getInstrumentationScopeInfo() { + return InstrumentationScopeInfo.empty(); + } + + @Override + public long getTimestampEpochNanos() { + return audit.getTimestampEpochNanos(); + } + + @Override + public long getObservedTimestampEpochNanos() { + return audit.getObservedTimestampEpochNanos(); + } + + @Override + public SpanContext getSpanContext() { + return SpanContext.getInvalid(); + } + + /** Audit records do not use severity; always returns {@code null}. */ + @Override + @Nullable + public Severity getSeverity() { + return null; + } + + @Override + @Nullable + public String getSeverityText() { + return null; + } + + @Override + @Nullable + public io.opentelemetry.api.common.Value getBodyValue() { + return audit.getBody(); + } + + @Override + public Attributes getAttributes() { + return mergedAttributes; + } + + @Override + public int getTotalAttributeCount() { + return mergedAttributes.size(); + } + + @Override + public String getEventName() { + return audit.getEventName(); + } +} diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java new file mode 100644 index 00000000000..ab5d78976f5 --- /dev/null +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java @@ -0,0 +1,153 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.http.audit; + +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.exporter.internal.http.HttpExporter; +import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; +import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler; +import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; +import io.opentelemetry.sdk.audit.AuditExportResult; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.internal.ComponentId; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.StringJoiner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Exports {@link AuditRecordData}s using OTLP/HTTP to the dedicated {@code /v1/audit} endpoint. + * + *

Audit records are serialized as OTLP {@code LogRecord} protobuf messages (reusing the + * {@code ExportLogsServiceRequest} envelope) with mandatory audit fields stored as attributes. + * + *

The OTLP receiver MUST NOT respond with {@code partial_success}; any partial-success response + * is treated as a hard failure and all records in the batch are retained for retry. + * + *

Create via {@link #builder()} or {@link #getDefault()}. + */ +@ThreadSafe +public final class OtlpHttpAuditRecordExporter implements AuditRecordExporter { + + static final String DEFAULT_ENDPOINT = "http://localhost:4318/v1/audit"; + private static final ComponentId COMPONENT_ID = + ComponentId.generateLazy("otlp_http_audit_exporter"); + + private final HttpExporterBuilder builder; + private final HttpExporter delegate; + + OtlpHttpAuditRecordExporter(HttpExporterBuilder builder, HttpExporter delegate) { + this.builder = builder; + this.delegate = delegate; + } + + /** Returns a new {@link OtlpHttpAuditRecordExporter} with default configuration. */ + public static OtlpHttpAuditRecordExporter getDefault() { + return builder().build(); + } + + /** Returns a new {@link OtlpHttpAuditRecordExporterBuilder}. */ + public static OtlpHttpAuditRecordExporterBuilder builder() { + return new OtlpHttpAuditRecordExporterBuilder(); + } + + /** + * Exports the given audit records to the configured OTLP {@code /v1/audit} endpoint. + * + *

Audit records are adapted to OTLP {@code LogRecord}s via {@link + * AuditLogRecordDataAdapter}. The {@code InstrumentationScope} is left empty and {@code + * SeverityNumber} is unset per the audit logging specification. + * + *

Returns synthetic {@link AuditReceipt}s on success. The {@code IntegrityHash} field is + * empty in this implementation; a future OTLP response extension will carry the sink-computed + * hash. + * + *

Any {@code partial_success} response is treated as a hard failure (all records are + * returned in the failure result for retry). + */ + @Override + public AuditExportResult export(Collection records) { + if (records.isEmpty()) { + return AuditExportResult.success(java.util.Collections.emptyList()); + } + + // Adapt AuditRecordData → LogRecordData for marshaling + List adapted = new ArrayList<>(records.size()); + for (AuditRecordData record : records) { + adapted.add(new AuditLogRecordDataAdapter(record)); + } + + // Serialize as ExportLogsServiceRequest and send to /v1/audit + LogsRequestMarshaler marshaler = LogsRequestMarshaler.create(adapted); + AtomicReference failureCause = new AtomicReference<>(); + AtomicBoolean succeeded = new AtomicBoolean(false); + CountDownLatch latch = new CountDownLatch(1); + + CompletableResultCode result = delegate.export(marshaler, adapted.size()); + result.whenComplete( + () -> { + if (result.isSuccess()) { + succeeded.set(true); + } else { + failureCause.set(result.getFailureThrowable()); + } + latch.countDown(); + }); + + try { + // Block until export completes (audit records must be acknowledged) + latch.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return AuditExportResult.failure(e); + } + + if (latch.getCount() > 0) { + return AuditExportResult.failure( + new io.opentelemetry.api.audit.AuditDeliveryException( + "OTLP export timed out waiting for /v1/audit acknowledgement")); + } + + if (!succeeded.get()) { + Throwable cause = failureCause.get(); + return cause != null ? AuditExportResult.failure(cause) : AuditExportResult.failure(); + } + + // Synthesize receipts (IntegrityHash populated by sink in future OTLP extension) + List receipts = new ArrayList<>(records.size()); + for (AuditRecordData record : records) { + receipts.add(AuditReceipt.create(record.getRecordId(), "", 0)); + } + return AuditExportResult.success(receipts); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(", ", "OtlpHttpAuditRecordExporter{", "}"); + joiner.add(builder.toString(false)); + return joiner.toString(); + } +} diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java new file mode 100644 index 00000000000..fe10357ca5b --- /dev/null +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.http.audit; + +import static io.opentelemetry.api.internal.Utils.checkArgument; +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; +import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; +import io.opentelemetry.sdk.common.export.RetryPolicy; +import io.opentelemetry.sdk.common.internal.ComponentId; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; + +/** + * Builder for {@link OtlpHttpAuditRecordExporter}. + * + *

Defaults: endpoint {@code http://localhost:4318/v1/audit}, 10 s timeout. + */ +public final class OtlpHttpAuditRecordExporterBuilder { + + private static final ComponentId COMPONENT_ID = + ComponentId.generateLazy("otlp_http_audit_exporter"); + + private final HttpExporterBuilder delegate; + + OtlpHttpAuditRecordExporterBuilder() { + this.delegate = + new HttpExporterBuilder(COMPONENT_ID, OtlpHttpAuditRecordExporter.DEFAULT_ENDPOINT); + OtlpUserAgent.addUserAgentHeader(delegate::addConstantHeaders); + } + + OtlpHttpAuditRecordExporterBuilder(HttpExporterBuilder delegate) { + this.delegate = delegate; + } + + /** Sets the maximum time to wait for the collector to process an exported batch. */ + public OtlpHttpAuditRecordExporterBuilder setTimeout(long timeout, TimeUnit unit) { + requireNonNull(unit, "unit"); + checkArgument(timeout >= 0, "timeout must be non-negative"); + return setTimeout(Duration.ofNanos(unit.toNanos(timeout))); + } + + /** Sets the maximum time to wait for the collector to process an exported batch. */ + public OtlpHttpAuditRecordExporterBuilder setTimeout(Duration timeout) { + requireNonNull(timeout, "timeout"); + delegate.setTimeout(timeout); + return this; + } + + /** Sets the OTLP endpoint URL. Defaults to {@code http://localhost:4318/v1/audit}. */ + public OtlpHttpAuditRecordExporterBuilder setEndpoint(String endpoint) { + requireNonNull(endpoint, "endpoint"); + delegate.setEndpoint(endpoint); + return this; + } + + /** Adds a constant HTTP header sent with every request. */ + public OtlpHttpAuditRecordExporterBuilder addHeader(String key, String value) { + delegate.addConstantHeaders(key, value); + return this; + } + + /** Adds constant HTTP headers sent with every request. */ + public OtlpHttpAuditRecordExporterBuilder setHeaders(Map headers) { + headers.forEach(delegate::addConstantHeaders); + return this; + } + + /** Configures TLS for the OTLP connection. */ + public OtlpHttpAuditRecordExporterBuilder setSslContext( + SSLContext sslContext, X509TrustManager trustManager) { + requireNonNull(sslContext, "sslContext"); + requireNonNull(trustManager, "trustManager"); + delegate.setSslContext(sslContext, trustManager); + return this; + } + + /** + * Sets the retry policy. Audit records MUST NOT be silently dropped on retry exhaustion; the + * exporter will surface a hard error instead. + */ + public OtlpHttpAuditRecordExporterBuilder setRetryPolicy(@Nullable RetryPolicy retryPolicy) { + delegate.setRetryPolicy(retryPolicy); + return this; + } + + /** Builds and returns the configured {@link OtlpHttpAuditRecordExporter}. */ + public OtlpHttpAuditRecordExporter build() { + return new OtlpHttpAuditRecordExporter(delegate, delegate.build()); + } +} diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/package-info.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/package-info.java new file mode 100644 index 00000000000..003db11e912 --- /dev/null +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** OTLP Audit Logging exporter. */ +@ParametersAreNonnullByDefault +package io.opentelemetry.exporter.otlp.http.audit; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java new file mode 100644 index 00000000000..73de13ad513 --- /dev/null +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.spi.audit; + +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; + +/** + * A service provider interface (SPI) for providing audit record exporters that can be used with + * the autoconfigured SDK. If the {@code otel.audit.exporter} property contains a value equal to + * what is returned by {@link #getName()}, the exporter returned by {@link + * #createExporter(ConfigProperties)} will be enabled and added to the audit pipeline. + * + *

This SPI is at {@code Development} stability; the interface may change in future releases. + */ +public interface ConfigurableAuditRecordExporterProvider { + + /** + * Returns an {@link AuditRecordExporter} that can be registered to the audit pipeline by + * providing the property value specified by {@link #getName()}. + */ + AuditRecordExporter createExporter(ConfigProperties config); + + /** + * Returns the name of this exporter, which can be specified with the {@code otel.audit.exporter} + * property to enable it. The name returned MUST NOT be the same as any other exporter name. + */ + String getName(); +} diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/package-info.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/package-info.java new file mode 100644 index 00000000000..78ed13d9040 --- /dev/null +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Java SPI (Service Provider Interface) for implementing extensions to SDK autoconfiguration of + * audit logging. + * + *

This package is at {@code Development} stability; the interfaces may change in future + * releases. + */ +@ParametersAreNonnullByDefault +package io.opentelemetry.sdk.autoconfigure.spi.audit; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java new file mode 100644 index 00000000000..6130eab6c0c --- /dev/null +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.audit.ConfigurableAuditRecordExporterProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.io.Closeable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +final class AuditExporterConfiguration { + + private static final String EXPORTER_NONE = "none"; + + static Map configureAuditRecordExporters( + ConfigProperties config, SpiHelper spiHelper, List closeables) { + Set exporterNames = DefaultConfigProperties.getSet(config, "otel.audit.exporter"); + + if (exporterNames.contains(EXPORTER_NONE)) { + if (exporterNames.size() > 1) { + throw new ConfigurationException( + "otel.audit.exporter contains " + EXPORTER_NONE + " along with other exporters"); + } + return Collections.emptyMap(); + } + + if (exporterNames.isEmpty()) { + exporterNames = Collections.singleton("otlp"); + } + + NamedSpiManager spiManager = + auditExporterSpiManager(config, spiHelper); + + Map map = new HashMap<>(); + for (String name : exporterNames) { + AuditRecordExporter exporter = spiManager.getByName(name); + if (exporter == null) { + throw new ConfigurationException("Unrecognized value for otel.audit.exporter: " + name); + } + closeables.add(exporter); + map.put(name, exporter); + } + return Collections.unmodifiableMap(map); + } + + static NamedSpiManager auditExporterSpiManager( + ConfigProperties config, SpiHelper spiHelper) { + return spiHelper.loadConfigurable( + ConfigurableAuditRecordExporterProvider.class, + ConfigurableAuditRecordExporterProvider::getName, + ConfigurableAuditRecordExporterProvider::createExporter, + config); + } + + private AuditExporterConfiguration() {} +} diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java new file mode 100644 index 00000000000..69a3ca2c78a --- /dev/null +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import io.opentelemetry.api.audit.GlobalAuditProvider; +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.audit.AuditRecordProcessor; +import io.opentelemetry.sdk.audit.SdkAuditProvider; +import io.opentelemetry.sdk.audit.SdkAuditProviderBuilder; +import io.opentelemetry.sdk.audit.export.SimpleAuditRecordProcessor; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; +import java.io.Closeable; +import java.util.List; +import java.util.Map; + +/** + * Configures an {@link SdkAuditProvider} from autoconfiguration properties and registers it as + * the global {@link io.opentelemetry.api.audit.AuditProvider}. + * + *

The property {@code otel.audit.exporter} controls which exporter is used (default: {@code + * otlp}). Use {@code otel.audit.exporter=none} to disable audit logging. + */ +final class AuditProviderConfiguration { + + static SdkAuditProvider configureAuditProvider( + Resource resource, + ConfigProperties config, + SpiHelper spiHelper, + List closeables) { + + Map exportersByName = + AuditExporterConfiguration.configureAuditRecordExporters(config, spiHelper, closeables); + + SdkAuditProviderBuilder builder = SdkAuditProvider.builder().setResource(resource); + + // Each configured exporter gets its own SimpleAuditRecordProcessor in the chain. + for (AuditRecordExporter exporter : exportersByName.values()) { + AuditRecordProcessor processor = SimpleAuditRecordProcessor.create(exporter); + closeables.add(processor); + builder.addAuditRecordProcessor(processor); + } + + SdkAuditProvider provider = builder.build(); + closeables.add(provider); + GlobalAuditProvider.set(provider); + return provider; + } + + private AuditProviderConfiguration() {} +} diff --git a/sdk/all/build.gradle.kts b/sdk/all/build.gradle.kts index 313726e1723..ca0b1dd4a1a 100644 --- a/sdk/all/build.gradle.kts +++ b/sdk/all/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { api(project(":sdk:trace")) api(project(":sdk:metrics")) api(project(":sdk:logs")) + api(project(":sdk:audit")) compileOnly(project(":api:incubator")) diff --git a/sdk/audit/build.gradle.kts b/sdk/audit/build.gradle.kts new file mode 100644 index 00000000000..08717f180d1 --- /dev/null +++ b/sdk/audit/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") + id("otel.animalsniffer-conventions") +} + +description = "OpenTelemetry Audit Logging SDK" +otelJava.moduleName.set("io.opentelemetry.sdk.audit") + +dependencies { + api(project(":api:audit")) + api(project(":sdk:common")) + + annotationProcessor("com.google.auto.value:auto-value") + + testImplementation("org.awaitility:awaitility") + testImplementation("com.google.guava:guava") +} diff --git a/sdk/audit/gradle.properties b/sdk/audit/gradle.properties new file mode 100644 index 00000000000..4476ae57e31 --- /dev/null +++ b/sdk/audit/gradle.properties @@ -0,0 +1 @@ +otel.release=alpha diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java new file mode 100644 index 00000000000..4b122fce7ec --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.AuditReceipt; +import java.util.Collections; +import java.util.List; + +/** + * The synchronous result of an {@link AuditRecordExporter#export} call. + * + *

On success, {@link #getReceipts()} contains one {@link AuditReceipt} per exported record in + * the same order as the input collection. On failure, the list is empty and {@link #isSuccess()} + * returns {@code false}. + */ +public final class AuditExportResult { + + private final boolean success; + private final List receipts; + private final Throwable failure; + + private AuditExportResult(boolean success, List receipts, Throwable failure) { + this.success = success; + this.receipts = receipts; + this.failure = failure; + } + + /** Creates a successful result with the given receipts. */ + public static AuditExportResult success(List receipts) { + return new AuditExportResult(true, Collections.unmodifiableList(receipts), null); + } + + /** Creates a failure result with the given cause. */ + public static AuditExportResult failure(Throwable cause) { + return new AuditExportResult(false, Collections.emptyList(), cause); + } + + /** Creates a failure result without a specific cause. */ + public static AuditExportResult failure() { + return new AuditExportResult(false, Collections.emptyList(), null); + } + + /** Returns {@code true} if all records were successfully acknowledged by the audit sink. */ + public boolean isSuccess() { + return success; + } + + /** + * Returns the {@link AuditReceipt}s returned by the audit sink, one per exported record. Empty + * if {@link #isSuccess()} is {@code false}. + */ + public List getReceipts() { + return receipts; + } + + /** + * Returns the cause of the failure, or {@code null} if the failure has no associated throwable + * or if the export succeeded. + */ + public Throwable getFailure() { + return failure; + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java new file mode 100644 index 00000000000..452c4c004a0 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java @@ -0,0 +1,112 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.sdk.resources.Resource; +import javax.annotation.Nullable; + +/** + * Immutable representation of an audit record for use by processors and exporters. + * + *

Instances are created internally by the SDK when {@link + * io.opentelemetry.api.audit.AuditRecordBuilder#emit()} is called. + */ +public interface AuditRecordData { + + /** Returns the {@link Resource} of the emitting service. */ + Resource getResource(); + + /** + * Returns the diagnostic name of the {@link io.opentelemetry.api.audit.AuditLogger} that emitted + * this record (for example {@code "com.example.auth"}). + */ + String getLoggerName(); + + /** Returns the optional version of the emitting component, or {@code null} if not set. */ + @Nullable + String getLoggerVersion(); + + /** Returns the optional schema URL, or {@code null} if not set. */ + @Nullable + String getSchemaUrl(); + + /** Returns the caller-generated unique identifier for this record. Never null or empty. */ + String getRecordId(); + + /** Returns the event time as nanoseconds since the UNIX epoch (UTC). */ + long getTimestampEpochNanos(); + + /** Returns the SDK observation time as nanoseconds since the UNIX epoch (UTC). */ + long getObservedTimestampEpochNanos(); + + /** Returns the semantic name of the audit event, e.g. {@code "user.login.success"}. */ + String getEventName(); + + /** Returns the identity of the actor that performed the action. */ + Value getActor(); + + /** Returns the type of the actor. */ + ActorType getActorType(); + + /** Returns the action verb, e.g. {@code "LOGIN"}, {@code "DELETE"}. */ + String getAction(); + + /** Returns the outcome of the action. */ + Outcome getOutcome(); + + /** Returns the target resource of the action, or {@code null} if not set. */ + @Nullable + Value getTargetResource(); + + /** Returns the source IP address, or {@code null} if not set. */ + @Nullable + String getSourceIp(); + + /** Returns the free-form body, or {@code null} if not set. */ + @Nullable + Value getBody(); + + /** Returns the attributes attached to this record (never null; may be empty). */ + Attributes getAttributes(); + + /** Returns the optional digital signature bytes, or {@code null} if not set. */ + @Nullable + byte[] getSignature(); + + /** Returns the signature algorithm, or {@code null} if {@link #getSignature()} is not set. */ + @Nullable + String getAlgorithm(); + + /** Returns the DER-encoded X.509 certificate, or {@code null} if not set. */ + @Nullable + byte[] getCertificate(); + + /** Returns the HMAC bytes, or {@code null} if not set. */ + @Nullable + byte[] getHmac(); + + /** Returns the HMAC algorithm, or {@code null} if {@link #getHmac()} is not set. */ + @Nullable + String getHmacAlgorithm(); + + /** + * Returns the monotonic sequence number for hash-chain continuity, or {@code 0} if not set. + */ + long getSequenceNo(); + + /** Returns the {@code IntegrityHash} of the preceding record for hash-chain linking, or {@code + * null} if not set. */ + @Nullable + String getPrevHash(); + + /** Returns the schema version of the audit payload, or {@code null} if not set. */ + @Nullable + String getSchemaVersion(); +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordExporter.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordExporter.java new file mode 100644 index 00000000000..6155dde0210 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordExporter.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.io.Closeable; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * Transmits {@link AuditRecordData}s to the configured audit sink. + * + *

Implementations MUST document their concurrency characteristics. {@link #export} MUST NOT be + * called concurrently on the same instance. + * + *

The audit logging specification prohibits partial success: if the receiver cannot process one + * or more records, the entire batch MUST be rejected. Implementations MUST treat a partial-success + * response from the OTLP receiver as a hard failure and retry the full batch. + */ +public interface AuditRecordExporter extends Closeable { + + /** + * Exports the given collection of {@link AuditRecordData}s to the audit sink. + * + *

MUST NOT be called concurrently on the same exporter instance. MUST NOT block indefinitely; + * the exporter MUST time out within the configured export timeout. + * + * @param records the records to export; the collection MUST NOT be mutated after this call + * @return an {@link AuditExportResult} containing one {@link + * io.opentelemetry.api.audit.AuditReceipt} per record on success, or a failure result + */ + AuditExportResult export(Collection records); + + /** + * Requests that any internally buffered records be exported immediately. + * + * @return a result indicating whether the flush succeeded + */ + CompletableResultCode flush(); + + /** + * Shuts down this exporter. On the first call, flushes any buffered records and releases all + * resources. Subsequent calls are no-ops. + * + * @return a result indicating whether the shutdown succeeded + */ + CompletableResultCode shutdown(); + + @Override + default void close() { + shutdown().join(10, TimeUnit.SECONDS); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java new file mode 100644 index 00000000000..23ee5ec7242 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.io.Closeable; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Interface for hooking into the audit record pipeline for enrichment and forwarding. + * + *

Processors MUST only add attributes to records (enrichment). They MUST NOT remove mandatory + * fields, filter records, aggregate records, or introduce sampling. Processors that would remove + * or filter records are rejected at configuration time by {@link SdkAuditProvider}. + */ +@ThreadSafe +public interface AuditRecordProcessor extends Closeable { + + /** + * Returns a composite {@link AuditRecordProcessor} that delegates to all given processors in + * order. + */ + static AuditRecordProcessor composite(AuditRecordProcessor... processors) { + return composite(Arrays.asList(processors)); + } + + /** + * Returns a composite {@link AuditRecordProcessor} that delegates to all given processors in + * order. + */ + static AuditRecordProcessor composite(Iterable processors) { + List list = new ArrayList<>(); + for (AuditRecordProcessor p : processors) { + list.add(p); + } + if (list.isEmpty()) { + return NoopAuditRecordProcessor.getInstance(); + } + if (list.size() == 1) { + return list.get(0); + } + return MultiAuditRecordProcessor.create(list); + } + + /** + * Called synchronously on the calling thread after the record has been enqueued and before the + * {@link io.opentelemetry.api.audit.AuditReceipt} is returned to the caller. + * + *

Implementations MAY enrich {@code record} by adding attributes. They MUST NOT block + * indefinitely. + * + * @param context the ambient {@link Context} at the time of {@code emit()} + * @param record the mutable record; enrichment only + */ + void onEmit(Context context, ReadWriteAuditRecord record); + + /** Shuts down this processor, flushing all buffered records. */ + default CompletableResultCode shutdown() { + return forceFlush(); + } + + /** Requests that all buffered records be exported as soon as possible. */ + default CompletableResultCode forceFlush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + default void close() { + shutdown().join(10, TimeUnit.SECONDS); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java new file mode 100644 index 00000000000..f61fd8f9285 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.util.ArrayList; +import java.util.List; + +/** Composite {@link AuditRecordProcessor} that delegates to multiple processors in order. */ +final class MultiAuditRecordProcessor implements AuditRecordProcessor { + + private final List processors; + + private MultiAuditRecordProcessor(List processors) { + this.processors = processors; + } + + static MultiAuditRecordProcessor create(List processors) { + return new MultiAuditRecordProcessor(new ArrayList<>(processors)); + } + + @Override + public void onEmit(Context context, ReadWriteAuditRecord record) { + for (AuditRecordProcessor processor : processors) { + processor.onEmit(context, record); + } + } + + @Override + public CompletableResultCode shutdown() { + List results = new ArrayList<>(processors.size()); + for (AuditRecordProcessor processor : processors) { + results.add(processor.shutdown()); + } + return CompletableResultCode.ofAll(results); + } + + @Override + public CompletableResultCode forceFlush() { + List results = new ArrayList<>(processors.size()); + for (AuditRecordProcessor processor : processors) { + results.add(processor.forceFlush()); + } + return CompletableResultCode.ofAll(results); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java new file mode 100644 index 00000000000..4d4e04f34bf --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; + +/** No-op {@link AuditRecordProcessor} returned when no processors are registered. */ +final class NoopAuditRecordProcessor implements AuditRecordProcessor { + + private static final AuditRecordProcessor INSTANCE = new NoopAuditRecordProcessor(); + + private NoopAuditRecordProcessor() {} + + static AuditRecordProcessor getInstance() { + return INSTANCE; + } + + @Override + public void onEmit(Context context, ReadWriteAuditRecord record) {} + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode forceFlush() { + return CompletableResultCode.ofSuccess(); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java new file mode 100644 index 00000000000..dce7cf62390 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Value; +import javax.annotation.Nullable; + +/** + * Mutable view of an {@link AuditRecordData} passed to {@link AuditRecordProcessor#onEmit}. + * + *

Processors MAY enrich the record by calling {@link #setAttribute}. They MUST NOT modify the + * mandatory audit fields ({@code EventName}, {@code Actor}, {@code ActorType}, {@code Action}, + * {@code Outcome}): those are exposed as read-only accessors. + * + *

The {@link #setReceipt}/{@link #getReceipt} pair is used internally by the SDK to thread the + * {@link AuditReceipt} returned by the exporter back to the calling {@code emit()} invocation. + */ +public interface ReadWriteAuditRecord { + + /** + * Adds or replaces an attribute on this record. A {@code null} value is a no-op. + * + *

MUST NOT be called to modify mandatory fields; only additional enrichment attributes are + * permitted. + */ + ReadWriteAuditRecord setAttribute(AttributeKey key, @Nullable T value); + + /** + * Stores the {@link AuditReceipt} returned by the exporter. Called by the SDK after a successful + * export. + */ + void setReceipt(AuditReceipt receipt); + + /** Returns the {@link AuditReceipt} set by the exporter, or {@code null} if not yet set. */ + @Nullable + AuditReceipt getReceipt(); + + /** Snapshots this record into an immutable {@link AuditRecordData} for export. */ + AuditRecordData toAuditRecordData(); + + // ── Read-only accessors for mandatory fields ────────────────────────────── + + String getRecordId(); + + long getTimestampEpochNanos(); + + String getEventName(); + + Value getActor(); + + ActorType getActorType(); + + String getAction(); + + Outcome getOutcome(); +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java new file mode 100644 index 00000000000..7bf006588ae --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.AuditLogger; +import io.opentelemetry.api.audit.AuditRecordBuilder; +import javax.annotation.concurrent.ThreadSafe; + +/** SDK implementation of {@link AuditLogger}. */ +@ThreadSafe +final class SdkAuditLogger implements AuditLogger { + + private final SdkAuditProvider provider; + private final SdkAuditProvider.AuditLoggerKey key; + + SdkAuditLogger(SdkAuditProvider provider, SdkAuditProvider.AuditLoggerKey key) { + this.provider = provider; + this.key = key; + } + + @Override + public AuditRecordBuilder auditRecordBuilder() { + return new SdkAuditRecordBuilder(provider, key); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java new file mode 100644 index 00000000000..260c511aa39 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.AuditLogger; +import io.opentelemetry.api.audit.AuditLoggerBuilder; +import javax.annotation.Nullable; + +/** SDK implementation of {@link AuditLoggerBuilder}. */ +final class SdkAuditLoggerBuilder implements AuditLoggerBuilder { + + private final SdkAuditProvider provider; + private final String name; + @Nullable private String version; + @Nullable private String schemaUrl; + + SdkAuditLoggerBuilder(SdkAuditProvider provider, String name) { + this.provider = provider; + this.name = name; + } + + @Override + public SdkAuditLoggerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public SdkAuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion) { + this.version = instrumentationVersion; + return this; + } + + @Override + public AuditLogger build() { + return provider.getOrCreateLogger( + new SdkAuditProvider.AuditLoggerKey(name, version, schemaUrl)); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java new file mode 100644 index 00000000000..18ed766b883 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java @@ -0,0 +1,179 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.AuditDeliveryException; +import io.opentelemetry.api.audit.AuditLogger; +import io.opentelemetry.api.audit.AuditLoggerBuilder; +import io.opentelemetry.api.audit.AuditProvider; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.resources.Resource; +import java.io.Closeable; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * SDK implementation of {@link AuditProvider}. + * + *

Create via {@link #builder()}. The provider maintains a dedicated queue and exporter pipeline + * that is completely independent of the Log signal pipeline. + * + *

The provider intentionally does NOT expose sampler configuration. Any attempt to configure + * sampling on the audit pipeline is a configuration error and will be rejected. + */ +public final class SdkAuditProvider implements AuditProvider, Closeable { + + private static final Logger logger = Logger.getLogger(SdkAuditProvider.class.getName()); + + private final Resource resource; + private final AuditRecordProcessor processor; + private final Clock clock; + private final ConcurrentHashMap loggerRegistry = + new ConcurrentHashMap<>(); + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + + SdkAuditProvider(Resource resource, List processors, Clock clock) { + this.resource = resource; + this.processor = AuditRecordProcessor.composite(processors); + this.clock = clock; + } + + /** Returns a new {@link SdkAuditProviderBuilder}. */ + public static SdkAuditProviderBuilder builder() { + return new SdkAuditProviderBuilder(); + } + + @Override + public AuditLoggerBuilder auditLoggerBuilder(String name) { + if (isShutdown.get()) { + throw new AuditDeliveryException( + "AuditProvider has been shut down; cannot create new AuditLoggers"); + } + if (name == null || name.isEmpty()) { + logger.log( + Level.WARNING, + "AuditProvider.auditLoggerBuilder() called with null or empty name; using 'unknown'"); + name = "unknown"; + } + return new SdkAuditLoggerBuilder(this, name); + } + + /** Returns the {@link SdkAuditLogger} for the given key, creating it if necessary. */ + SdkAuditLogger getOrCreateLogger(AuditLoggerKey key) { + return loggerRegistry.computeIfAbsent(key, k -> new SdkAuditLogger(this, k)); + } + + Resource getResource() { + return resource; + } + + AuditRecordProcessor getProcessor() { + return processor; + } + + Clock getClock() { + return clock; + } + + boolean isShutdown() { + return isShutdown.get(); + } + + /** + * Shuts down this provider. Calls {@link #forceFlush()} then shuts down all registered + * processors. + * + * @return a result indicating whether shutdown succeeded + */ + public CompletableResultCode shutdown() { + if (!isShutdown.compareAndSet(false, true)) { + return CompletableResultCode.ofSuccess(); + } + CompletableResultCode result = new CompletableResultCode(); + CompletableResultCode flushResult = forceFlush(); + flushResult.whenComplete( + () -> { + CompletableResultCode shutdownResult = processor.shutdown(); + shutdownResult.whenComplete( + () -> { + if (!flushResult.isSuccess() || !shutdownResult.isSuccess()) { + result.fail(); + } else { + result.succeed(); + } + }); + }); + return result; + } + + /** + * Forces all buffered audit records to be exported. + * + * @return a result indicating whether the flush succeeded + */ + public CompletableResultCode forceFlush() { + if (isShutdown.get()) { + return CompletableResultCode.ofSuccess(); + } + return processor.forceFlush(); + } + + @Override + public void close() { + shutdown().join(10, TimeUnit.SECONDS); + } + + // ── Inner key type ──────────────────────────────────────────────────────── + + static final class AuditLoggerKey { + + private final String name; + @Nullable private final String version; + @Nullable private final String schemaUrl; + + AuditLoggerKey(String name, @Nullable String version, @Nullable String schemaUrl) { + this.name = name; + this.version = version; + this.schemaUrl = schemaUrl; + } + + String getName() { + return name; + } + + @Nullable + String getVersion() { + return version; + } + + @Nullable + String getSchemaUrl() { + return schemaUrl; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof AuditLoggerKey)) return false; + AuditLoggerKey other = (AuditLoggerKey) obj; + return name.equals(other.name) + && Objects.equals(version, other.version) + && Objects.equals(schemaUrl, other.schemaUrl); + } + + @Override + public int hashCode() { + return Objects.hash(name, version, schemaUrl); + } + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java new file mode 100644 index 00000000000..9f0fae27abb --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.resources.Resource; +import java.util.ArrayList; +import java.util.List; + +/** Builder for {@link SdkAuditProvider}. */ +public final class SdkAuditProviderBuilder { + + private Resource resource = Resource.getDefault(); + private Clock clock = Clock.getDefault(); + private final List processors = new ArrayList<>(); + + SdkAuditProviderBuilder() {} + + /** + * Sets the {@link Resource} to be associated with all audit records emitted by this provider. + */ + public SdkAuditProviderBuilder setResource(Resource resource) { + if (resource == null) { + throw new NullPointerException("resource"); + } + this.resource = resource; + return this; + } + + /** Sets the {@link Clock} used for {@code ObservedTimestamp} generation. */ + public SdkAuditProviderBuilder setClock(Clock clock) { + if (clock == null) { + throw new NullPointerException("clock"); + } + this.clock = clock; + return this; + } + + /** + * Adds an {@link AuditRecordProcessor} to the pipeline. Processors are invoked in the order + * they are added. + * + *

The last processor in the chain is responsible for forwarding records to the exporter and + * setting the {@link io.opentelemetry.api.audit.AuditReceipt} on the record. + */ + public SdkAuditProviderBuilder addAuditRecordProcessor(AuditRecordProcessor processor) { + if (processor == null) { + throw new NullPointerException("processor"); + } + processors.add(processor); + return this; + } + + /** Builds and returns the configured {@link SdkAuditProvider}. */ + public SdkAuditProvider build() { + return new SdkAuditProvider(resource, processors, clock); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java new file mode 100644 index 00000000000..8af4da2892e --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -0,0 +1,260 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.AuditDeliveryException; +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.api.audit.AuditRecordBuilder; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.internal.AttributesMap; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** SDK implementation of {@link AuditRecordBuilder}. */ +final class SdkAuditRecordBuilder implements AuditRecordBuilder { + + private final SdkAuditProvider provider; + private final SdkAuditProvider.AuditLoggerKey loggerKey; + + // Required fields + @Nullable private String recordId; + private long timestampEpochNanos; + @Nullable private String eventName; + @Nullable private Value actor; + @Nullable private ActorType actorType; + @Nullable private String action; + @Nullable private Outcome outcome; + + // Optional fields + private long observedTimestampEpochNanos; + @Nullable private String schemaVersion; + @Nullable private Value targetResource; + @Nullable private String sourceIp; + @Nullable private Value body; + @Nullable private AttributesMap attributes; + @Nullable private byte[] signature; + @Nullable private String algorithm; + @Nullable private byte[] certificate; + @Nullable private byte[] hmac; + @Nullable private String hmacAlgorithm; + private long sequenceNo; + @Nullable private String prevHash; + + SdkAuditRecordBuilder(SdkAuditProvider provider, SdkAuditProvider.AuditLoggerKey loggerKey) { + this.provider = provider; + this.loggerKey = loggerKey; + } + + @Override + public SdkAuditRecordBuilder setRecordId(String recordId) { + this.recordId = recordId; + return this; + } + + @Override + public SdkAuditRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { + this.timestampEpochNanos = unit.toNanos(timestamp); + return this; + } + + @Override + public SdkAuditRecordBuilder setTimestamp(Instant instant) { + this.timestampEpochNanos = + TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano(); + return this; + } + + @Override + public SdkAuditRecordBuilder setEventName(String eventName) { + this.eventName = eventName; + return this; + } + + @Override + public SdkAuditRecordBuilder setActor(Value actor) { + this.actor = actor; + return this; + } + + @Override + public SdkAuditRecordBuilder setActorType(ActorType actorType) { + this.actorType = actorType; + return this; + } + + @Override + public SdkAuditRecordBuilder setAction(String action) { + this.action = action; + return this; + } + + @Override + public SdkAuditRecordBuilder setOutcome(Outcome outcome) { + this.outcome = outcome; + return this; + } + + @Override + public SdkAuditRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) { + this.observedTimestampEpochNanos = unit.toNanos(timestamp); + return this; + } + + @Override + public SdkAuditRecordBuilder setObservedTimestamp(Instant instant) { + this.observedTimestampEpochNanos = + TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano(); + return this; + } + + @Override + public SdkAuditRecordBuilder setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + return this; + } + + @Override + public SdkAuditRecordBuilder setTargetResource(Value targetResource) { + this.targetResource = targetResource; + return this; + } + + @Override + public SdkAuditRecordBuilder setSourceIp(String sourceIp) { + this.sourceIp = sourceIp; + return this; + } + + @Override + public SdkAuditRecordBuilder setBody(Value body) { + this.body = body; + return this; + } + + @Override + public SdkAuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value) { + if (key == null || value == null) { + return this; + } + if (attributes == null) { + attributes = AttributesMap.create(128, Integer.MAX_VALUE); + } + attributes.put(key, value); + return this; + } + + @Override + public SdkAuditRecordBuilder setSignature(byte[] signature, String algorithm) { + this.signature = signature; + this.algorithm = algorithm; + return this; + } + + @Override + public SdkAuditRecordBuilder setCertificate(byte[] certificate) { + this.certificate = certificate; + return this; + } + + @Override + public SdkAuditRecordBuilder setHmac(byte[] hmac, String algorithm) { + this.hmac = hmac; + this.hmacAlgorithm = algorithm; + return this; + } + + @Override + public SdkAuditRecordBuilder setSequenceNo(long sequenceNo) { + this.sequenceNo = sequenceNo; + return this; + } + + @Override + public SdkAuditRecordBuilder setPrevHash(String prevHash) { + this.prevHash = prevHash; + return this; + } + + @Override + public AuditReceipt emit() { + if (provider.isShutdown()) { + throw new AuditDeliveryException( + "AuditProvider has been shut down; cannot emit audit records"); + } + + // Step 1: Generate RecordId if absent + if (recordId == null || recordId.isEmpty()) { + recordId = UUID.randomUUID().toString(); + } + + // Step 2: Set ObservedTimestamp if absent + if (observedTimestampEpochNanos == 0) { + observedTimestampEpochNanos = provider.getClock().now(); + } + + // Step 3: Validate required fields + validateRequired("Timestamp", timestampEpochNanos != 0, "Timestamp must be set"); + validateRequired("EventName", eventName != null && !eventName.isEmpty(), "EventName must be set and non-empty"); + validateRequired("Actor", actor != null, "Actor must be set"); + validateRequired("ActorType", actorType != null, "ActorType must be set"); + validateRequired("Action", action != null && !action.isEmpty(), "Action must be set and non-empty"); + validateRequired("Outcome", outcome != null, "Outcome must be set"); + + // Step 4+5: Create the mutable record and pass it through all processors. + // Transfer ownership of the attributes map to the record (builder must not be reused). + AttributesMap recordAttributes = this.attributes; + this.attributes = null; + SdkReadWriteAuditRecord rwRecord = + new SdkReadWriteAuditRecord( + provider.getResource(), + loggerKey.getName(), + loggerKey.getVersion(), + loggerKey.getSchemaUrl(), + recordId, + timestampEpochNanos, + observedTimestampEpochNanos, + eventName, + actor, + actorType, + action, + outcome, + targetResource, + sourceIp, + body, + recordAttributes, + signature, + algorithm, + certificate, + hmac, + hmacAlgorithm, + sequenceNo, + prevHash, + schemaVersion); + + provider.getProcessor().onEmit(Context.current(), rwRecord); + + // Step 6+7: Retrieve and return the receipt set by the exporter-wrapping processor + AuditReceipt receipt = rwRecord.getReceipt(); + if (receipt == null) { + throw new AuditDeliveryException( + "Audit pipeline returned no receipt; ensure an AuditRecordExporter is configured"); + } + return receipt; + } + + private static void validateRequired(String field, boolean condition, String message) { + if (!condition) { + throw new IllegalArgumentException( + "AuditRecord validation failed for field '" + field + "': " + message); + } + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java new file mode 100644 index 00000000000..413bc703adf --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.sdk.resources.Resource; +import javax.annotation.Nullable; + +/** Immutable AutoValue implementation of {@link AuditRecordData}. */ +@AutoValue +public abstract class SdkAuditRecordData implements AuditRecordData { + + SdkAuditRecordData() {} + + /** Creates a new {@link SdkAuditRecordData}. */ + @SuppressWarnings("TooManyParameters") + public static SdkAuditRecordData create( + Resource resource, + String loggerName, + @Nullable String loggerVersion, + @Nullable String schemaUrl, + String recordId, + long timestampEpochNanos, + long observedTimestampEpochNanos, + String eventName, + Value actor, + ActorType actorType, + String action, + Outcome outcome, + @Nullable Value targetResource, + @Nullable String sourceIp, + @Nullable Value body, + Attributes attributes, + @Nullable byte[] signature, + @Nullable String algorithm, + @Nullable byte[] certificate, + @Nullable byte[] hmac, + @Nullable String hmacAlgorithm, + long sequenceNo, + @Nullable String prevHash, + @Nullable String schemaVersion) { + return new AutoValue_SdkAuditRecordData( + resource, + loggerName, + loggerVersion, + schemaUrl, + recordId, + timestampEpochNanos, + observedTimestampEpochNanos, + eventName, + actor, + actorType, + action, + outcome, + targetResource, + sourceIp, + body, + attributes, + signature, + algorithm, + certificate, + hmac, + hmacAlgorithm, + sequenceNo, + prevHash, + schemaVersion); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java new file mode 100644 index 00000000000..206c66bc37a --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java @@ -0,0 +1,211 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.internal.GuardedBy; +import io.opentelemetry.sdk.common.internal.AttributesMap; +import io.opentelemetry.sdk.resources.Resource; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Mutable view of an audit record passed to {@link AuditRecordProcessor#onEmit}. Processors MAY + * enrich the record by adding attributes but MUST NOT modify the mandatory fields. + */ +@ThreadSafe +final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { + + private final Resource resource; + private final String loggerName; + @Nullable private final String loggerVersion; + @Nullable private final String schemaUrl; + private final String recordId; + private final long timestampEpochNanos; + private final long observedTimestampEpochNanos; + private final String eventName; + private final Value actor; + private final ActorType actorType; + private final String action; + private final Outcome outcome; + @Nullable private final Value targetResource; + @Nullable private final String sourceIp; + @Nullable private final Value body; + @Nullable private final byte[] signature; + @Nullable private final String algorithm; + @Nullable private final byte[] certificate; + @Nullable private final byte[] hmac; + @Nullable private final String hmacAlgorithm; + private final long sequenceNo; + @Nullable private final String prevHash; + @Nullable private final String schemaVersion; + + private final Object lock = new Object(); + + @GuardedBy("lock") + @Nullable + private AttributesMap attributes; + + @GuardedBy("lock") + @Nullable + private AuditReceipt receipt; + + @SuppressWarnings("TooManyParameters") + SdkReadWriteAuditRecord( + Resource resource, + String loggerName, + @Nullable String loggerVersion, + @Nullable String schemaUrl, + String recordId, + long timestampEpochNanos, + long observedTimestampEpochNanos, + String eventName, + Value actor, + ActorType actorType, + String action, + Outcome outcome, + @Nullable Value targetResource, + @Nullable String sourceIp, + @Nullable Value body, + @Nullable AttributesMap attributes, + @Nullable byte[] signature, + @Nullable String algorithm, + @Nullable byte[] certificate, + @Nullable byte[] hmac, + @Nullable String hmacAlgorithm, + long sequenceNo, + @Nullable String prevHash, + @Nullable String schemaVersion) { + this.resource = resource; + this.loggerName = loggerName; + this.loggerVersion = loggerVersion; + this.schemaUrl = schemaUrl; + this.recordId = recordId; + this.timestampEpochNanos = timestampEpochNanos; + this.observedTimestampEpochNanos = observedTimestampEpochNanos; + this.eventName = eventName; + this.actor = actor; + this.actorType = actorType; + this.action = action; + this.outcome = outcome; + this.targetResource = targetResource; + this.sourceIp = sourceIp; + this.body = body; + this.attributes = attributes; + this.signature = signature; + this.algorithm = algorithm; + this.certificate = certificate; + this.hmac = hmac; + this.hmacAlgorithm = hmacAlgorithm; + this.sequenceNo = sequenceNo; + this.prevHash = prevHash; + this.schemaVersion = schemaVersion; + } + + @Override + public ReadWriteAuditRecord setAttribute(AttributeKey key, @Nullable T value) { + if (key == null || value == null) { + return this; + } + synchronized (lock) { + if (attributes == null) { + attributes = AttributesMap.create(128, Integer.MAX_VALUE); + } + attributes.put(key, value); + } + return this; + } + + @Override + public void setReceipt(AuditReceipt receipt) { + synchronized (lock) { + this.receipt = receipt; + } + } + + @Override + @Nullable + public AuditReceipt getReceipt() { + synchronized (lock) { + return receipt; + } + } + + @Override + public AuditRecordData toAuditRecordData() { + final Attributes frozenAttributes; + synchronized (lock) { + frozenAttributes = attributes != null ? attributes.immutableCopy() : Attributes.empty(); + } + return SdkAuditRecordData.create( + resource, + loggerName, + loggerVersion, + schemaUrl, + recordId, + timestampEpochNanos, + observedTimestampEpochNanos, + eventName, + actor, + actorType, + action, + outcome, + targetResource, + sourceIp, + body, + frozenAttributes, + signature, + algorithm, + certificate, + hmac, + hmacAlgorithm, + sequenceNo, + prevHash, + schemaVersion); + } + + // ── Read accessors for processors ───────────────────────────────────────── + + @Override + public String getRecordId() { + return recordId; + } + + @Override + public long getTimestampEpochNanos() { + return timestampEpochNanos; + } + + @Override + public String getEventName() { + return eventName; + } + + @Override + public Value getActor() { + return actor; + } + + @Override + public ActorType getActorType() { + return actorType; + } + + @Override + public String getAction() { + return action; + } + + @Override + public Outcome getOutcome() { + return outcome; + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java new file mode 100644 index 00000000000..095f3e8ab8f --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java @@ -0,0 +1,257 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit.export; + +import io.opentelemetry.api.audit.AuditDeliveryException; +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.audit.AuditExportResult; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.audit.AuditRecordProcessor; +import io.opentelemetry.sdk.audit.ReadWriteAuditRecord; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An {@link AuditRecordProcessor} that batches records for efficient export while still blocking + * the calling thread until the batch containing the record is acknowledged by the audit sink. + * + *

Records are never dropped when the queue is full: the calling thread is blocked (back-pressure + * is applied to the application) until there is space in the queue or the configured timeout + * elapses. + * + *

Build via {@link BatchAuditRecordProcessorBuilder}: + * + *

{@code
+ * BatchAuditRecordProcessor processor = BatchAuditRecordProcessor.builder(exporter)
+ *     .setMaxQueueSize(4096)
+ *     .setScheduledDelayMillis(1000)
+ *     .build();
+ * }
+ */ +public final class BatchAuditRecordProcessor implements AuditRecordProcessor { + + private static final Logger logger = + Logger.getLogger(BatchAuditRecordProcessor.class.getName()); + + private final AuditRecordExporter exporter; + private final int maxExportBatchSize; + private final long exportTimeoutMillis; + private final long scheduledDelayMillis; + private final int maxRetryCount; + private final long initialBackoffMillis; + + // Queue of pending records. BlockingQueue to apply back-pressure when full. + private final BlockingQueue queue; + private final int maxQueueSize; + + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + private final AtomicReference flushRequested = new AtomicReference<>(); + private final BlockingQueue signal = new ArrayBlockingQueue<>(1); + + private final Thread worker; + + BatchAuditRecordProcessor( + AuditRecordExporter exporter, + int maxQueueSize, + int maxExportBatchSize, + long scheduledDelayMillis, + long exportTimeoutMillis, + int maxRetryCount, + long initialBackoffMillis) { + this.exporter = exporter; + this.maxQueueSize = maxQueueSize; + this.maxExportBatchSize = maxExportBatchSize; + this.scheduledDelayMillis = scheduledDelayMillis; + this.exportTimeoutMillis = exportTimeoutMillis; + this.maxRetryCount = maxRetryCount; + this.initialBackoffMillis = initialBackoffMillis; + this.queue = new ArrayBlockingQueue<>(maxQueueSize); + this.worker = new Thread(new Worker(), "otel-audit-batch-worker"); + this.worker.setDaemon(true); + this.worker.start(); + } + + /** Returns a new {@link BatchAuditRecordProcessorBuilder} for the given exporter. */ + public static BatchAuditRecordProcessorBuilder builder(AuditRecordExporter exporter) { + return new BatchAuditRecordProcessorBuilder(exporter); + } + + @Override + public void onEmit(Context context, ReadWriteAuditRecord record) { + if (isShutdown.get()) { + throw new AuditDeliveryException( + "BatchAuditRecordProcessor has been shut down; refusing to emit record"); + } + CompletableFuture future = new CompletableFuture<>(); + PendingRecord pending = new PendingRecord(record.toAuditRecordData(), future); + // Block until there is space in the queue (back-pressure as per spec) + boolean offered = false; + try { + offered = queue.offer(pending, exportTimeoutMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AuditDeliveryException("Interrupted while enqueuing audit record", e); + } + if (!offered) { + throw new AuditDeliveryException( + "Audit record queue is full and back-pressure timeout elapsed; record rejected"); + } + // Signal the worker in case it is waiting for work + signal.offer(Boolean.TRUE); + // Block until the worker exports the batch and completes the future + try { + AuditReceipt receipt = future.get(exportTimeoutMillis, TimeUnit.MILLISECONDS); + record.setReceipt(receipt); + } catch (TimeoutException e) { + future.cancel(false); + throw new AuditDeliveryException( + "Timed out waiting for audit export acknowledgement after " + exportTimeoutMillis + "ms", + e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof AuditDeliveryException) { + throw (AuditDeliveryException) cause; + } + throw new AuditDeliveryException("Audit export failed", cause); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AuditDeliveryException("Interrupted while waiting for audit export", e); + } + } + + @Override + public CompletableResultCode shutdown() { + if (!isShutdown.compareAndSet(false, true)) { + return CompletableResultCode.ofSuccess(); + } + CompletableResultCode result = forceFlush(); + result.whenComplete( + () -> { + worker.interrupt(); + exporter.shutdown(); + }); + return result; + } + + @Override + public CompletableResultCode forceFlush() { + CompletableResultCode flushResult = new CompletableResultCode(); + if (flushRequested.compareAndSet(null, flushResult)) { + signal.offer(Boolean.TRUE); + } + CompletableResultCode existing = flushRequested.get(); + return existing != null ? existing : flushResult; + } + + // ── Worker ──────────────────────────────────────────────────────────────── + + private final class Worker implements Runnable { + + @Override + public void run() { + List batch = new ArrayList<>(maxExportBatchSize); + while (!isShutdown.get()) { + try { + // Wait for work or the scheduled delay + signal.poll(scheduledDelayMillis, TimeUnit.MILLISECONDS); + exportBatch(batch); + CompletableResultCode flush = flushRequested.getAndSet(null); + if (flush != null) { + flush.succeed(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + // Drain remaining records on shutdown + exportBatch(batch); + } + + private void exportBatch(List batch) { + batch.clear(); + queue.drainTo(batch, maxExportBatchSize); + if (batch.isEmpty()) { + return; + } + List records = new ArrayList<>(batch.size()); + for (PendingRecord p : batch) { + records.add(p.data); + } + AuditExportResult result = exportWithRetry(records); + if (result.isSuccess()) { + List receipts = result.getReceipts(); + for (int i = 0; i < batch.size(); i++) { + AuditReceipt receipt = i < receipts.size() ? receipts.get(i) : null; + if (receipt != null) { + batch.get(i).future.complete(receipt); + } else { + batch.get(i).future.completeExceptionally( + new AuditDeliveryException("Exporter returned no receipt for record " + i)); + } + } + } else { + AuditDeliveryException ex = + new AuditDeliveryException( + "Audit export failed after " + maxRetryCount + " retries", + result.getFailure()); + for (PendingRecord p : batch) { + p.future.completeExceptionally(ex); + } + } + } + + private AuditExportResult exportWithRetry(List records) { + long backoff = initialBackoffMillis; + for (int attempt = 0; attempt <= maxRetryCount; attempt++) { + AuditExportResult result = exporter.export(records); + if (result.isSuccess()) { + return result; + } + if (attempt < maxRetryCount) { + logger.log( + Level.WARNING, + "Audit export attempt {0} failed; retrying in {1}ms", + new Object[] {attempt + 1, backoff}); + try { + Thread.sleep(backoff); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return AuditExportResult.failure(e); + } + backoff = Math.min(backoff * 2, exportTimeoutMillis); + } + } + return AuditExportResult.failure( + new AuditDeliveryException("Max retries exhausted: " + maxRetryCount)); + } + } + + // ── Holder ──────────────────────────────────────────────────────────────── + + private static final class PendingRecord { + final AuditRecordData data; + final CompletableFuture future; + + PendingRecord(AuditRecordData data, CompletableFuture future) { + this.data = data; + this.future = future; + } + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorBuilder.java new file mode 100644 index 00000000000..e5b781d6a32 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorBuilder.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit.export; + +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.sdk.audit.AuditRecordExporter; + +/** Builder for {@link BatchAuditRecordProcessor}. */ +public final class BatchAuditRecordProcessorBuilder { + + static final int DEFAULT_MAX_QUEUE_SIZE = 2048; + static final int DEFAULT_MAX_EXPORT_BATCH_SIZE = 512; + static final long DEFAULT_SCHEDULED_DELAY_MILLIS = 5_000; + static final long DEFAULT_EXPORT_TIMEOUT_MILLIS = 30_000; + static final int DEFAULT_MAX_RETRY_COUNT = 5; + static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 1_000; + + private final AuditRecordExporter exporter; + private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE; + private long scheduledDelayMillis = DEFAULT_SCHEDULED_DELAY_MILLIS; + private long exportTimeoutMillis = DEFAULT_EXPORT_TIMEOUT_MILLIS; + private int maxRetryCount = DEFAULT_MAX_RETRY_COUNT; + private long initialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS; + + BatchAuditRecordProcessorBuilder(AuditRecordExporter exporter) { + this.exporter = requireNonNull(exporter, "exporter"); + } + + /** + * Sets the maximum number of records held in the queue before back-pressure is applied to the + * calling thread. Default: {@value #DEFAULT_MAX_QUEUE_SIZE}. + */ + public BatchAuditRecordProcessorBuilder setMaxQueueSize(int maxQueueSize) { + this.maxQueueSize = maxQueueSize; + return this; + } + + /** + * Sets the maximum number of records per exported batch. Default: {@value + * #DEFAULT_MAX_EXPORT_BATCH_SIZE}. + */ + public BatchAuditRecordProcessorBuilder setMaxExportBatchSize(int maxExportBatchSize) { + this.maxExportBatchSize = maxExportBatchSize; + return this; + } + + /** + * Sets the delay in milliseconds between two consecutive exports when the batch is not full. + * Default: {@value #DEFAULT_SCHEDULED_DELAY_MILLIS} ms. + */ + public BatchAuditRecordProcessorBuilder setScheduledDelayMillis(long scheduledDelayMillis) { + this.scheduledDelayMillis = scheduledDelayMillis; + return this; + } + + /** + * Sets the maximum time in milliseconds allowed for a single export call before it is considered + * a failure. Default: {@value #DEFAULT_EXPORT_TIMEOUT_MILLIS} ms. + */ + public BatchAuditRecordProcessorBuilder setExportTimeoutMillis(long exportTimeoutMillis) { + this.exportTimeoutMillis = exportTimeoutMillis; + return this; + } + + /** + * Sets the maximum number of export retry attempts before surfacing a hard error. Default: + * {@value #DEFAULT_MAX_RETRY_COUNT}. + */ + public BatchAuditRecordProcessorBuilder setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + return this; + } + + /** + * Sets the initial back-off delay in milliseconds between retries; doubled on each attempt. + * Default: {@value #DEFAULT_INITIAL_BACKOFF_MILLIS} ms. + */ + public BatchAuditRecordProcessorBuilder setInitialBackoffMillis(long initialBackoffMillis) { + this.initialBackoffMillis = initialBackoffMillis; + return this; + } + + /** Builds and returns the configured {@link BatchAuditRecordProcessor}. */ + public BatchAuditRecordProcessor build() { + return new BatchAuditRecordProcessor( + exporter, + maxQueueSize, + maxExportBatchSize, + scheduledDelayMillis, + exportTimeoutMillis, + maxRetryCount, + initialBackoffMillis); + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/InMemoryAuditRecordExporter.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/InMemoryAuditRecordExporter.java new file mode 100644 index 00000000000..9e79537c637 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/InMemoryAuditRecordExporter.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit.export; + +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.sdk.audit.AuditExportResult; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An in-memory {@link AuditRecordExporter} for use in tests. + * + *

Stores all exported {@link AuditRecordData}s in a list and returns synthetic {@link + * AuditReceipt}s with empty integrity hashes (since there is no real audit sink). + * + *

{@code
+ * InMemoryAuditRecordExporter exporter = InMemoryAuditRecordExporter.create();
+ * SdkAuditProvider provider = SdkAuditProvider.builder()
+ *     .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(exporter))
+ *     .build();
+ * // ...
+ * List records = exporter.getFinishedAuditRecords();
+ * }
+ */ +public final class InMemoryAuditRecordExporter implements AuditRecordExporter { + + private final List finishedRecords = new ArrayList<>(); + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + private final Object lock = new Object(); + + private InMemoryAuditRecordExporter() {} + + /** Creates a new {@link InMemoryAuditRecordExporter}. */ + public static InMemoryAuditRecordExporter create() { + return new InMemoryAuditRecordExporter(); + } + + @Override + public AuditExportResult export(Collection records) { + if (isShutdown.get()) { + return AuditExportResult.failure(new IllegalStateException("Exporter has been shut down")); + } + List receipts = new ArrayList<>(records.size()); + synchronized (lock) { + for (AuditRecordData record : records) { + finishedRecords.add(record); + // Synthetic receipt: echo the recordId; integrity hash is empty (no real sink) + receipts.add(AuditReceipt.create(record.getRecordId(), "", 0)); + } + } + return AuditExportResult.success(receipts); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + isShutdown.set(true); + return CompletableResultCode.ofSuccess(); + } + + /** + * Returns an unmodifiable snapshot of all exported {@link AuditRecordData}s in the order they + * were exported. + */ + public List getFinishedAuditRecords() { + synchronized (lock) { + return Collections.unmodifiableList(new ArrayList<>(finishedRecords)); + } + } + + /** Clears the list of finished records. */ + public void reset() { + synchronized (lock) { + finishedRecords.clear(); + } + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java new file mode 100644 index 00000000000..c1ad684df82 --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit.export; + +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.api.audit.AuditDeliveryException; +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.audit.AuditExportResult; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.audit.AuditRecordProcessor; +import io.opentelemetry.sdk.audit.ReadWriteAuditRecord; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An {@link AuditRecordProcessor} that passes each {@link AuditRecordData} directly to the + * configured {@link AuditRecordExporter} synchronously on the calling thread. + * + *

This is the default processor for synchronous {@code emit()} calls. It guarantees that + * {@code emit()} blocks until the exporter has acknowledged the record and returns the {@link + * AuditReceipt} from the sink. + * + *

For high-volume scenarios, consider {@link BatchAuditRecordProcessor}. + */ +public final class SimpleAuditRecordProcessor implements AuditRecordProcessor { + + private static final Logger logger = + Logger.getLogger(SimpleAuditRecordProcessor.class.getName()); + + private final AuditRecordExporter exporter; + private final Object exporterLock = new Object(); + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + + private SimpleAuditRecordProcessor(AuditRecordExporter exporter) { + this.exporter = exporter; + } + + /** + * Creates a new {@link SimpleAuditRecordProcessor} that synchronously exports to the given + * {@link AuditRecordExporter}. + */ + public static SimpleAuditRecordProcessor create(AuditRecordExporter exporter) { + requireNonNull(exporter, "exporter"); + return new SimpleAuditRecordProcessor(exporter); + } + + @Override + public void onEmit(Context context, ReadWriteAuditRecord record) { + if (isShutdown.get()) { + throw new AuditDeliveryException( + "SimpleAuditRecordProcessor has been shut down; refusing to emit record"); + } + List batch = Collections.singletonList(record.toAuditRecordData()); + AuditExportResult result; + synchronized (exporterLock) { + result = exporter.export(batch); + } + if (!result.isSuccess()) { + Throwable cause = result.getFailure(); + String msg = "Audit record export failed"; + if (cause != null) { + throw new AuditDeliveryException(msg, cause); + } + throw new AuditDeliveryException(msg); + } + List receipts = result.getReceipts(); + if (receipts.isEmpty()) { + throw new AuditDeliveryException( + "Exporter returned success but no AuditReceipt; check exporter implementation"); + } + record.setReceipt(receipts.get(0)); + } + + @Override + public CompletableResultCode shutdown() { + if (isShutdown.getAndSet(true)) { + return CompletableResultCode.ofSuccess(); + } + CompletableResultCode result = new CompletableResultCode(); + CompletableResultCode shutdownResult = exporter.shutdown(); + shutdownResult.whenComplete( + () -> { + if (shutdownResult.isSuccess()) { + result.succeed(); + } else { + logger.log(Level.WARNING, "Exporter failed to shut down cleanly"); + result.fail(); + } + }); + return result; + } + + @Override + public CompletableResultCode forceFlush() { + return exporter.flush(); + } + + /** Returns the configured {@link AuditRecordExporter}. */ + public AuditRecordExporter getExporter() { + return exporter; + } + + @Override + public String toString() { + return "SimpleAuditRecordProcessor{exporter=" + exporter + '}'; + } +} diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/package-info.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/package-info.java new file mode 100644 index 00000000000..ae58a6cbf1e --- /dev/null +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** OpenTelemetry Audit Logging SDK. */ +@ParametersAreNonnullByDefault +package io.opentelemetry.sdk.audit; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/settings.gradle.kts b/settings.gradle.kts index ecbfc83fdeb..1abd0937f18 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ dependencyResolutionManagement { rootProject.name = "opentelemetry-java" include(":all") include(":api:all") +include(":api:audit") include(":api:incubator") include(":api:testing-internal") include(":bom") @@ -44,6 +45,7 @@ include(":exporters:sender:okhttp") include(":exporters:logging") include(":exporters:logging-otlp") include(":exporters:otlp:all") +include(":exporters:otlp:audit") include(":exporters:otlp:common") include(":exporters:otlp:profiles") include(":exporters:otlp:testing-internal") @@ -61,6 +63,7 @@ include(":opentelemetry-jfr-profiles-shim") include(":opentracing-shim") include(":perf-harness") include(":sdk:all") +include(":sdk:audit") include(":sdk:common") include(":sdk:logs") include(":sdk:metrics") From 286d3a2d9d2a7915a6089b27090090458f81f64a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 28 Apr 2026 11:37:29 +0200 Subject: [PATCH 20/45] Ensure GHA work properly on fork or are disabled! Signed-off-by: Hilmar Falkenberg --- .github/workflows/build-daily.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index ec56fab0d91..01b89c059f0 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -17,6 +17,7 @@ jobs: publish-snapshots: if: github.repository == 'open-telemetry/opentelemetry-java' environment: protected + if: github.repository == 'open-telemetry/opentelemetry-java' runs-on: ubuntu-24.04 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 From ab977fac3f12c75a0bc989adb007f20dd93fd0b7 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 28 Apr 2026 17:32:09 +0200 Subject: [PATCH 21/45] refactor: migrated the separate package `api\audit` into `api\all` Signed-off-by: Hilmar Falkenberg --- .../java/io/opentelemetry/api/audit/ActorType.java | 0 .../api/audit/AuditDeliveryException.java | 0 .../io/opentelemetry/api/audit/AuditLogger.java | 0 .../opentelemetry/api/audit/AuditLoggerBuilder.java | 0 .../io/opentelemetry/api/audit/AuditProvider.java | 0 .../io/opentelemetry/api/audit/AuditReceipt.java | 0 .../opentelemetry/api/audit/AuditRecordBuilder.java | 0 .../opentelemetry/api/audit/DefaultAuditLogger.java | 0 .../api/audit/DefaultAuditProvider.java | 0 .../api/audit/GlobalAuditProvider.java | 0 .../java/io/opentelemetry/api/audit/Outcome.java | 0 .../io/opentelemetry/api/audit/package-info.java | 0 api/audit/build.gradle.kts | 13 ------------- api/audit/gradle.properties | 1 - sdk/audit/build.gradle.kts | 2 +- settings.gradle.kts | 1 - 16 files changed, 1 insertion(+), 16 deletions(-) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/ActorType.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/AuditLogger.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/AuditProvider.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/Outcome.java (100%) rename api/{audit => all}/src/main/java/io/opentelemetry/api/audit/package-info.java (100%) delete mode 100644 api/audit/build.gradle.kts delete mode 100644 api/audit/gradle.properties diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/ActorType.java b/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/ActorType.java rename to api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java rename to api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/AuditLogger.java rename to api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java rename to api/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/AuditProvider.java rename to api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java rename to api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java rename to api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java rename to api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java rename to api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java rename to api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/Outcome.java b/api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/Outcome.java rename to api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java diff --git a/api/audit/src/main/java/io/opentelemetry/api/audit/package-info.java b/api/all/src/main/java/io/opentelemetry/api/audit/package-info.java similarity index 100% rename from api/audit/src/main/java/io/opentelemetry/api/audit/package-info.java rename to api/all/src/main/java/io/opentelemetry/api/audit/package-info.java diff --git a/api/audit/build.gradle.kts b/api/audit/build.gradle.kts deleted file mode 100644 index 8042641c88d..00000000000 --- a/api/audit/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id("otel.java-conventions") - id("otel.publish-conventions") - id("otel.animalsniffer-conventions") -} - -description = "OpenTelemetry Audit Logging API" -otelJava.moduleName.set("io.opentelemetry.api.audit") - -dependencies { - api(project(":api:all")) - api(project(":context")) -} diff --git a/api/audit/gradle.properties b/api/audit/gradle.properties deleted file mode 100644 index 4476ae57e31..00000000000 --- a/api/audit/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -otel.release=alpha diff --git a/sdk/audit/build.gradle.kts b/sdk/audit/build.gradle.kts index 08717f180d1..fb81b3580c5 100644 --- a/sdk/audit/build.gradle.kts +++ b/sdk/audit/build.gradle.kts @@ -8,7 +8,7 @@ description = "OpenTelemetry Audit Logging SDK" otelJava.moduleName.set("io.opentelemetry.sdk.audit") dependencies { - api(project(":api:audit")) + api(project(":api:all")) api(project(":sdk:common")) annotationProcessor("com.google.auto.value:auto-value") diff --git a/settings.gradle.kts b/settings.gradle.kts index 1abd0937f18..87ecd3a7c4d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,7 +26,6 @@ dependencyResolutionManagement { rootProject.name = "opentelemetry-java" include(":all") include(":api:all") -include(":api:audit") include(":api:incubator") include(":api:testing-internal") include(":bom") From 0cf1ed0f0b7bca356d606649a9f24424cdcb33c3 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 29 Apr 2026 17:02:26 +0200 Subject: [PATCH 22/45] refactored fields to attributes Signed-off-by: Hilmar Falkenberg --- .../all/NoSharedInternalCodeTest.java | 2 + .../api/audit/AuditRecordBuilder.java | 83 ++++++++++------- .../api/audit/DefaultAuditLogger.java | 16 +++- .../api/audit/GlobalAuditProvider.java | 3 +- .../current_vs_latest/opentelemetry-api.txt | 89 +++++++++++++++++- ...emetry-sdk-extension-autoconfigure-spi.txt | 6 +- .../http/audit/AuditLogRecordDataAdapter.java | 90 +++++++++++++------ .../audit/OtlpHttpAuditRecordExporter.java | 31 +++---- .../OtlpHttpAuditRecordExporterBuilder.java | 11 +-- ...nfigurableAuditRecordExporterProvider.java | 6 +- .../AuditExporterConfiguration.java | 3 +- .../AuditProviderConfiguration.java | 9 +- .../sdk/audit/AuditExportResult.java | 21 +++-- .../sdk/audit/AuditRecordData.java | 41 +++++---- .../sdk/audit/AuditRecordProcessor.java | 6 +- .../sdk/audit/ReadWriteAuditRecord.java | 7 +- .../sdk/audit/SdkAuditProvider.java | 9 +- .../sdk/audit/SdkAuditProviderBuilder.java | 8 +- .../sdk/audit/SdkAuditRecordBuilder.java | 66 ++++++++++---- .../sdk/audit/SdkAuditRecordData.java | 17 ++-- .../sdk/audit/SdkReadWriteAuditRecord.java | 40 +++++---- .../export/BatchAuditRecordProcessor.java | 24 +++-- .../export/SimpleAuditRecordProcessor.java | 11 ++- 23 files changed, 399 insertions(+), 200 deletions(-) diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java index 1029239b65a..d700f568f59 100644 --- a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java +++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java @@ -35,10 +35,12 @@ class NoSharedInternalCodeTest { "opentelemetry-exporter-common", "opentelemetry-exporter-logging", "opentelemetry-exporter-logging-otlp", + "opentelemetry-exporter-otlp-audit", "opentelemetry-exporter-prometheus", "opentelemetry-extension-trace-propagators", "opentelemetry-opencensus-shim", "opentelemetry-sdk-common", + "opentelemetry-sdk-audit", "opentelemetry-sdk-logs", "opentelemetry-sdk-metrics", "opentelemetry-sdk-profiles", diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java index 9879ce78111..b3ca33a0b12 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java @@ -28,8 +28,9 @@ public interface AuditRecordBuilder { // ── Required fields ────────────────────────────────────────────────────── /** - * Sets the caller-generated unique identifier for this record. If not set, the SDK MUST generate - * a UUID v4. The value MUST remain stable across retries of the same event. + * Sets the caller-generated unique identifier for this record ({@code audit.record.id}). If not + * set, the SDK MUST generate a UUID v4. The value MUST remain stable across retries of the same + * event. */ AuditRecordBuilder setRecordId(String recordId); @@ -44,34 +45,28 @@ public interface AuditRecordBuilder { AuditRecordBuilder setTimestamp(Instant instant); /** - * Sets the semantic name that uniquely identifies the type of audit event, e.g. {@code - * "user.login.success"}. MUST be non-empty and stable across releases. + * Sets the semantic name that uniquely identifies the type of audit event ({@code EventName}), + * e.g. {@code "user.login.success"}. MUST be non-empty and stable across releases. */ AuditRecordBuilder setEventName(String eventName); /** - * Sets the identity of the entity that performed the auditable action. + * Sets the identity of the entity that performed the auditable action ({@code audit.actor.id}). * - *

MAY be a structured value. If the actor cannot be determined, set to a sentinel such as - * {@code "anonymous"}. + *

If the actor cannot be determined, set to a sentinel such as {@code "anonymous"}. */ - AuditRecordBuilder setActor(Value actor); + AuditRecordBuilder setActorId(String actorId); - /** Convenience overload of {@link #setActor(Value)} accepting a plain string. */ - default AuditRecordBuilder setActor(String actor) { - return setActor(Value.of(actor)); - } - - /** Sets the type of the actor. */ + /** Sets the type of the actor ({@code audit.actor.type}). */ AuditRecordBuilder setActorType(ActorType actorType); /** - * Sets the verb that describes what the actor did, e.g. {@code "LOGIN"}, {@code "READ"}, {@code - * "DELETE"}. MUST be non-empty and stable across releases. + * Sets the verb that describes what the actor did ({@code audit.action}), e.g. {@code "LOGIN"}, + * {@code "READ"}, {@code "DELETE"}. MUST be non-empty and stable across releases. */ AuditRecordBuilder setAction(String action); - /** Sets the result of the auditable action. */ + /** Sets the result of the auditable action ({@code audit.outcome}). */ AuditRecordBuilder setOutcome(Outcome outcome); // ── Optional fields ─────────────────────────────────────────────────────── @@ -85,17 +80,35 @@ default AuditRecordBuilder setActor(String actor) { /** Sets the epoch observed-timestamp using the given {@link Instant}. */ AuditRecordBuilder setObservedTimestamp(Instant instant); - /** Sets the schema version of the audit payload, e.g. {@code "1.0.0"}. */ + /** + * Sets the schema version of the audit payload ({@code audit.schema.version}), e.g. {@code + * "1.0.0"}. + */ AuditRecordBuilder setSchemaVersion(String schemaVersion); /** - * Sets the object upon which the action was performed, e.g. a file path, database row, or - * structured resource descriptor. + * Sets the identifier of the resource acted upon ({@code audit.target.id}), e.g. a file path, + * REST endpoint, or database table name. + */ + AuditRecordBuilder setTargetId(String targetId); + + /** + * Sets the type of the target resource ({@code audit.target.type}), e.g. {@code "file"}, {@code + * "http.endpoint"}, {@code "k8s.configmap"}. + */ + AuditRecordBuilder setTargetType(String targetType); + + /** + * Sets the network address or identifier of the source ({@code audit.source.id}), e.g. {@code + * "203.0.113.42"}. */ - AuditRecordBuilder setTargetResource(Value targetResource); + AuditRecordBuilder setSourceId(String sourceId); - /** Sets the source network address of the auditable action, e.g. {@code "203.0.113.42"}. */ - AuditRecordBuilder setSourceIp(String sourceIp); + /** + * Sets the type of the source address ({@code audit.source.type}), e.g. {@code "ipv4"}, {@code + * "ipv6"}, {@code "hostname"}. + */ + AuditRecordBuilder setSourceType(String sourceType); /** Sets free-form additional information about the audit event. */ AuditRecordBuilder setBody(Value body); @@ -106,8 +119,8 @@ default AuditRecordBuilder setBody(String body) { } /** - * Sets an attribute on this record. If the record already contains a mapping for the key, the - * old value is replaced. + * Sets an attribute on this record. If the record already contains a mapping for the key, the old + * value is replaced. * *

Providing a {@code null} value is a no-op and does not remove previously set values. */ @@ -115,32 +128,34 @@ default AuditRecordBuilder setBody(String body) { /** * Sets an asymmetric digital signature over the canonical serialization of this record and the - * algorithm used (e.g. {@code "ES256"}). MUST NOT be set together with {@link - * #setHmac(byte[], String)}. + * algorithm used (e.g. {@code "ES256"}). The encoded value is stored as {@code + * audit.integrity.value}. MUST NOT be set together with {@link #setHmac(byte[], String)}. */ AuditRecordBuilder setSignature(byte[] signature, String algorithm); /** - * Sets the DER-encoded X.509 public-key certificate corresponding to the signing key. Only - * meaningful when {@link #setSignature(byte[], String)} is also set. + * Sets the DER-encoded X.509 public-key certificate corresponding to the signing key ({@code + * audit.integrity.certificate} Resource attribute). Only meaningful when {@link + * #setSignature(byte[], String)} is also set. */ AuditRecordBuilder setCertificate(byte[] certificate); /** * Sets a symmetric HMAC over the canonical serialization of this record and the algorithm used - * (e.g. {@code "HMAC-SHA256"}). MUST NOT be set together with {@link - * #setSignature(byte[], String)}. + * (e.g. {@code "HMAC-SHA256"}). The encoded value is stored as {@code audit.integrity.value}. + * MUST NOT be set together with {@link #setSignature(byte[], String)}. */ AuditRecordBuilder setHmac(byte[] hmac, String algorithm); /** - * Sets the monotonically increasing sequence number for hash-chain continuity. When set, - * receivers can detect gaps that indicate lost or deleted records. + * Sets the monotonically increasing sequence number ({@code audit.sequence.number}) for + * hash-chain continuity. When set, receivers can detect gaps that indicate lost or deleted + * records. */ AuditRecordBuilder setSequenceNo(long sequenceNo); /** - * Sets the {@code IntegrityHash} of the immediately preceding record in the same audit stream, + * Sets the {@code audit.prev.hash} of the immediately preceding record in the same audit stream, * enabling hash-chain validation. */ AuditRecordBuilder setPrevHash(String prevHash); diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java index 162d4288323..973ee4d2321 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java @@ -53,7 +53,7 @@ public AuditRecordBuilder setEventName(String eventName) { } @Override - public AuditRecordBuilder setActor(Value actor) { + public AuditRecordBuilder setActorId(String actorId) { return this; } @@ -88,12 +88,22 @@ public AuditRecordBuilder setSchemaVersion(String schemaVersion) { } @Override - public AuditRecordBuilder setTargetResource(Value targetResource) { + public AuditRecordBuilder setTargetId(String targetId) { return this; } @Override - public AuditRecordBuilder setSourceIp(String sourceIp) { + public AuditRecordBuilder setTargetType(String targetType) { + return this; + } + + @Override + public AuditRecordBuilder setSourceId(String sourceId) { + return this; + } + + @Override + public AuditRecordBuilder setSourceType(String sourceType) { return this; } diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java index e5dbdf2fcac..8f660a7fa54 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java @@ -5,6 +5,7 @@ package io.opentelemetry.api.audit; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; /** @@ -26,7 +27,7 @@ private GlobalAuditProvider() {} /** Returns the globally registered {@link AuditProvider}, or the no-op instance if none set. */ public static AuditProvider get() { - return globalProvider.get(); + return Objects.requireNonNull(globalProvider.get()); } /** diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index d55eea5331d..ac7e1f91e5c 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,2 +1,89 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against opentelemetry-api-1.63.0.jar -No changes. \ No newline at end of file ++++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.lang.constant.Constable + +++ NEW INTERFACE: java.lang.Comparable + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.Enum + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType SYSTEM + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType SERVICE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType USER + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType valueOf(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType[] values() ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditDeliveryException (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.RuntimeException + +++ NEW CONSTRUCTOR: PUBLIC(+) AuditDeliveryException(java.lang.String) + +++ NEW CONSTRUCTOR: PUBLIC(+) AuditDeliveryException(java.lang.String, java.lang.Throwable) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLogger (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder auditRecordBuilder() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLogger build() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder setInstrumentationVersion(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder setSchemaUrl(java.lang.String) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditProvider (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder auditLoggerBuilder(java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditLogger get(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider noop() ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditReceipt (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditReceipt create(java.lang.String, java.lang.String, long) + +++ NEW METHOD: PUBLIC(+) boolean equals(java.lang.Object) + +++ NEW METHOD: PUBLIC(+) int hashCode() + +++ NEW METHOD: PUBLIC(+) java.lang.String integrityHash() + +++ NEW METHOD: PUBLIC(+) java.lang.String recordId() + +++ NEW METHOD: PUBLIC(+) long sinkTimestampEpochNanos() + +++ NEW METHOD: PUBLIC(+) java.lang.String toString() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditReceipt emit() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAction(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorType(io.opentelemetry.api.audit.ActorType) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAttribute(io.opentelemetry.api.common.AttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(io.opentelemetry.api.common.Value) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setCertificate(byte[]) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setEventName(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setHmac(byte[], java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(long, java.util.concurrent.TimeUnit) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(java.time.Instant) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setOutcome(io.opentelemetry.api.audit.Outcome) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setPrevHash(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setRecordId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSchemaVersion(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSequenceNo(long) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSignature(byte[], java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceType(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetType(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(long, java.util.concurrent.TimeUnit) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(java.time.Instant) ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.GlobalAuditProvider (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider get() + +++ NEW METHOD: PUBLIC(+) STATIC(+) void resetForTest() + +++ NEW METHOD: PUBLIC(+) STATIC(+) void set(io.opentelemetry.api.audit.AuditProvider) ++++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.lang.constant.Constable + +++ NEW INTERFACE: java.lang.Comparable + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.Enum + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome SUCCESS + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome UNKNOWN + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome FAILURE + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.Outcome valueOf(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.Outcome[] values() diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt index bc9b0beb5fc..e75f666a23a 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt @@ -1,2 +1,6 @@ Comparing source compatibility of opentelemetry-sdk-extension-autoconfigure-spi-1.64.0-SNAPSHOT.jar against opentelemetry-sdk-extension-autoconfigure-spi-1.63.0.jar -No changes. \ No newline at end of file ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.audit.ConfigurableAuditRecordExporterProvider (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.audit.AuditRecordExporter createExporter(io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String getName() diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java index 0be32c753ec..6dca46cb459 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java @@ -8,12 +8,16 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.audit.AuditRecordData; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.Body; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; +import java.util.Base64; +import java.util.Locale; import javax.annotation.Nullable; /** @@ -26,21 +30,27 @@ *

    *
  • {@code SeverityNumber} MUST remain unset ({@code null}). *
  • {@code InstrumentationScope} MUST be empty. - *
  • Mandatory audit fields are stored as {@code Attributes} with well-known keys. + *
  • Mandatory audit fields are stored as {@code Attributes} with {@code audit.*} keys. *
*/ final class AuditLogRecordDataAdapter implements LogRecordData { - private static final String ATTR_RECORD_ID = "audit.record_id"; - private static final String ATTR_ACTOR = "audit.actor"; - private static final String ATTR_ACTOR_TYPE = "audit.actor_type"; + // Mandatory attributes – spec section: Audit Semantic Attributes + private static final String ATTR_RECORD_ID = "audit.record.id"; + private static final String ATTR_ACTOR_ID = "audit.actor.id"; + private static final String ATTR_ACTOR_TYPE = "audit.actor.type"; private static final String ATTR_ACTION = "audit.action"; private static final String ATTR_OUTCOME = "audit.outcome"; - private static final String ATTR_TARGET_RESOURCE = "audit.target_resource"; - private static final String ATTR_SOURCE_IP = "audit.source_ip"; - private static final String ATTR_SCHEMA_VERSION = "audit.schema_version"; - private static final String ATTR_SEQUENCE_NO = "audit.sequence_no"; - private static final String ATTR_PREV_HASH = "audit.prev_hash"; + + // Optional attributes + private static final String ATTR_TARGET_ID = "audit.target.id"; + private static final String ATTR_TARGET_TYPE = "audit.target.type"; + private static final String ATTR_SOURCE_ID = "audit.source.id"; + private static final String ATTR_SOURCE_TYPE = "audit.source.type"; + private static final String ATTR_INTEGRITY_VALUE = "audit.integrity.value"; + private static final String ATTR_SEQUENCE_NUMBER = "audit.sequence.number"; + private static final String ATTR_PREV_HASH = "audit.prev.hash"; + private static final String ATTR_SCHEMA_VERSION = "audit.schema.version"; private final AuditRecordData audit; private final Attributes mergedAttributes; @@ -52,28 +62,52 @@ final class AuditLogRecordDataAdapter implements LogRecordData { private static Attributes buildAttributes(AuditRecordData a) { AttributesBuilder b = Attributes.builder(); - // Mandatory audit fields as attributes + + // Mandatory audit fields – spec-defined attribute keys and lowercase values b.put(AttributeKey.stringKey(ATTR_RECORD_ID), a.getRecordId()); - b.put(AttributeKey.stringKey(ATTR_ACTOR), a.getActor().asString()); - b.put(AttributeKey.stringKey(ATTR_ACTOR_TYPE), a.getActorType().name()); + b.put(AttributeKey.stringKey(ATTR_ACTOR_ID), a.getActorId()); + b.put( + AttributeKey.stringKey(ATTR_ACTOR_TYPE), a.getActorType().name().toLowerCase(Locale.ROOT)); b.put(AttributeKey.stringKey(ATTR_ACTION), a.getAction()); - b.put(AttributeKey.stringKey(ATTR_OUTCOME), a.getOutcome().name()); - // Optional audit fields - if (a.getTargetResource() != null) { - b.put(AttributeKey.stringKey(ATTR_TARGET_RESOURCE), a.getTargetResource().asString()); + b.put(AttributeKey.stringKey(ATTR_OUTCOME), a.getOutcome().name().toLowerCase(Locale.ROOT)); + + // Optional target attributes + if (a.getTargetId() != null) { + b.put(AttributeKey.stringKey(ATTR_TARGET_ID), a.getTargetId()); } - if (a.getSourceIp() != null) { - b.put(AttributeKey.stringKey(ATTR_SOURCE_IP), a.getSourceIp()); + if (a.getTargetType() != null) { + b.put(AttributeKey.stringKey(ATTR_TARGET_TYPE), a.getTargetType()); } - if (a.getSchemaVersion() != null) { - b.put(AttributeKey.stringKey(ATTR_SCHEMA_VERSION), a.getSchemaVersion()); + + // Optional source attributes + if (a.getSourceId() != null) { + b.put(AttributeKey.stringKey(ATTR_SOURCE_ID), a.getSourceId()); } + if (a.getSourceType() != null) { + b.put(AttributeKey.stringKey(ATTR_SOURCE_TYPE), a.getSourceType()); + } + + // Integrity value: base64-encode signature or HMAC into audit.integrity.value + byte[] integrityBytes = a.getSignature() != null ? a.getSignature() : a.getHmac(); + if (integrityBytes != null) { + b.put( + AttributeKey.stringKey(ATTR_INTEGRITY_VALUE), + Base64.getEncoder().encodeToString(integrityBytes)); + } + + // Ordering attributes if (a.getSequenceNo() != 0) { - b.put(AttributeKey.longKey(ATTR_SEQUENCE_NO), a.getSequenceNo()); + b.put(AttributeKey.longKey(ATTR_SEQUENCE_NUMBER), a.getSequenceNo()); } if (a.getPrevHash() != null) { b.put(AttributeKey.stringKey(ATTR_PREV_HASH), a.getPrevHash()); } + + // Schema version + if (a.getSchemaVersion() != null) { + b.put(AttributeKey.stringKey(ATTR_SCHEMA_VERSION), a.getSchemaVersion()); + } + // User-supplied attributes (merged last so they can override if needed) a.getAttributes() .forEach( @@ -82,6 +116,7 @@ private static Attributes buildAttributes(AuditRecordData a) { AttributeKey castKey = (AttributeKey) key; b.put(castKey, value); }); + return b.build(); } @@ -111,11 +146,10 @@ public SpanContext getSpanContext() { return SpanContext.getInvalid(); } - /** Audit records do not use severity; always returns {@code null}. */ + /** Audit records do not use severity. */ @Override - @Nullable public Severity getSeverity() { - return null; + return Severity.UNDEFINED_SEVERITY_NUMBER; } @Override @@ -124,9 +158,15 @@ public String getSeverityText() { return null; } + @Override + @Deprecated + public Body getBody() { + return audit.getBody() != null ? Body.string(audit.getBody().asString()) : Body.empty(); + } + @Override @Nullable - public io.opentelemetry.api.common.Value getBodyValue() { + public Value getBodyValue() { return audit.getBody(); } diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java index ab5d78976f5..6607c8beb9a 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java @@ -5,21 +5,19 @@ package io.opentelemetry.exporter.otlp.http.audit; -import static java.util.Objects.requireNonNull; - +import io.opentelemetry.api.audit.AuditDeliveryException; import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.exporter.internal.http.HttpExporter; import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler; -import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; import io.opentelemetry.sdk.audit.AuditExportResult; import io.opentelemetry.sdk.audit.AuditRecordData; import io.opentelemetry.sdk.audit.AuditRecordExporter; import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.internal.ComponentId; import io.opentelemetry.sdk.logs.data.LogRecordData; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.StringJoiner; import java.util.concurrent.CountDownLatch; @@ -31,8 +29,8 @@ /** * Exports {@link AuditRecordData}s using OTLP/HTTP to the dedicated {@code /v1/audit} endpoint. * - *

Audit records are serialized as OTLP {@code LogRecord} protobuf messages (reusing the - * {@code ExportLogsServiceRequest} envelope) with mandatory audit fields stored as attributes. + *

Audit records are serialized as OTLP {@code LogRecord} protobuf messages (reusing the {@code + * ExportLogsServiceRequest} envelope) with mandatory audit fields stored as attributes. * *

The OTLP receiver MUST NOT respond with {@code partial_success}; any partial-success response * is treated as a hard failure and all records in the batch are retained for retry. @@ -43,8 +41,6 @@ public final class OtlpHttpAuditRecordExporter implements AuditRecordExporter { static final String DEFAULT_ENDPOINT = "http://localhost:4318/v1/audit"; - private static final ComponentId COMPONENT_ID = - ComponentId.generateLazy("otlp_http_audit_exporter"); private final HttpExporterBuilder builder; private final HttpExporter delegate; @@ -67,21 +63,20 @@ public static OtlpHttpAuditRecordExporterBuilder builder() { /** * Exports the given audit records to the configured OTLP {@code /v1/audit} endpoint. * - *

Audit records are adapted to OTLP {@code LogRecord}s via {@link - * AuditLogRecordDataAdapter}. The {@code InstrumentationScope} is left empty and {@code - * SeverityNumber} is unset per the audit logging specification. + *

Audit records are adapted to OTLP {@code LogRecord}s via {@link AuditLogRecordDataAdapter}. + * The {@code InstrumentationScope} is left empty and {@code SeverityNumber} is unset per the + * audit logging specification. * - *

Returns synthetic {@link AuditReceipt}s on success. The {@code IntegrityHash} field is - * empty in this implementation; a future OTLP response extension will carry the sink-computed - * hash. + *

Returns synthetic {@link AuditReceipt}s on success. The {@code IntegrityHash} field is empty + * in this implementation; a future OTLP response extension will carry the sink-computed hash. * - *

Any {@code partial_success} response is treated as a hard failure (all records are - * returned in the failure result for retry). + *

Any {@code partial_success} response is treated as a hard failure (all records are returned + * in the failure result for retry). */ @Override public AuditExportResult export(Collection records) { if (records.isEmpty()) { - return AuditExportResult.success(java.util.Collections.emptyList()); + return AuditExportResult.success(Collections.emptyList()); } // Adapt AuditRecordData → LogRecordData for marshaling @@ -117,7 +112,7 @@ public AuditExportResult export(Collection records) { if (latch.getCount() > 0) { return AuditExportResult.failure( - new io.opentelemetry.api.audit.AuditDeliveryException( + new AuditDeliveryException( "OTLP export timed out waiting for /v1/audit acknowledgement")); } diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java index fe10357ca5b..224a5cd73be 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java @@ -9,9 +9,8 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; -import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; import io.opentelemetry.sdk.common.export.RetryPolicy; -import io.opentelemetry.sdk.common.internal.ComponentId; +import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.time.Duration; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -26,15 +25,13 @@ */ public final class OtlpHttpAuditRecordExporterBuilder { - private static final ComponentId COMPONENT_ID = - ComponentId.generateLazy("otlp_http_audit_exporter"); - private final HttpExporterBuilder delegate; OtlpHttpAuditRecordExporterBuilder() { this.delegate = - new HttpExporterBuilder(COMPONENT_ID, OtlpHttpAuditRecordExporter.DEFAULT_ENDPOINT); - OtlpUserAgent.addUserAgentHeader(delegate::addConstantHeaders); + new HttpExporterBuilder( + StandardComponentId.ExporterType.OTLP_HTTP_LOG_EXPORTER, + OtlpHttpAuditRecordExporter.DEFAULT_ENDPOINT); } OtlpHttpAuditRecordExporterBuilder(HttpExporterBuilder delegate) { diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java index 73de13ad513..d714900f6c2 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/audit/ConfigurableAuditRecordExporterProvider.java @@ -9,9 +9,9 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; /** - * A service provider interface (SPI) for providing audit record exporters that can be used with - * the autoconfigured SDK. If the {@code otel.audit.exporter} property contains a value equal to - * what is returned by {@link #getName()}, the exporter returned by {@link + * A service provider interface (SPI) for providing audit record exporters that can be used with the + * autoconfigured SDK. If the {@code otel.audit.exporter} property contains a value equal to what is + * returned by {@link #getName()}, the exporter returned by {@link * #createExporter(ConfigProperties)} will be enabled and added to the audit pipeline. * *

This SPI is at {@code Development} stability; the interface may change in future releases. diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java index 6130eab6c0c..0c73d19f0c8 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditExporterConfiguration.java @@ -39,8 +39,7 @@ static Map configureAuditRecordExporters( exporterNames = Collections.singleton("otlp"); } - NamedSpiManager spiManager = - auditExporterSpiManager(config, spiHelper); + NamedSpiManager spiManager = auditExporterSpiManager(config, spiHelper); Map map = new HashMap<>(); for (String name : exporterNames) { diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java index 69a3ca2c78a..1a4bda0df96 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AuditProviderConfiguration.java @@ -19,8 +19,8 @@ import java.util.Map; /** - * Configures an {@link SdkAuditProvider} from autoconfiguration properties and registers it as - * the global {@link io.opentelemetry.api.audit.AuditProvider}. + * Configures an {@link SdkAuditProvider} from autoconfiguration properties and registers it as the + * global {@link io.opentelemetry.api.audit.AuditProvider}. * *

The property {@code otel.audit.exporter} controls which exporter is used (default: {@code * otlp}). Use {@code otel.audit.exporter=none} to disable audit logging. @@ -28,10 +28,7 @@ final class AuditProviderConfiguration { static SdkAuditProvider configureAuditProvider( - Resource resource, - ConfigProperties config, - SpiHelper spiHelper, - List closeables) { + Resource resource, ConfigProperties config, SpiHelper spiHelper, List closeables) { Map exportersByName = AuditExporterConfiguration.configureAuditRecordExporters(config, spiHelper, closeables); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java index 4b122fce7ec..20fbc531ab3 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.audit.AuditReceipt; import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; /** * The synchronous result of an {@link AuditRecordExporter#export} call. @@ -20,9 +21,10 @@ public final class AuditExportResult { private final boolean success; private final List receipts; - private final Throwable failure; + @Nullable private final Throwable failure; - private AuditExportResult(boolean success, List receipts, Throwable failure) { + private AuditExportResult( + boolean success, List receipts, @Nullable Throwable failure) { this.success = success; this.receipts = receipts; this.failure = failure; @@ -30,17 +32,17 @@ private AuditExportResult(boolean success, List receipts, Throwabl /** Creates a successful result with the given receipts. */ public static AuditExportResult success(List receipts) { - return new AuditExportResult(true, Collections.unmodifiableList(receipts), null); + return new AuditExportResult(/* success= */ true, Collections.unmodifiableList(receipts), null); } /** Creates a failure result with the given cause. */ public static AuditExportResult failure(Throwable cause) { - return new AuditExportResult(false, Collections.emptyList(), cause); + return new AuditExportResult(/* success= */ false, Collections.emptyList(), cause); } /** Creates a failure result without a specific cause. */ public static AuditExportResult failure() { - return new AuditExportResult(false, Collections.emptyList(), null); + return new AuditExportResult(/* success= */ false, Collections.emptyList(), null); } /** Returns {@code true} if all records were successfully acknowledged by the audit sink. */ @@ -49,17 +51,18 @@ public boolean isSuccess() { } /** - * Returns the {@link AuditReceipt}s returned by the audit sink, one per exported record. Empty - * if {@link #isSuccess()} is {@code false}. + * Returns the {@link AuditReceipt}s returned by the audit sink, one per exported record. Empty if + * {@link #isSuccess()} is {@code false}. */ public List getReceipts() { return receipts; } /** - * Returns the cause of the failure, or {@code null} if the failure has no associated throwable - * or if the export succeeded. + * Returns the cause of the failure, or {@code null} if the failure has no associated throwable or + * if the export succeeded. */ + @Nullable public Throwable getFailure() { return failure; } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java index 452c4c004a0..720cba5f64a 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java @@ -49,25 +49,33 @@ public interface AuditRecordData { /** Returns the semantic name of the audit event, e.g. {@code "user.login.success"}. */ String getEventName(); - /** Returns the identity of the actor that performed the action. */ - Value getActor(); + /** Returns the identity of the actor ({@code audit.actor.id}). */ + String getActorId(); - /** Returns the type of the actor. */ + /** Returns the type of the actor ({@code audit.actor.type}). */ ActorType getActorType(); - /** Returns the action verb, e.g. {@code "LOGIN"}, {@code "DELETE"}. */ + /** Returns the action verb ({@code audit.action}), e.g. {@code "LOGIN"}, {@code "DELETE"}. */ String getAction(); - /** Returns the outcome of the action. */ + /** Returns the outcome of the action ({@code audit.outcome}). */ Outcome getOutcome(); - /** Returns the target resource of the action, or {@code null} if not set. */ + /** Returns the {@code audit.target.id}, or {@code null} if not set. */ @Nullable - Value getTargetResource(); + String getTargetId(); - /** Returns the source IP address, or {@code null} if not set. */ + /** Returns the {@code audit.target.type}, or {@code null} if not set. */ @Nullable - String getSourceIp(); + String getTargetType(); + + /** Returns the {@code audit.source.id}, or {@code null} if not set. */ + @Nullable + String getSourceId(); + + /** Returns the {@code audit.source.type}, or {@code null} if not set. */ + @Nullable + String getSourceType(); /** Returns the free-form body, or {@code null} if not set. */ @Nullable @@ -77,6 +85,7 @@ public interface AuditRecordData { Attributes getAttributes(); /** Returns the optional digital signature bytes, or {@code null} if not set. */ + @SuppressWarnings("mutable") @Nullable byte[] getSignature(); @@ -85,10 +94,12 @@ public interface AuditRecordData { String getAlgorithm(); /** Returns the DER-encoded X.509 certificate, or {@code null} if not set. */ + @SuppressWarnings("mutable") @Nullable byte[] getCertificate(); /** Returns the HMAC bytes, or {@code null} if not set. */ + @SuppressWarnings("mutable") @Nullable byte[] getHmac(); @@ -96,17 +107,17 @@ public interface AuditRecordData { @Nullable String getHmacAlgorithm(); - /** - * Returns the monotonic sequence number for hash-chain continuity, or {@code 0} if not set. - */ + /** Returns the monotonic sequence number for hash-chain continuity, or {@code 0} if not set. */ long getSequenceNo(); - /** Returns the {@code IntegrityHash} of the preceding record for hash-chain linking, or {@code - * null} if not set. */ + /** + * Returns the {@code audit.prev.hash} of the preceding record for hash-chain linking, or {@code + * null} if not set. + */ @Nullable String getPrevHash(); - /** Returns the schema version of the audit payload, or {@code null} if not set. */ + /** Returns the {@code audit.schema.version}, or {@code null} if not set. */ @Nullable String getSchemaVersion(); } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java index 23ee5ec7242..5e058aae881 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java @@ -8,8 +8,8 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.CompletableResultCode; import java.io.Closeable; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.ThreadSafe; @@ -18,8 +18,8 @@ * Interface for hooking into the audit record pipeline for enrichment and forwarding. * *

Processors MUST only add attributes to records (enrichment). They MUST NOT remove mandatory - * fields, filter records, aggregate records, or introduce sampling. Processors that would remove - * or filter records are rejected at configuration time by {@link SdkAuditProvider}. + * fields, filter records, aggregate records, or introduce sampling. Processors that would remove or + * filter records are rejected at configuration time by {@link SdkAuditProvider}. */ @ThreadSafe public interface AuditRecordProcessor extends Closeable { diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java index dce7cf62390..b438a49d18a 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/ReadWriteAuditRecord.java @@ -5,18 +5,17 @@ package io.opentelemetry.sdk.audit; -import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Value; import javax.annotation.Nullable; /** * Mutable view of an {@link AuditRecordData} passed to {@link AuditRecordProcessor#onEmit}. * *

Processors MAY enrich the record by calling {@link #setAttribute}. They MUST NOT modify the - * mandatory audit fields ({@code EventName}, {@code Actor}, {@code ActorType}, {@code Action}, + * mandatory audit fields ({@code EventName}, {@code ActorId}, {@code ActorType}, {@code Action}, * {@code Outcome}): those are exposed as read-only accessors. * *

The {@link #setReceipt}/{@link #getReceipt} pair is used internally by the SDK to thread the @@ -53,7 +52,7 @@ public interface ReadWriteAuditRecord { String getEventName(); - Value getActor(); + String getActorId(); ActorType getActorType(); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java index 18ed766b883..1bb5e646735 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java @@ -6,7 +6,6 @@ package io.opentelemetry.sdk.audit; import io.opentelemetry.api.audit.AuditDeliveryException; -import io.opentelemetry.api.audit.AuditLogger; import io.opentelemetry.api.audit.AuditLoggerBuilder; import io.opentelemetry.api.audit.AuditProvider; import io.opentelemetry.sdk.common.Clock; @@ -163,8 +162,12 @@ String getSchemaUrl() { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof AuditLoggerKey)) return false; + if (this == obj) { + return true; + } + if (!(obj instanceof AuditLoggerKey)) { + return false; + } AuditLoggerKey other = (AuditLoggerKey) obj; return name.equals(other.name) && Objects.equals(version, other.version) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java index 9f0fae27abb..af940bf027a 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java @@ -19,9 +19,7 @@ public final class SdkAuditProviderBuilder { SdkAuditProviderBuilder() {} - /** - * Sets the {@link Resource} to be associated with all audit records emitted by this provider. - */ + /** Sets the {@link Resource} to be associated with all audit records emitted by this provider. */ public SdkAuditProviderBuilder setResource(Resource resource) { if (resource == null) { throw new NullPointerException("resource"); @@ -40,8 +38,8 @@ public SdkAuditProviderBuilder setClock(Clock clock) { } /** - * Adds an {@link AuditRecordProcessor} to the pipeline. Processors are invoked in the order - * they are added. + * Adds an {@link AuditRecordProcessor} to the pipeline. Processors are invoked in the order they + * are added. * *

The last processor in the chain is responsible for forwarding records to the exporter and * setting the {@link io.opentelemetry.api.audit.AuditReceipt} on the record. diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index 8af4da2892e..aa609cc05ca 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -15,6 +15,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.internal.AttributesMap; import java.time.Instant; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -29,7 +30,7 @@ final class SdkAuditRecordBuilder implements AuditRecordBuilder { @Nullable private String recordId; private long timestampEpochNanos; @Nullable private String eventName; - @Nullable private Value actor; + @Nullable private String actorId; @Nullable private ActorType actorType; @Nullable private String action; @Nullable private Outcome outcome; @@ -37,8 +38,10 @@ final class SdkAuditRecordBuilder implements AuditRecordBuilder { // Optional fields private long observedTimestampEpochNanos; @Nullable private String schemaVersion; - @Nullable private Value targetResource; - @Nullable private String sourceIp; + @Nullable private String targetId; + @Nullable private String targetType; + @Nullable private String sourceId; + @Nullable private String sourceType; @Nullable private Value body; @Nullable private AttributesMap attributes; @Nullable private byte[] signature; @@ -80,8 +83,8 @@ public SdkAuditRecordBuilder setEventName(String eventName) { } @Override - public SdkAuditRecordBuilder setActor(Value actor) { - this.actor = actor; + public SdkAuditRecordBuilder setActorId(String actorId) { + this.actorId = actorId; return this; } @@ -123,14 +126,26 @@ public SdkAuditRecordBuilder setSchemaVersion(String schemaVersion) { } @Override - public SdkAuditRecordBuilder setTargetResource(Value targetResource) { - this.targetResource = targetResource; + public SdkAuditRecordBuilder setTargetId(String targetId) { + this.targetId = targetId; return this; } @Override - public SdkAuditRecordBuilder setSourceIp(String sourceIp) { - this.sourceIp = sourceIp; + public SdkAuditRecordBuilder setTargetType(String targetType) { + this.targetType = targetType; + return this; + } + + @Override + public SdkAuditRecordBuilder setSourceId(String sourceId) { + this.sourceId = sourceId; + return this; + } + + @Override + public SdkAuditRecordBuilder setSourceType(String sourceType) { + this.sourceType = sourceType; return this; } @@ -203,12 +218,23 @@ public AuditReceipt emit() { // Step 3: Validate required fields validateRequired("Timestamp", timestampEpochNanos != 0, "Timestamp must be set"); - validateRequired("EventName", eventName != null && !eventName.isEmpty(), "EventName must be set and non-empty"); - validateRequired("Actor", actor != null, "Actor must be set"); + validateRequired( + "EventName", + eventName != null && !eventName.isEmpty(), + "EventName must be set and non-empty"); + validateRequired( + "ActorId", actorId != null && !actorId.isEmpty(), "ActorId must be set and non-empty"); validateRequired("ActorType", actorType != null, "ActorType must be set"); - validateRequired("Action", action != null && !action.isEmpty(), "Action must be set and non-empty"); + validateRequired( + "Action", action != null && !action.isEmpty(), "Action must be set and non-empty"); validateRequired("Outcome", outcome != null, "Outcome must be set"); + String validatedEventName = Objects.requireNonNull(eventName); + String validatedActorId = Objects.requireNonNull(actorId); + ActorType validatedActorType = Objects.requireNonNull(actorType); + String validatedAction = Objects.requireNonNull(action); + Outcome validatedOutcome = Objects.requireNonNull(outcome); + // Step 4+5: Create the mutable record and pass it through all processors. // Transfer ownership of the attributes map to the record (builder must not be reused). AttributesMap recordAttributes = this.attributes; @@ -222,13 +248,15 @@ public AuditReceipt emit() { recordId, timestampEpochNanos, observedTimestampEpochNanos, - eventName, - actor, - actorType, - action, - outcome, - targetResource, - sourceIp, + validatedEventName, + validatedActorId, + validatedActorType, + validatedAction, + validatedOutcome, + targetId, + targetType, + sourceId, + sourceType, body, recordAttributes, signature, diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java index 413bc703adf..2416faa6417 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java @@ -15,6 +15,7 @@ /** Immutable AutoValue implementation of {@link AuditRecordData}. */ @AutoValue +@SuppressWarnings("AutoValueMutable") public abstract class SdkAuditRecordData implements AuditRecordData { SdkAuditRecordData() {} @@ -30,12 +31,14 @@ public static SdkAuditRecordData create( long timestampEpochNanos, long observedTimestampEpochNanos, String eventName, - Value actor, + String actorId, ActorType actorType, String action, Outcome outcome, - @Nullable Value targetResource, - @Nullable String sourceIp, + @Nullable String targetId, + @Nullable String targetType, + @Nullable String sourceId, + @Nullable String sourceType, @Nullable Value body, Attributes attributes, @Nullable byte[] signature, @@ -55,12 +58,14 @@ public static SdkAuditRecordData create( timestampEpochNanos, observedTimestampEpochNanos, eventName, - actor, + actorId, actorType, action, outcome, - targetResource, - sourceIp, + targetId, + targetType, + sourceId, + sourceType, body, attributes, signature, diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java index 206c66bc37a..01ddfedd77c 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java @@ -5,8 +5,8 @@ package io.opentelemetry.sdk.audit; -import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -32,12 +32,14 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { private final long timestampEpochNanos; private final long observedTimestampEpochNanos; private final String eventName; - private final Value actor; + private final String actorId; private final ActorType actorType; private final String action; private final Outcome outcome; - @Nullable private final Value targetResource; - @Nullable private final String sourceIp; + @Nullable private final String targetId; + @Nullable private final String targetType; + @Nullable private final String sourceId; + @Nullable private final String sourceType; @Nullable private final Value body; @Nullable private final byte[] signature; @Nullable private final String algorithm; @@ -68,12 +70,14 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { long timestampEpochNanos, long observedTimestampEpochNanos, String eventName, - Value actor, + String actorId, ActorType actorType, String action, Outcome outcome, - @Nullable Value targetResource, - @Nullable String sourceIp, + @Nullable String targetId, + @Nullable String targetType, + @Nullable String sourceId, + @Nullable String sourceType, @Nullable Value body, @Nullable AttributesMap attributes, @Nullable byte[] signature, @@ -92,12 +96,14 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { this.timestampEpochNanos = timestampEpochNanos; this.observedTimestampEpochNanos = observedTimestampEpochNanos; this.eventName = eventName; - this.actor = actor; + this.actorId = actorId; this.actorType = actorType; this.action = action; this.outcome = outcome; - this.targetResource = targetResource; - this.sourceIp = sourceIp; + this.targetId = targetId; + this.targetType = targetType; + this.sourceId = sourceId; + this.sourceType = sourceType; this.body = body; this.attributes = attributes; this.signature = signature; @@ -141,7 +147,7 @@ public AuditReceipt getReceipt() { @Override public AuditRecordData toAuditRecordData() { - final Attributes frozenAttributes; + Attributes frozenAttributes; synchronized (lock) { frozenAttributes = attributes != null ? attributes.immutableCopy() : Attributes.empty(); } @@ -154,12 +160,14 @@ public AuditRecordData toAuditRecordData() { timestampEpochNanos, observedTimestampEpochNanos, eventName, - actor, + actorId, actorType, action, outcome, - targetResource, - sourceIp, + targetId, + targetType, + sourceId, + sourceType, body, frozenAttributes, signature, @@ -190,8 +198,8 @@ public String getEventName() { } @Override - public Value getActor() { - return actor; + public String getActorId() { + return actorId; } @Override diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java index 095f3e8ab8f..995db2f9f5b 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessor.java @@ -46,8 +46,7 @@ */ public final class BatchAuditRecordProcessor implements AuditRecordProcessor { - private static final Logger logger = - Logger.getLogger(BatchAuditRecordProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(BatchAuditRecordProcessor.class.getName()); private final AuditRecordExporter exporter; private final int maxExportBatchSize; @@ -58,7 +57,6 @@ public final class BatchAuditRecordProcessor implements AuditRecordProcessor { // Queue of pending records. BlockingQueue to apply back-pressure when full. private final BlockingQueue queue; - private final int maxQueueSize; private final AtomicBoolean isShutdown = new AtomicBoolean(false); private final AtomicReference flushRequested = new AtomicReference<>(); @@ -75,7 +73,6 @@ public final class BatchAuditRecordProcessor implements AuditRecordProcessor { int maxRetryCount, long initialBackoffMillis) { this.exporter = exporter; - this.maxQueueSize = maxQueueSize; this.maxExportBatchSize = maxExportBatchSize; this.scheduledDelayMillis = scheduledDelayMillis; this.exportTimeoutMillis = exportTimeoutMillis; @@ -128,7 +125,7 @@ public void onEmit(Context context, ReadWriteAuditRecord record) { if (cause instanceof AuditDeliveryException) { throw (AuditDeliveryException) cause; } - throw new AuditDeliveryException("Audit export failed", cause); + throw new AuditDeliveryException("Audit export failed", cause != null ? cause : e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new AuditDeliveryException("Interrupted while waiting for audit export", e); @@ -141,11 +138,7 @@ public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } CompletableResultCode result = forceFlush(); - result.whenComplete( - () -> { - worker.interrupt(); - exporter.shutdown(); - }); + result.whenComplete(exporter::shutdown); return result; } @@ -202,15 +195,20 @@ private void exportBatch(List batch) { if (receipt != null) { batch.get(i).future.complete(receipt); } else { - batch.get(i).future.completeExceptionally( - new AuditDeliveryException("Exporter returned no receipt for record " + i)); + batch + .get(i) + .future + .completeExceptionally( + new AuditDeliveryException("Exporter returned no receipt for record " + i)); } } } else { AuditDeliveryException ex = new AuditDeliveryException( "Audit export failed after " + maxRetryCount + " retries", - result.getFailure()); + result.getFailure() != null + ? result.getFailure() + : new IllegalStateException("Audit export failed without cause")); for (PendingRecord p : batch) { p.future.completeExceptionally(ex); } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java index c1ad684df82..6087552b824 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessor.java @@ -26,16 +26,15 @@ * An {@link AuditRecordProcessor} that passes each {@link AuditRecordData} directly to the * configured {@link AuditRecordExporter} synchronously on the calling thread. * - *

This is the default processor for synchronous {@code emit()} calls. It guarantees that - * {@code emit()} blocks until the exporter has acknowledged the record and returns the {@link + *

This is the default processor for synchronous {@code emit()} calls. It guarantees that {@code + * emit()} blocks until the exporter has acknowledged the record and returns the {@link * AuditReceipt} from the sink. * *

For high-volume scenarios, consider {@link BatchAuditRecordProcessor}. */ public final class SimpleAuditRecordProcessor implements AuditRecordProcessor { - private static final Logger logger = - Logger.getLogger(SimpleAuditRecordProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(SimpleAuditRecordProcessor.class.getName()); private final AuditRecordExporter exporter; private final Object exporterLock = new Object(); @@ -46,8 +45,8 @@ private SimpleAuditRecordProcessor(AuditRecordExporter exporter) { } /** - * Creates a new {@link SimpleAuditRecordProcessor} that synchronously exports to the given - * {@link AuditRecordExporter}. + * Creates a new {@link SimpleAuditRecordProcessor} that synchronously exports to the given {@link + * AuditRecordExporter}. */ public static SimpleAuditRecordProcessor create(AuditRecordExporter exporter) { requireNonNull(exporter, "exporter"); From 2b42761bb4ed08cb9afa3caa986c3e4e44a2a835 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 29 Apr 2026 17:36:12 +0200 Subject: [PATCH 23/45] integrityValue + algorithm + certificate Signed-off-by: Hilmar Falkenberg --- .../api/audit/AuditRecordBuilder.java | 23 ++------ .../api/audit/DefaultAuditLogger.java | 12 +--- .../current_vs_latest/opentelemetry-api.txt | 4 +- .../http/audit/AuditLogRecordDataAdapter.java | 4 +- .../sdk/audit/AuditRecordData.java | 26 ++------- .../sdk/audit/SdkAuditProviderBuilder.java | 57 ++++++++++++++++++- .../sdk/audit/SdkAuditRecordBuilder.java | 30 ++-------- .../sdk/audit/SdkAuditRecordData.java | 12 +--- .../sdk/audit/SdkReadWriteAuditRecord.java | 24 ++------ 9 files changed, 81 insertions(+), 111 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java index b3ca33a0b12..547ae61b6c8 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java @@ -127,25 +127,12 @@ default AuditRecordBuilder setBody(String body) { AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value); /** - * Sets an asymmetric digital signature over the canonical serialization of this record and the - * algorithm used (e.g. {@code "ES256"}). The encoded value is stored as {@code - * audit.integrity.value}. MUST NOT be set together with {@link #setHmac(byte[], String)}. + * Sets the raw bytes of the cryptographic integrity proof ({@code audit.integrity.value}). The + * value is base64-encoded by the SDK before storing as an attribute. The algorithm used to + * compute the proof (e.g. {@code "ES256"} or {@code "HMAC-SHA256"}) MUST be declared once via + * {@code SdkAuditProviderBuilder.setIntegrityAlgorithm(String)}. */ - AuditRecordBuilder setSignature(byte[] signature, String algorithm); - - /** - * Sets the DER-encoded X.509 public-key certificate corresponding to the signing key ({@code - * audit.integrity.certificate} Resource attribute). Only meaningful when {@link - * #setSignature(byte[], String)} is also set. - */ - AuditRecordBuilder setCertificate(byte[] certificate); - - /** - * Sets a symmetric HMAC over the canonical serialization of this record and the algorithm used - * (e.g. {@code "HMAC-SHA256"}). The encoded value is stored as {@code audit.integrity.value}. - * MUST NOT be set together with {@link #setSignature(byte[], String)}. - */ - AuditRecordBuilder setHmac(byte[] hmac, String algorithm); + AuditRecordBuilder setIntegrityValue(byte[] integrityValue); /** * Sets the monotonically increasing sequence number ({@code audit.sequence.number}) for diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java index 973ee4d2321..cc299aa8b0b 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java @@ -118,17 +118,7 @@ public AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T valu } @Override - public AuditRecordBuilder setSignature(byte[] signature, String algorithm) { - return this; - } - - @Override - public AuditRecordBuilder setCertificate(byte[] certificate) { - return this; - } - - @Override - public AuditRecordBuilder setHmac(byte[] hmac, String algorithm) { + public AuditRecordBuilder setIntegrityValue(byte[] integrityValue) { return this; } diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index ac7e1f91e5c..778e0c94977 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -53,9 +53,8 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against GENERIC TEMPLATES: +++ T:java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(io.opentelemetry.api.common.Value) +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setCertificate(byte[]) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setEventName(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setHmac(byte[], java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setIntegrityValue(byte[]) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(long, java.util.concurrent.TimeUnit) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(java.time.Instant) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setOutcome(io.opentelemetry.api.audit.Outcome) @@ -63,7 +62,6 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setRecordId(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSchemaVersion(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSequenceNo(long) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSignature(byte[], java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceId(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceType(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetId(java.lang.String) diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java index 6dca46cb459..40e24ab1079 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java @@ -87,8 +87,8 @@ private static Attributes buildAttributes(AuditRecordData a) { b.put(AttributeKey.stringKey(ATTR_SOURCE_TYPE), a.getSourceType()); } - // Integrity value: base64-encode signature or HMAC into audit.integrity.value - byte[] integrityBytes = a.getSignature() != null ? a.getSignature() : a.getHmac(); + // Integrity value: base64-encode the proof into audit.integrity.value + byte[] integrityBytes = a.getIntegrityValue(); if (integrityBytes != null) { b.put( AttributeKey.stringKey(ATTR_INTEGRITY_VALUE), diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java index 720cba5f64a..f04d03805a3 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java @@ -84,28 +84,14 @@ public interface AuditRecordData { /** Returns the attributes attached to this record (never null; may be empty). */ Attributes getAttributes(); - /** Returns the optional digital signature bytes, or {@code null} if not set. */ - @SuppressWarnings("mutable") - @Nullable - byte[] getSignature(); - - /** Returns the signature algorithm, or {@code null} if {@link #getSignature()} is not set. */ - @Nullable - String getAlgorithm(); - - /** Returns the DER-encoded X.509 certificate, or {@code null} if not set. */ - @SuppressWarnings("mutable") - @Nullable - byte[] getCertificate(); - - /** Returns the HMAC bytes, or {@code null} if not set. */ + /** + * Returns the raw bytes of the cryptographic integrity proof ({@code audit.integrity.value}), or + * {@code null} if not set. The algorithm is carried as the {@code audit.integrity.algorithm} + * Resource attribute. + */ @SuppressWarnings("mutable") @Nullable - byte[] getHmac(); - - /** Returns the HMAC algorithm, or {@code null} if {@link #getHmac()} is not set. */ - @Nullable - String getHmacAlgorithm(); + byte[] getIntegrityValue(); /** Returns the monotonic sequence number for hash-chain continuity, or {@code 0} if not set. */ long getSequenceNo(); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java index af940bf027a..ee6c3839377 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java @@ -5,17 +5,28 @@ package io.opentelemetry.sdk.audit; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.resources.Resource; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; /** Builder for {@link SdkAuditProvider}. */ public final class SdkAuditProviderBuilder { + private static final AttributeKey ATTR_INTEGRITY_ALGORITHM = + AttributeKey.stringKey("audit.integrity.algorithm"); + private static final AttributeKey ATTR_INTEGRITY_CERTIFICATE = + AttributeKey.stringKey("audit.integrity.certificate"); + private Resource resource = Resource.getDefault(); private Clock clock = Clock.getDefault(); private final List processors = new ArrayList<>(); + @Nullable private String integrityAlgorithm; + @Nullable private String integrityCertificate; SdkAuditProviderBuilder() {} @@ -52,8 +63,52 @@ public SdkAuditProviderBuilder addAuditRecordProcessor(AuditRecordProcessor proc return this; } + /** + * Sets the {@code audit.integrity.algorithm} Resource attribute. MUST be set when any record + * emitted by this provider carries an {@code audit.integrity.value} (i.e. when {@link + * io.opentelemetry.api.audit.AuditRecordBuilder#setIntegrityValue(byte[])} is used). + * + *

For asymmetric signatures use a JWA algorithm identifier (e.g. {@code "ES256"}, {@code + * "RS256"}, {@code "EdDSA"}). For HMACs use an IANA MAC algorithm identifier (e.g. {@code + * "HMAC-SHA256"}). + */ + public SdkAuditProviderBuilder setIntegrityAlgorithm(String algorithm) { + if (algorithm == null) { + throw new NullPointerException("algorithm"); + } + this.integrityAlgorithm = algorithm; + return this; + } + + /** + * Sets the {@code audit.integrity.certificate} Resource attribute. MUST NOT be set for HMAC + * algorithms. + * + *

The value MUST be one of: base64-encoded DER certificate, fingerprint ({@code sha256:} + * or {@code sha1:}), JWK Key ID, Subject Key Identifier (colon-separated hex), or + * Issuer+Serial ({@code CN=...,O=.../serial}). + */ + public SdkAuditProviderBuilder setIntegrityCertificate(String certificate) { + if (certificate == null) { + throw new NullPointerException("certificate"); + } + this.integrityCertificate = certificate; + return this; + } + /** Builds and returns the configured {@link SdkAuditProvider}. */ public SdkAuditProvider build() { - return new SdkAuditProvider(resource, processors, clock); + Resource effectiveResource = resource; + if (integrityAlgorithm != null || integrityCertificate != null) { + AttributesBuilder integrityAttrs = Attributes.builder(); + if (integrityAlgorithm != null) { + integrityAttrs.put(ATTR_INTEGRITY_ALGORITHM, integrityAlgorithm); + } + if (integrityCertificate != null) { + integrityAttrs.put(ATTR_INTEGRITY_CERTIFICATE, integrityCertificate); + } + effectiveResource = resource.merge(Resource.create(integrityAttrs.build())); + } + return new SdkAuditProvider(effectiveResource, processors, clock); } } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index aa609cc05ca..b229a5d4340 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -44,11 +44,7 @@ final class SdkAuditRecordBuilder implements AuditRecordBuilder { @Nullable private String sourceType; @Nullable private Value body; @Nullable private AttributesMap attributes; - @Nullable private byte[] signature; - @Nullable private String algorithm; - @Nullable private byte[] certificate; - @Nullable private byte[] hmac; - @Nullable private String hmacAlgorithm; + @Nullable private byte[] integrityValue; private long sequenceNo; @Nullable private String prevHash; @@ -168,22 +164,8 @@ public SdkAuditRecordBuilder setAttribute(AttributeKey key, @Nullable T v } @Override - public SdkAuditRecordBuilder setSignature(byte[] signature, String algorithm) { - this.signature = signature; - this.algorithm = algorithm; - return this; - } - - @Override - public SdkAuditRecordBuilder setCertificate(byte[] certificate) { - this.certificate = certificate; - return this; - } - - @Override - public SdkAuditRecordBuilder setHmac(byte[] hmac, String algorithm) { - this.hmac = hmac; - this.hmacAlgorithm = algorithm; + public SdkAuditRecordBuilder setIntegrityValue(byte[] integrityValue) { + this.integrityValue = integrityValue; return this; } @@ -259,11 +241,7 @@ public AuditReceipt emit() { sourceType, body, recordAttributes, - signature, - algorithm, - certificate, - hmac, - hmacAlgorithm, + integrityValue, sequenceNo, prevHash, schemaVersion); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java index 2416faa6417..deb068841b3 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java @@ -41,11 +41,7 @@ public static SdkAuditRecordData create( @Nullable String sourceType, @Nullable Value body, Attributes attributes, - @Nullable byte[] signature, - @Nullable String algorithm, - @Nullable byte[] certificate, - @Nullable byte[] hmac, - @Nullable String hmacAlgorithm, + @Nullable byte[] integrityValue, long sequenceNo, @Nullable String prevHash, @Nullable String schemaVersion) { @@ -68,11 +64,7 @@ public static SdkAuditRecordData create( sourceType, body, attributes, - signature, - algorithm, - certificate, - hmac, - hmacAlgorithm, + integrityValue, sequenceNo, prevHash, schemaVersion); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java index 01ddfedd77c..35df5476509 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java @@ -41,11 +41,7 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { @Nullable private final String sourceId; @Nullable private final String sourceType; @Nullable private final Value body; - @Nullable private final byte[] signature; - @Nullable private final String algorithm; - @Nullable private final byte[] certificate; - @Nullable private final byte[] hmac; - @Nullable private final String hmacAlgorithm; + @Nullable private final byte[] integrityValue; private final long sequenceNo; @Nullable private final String prevHash; @Nullable private final String schemaVersion; @@ -80,11 +76,7 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { @Nullable String sourceType, @Nullable Value body, @Nullable AttributesMap attributes, - @Nullable byte[] signature, - @Nullable String algorithm, - @Nullable byte[] certificate, - @Nullable byte[] hmac, - @Nullable String hmacAlgorithm, + @Nullable byte[] integrityValue, long sequenceNo, @Nullable String prevHash, @Nullable String schemaVersion) { @@ -106,11 +98,7 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { this.sourceType = sourceType; this.body = body; this.attributes = attributes; - this.signature = signature; - this.algorithm = algorithm; - this.certificate = certificate; - this.hmac = hmac; - this.hmacAlgorithm = hmacAlgorithm; + this.integrityValue = integrityValue; this.sequenceNo = sequenceNo; this.prevHash = prevHash; this.schemaVersion = schemaVersion; @@ -170,11 +158,7 @@ public AuditRecordData toAuditRecordData() { sourceType, body, frozenAttributes, - signature, - algorithm, - certificate, - hmac, - hmacAlgorithm, + integrityValue, sequenceNo, prevHash, schemaVersion); From 7228c891ae6af70cc56a25da93424b24060fc925 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 5 Jun 2026 17:34:18 +0200 Subject: [PATCH 24/45] Add audit signal tests, fix exporter imports, update apidiffs and build config - Add unit tests for SdkAuditProvider, SimpleAuditRecordProcessor, BatchAuditRecordProcessor, and AuditLogRecordDataAdapter - Fix OtlpHttpAuditRecordExporter/Builder to use otlp.internal HTTP classes instead of removed exporter.internal.http package - Fix Checkstyle import order in SimpleAuditRecordProcessorTest - Update build.gradle.kts dependencies for audit exporter tests - Refresh apidiffs to compare against 1.63.0 baseline - Disable Gradle parallel/caching/config-cache for reproducible local builds Signed-off-by: Hilmar Falkenberg --- .../current_vs_latest/opentelemetry-api.txt | 2 +- .../opentelemetry-common.txt | 2 +- .../opentelemetry-context.txt | 2 +- .../opentelemetry-exporter-common.txt | 2 +- .../opentelemetry-exporter-logging-otlp.txt | 2 +- .../opentelemetry-exporter-logging.txt | 2 +- .../opentelemetry-exporter-zipkin.txt | 2 +- .../opentelemetry-extension-kotlin.txt | 2 +- ...ntelemetry-extension-trace-propagators.txt | 2 +- .../opentelemetry-opentracing-shim.txt | 2 +- .../current_vs_latest/opentelemetry-sdk.txt | 2 +- exporters/otlp/audit/build.gradle.kts | 5 +- .../audit/OtlpHttpAuditRecordExporter.java | 4 +- .../OtlpHttpAuditRecordExporterBuilder.java | 2 +- .../audit/AuditLogRecordDataAdapterTest.java | 176 +++++++ gradle.properties | 8 +- .../sdk/audit/SdkAuditProviderTest.java | 445 ++++++++++++++++++ .../export/BatchAuditRecordProcessorTest.java | 150 ++++++ .../SimpleAuditRecordProcessorTest.java | 161 +++++++ 19 files changed, 953 insertions(+), 20 deletions(-) create mode 100644 exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java create mode 100644 sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java create mode 100644 sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java create mode 100644 sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index 778e0c94977..3857fdf5c51 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,4 +1,4 @@ -Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against opentelemetry-api-1.63.0.jar +Comparing source compatibility of opentelemetry-api-1.63.0-SNAPSHOT.jar against opentelemetry-api-1.63.0.jar +++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType (compatible) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW INTERFACE: java.lang.constant.Constable diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-common.txt index b87a4219664..96ee61f1590 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-common.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-common-1.64.0-SNAPSHOT.jar against opentelemetry-common-1.63.0.jar +Comparing source compatibility of opentelemetry-common-1.63.0-SNAPSHOT.jar against opentelemetry-common-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-context.txt b/docs/apidiffs/current_vs_latest/opentelemetry-context.txt index bcab287d5c7..46246e2e44d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-context.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-context.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-context-1.64.0-SNAPSHOT.jar against opentelemetry-context-1.63.0.jar +Comparing source compatibility of opentelemetry-context-1.63.0-SNAPSHOT.jar against opentelemetry-context-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt index 22afdaa8b4f..6f720d63b8b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-common-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-common-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-common-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-common-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt index ad3f5666644..4aedf6fc51f 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-logging-otlp-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-logging-otlp-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-logging-otlp-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-otlp-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt index 896fc5c1e9c..81214095e37 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-logging-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-logging-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-logging-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt index 3636b6d0934..4dd2797a98b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-zipkin-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-zipkin-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt index cecff5b21e0..d1b8407f0f6 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-extension-kotlin-1.64.0-SNAPSHOT.jar against opentelemetry-extension-kotlin-1.63.0.jar +Comparing source compatibility of opentelemetry-extension-kotlin-1.63.0-SNAPSHOT.jar against opentelemetry-extension-kotlin-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt b/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt index a46b40ff16d..c1d35474e27 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-extension-trace-propagators-1.64.0-SNAPSHOT.jar against opentelemetry-extension-trace-propagators-1.63.0.jar +Comparing source compatibility of opentelemetry-extension-trace-propagators-1.63.0-SNAPSHOT.jar against opentelemetry-extension-trace-propagators-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt b/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt index 54a8f4aab5f..69836ec3bc8 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-opentracing-shim-1.64.0-SNAPSHOT.jar against opentelemetry-opentracing-shim-1.63.0.jar +Comparing source compatibility of opentelemetry-opentracing-shim-1.63.0-SNAPSHOT.jar against opentelemetry-opentracing-shim-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt index d1912339829..7c0860b901d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-sdk-1.64.0-SNAPSHOT.jar against opentelemetry-sdk-1.63.0.jar +Comparing source compatibility of opentelemetry-sdk-1.63.0-SNAPSHOT.jar against opentelemetry-sdk-1.63.0.jar No changes. \ No newline at end of file diff --git a/exporters/otlp/audit/build.gradle.kts b/exporters/otlp/audit/build.gradle.kts index d03630a23c2..ddd1ee333de 100644 --- a/exporters/otlp/audit/build.gradle.kts +++ b/exporters/otlp/audit/build.gradle.kts @@ -10,9 +10,10 @@ otelJava.moduleName.set("io.opentelemetry.exporter.otlp.audit") dependencies { api(project(":sdk:audit")) api(project(":sdk:logs")) + implementation(project(":exporters:otlp:all")) implementation(project(":exporters:otlp:common")) implementation(project(":exporters:sender:okhttp")) - testImplementation(project(":exporters:otlp:testing-internal")) - testImplementation("com.linecorp.armeria:armeria-junit5") + testImplementation(project(":sdk:audit")) + testImplementation(project(":sdk:logs")) } diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java index 6607c8beb9a..4c492073852 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java @@ -7,8 +7,8 @@ import io.opentelemetry.api.audit.AuditDeliveryException; import io.opentelemetry.api.audit.AuditReceipt; -import io.opentelemetry.exporter.internal.http.HttpExporter; -import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; +import io.opentelemetry.exporter.otlp.internal.HttpExporter; +import io.opentelemetry.exporter.otlp.internal.HttpExporterBuilder; import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler; import io.opentelemetry.sdk.audit.AuditExportResult; import io.opentelemetry.sdk.audit.AuditRecordData; diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java index 224a5cd73be..6bc63d95740 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporterBuilder.java @@ -8,7 +8,7 @@ import static io.opentelemetry.api.internal.Utils.checkArgument; import static java.util.Objects.requireNonNull; -import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; +import io.opentelemetry.exporter.otlp.internal.HttpExporterBuilder; import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.time.Duration; diff --git a/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java b/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java new file mode 100644 index 00000000000..86add7bd22f --- /dev/null +++ b/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java @@ -0,0 +1,176 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.http.audit; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.SdkAuditRecordData; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Base64; +import org.junit.jupiter.api.Test; + +class AuditLogRecordDataAdapterTest { + + private static final long TIMESTAMP_NANOS = 1_714_041_600_000_000_000L; + private static final long OBSERVED_NANOS = 1_714_041_600_001_000_000L; + + @Test + void mandatoryAttributesMapped() { + AuditRecordData data = buildMinimal(); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + Attributes attrs = adapted.getAttributes(); + + assertThat(attrs.get(AttributeKey.stringKey("audit.record.id"))).isEqualTo("test-record-id"); + assertThat(attrs.get(AttributeKey.stringKey("audit.actor.id"))).isEqualTo("u8472"); + assertThat(attrs.get(AttributeKey.stringKey("audit.actor.type"))).isEqualTo("user"); + assertThat(attrs.get(AttributeKey.stringKey("audit.action"))).isEqualTo("LOGIN"); + assertThat(attrs.get(AttributeKey.stringKey("audit.outcome"))).isEqualTo("success"); + } + + @Test + void actorType_isLowercase() { + AuditRecordData data = + SdkAuditRecordData.create( + Resource.getDefault(), "test", null, null, + "id1", TIMESTAMP_NANOS, OBSERVED_NANOS, + "test.event", "svc-1", ActorType.SERVICE, + "CREATE", Outcome.SUCCESS, + null, null, null, null, null, Attributes.empty(), null, 0, null, null); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + assertThat(adapted.getAttributes().get(AttributeKey.stringKey("audit.actor.type"))) + .isEqualTo("service"); + } + + @Test + void outcome_isLowercase() { + AuditRecordData data = + SdkAuditRecordData.create( + Resource.getDefault(), "test", null, null, + "id2", TIMESTAMP_NANOS, OBSERVED_NANOS, + "test.event", "sys", ActorType.SYSTEM, + "REBOOT", Outcome.FAILURE, + null, null, null, null, null, Attributes.empty(), null, 0, null, null); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + assertThat(adapted.getAttributes().get(AttributeKey.stringKey("audit.outcome"))) + .isEqualTo("failure"); + } + + @Test + void optionalAttributes_mappedWhenPresent() { + AuditRecordData data = + SdkAuditRecordData.create( + Resource.getDefault(), "test", null, null, + "id3", TIMESTAMP_NANOS, OBSERVED_NANOS, + "resource.access", "u1", ActorType.USER, + "READ", Outcome.SUCCESS, + "/api/data/123", "http.endpoint", + "10.0.0.1", "ipv4", + Value.of("body text"), + Attributes.empty(), null, 42L, "prevhash123", "1.0.0"); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + Attributes attrs = adapted.getAttributes(); + + assertThat(attrs.get(AttributeKey.stringKey("audit.target.id"))).isEqualTo("/api/data/123"); + assertThat(attrs.get(AttributeKey.stringKey("audit.target.type"))).isEqualTo("http.endpoint"); + assertThat(attrs.get(AttributeKey.stringKey("audit.source.id"))).isEqualTo("10.0.0.1"); + assertThat(attrs.get(AttributeKey.stringKey("audit.source.type"))).isEqualTo("ipv4"); + assertThat(attrs.get(AttributeKey.longKey("audit.sequence.number"))).isEqualTo(42L); + assertThat(attrs.get(AttributeKey.stringKey("audit.prev.hash"))).isEqualTo("prevhash123"); + assertThat(attrs.get(AttributeKey.stringKey("audit.schema.version"))).isEqualTo("1.0.0"); + } + + @Test + void integrityValue_base64Encoded() { + byte[] proof = new byte[] {0x01, 0x02, 0x03}; + AuditRecordData data = + SdkAuditRecordData.create( + Resource.getDefault(), "test", null, null, + "id4", TIMESTAMP_NANOS, OBSERVED_NANOS, + "signed.event", "svc", ActorType.SERVICE, + "SIGN", Outcome.SUCCESS, + null, null, null, null, null, Attributes.empty(), proof, 0, null, null); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + String encoded = adapted.getAttributes().get(AttributeKey.stringKey("audit.integrity.value")); + assertThat(encoded).isEqualTo(Base64.getEncoder().encodeToString(proof)); + } + + @Test + void instrumentationScope_isEmpty() { + LogRecordData adapted = new AuditLogRecordDataAdapter(buildMinimal()); + assertThat(adapted.getInstrumentationScopeInfo().getName()).isEmpty(); + } + + @Test + void severityNumber_isUndefined() { + LogRecordData adapted = new AuditLogRecordDataAdapter(buildMinimal()); + assertThat(adapted.getSeverity()) + .isEqualTo(Severity.UNDEFINED_SEVERITY_NUMBER); + assertThat(adapted.getSeverityText()).isNull(); + } + + @Test + void timestampsPreserved() { + LogRecordData adapted = new AuditLogRecordDataAdapter(buildMinimal()); + assertThat(adapted.getTimestampEpochNanos()).isEqualTo(TIMESTAMP_NANOS); + assertThat(adapted.getObservedTimestampEpochNanos()).isEqualTo(OBSERVED_NANOS); + } + + @Test + void eventNamePreserved() { + LogRecordData adapted = new AuditLogRecordDataAdapter(buildMinimal()); + assertThat(adapted.getEventName()).isEqualTo("user.login.success"); + } + + @Test + void resourcePreserved() { + Resource resource = Resource.builder().put("service.name", "auth-svc").build(); + AuditRecordData data = + SdkAuditRecordData.create( + resource, "test", null, null, + "id5", TIMESTAMP_NANOS, OBSERVED_NANOS, + "user.login.success", "u1", ActorType.USER, + "LOGIN", Outcome.SUCCESS, + null, null, null, null, null, Attributes.empty(), null, 0, null, null); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + assertThat(adapted.getResource()).isEqualTo(resource); + } + + @Test + void userAttributes_mergedIntoAdaptedAttributes() { + Attributes userAttrs = Attributes.of(AttributeKey.stringKey("custom.key"), "custom-value"); + AuditRecordData data = + SdkAuditRecordData.create( + Resource.getDefault(), "test", null, null, + "id6", TIMESTAMP_NANOS, OBSERVED_NANOS, + "custom.event", "u1", ActorType.USER, + "READ", Outcome.SUCCESS, + null, null, null, null, null, userAttrs, null, 0, null, null); + LogRecordData adapted = new AuditLogRecordDataAdapter(data); + assertThat(adapted.getAttributes().get(AttributeKey.stringKey("custom.key"))) + .isEqualTo("custom-value"); + } + + private static AuditRecordData buildMinimal() { + return SdkAuditRecordData.create( + Resource.getDefault(), + "test-logger", null, null, + "test-record-id", + TIMESTAMP_NANOS, OBSERVED_NANOS, + "user.login.success", + "u8472", ActorType.USER, + "LOGIN", Outcome.SUCCESS, + null, null, null, null, null, + Attributes.empty(), null, 0, null, null); + } +} diff --git a/gradle.properties b/gradle.properties index 0d9cc9c5012..326a5922813 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ -org.gradle.parallel=true -org.gradle.caching=true -org.gradle.configuration-cache=true -org.gradle.configuration-cache.parallel=true +org.gradle.parallel=false +org.gradle.caching=false +org.gradle.configuration-cache=false +org.gradle.configuration-cache.parallel=false org.gradle.priority=low diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java new file mode 100644 index 00000000000..1e72ab8dd34 --- /dev/null +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java @@ -0,0 +1,445 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.AuditDeliveryException; +import io.opentelemetry.api.audit.AuditLogger; +import io.opentelemetry.api.audit.AuditProvider; +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.audit.export.InMemoryAuditRecordExporter; +import io.opentelemetry.sdk.audit.export.SimpleAuditRecordProcessor; +import io.opentelemetry.sdk.resources.Resource; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SdkAuditProviderTest { + + private InMemoryAuditRecordExporter exporter; + private SdkAuditProvider provider; + + @BeforeEach + void setUp() { + exporter = InMemoryAuditRecordExporter.create(); + provider = + SdkAuditProvider.builder() + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(exporter)) + .build(); + } + + @AfterEach + void tearDown() { + provider.close(); + } + + @Test + void emitMinimalRecord_returnsReceiptAndStoresRecord() { + AuditLogger logger = provider.get("com.example.auth"); + + AuditReceipt receipt = + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("user.login.success") + .setActorId("u8472") + .setActorType(ActorType.USER) + .setAction("LOGIN") + .setOutcome(Outcome.SUCCESS) + .emit(); + + assertThat(receipt).isNotNull(); + assertThat(receipt.recordId()).isNotEmpty(); + + List records = exporter.getFinishedAuditRecords(); + assertThat(records).hasSize(1); + AuditRecordData data = records.get(0); + assertThat(data.getEventName()).isEqualTo("user.login.success"); + assertThat(data.getActorId()).isEqualTo("u8472"); + assertThat(data.getActorType()).isEqualTo(ActorType.USER); + assertThat(data.getAction()).isEqualTo("LOGIN"); + assertThat(data.getOutcome()).isEqualTo(Outcome.SUCCESS); + } + + @Test + void emit_autoGeneratesRecordId_whenCallerOmitsIt() { + AuditLogger logger = provider.get("com.example.test"); + AuditReceipt receipt = + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("config.change") + .setActorId("svc-deployer") + .setActorType(ActorType.SERVICE) + .setAction("UPDATE") + .setOutcome(Outcome.SUCCESS) + .emit(); + + assertThat(receipt.recordId()).isNotEmpty(); + List records = exporter.getFinishedAuditRecords(); + assertThat(records.get(0).getRecordId()).isEqualTo(receipt.recordId()); + } + + @Test + void emit_stableRecordId_whenCallerSetsIt() { + AuditLogger logger = provider.get("com.example.test"); + String fixedId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; + logger + .auditRecordBuilder() + .setRecordId(fixedId) + .setTimestamp(Instant.now()) + .setEventName("file.read") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + + assertThat(exporter.getFinishedAuditRecords().get(0).getRecordId()).isEqualTo(fixedId); + } + + @Test + void emit_setsObservedTimestamp_whenCallerOmitsIt() { + AuditLogger logger = provider.get("com.example.test"); + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("data.delete") + .setActorId("u2") + .setActorType(ActorType.USER) + .setAction("DELETE") + .setOutcome(Outcome.SUCCESS) + .emit(); + + AuditRecordData data = exporter.getFinishedAuditRecords().get(0); + assertThat(data.getObservedTimestampEpochNanos()).isGreaterThan(0); + } + + @Test + void emit_failsHard_whenTimestampMissing() { + AuditLogger logger = provider.get("com.example.test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setEventName("user.login") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("LOGIN") + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Timestamp"); + } + + @Test + void emit_failsHard_whenEventNameMissing() { + AuditLogger logger = provider.get("com.example.test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("LOGIN") + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("EventName"); + } + + @Test + void emit_failsHard_whenActorIdMissing() { + AuditLogger logger = provider.get("com.example.test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("user.login") + .setActorType(ActorType.USER) + .setAction("LOGIN") + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("ActorId"); + } + + @Test + void emit_failsHard_whenActorTypeMissing() { + AuditLogger logger = provider.get("com.example.test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("user.login") + .setActorId("u1") + .setAction("LOGIN") + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("ActorType"); + } + + @Test + void emit_failsHard_whenActionMissing() { + AuditLogger logger = provider.get("com.example.test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("user.login") + .setActorId("u1") + .setActorType(ActorType.USER) + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Action"); + } + + @Test + void emit_failsHard_whenOutcomeMissing() { + AuditLogger logger = provider.get("com.example.test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("user.login") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("LOGIN") + .emit()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Outcome"); + } + + @Test + void emit_failsHard_afterShutdown() { + AuditLogger logger = provider.get("com.example.test"); + provider.shutdown().join(5, TimeUnit.SECONDS); + + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("user.login") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("LOGIN") + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(AuditDeliveryException.class) + .hasMessageContaining("shut down"); + } + + @Test + void getAuditLogger_failsHard_afterShutdown() { + provider.shutdown().join(5, TimeUnit.SECONDS); + assertThatThrownBy(() -> provider.get("x")) + .isInstanceOf(AuditDeliveryException.class) + .hasMessageContaining("shut down"); + } + + @Test + void emit_setsOptionalFields() { + AuditLogger logger = provider.get("com.example.test"); + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("resource.access") + .setActorId("svc-1") + .setActorType(ActorType.SERVICE) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .setTargetId("/api/data/123") + .setTargetType("http.endpoint") + .setSourceId("10.0.0.1") + .setSourceType("ipv4") + .setSchemaVersion("1.0.0") + .setBody("additional context") + .emit(); + + AuditRecordData data = exporter.getFinishedAuditRecords().get(0); + assertThat(data.getTargetId()).isEqualTo("/api/data/123"); + assertThat(data.getTargetType()).isEqualTo("http.endpoint"); + assertThat(data.getSourceId()).isEqualTo("10.0.0.1"); + assertThat(data.getSourceType()).isEqualTo("ipv4"); + assertThat(data.getSchemaVersion()).isEqualTo("1.0.0"); + assertThat(data.getBody()).isNotNull(); + } + + @Test + void sameLoggerReturnedForSameName() { + AuditLogger l1 = provider.get("com.example.auth"); + AuditLogger l2 = provider.get("com.example.auth"); + assertThat(l1).isSameAs(l2); + } + + @Test + void resourceAttributesCarriedToRecord() { + Resource resource = + Resource.builder().put("service.name", "audit-svc").put("service.version", "1.0").build(); + InMemoryAuditRecordExporter exp = InMemoryAuditRecordExporter.create(); + try (SdkAuditProvider p = + SdkAuditProvider.builder() + .setResource(resource) + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(exp)) + .build()) { + p.get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("test.event") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + Resource actual = exp.getFinishedAuditRecords().get(0).getResource(); + assertThat(actual.getAttribute(AttributeKey.stringKey("service.name"))).isEqualTo("audit-svc"); + assertThat(actual.getAttribute(AttributeKey.stringKey("service.version"))).isEqualTo("1.0"); + } + + @Test + void integrityAlgorithmStoredAsResourceAttribute() { + InMemoryAuditRecordExporter exp = InMemoryAuditRecordExporter.create(); + try (SdkAuditProvider p = + SdkAuditProvider.builder() + .setIntegrityAlgorithm("HMAC-SHA256") + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(exp)) + .build()) { + p.get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("test.event") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + String algo = + exp.getFinishedAuditRecords() + .get(0) + .getResource() + .getAttribute(AttributeKey.stringKey("audit.integrity.algorithm")); + assertThat(algo).isEqualTo("HMAC-SHA256"); + } + + @Test + void processorCanEnrichRecord() { + InMemoryAuditRecordExporter exp = InMemoryAuditRecordExporter.create(); + AuditRecordProcessor enricher = + (ctx, record) -> + record.setAttribute( + AttributeKey.stringKey("enriched.by"), "test-processor"); + try (SdkAuditProvider p = + SdkAuditProvider.builder() + .addAuditRecordProcessor(enricher) + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(exp)) + .build()) { + p.get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("test.event") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + AuditRecordData data = exp.getFinishedAuditRecords().get(0); + assertThat(data.getAttributes().get(AttributeKey.stringKey("enriched.by"))) + .isEqualTo("test-processor"); + } + + @Test + void multipleRecords_allDelivered() { + AuditLogger logger = provider.get("com.example.bulk"); + int count = 10; + for (int i = 0; i < count; i++) { + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("bulk.event") + .setActorId("u" + i) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + assertThat(exporter.getFinishedAuditRecords()).hasSize(count); + } + + @Test + void sequenceNoAndPrevHash_storedOnRecord() { + AuditLogger logger = provider.get("com.example.test"); + String prevHash = "deadbeef1234"; + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("chain.event") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .setSequenceNo(42L) + .setPrevHash(prevHash) + .emit(); + + AuditRecordData data = exporter.getFinishedAuditRecords().get(0); + assertThat(data.getSequenceNo()).isEqualTo(42L); + assertThat(data.getPrevHash()).isEqualTo(prevHash); + } + + @Test + void integrityValue_storedOnRecord() { + AuditLogger logger = provider.get("com.example.test"); + byte[] proof = new byte[] {0x01, 0x02, 0x03}; + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("signed.event") + .setActorId("svc") + .setActorType(ActorType.SERVICE) + .setAction("CREATE") + .setOutcome(Outcome.SUCCESS) + .setIntegrityValue(proof) + .emit(); + + AuditRecordData data = exporter.getFinishedAuditRecords().get(0); + assertThat(data.getIntegrityValue()).containsExactly(0x01, 0x02, 0x03); + } + + @Test + void noopProvider_emitReturnsReceiptWithoutError() { + AuditProvider noop = AuditProvider.noop(); + AuditReceipt receipt = + noop.get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("noop.event") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + assertThat(receipt).isNotNull(); + } +} diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java new file mode 100644 index 00000000000..621ea639413 --- /dev/null +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java @@ -0,0 +1,150 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit.export; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.SdkAuditProvider; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BatchAuditRecordProcessorTest { + + private InMemoryAuditRecordExporter exporter; + private BatchAuditRecordProcessor processor; + private SdkAuditProvider provider; + + @BeforeEach + void setUp() { + exporter = InMemoryAuditRecordExporter.create(); + processor = + BatchAuditRecordProcessor.builder(exporter) + .setScheduledDelayMillis(100) + .setMaxQueueSize(1000) + .setMaxExportBatchSize(50) + .build(); + provider = SdkAuditProvider.builder().addAuditRecordProcessor(processor).build(); + } + + @AfterEach + void tearDown() { + provider.close(); + } + + @Test + void batchExports_allRecordsDelivered() throws InterruptedException { + int count = 20; + CountDownLatch latch = new CountDownLatch(count); + + for (int i = 0; i < count; i++) { + int idx = i; + Thread t = + new Thread( + () -> { + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("batch.event") + .setActorId("u" + idx) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + latch.countDown(); + }); + t.start(); + } + + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(exporter.getFinishedAuditRecords()).hasSize(count); + } + + @Test + void forceFlush_exportsAllPendingRecords() { + int count = 5; + for (int i = 0; i < count; i++) { + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("flush.event") + .setActorId("u" + i) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + provider.forceFlush().join(5, TimeUnit.SECONDS); + assertThat(exporter.getFinishedAuditRecords()).hasSize(count); + } + + @Test + void receipts_returnedToCallers() { + for (int i = 0; i < 3; i++) { + AuditReceipt receipt = + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("receipt.event") + .setActorId("u" + i) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + assertThat(receipt).isNotNull(); + assertThat(receipt.recordId()).isNotEmpty(); + } + } + + @Test + void recordIds_areUnique_acrossBatch() { + int count = 10; + for (int i = 0; i < count; i++) { + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("unique.event") + .setActorId("u" + i) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + List records = exporter.getFinishedAuditRecords(); + long distinctIds = records.stream().map(AuditRecordData::getRecordId).distinct().count(); + assertThat(distinctIds).isEqualTo(count); + } + + @Test + void shutdown_exportsRemainingRecords() { + for (int i = 0; i < 3; i++) { + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("shutdown.event") + .setActorId("u" + i) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + provider.shutdown().join(10, TimeUnit.SECONDS); + assertThat(exporter.getFinishedAuditRecords()).hasSize(3); + } +} diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java new file mode 100644 index 00000000000..d1599128c67 --- /dev/null +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java @@ -0,0 +1,161 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.audit.export; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.AuditDeliveryException; +import io.opentelemetry.api.audit.AuditLogger; +import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.api.audit.Outcome; +import io.opentelemetry.sdk.audit.AuditExportResult; +import io.opentelemetry.sdk.audit.AuditRecordData; +import io.opentelemetry.sdk.audit.AuditRecordExporter; +import io.opentelemetry.sdk.audit.SdkAuditProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class SimpleAuditRecordProcessorTest { + + @Test + void exportsRecordSynchronously() { + InMemoryAuditRecordExporter exporter = InMemoryAuditRecordExporter.create(); + try (SdkAuditProvider provider = + SdkAuditProvider.builder() + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(exporter)) + .build()) { + + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("simple.test") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + + assertThat(exporter.getFinishedAuditRecords()).hasSize(1); + } + } + + @Test + void throwsDeliveryException_onExporterFailure() { + AuditRecordExporter failingExporter = + new AuditRecordExporter() { + @Override + public AuditExportResult export(Collection records) { + return AuditExportResult.failure(new RuntimeException("sink unavailable")); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + }; + + try (SdkAuditProvider provider = + SdkAuditProvider.builder() + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(failingExporter)) + .build()) { + AuditLogger logger = provider.get("test"); + assertThatThrownBy( + () -> + logger + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("fail.test") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.FAILURE) + .emit()) + .isInstanceOf(AuditDeliveryException.class); + } + } + + @Test + void throwsDeliveryException_afterShutdown() { + InMemoryAuditRecordExporter exporter = InMemoryAuditRecordExporter.create(); + SimpleAuditRecordProcessor processor = SimpleAuditRecordProcessor.create(exporter); + try (SdkAuditProvider provider = + SdkAuditProvider.builder().addAuditRecordProcessor(processor).build()) { + provider.shutdown().join(5, TimeUnit.SECONDS); + + assertThatThrownBy( + () -> + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("after.shutdown") + .setActorId("u1") + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit()) + .isInstanceOf(AuditDeliveryException.class); + } + } + + @Test + void exporterCalledOnce_perEmit() { + AtomicInteger exportCallCount = new AtomicInteger(0); + AuditRecordExporter countingExporter = + new AuditRecordExporter() { + @Override + public AuditExportResult export(Collection records) { + exportCallCount.incrementAndGet(); + return AuditExportResult.success( + Collections.singletonList( + AuditReceipt.create( + records.iterator().next().getRecordId(), "", 0))); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + }; + + try (SdkAuditProvider provider = + SdkAuditProvider.builder() + .addAuditRecordProcessor(SimpleAuditRecordProcessor.create(countingExporter)) + .build()) { + for (int i = 0; i < 5; i++) { + provider + .get("test") + .auditRecordBuilder() + .setTimestamp(Instant.now()) + .setEventName("count.event") + .setActorId("u" + i) + .setActorType(ActorType.USER) + .setAction("READ") + .setOutcome(Outcome.SUCCESS) + .emit(); + } + assertThat(exportCallCount.get()).isEqualTo(5); + } + } +} From ff2dc41da6ba86c343c1abbd73910d5fb67fb124 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 8 Jun 2026 16:27:10 +0200 Subject: [PATCH 25/45] sync with main Signed-off-by: Hilmar Falkenberg --- .../current_vs_latest/opentelemetry-api.txt | 99 +++---------------- .../opentelemetry-common.txt | 2 +- .../opentelemetry-context.txt | 2 +- .../opentelemetry-exporter-common.txt | 2 +- .../opentelemetry-exporter-logging-otlp.txt | 2 +- .../opentelemetry-exporter-logging.txt | 2 +- .../opentelemetry-exporter-zipkin.txt | 2 +- .../opentelemetry-extension-kotlin.txt | 2 +- ...ntelemetry-extension-trace-propagators.txt | 2 +- .../opentelemetry-opentracing-shim.txt | 10 +- .../current_vs_latest/opentelemetry-sdk.txt | 2 +- 11 files changed, 29 insertions(+), 98 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index 3857fdf5c51..ee4d560df8c 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,87 +1,12 @@ -Comparing source compatibility of opentelemetry-api-1.63.0-SNAPSHOT.jar against opentelemetry-api-1.63.0.jar -+++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType (compatible) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: java.lang.constant.Constable - +++ NEW INTERFACE: java.lang.Comparable - +++ NEW INTERFACE: java.io.Serializable - +++ NEW SUPERCLASS: java.lang.Enum - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType SYSTEM - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType SERVICE - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType USER - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType valueOf(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType[] values() -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditDeliveryException (compatible) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: java.io.Serializable - +++ NEW SUPERCLASS: java.lang.RuntimeException - +++ NEW CONSTRUCTOR: PUBLIC(+) AuditDeliveryException(java.lang.String) - +++ NEW CONSTRUCTOR: PUBLIC(+) AuditDeliveryException(java.lang.String, java.lang.Throwable) -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLogger (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder auditRecordBuilder() -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLogger build() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder setInstrumentationVersion(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder setSchemaUrl(java.lang.String) -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditProvider (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder auditLoggerBuilder(java.lang.String) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditLogger get(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider noop() -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditReceipt (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditReceipt create(java.lang.String, java.lang.String, long) - +++ NEW METHOD: PUBLIC(+) boolean equals(java.lang.Object) - +++ NEW METHOD: PUBLIC(+) int hashCode() - +++ NEW METHOD: PUBLIC(+) java.lang.String integrityHash() - +++ NEW METHOD: PUBLIC(+) java.lang.String recordId() - +++ NEW METHOD: PUBLIC(+) long sinkTimestampEpochNanos() - +++ NEW METHOD: PUBLIC(+) java.lang.String toString() -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditReceipt emit() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAction(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorType(io.opentelemetry.api.audit.ActorType) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAttribute(io.opentelemetry.api.common.AttributeKey, java.lang.Object) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(io.opentelemetry.api.common.Value) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setEventName(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setIntegrityValue(byte[]) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(long, java.util.concurrent.TimeUnit) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(java.time.Instant) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setOutcome(io.opentelemetry.api.audit.Outcome) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setPrevHash(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setRecordId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSchemaVersion(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSequenceNo(long) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceType(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetType(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(long, java.util.concurrent.TimeUnit) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(java.time.Instant) -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.GlobalAuditProvider (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider get() - +++ NEW METHOD: PUBLIC(+) STATIC(+) void resetForTest() - +++ NEW METHOD: PUBLIC(+) STATIC(+) void set(io.opentelemetry.api.audit.AuditProvider) -+++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome (compatible) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: java.lang.constant.Constable - +++ NEW INTERFACE: java.lang.Comparable - +++ NEW INTERFACE: java.io.Serializable - +++ NEW SUPERCLASS: java.lang.Enum - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome SUCCESS - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome UNKNOWN - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome FAILURE - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.Outcome valueOf(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.Outcome[] values() +Comparing source compatibility of opentelemetry-api-1.63.0-SNAPSHOT.jar against opentelemetry-api-1.62.0.jar ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.impl.InstrumentationUtil (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) boolean shouldSuppressInstrumentation(io.opentelemetry.context.Context) + +++ NEW METHOD: PUBLIC(+) STATIC(+) void suppressInstrumentation(java.lang.Runnable) +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.logs.LogRecordBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAttribute(java.lang.String, io.opentelemetry.api.common.Value) +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.Span (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.trace.Span setAttribute(java.lang.String, io.opentelemetry.api.common.Value) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-common.txt index 96ee61f1590..0d77374fee2 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-common.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-common-1.63.0-SNAPSHOT.jar against opentelemetry-common-1.63.0.jar +Comparing source compatibility of opentelemetry-common-1.63.0-SNAPSHOT.jar against opentelemetry-common-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-context.txt b/docs/apidiffs/current_vs_latest/opentelemetry-context.txt index 46246e2e44d..39dc4a89d9b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-context.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-context.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-context-1.63.0-SNAPSHOT.jar against opentelemetry-context-1.63.0.jar +Comparing source compatibility of opentelemetry-context-1.63.0-SNAPSHOT.jar against opentelemetry-context-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt index 6f720d63b8b..1a5b477f6d7 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-common-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-common-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-common-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-common-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt index 4aedf6fc51f..18a27dbbd9a 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-logging-otlp-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-otlp-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-logging-otlp-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-otlp-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt index 81214095e37..af0efdb5299 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-logging-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-logging-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt index 4dd2797a98b..f331bc7e6da 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-zipkin-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.63.0.jar +Comparing source compatibility of opentelemetry-exporter-zipkin-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt index d1b8407f0f6..eb2bde0e353 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-extension-kotlin-1.63.0-SNAPSHOT.jar against opentelemetry-extension-kotlin-1.63.0.jar +Comparing source compatibility of opentelemetry-extension-kotlin-1.63.0-SNAPSHOT.jar against opentelemetry-extension-kotlin-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt b/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt index c1d35474e27..078d49df028 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-extension-trace-propagators-1.63.0-SNAPSHOT.jar against opentelemetry-extension-trace-propagators-1.63.0.jar +Comparing source compatibility of opentelemetry-extension-trace-propagators-1.63.0-SNAPSHOT.jar against opentelemetry-extension-trace-propagators-1.62.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt b/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt index 69836ec3bc8..1b367944140 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt @@ -1,2 +1,8 @@ -Comparing source compatibility of opentelemetry-opentracing-shim-1.63.0-SNAPSHOT.jar against opentelemetry-opentracing-shim-1.63.0.jar -No changes. \ No newline at end of file +Comparing source compatibility of opentelemetry-opentracing-shim-1.63.0-SNAPSHOT.jar against opentelemetry-opentracing-shim-1.62.0.jar +=== UNCHANGED CLASS: PUBLIC FINAL io.opentelemetry.opentracingshim.OpenTracingShim (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + === UNCHANGED METHOD: PUBLIC STATIC io.opentracing.Tracer createTracerShim(io.opentelemetry.api.OpenTelemetry) + +++ NEW ANNOTATION: java.lang.Deprecated + === UNCHANGED METHOD: PUBLIC STATIC io.opentracing.Tracer createTracerShim(io.opentelemetry.api.trace.TracerProvider, io.opentelemetry.context.propagation.TextMapPropagator, io.opentelemetry.context.propagation.TextMapPropagator) + +++ NEW ANNOTATION: java.lang.Deprecated + +++ NEW ANNOTATION: java.lang.Deprecated diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt index 7c0860b901d..94b12da6313 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-sdk-1.63.0-SNAPSHOT.jar against opentelemetry-sdk-1.63.0.jar +Comparing source compatibility of opentelemetry-sdk-1.63.0-SNAPSHOT.jar against opentelemetry-sdk-1.62.0.jar No changes. \ No newline at end of file From 506fe01aae6ff28596eb9428ebf853a18984a19f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 9 Jun 2026 15:08:16 +0200 Subject: [PATCH 26/45] gradle jApiCmp Signed-off-by: Hilmar Falkenberg --- .../current_vs_latest/opentelemetry-api.txt | 99 ++++++++++++++++--- 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index ee4d560df8c..778e0c94977 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,12 +1,87 @@ -Comparing source compatibility of opentelemetry-api-1.63.0-SNAPSHOT.jar against opentelemetry-api-1.62.0.jar -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.impl.InstrumentationUtil (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) STATIC(+) boolean shouldSuppressInstrumentation(io.opentelemetry.context.Context) - +++ NEW METHOD: PUBLIC(+) STATIC(+) void suppressInstrumentation(java.lang.Runnable) -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.logs.LogRecordBuilder (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAttribute(java.lang.String, io.opentelemetry.api.common.Value) -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.Span (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.trace.Span setAttribute(java.lang.String, io.opentelemetry.api.common.Value) +Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against opentelemetry-api-1.63.0.jar ++++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.lang.constant.Constable + +++ NEW INTERFACE: java.lang.Comparable + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.Enum + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType SYSTEM + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType SERVICE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType USER + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType valueOf(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType[] values() ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditDeliveryException (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.RuntimeException + +++ NEW CONSTRUCTOR: PUBLIC(+) AuditDeliveryException(java.lang.String) + +++ NEW CONSTRUCTOR: PUBLIC(+) AuditDeliveryException(java.lang.String, java.lang.Throwable) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLogger (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder auditRecordBuilder() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLogger build() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder setInstrumentationVersion(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder setSchemaUrl(java.lang.String) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditProvider (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder auditLoggerBuilder(java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditLogger get(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider noop() ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditReceipt (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditReceipt create(java.lang.String, java.lang.String, long) + +++ NEW METHOD: PUBLIC(+) boolean equals(java.lang.Object) + +++ NEW METHOD: PUBLIC(+) int hashCode() + +++ NEW METHOD: PUBLIC(+) java.lang.String integrityHash() + +++ NEW METHOD: PUBLIC(+) java.lang.String recordId() + +++ NEW METHOD: PUBLIC(+) long sinkTimestampEpochNanos() + +++ NEW METHOD: PUBLIC(+) java.lang.String toString() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditReceipt emit() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAction(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorType(io.opentelemetry.api.audit.ActorType) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAttribute(io.opentelemetry.api.common.AttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(io.opentelemetry.api.common.Value) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setEventName(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setIntegrityValue(byte[]) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(long, java.util.concurrent.TimeUnit) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setObservedTimestamp(java.time.Instant) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setOutcome(io.opentelemetry.api.audit.Outcome) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setPrevHash(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setRecordId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSchemaVersion(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSequenceNo(long) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceType(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetId(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetType(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(long, java.util.concurrent.TimeUnit) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(java.time.Instant) ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.GlobalAuditProvider (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider get() + +++ NEW METHOD: PUBLIC(+) STATIC(+) void resetForTest() + +++ NEW METHOD: PUBLIC(+) STATIC(+) void set(io.opentelemetry.api.audit.AuditProvider) ++++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.lang.constant.Constable + +++ NEW INTERFACE: java.lang.Comparable + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.Enum + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome SUCCESS + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome UNKNOWN + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome FAILURE + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.Outcome valueOf(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.Outcome[] values() From d9f3f82f03b0e1d2e8df368d100e00e0460ff4f2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 9 Jun 2026 16:18:01 +0200 Subject: [PATCH 27/45] fix spotlessJavaCheck issues Signed-off-by: Hilmar Falkenberg --- .../audit/OtlpHttpAuditRecordExporter.java | 2 +- .../audit/AuditLogRecordDataAdapterTest.java | 195 ++++++++++++++---- .../sdk/audit/SdkAuditProviderTest.java | 3 +- .../SimpleAuditRecordProcessorTest.java | 3 +- 4 files changed, 156 insertions(+), 47 deletions(-) diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java index 4c492073852..f6174ebfce1 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/OtlpHttpAuditRecordExporter.java @@ -7,9 +7,9 @@ import io.opentelemetry.api.audit.AuditDeliveryException; import io.opentelemetry.api.audit.AuditReceipt; +import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler; import io.opentelemetry.exporter.otlp.internal.HttpExporter; import io.opentelemetry.exporter.otlp.internal.HttpExporterBuilder; -import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler; import io.opentelemetry.sdk.audit.AuditExportResult; import io.opentelemetry.sdk.audit.AuditRecordData; import io.opentelemetry.sdk.audit.AuditRecordExporter; diff --git a/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java b/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java index 86add7bd22f..609952a393d 100644 --- a/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java +++ b/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java @@ -7,11 +7,11 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.audit.ActorType; -import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.sdk.audit.AuditRecordData; import io.opentelemetry.sdk.audit.SdkAuditRecordData; @@ -42,11 +42,28 @@ void mandatoryAttributesMapped() { void actorType_isLowercase() { AuditRecordData data = SdkAuditRecordData.create( - Resource.getDefault(), "test", null, null, - "id1", TIMESTAMP_NANOS, OBSERVED_NANOS, - "test.event", "svc-1", ActorType.SERVICE, - "CREATE", Outcome.SUCCESS, - null, null, null, null, null, Attributes.empty(), null, 0, null, null); + Resource.getDefault(), + "test", + null, + null, + "id1", + TIMESTAMP_NANOS, + OBSERVED_NANOS, + "test.event", + "svc-1", + ActorType.SERVICE, + "CREATE", + Outcome.SUCCESS, + null, + null, + null, + null, + null, + Attributes.empty(), + null, + 0, + null, + null); LogRecordData adapted = new AuditLogRecordDataAdapter(data); assertThat(adapted.getAttributes().get(AttributeKey.stringKey("audit.actor.type"))) .isEqualTo("service"); @@ -56,11 +73,28 @@ void actorType_isLowercase() { void outcome_isLowercase() { AuditRecordData data = SdkAuditRecordData.create( - Resource.getDefault(), "test", null, null, - "id2", TIMESTAMP_NANOS, OBSERVED_NANOS, - "test.event", "sys", ActorType.SYSTEM, - "REBOOT", Outcome.FAILURE, - null, null, null, null, null, Attributes.empty(), null, 0, null, null); + Resource.getDefault(), + "test", + null, + null, + "id2", + TIMESTAMP_NANOS, + OBSERVED_NANOS, + "test.event", + "sys", + ActorType.SYSTEM, + "REBOOT", + Outcome.FAILURE, + null, + null, + null, + null, + null, + Attributes.empty(), + null, + 0, + null, + null); LogRecordData adapted = new AuditLogRecordDataAdapter(data); assertThat(adapted.getAttributes().get(AttributeKey.stringKey("audit.outcome"))) .isEqualTo("failure"); @@ -70,14 +104,28 @@ void outcome_isLowercase() { void optionalAttributes_mappedWhenPresent() { AuditRecordData data = SdkAuditRecordData.create( - Resource.getDefault(), "test", null, null, - "id3", TIMESTAMP_NANOS, OBSERVED_NANOS, - "resource.access", "u1", ActorType.USER, - "READ", Outcome.SUCCESS, - "/api/data/123", "http.endpoint", - "10.0.0.1", "ipv4", + Resource.getDefault(), + "test", + null, + null, + "id3", + TIMESTAMP_NANOS, + OBSERVED_NANOS, + "resource.access", + "u1", + ActorType.USER, + "READ", + Outcome.SUCCESS, + "/api/data/123", + "http.endpoint", + "10.0.0.1", + "ipv4", Value.of("body text"), - Attributes.empty(), null, 42L, "prevhash123", "1.0.0"); + Attributes.empty(), + null, + 42L, + "prevhash123", + "1.0.0"); LogRecordData adapted = new AuditLogRecordDataAdapter(data); Attributes attrs = adapted.getAttributes(); @@ -95,11 +143,28 @@ void integrityValue_base64Encoded() { byte[] proof = new byte[] {0x01, 0x02, 0x03}; AuditRecordData data = SdkAuditRecordData.create( - Resource.getDefault(), "test", null, null, - "id4", TIMESTAMP_NANOS, OBSERVED_NANOS, - "signed.event", "svc", ActorType.SERVICE, - "SIGN", Outcome.SUCCESS, - null, null, null, null, null, Attributes.empty(), proof, 0, null, null); + Resource.getDefault(), + "test", + null, + null, + "id4", + TIMESTAMP_NANOS, + OBSERVED_NANOS, + "signed.event", + "svc", + ActorType.SERVICE, + "SIGN", + Outcome.SUCCESS, + null, + null, + null, + null, + null, + Attributes.empty(), + proof, + 0, + null, + null); LogRecordData adapted = new AuditLogRecordDataAdapter(data); String encoded = adapted.getAttributes().get(AttributeKey.stringKey("audit.integrity.value")); assertThat(encoded).isEqualTo(Base64.getEncoder().encodeToString(proof)); @@ -114,8 +179,7 @@ void instrumentationScope_isEmpty() { @Test void severityNumber_isUndefined() { LogRecordData adapted = new AuditLogRecordDataAdapter(buildMinimal()); - assertThat(adapted.getSeverity()) - .isEqualTo(Severity.UNDEFINED_SEVERITY_NUMBER); + assertThat(adapted.getSeverity()).isEqualTo(Severity.UNDEFINED_SEVERITY_NUMBER); assertThat(adapted.getSeverityText()).isNull(); } @@ -137,11 +201,28 @@ void resourcePreserved() { Resource resource = Resource.builder().put("service.name", "auth-svc").build(); AuditRecordData data = SdkAuditRecordData.create( - resource, "test", null, null, - "id5", TIMESTAMP_NANOS, OBSERVED_NANOS, - "user.login.success", "u1", ActorType.USER, - "LOGIN", Outcome.SUCCESS, - null, null, null, null, null, Attributes.empty(), null, 0, null, null); + resource, + "test", + null, + null, + "id5", + TIMESTAMP_NANOS, + OBSERVED_NANOS, + "user.login.success", + "u1", + ActorType.USER, + "LOGIN", + Outcome.SUCCESS, + null, + null, + null, + null, + null, + Attributes.empty(), + null, + 0, + null, + null); LogRecordData adapted = new AuditLogRecordDataAdapter(data); assertThat(adapted.getResource()).isEqualTo(resource); } @@ -151,11 +232,28 @@ void userAttributes_mergedIntoAdaptedAttributes() { Attributes userAttrs = Attributes.of(AttributeKey.stringKey("custom.key"), "custom-value"); AuditRecordData data = SdkAuditRecordData.create( - Resource.getDefault(), "test", null, null, - "id6", TIMESTAMP_NANOS, OBSERVED_NANOS, - "custom.event", "u1", ActorType.USER, - "READ", Outcome.SUCCESS, - null, null, null, null, null, userAttrs, null, 0, null, null); + Resource.getDefault(), + "test", + null, + null, + "id6", + TIMESTAMP_NANOS, + OBSERVED_NANOS, + "custom.event", + "u1", + ActorType.USER, + "READ", + Outcome.SUCCESS, + null, + null, + null, + null, + null, + userAttrs, + null, + 0, + null, + null); LogRecordData adapted = new AuditLogRecordDataAdapter(data); assertThat(adapted.getAttributes().get(AttributeKey.stringKey("custom.key"))) .isEqualTo("custom-value"); @@ -164,13 +262,26 @@ void userAttributes_mergedIntoAdaptedAttributes() { private static AuditRecordData buildMinimal() { return SdkAuditRecordData.create( Resource.getDefault(), - "test-logger", null, null, + "test-logger", + null, + null, "test-record-id", - TIMESTAMP_NANOS, OBSERVED_NANOS, + TIMESTAMP_NANOS, + OBSERVED_NANOS, "user.login.success", - "u8472", ActorType.USER, - "LOGIN", Outcome.SUCCESS, - null, null, null, null, null, - Attributes.empty(), null, 0, null, null); + "u8472", + ActorType.USER, + "LOGIN", + Outcome.SUCCESS, + null, + null, + null, + null, + null, + Attributes.empty(), + null, + 0, + null, + null); } } diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java index 1e72ab8dd34..a469c3c95db 100644 --- a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java @@ -347,8 +347,7 @@ void processorCanEnrichRecord() { InMemoryAuditRecordExporter exp = InMemoryAuditRecordExporter.create(); AuditRecordProcessor enricher = (ctx, record) -> - record.setAttribute( - AttributeKey.stringKey("enriched.by"), "test-processor"); + record.setAttribute(AttributeKey.stringKey("enriched.by"), "test-processor"); try (SdkAuditProvider p = SdkAuditProvider.builder() .addAuditRecordProcessor(enricher) diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java index d1599128c67..18ec723d7d5 100644 --- a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java @@ -124,8 +124,7 @@ public AuditExportResult export(Collection records) { exportCallCount.incrementAndGet(); return AuditExportResult.success( Collections.singletonList( - AuditReceipt.create( - records.iterator().next().getRecordId(), "", 0))); + AuditReceipt.create(records.iterator().next().getRecordId(), "", 0))); } @Override From 98269971c624f1946cae7f5e1062e1cad2c8cedd Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 14:21:52 +0200 Subject: [PATCH 28/45] audit.* semantic conventions Signed-off-by: Hilmar Falkenberg --- .../io/opentelemetry/api/audit/Audit.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 api/all/src/main/java/io/opentelemetry/api/audit/Audit.java diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java b/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java new file mode 100644 index 00000000000..89a21dbc6cb --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.audit; + +/** + * Semantic convention attribute names for OpenTelemetry audit records. + * + *

These constants correspond to the {@code audit.*} attributes defined by the audit record data + * model. + */ +public final class Audit { + + /** Unique stable identifier for an audit record. */ + public static final String RECORD_ID = "audit.record.id"; + + /** Identity of the actor that performed the action. */ + public static final String ACTOR_ID = "audit.actor.id"; + + /** Type of the actor that performed the action. */ + public static final String ACTOR_TYPE = "audit.actor.type"; + + /** Verb describing what the actor did. */ + public static final String ACTION = "audit.action"; + + /** Result of the auditable action. */ + public static final String OUTCOME = "audit.outcome"; + + /** Identifier of the target resource acted upon. */ + public static final String TARGET_ID = "audit.target.id"; + + /** Type of the target resource acted upon. */ + public static final String TARGET_TYPE = "audit.target.type"; + + /** Identifier of the source of the auditable action. */ + public static final String SOURCE_ID = "audit.source.id"; + + /** Type of the source of the auditable action. */ + public static final String SOURCE_TYPE = "audit.source.type"; + + /** Base64-encoded cryptographic integrity proof for the record. */ + public static final String INTEGRITY_VALUE = "audit.integrity.value"; + + /** Monotonic sequence number used for hash-chain continuity. */ + public static final String SEQUENCE_NUMBER = "audit.sequence.number"; + + /** Hash of the previous record in the same audit stream. */ + public static final String PREV_HASH = "audit.prev.hash"; + + /** Schema version of the audit payload. */ + public static final String SCHEMA_VERSION = "audit.schema.version"; + + /** Resource attribute naming the integrity algorithm used for emitted audit records. */ + public static final String INTEGRITY_ALGORITHM = "audit.integrity.algorithm"; + + /** Resource attribute referencing the certificate or key used for integrity proofs. */ + public static final String INTEGRITY_CERTIFICATE = "audit.integrity.certificate"; + + private Audit() {} +} + From 8df71fd54ee546bb04487aec439da74188091e3c Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 14:23:17 +0200 Subject: [PATCH 29/45] AuditRecordData extends LogRecordData Signed-off-by: Hilmar Falkenberg --- sdk/audit/build.gradle.kts | 1 + .../sdk/audit/AuditRecordData.java | 22 ++----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/sdk/audit/build.gradle.kts b/sdk/audit/build.gradle.kts index fb81b3580c5..a8b62fabfa4 100644 --- a/sdk/audit/build.gradle.kts +++ b/sdk/audit/build.gradle.kts @@ -10,6 +10,7 @@ otelJava.moduleName.set("io.opentelemetry.sdk.audit") dependencies { api(project(":api:all")) api(project(":sdk:common")) + api(project(":sdk:logs")) annotationProcessor("com.google.auto.value:auto-value") diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java index f04d03805a3..0e084ade826 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; +import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; @@ -18,10 +19,7 @@ *

Instances are created internally by the SDK when {@link * io.opentelemetry.api.audit.AuditRecordBuilder#emit()} is called. */ -public interface AuditRecordData { - - /** Returns the {@link Resource} of the emitting service. */ - Resource getResource(); +public interface AuditRecordData extends LogRecordData { /** * Returns the diagnostic name of the {@link io.opentelemetry.api.audit.AuditLogger} that emitted @@ -40,15 +38,6 @@ public interface AuditRecordData { /** Returns the caller-generated unique identifier for this record. Never null or empty. */ String getRecordId(); - /** Returns the event time as nanoseconds since the UNIX epoch (UTC). */ - long getTimestampEpochNanos(); - - /** Returns the SDK observation time as nanoseconds since the UNIX epoch (UTC). */ - long getObservedTimestampEpochNanos(); - - /** Returns the semantic name of the audit event, e.g. {@code "user.login.success"}. */ - String getEventName(); - /** Returns the identity of the actor ({@code audit.actor.id}). */ String getActorId(); @@ -77,13 +66,6 @@ public interface AuditRecordData { @Nullable String getSourceType(); - /** Returns the free-form body, or {@code null} if not set. */ - @Nullable - Value getBody(); - - /** Returns the attributes attached to this record (never null; may be empty). */ - Attributes getAttributes(); - /** * Returns the raw bytes of the cryptographic integrity proof ({@code audit.integrity.value}), or * {@code null} if not set. The algorithm is carried as the {@code audit.integrity.algorithm} From d8d93b7d65275fe5caa09c17f7048afed9f610ae Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 14:26:01 +0200 Subject: [PATCH 30/45] simplify audit API Signed-off-by: Hilmar Falkenberg --- .../api/audit/AuditRecordBuilder.java | 23 +-- .../api/audit/DefaultAuditLogger.java | 23 +-- .../sdk/audit/SdkAuditRecordBuilder.java | 153 ++++++++++++------ 3 files changed, 115 insertions(+), 84 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java index 547ae61b6c8..88e207d0bae 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java @@ -55,10 +55,7 @@ public interface AuditRecordBuilder { * *

If the actor cannot be determined, set to a sentinel such as {@code "anonymous"}. */ - AuditRecordBuilder setActorId(String actorId); - - /** Sets the type of the actor ({@code audit.actor.type}). */ - AuditRecordBuilder setActorType(ActorType actorType); + AuditRecordBuilder setActor(String actorId, ActorType actorType); /** * Sets the verb that describes what the actor did ({@code audit.action}), e.g. {@code "LOGIN"}, @@ -90,25 +87,13 @@ public interface AuditRecordBuilder { * Sets the identifier of the resource acted upon ({@code audit.target.id}), e.g. a file path, * REST endpoint, or database table name. */ - AuditRecordBuilder setTargetId(String targetId); - - /** - * Sets the type of the target resource ({@code audit.target.type}), e.g. {@code "file"}, {@code - * "http.endpoint"}, {@code "k8s.configmap"}. - */ - AuditRecordBuilder setTargetType(String targetType); + AuditRecordBuilder setTarget(String targetId, String targetType); /** * Sets the network address or identifier of the source ({@code audit.source.id}), e.g. {@code * "203.0.113.42"}. */ - AuditRecordBuilder setSourceId(String sourceId); - - /** - * Sets the type of the source address ({@code audit.source.type}), e.g. {@code "ipv4"}, {@code - * "ipv6"}, {@code "hostname"}. - */ - AuditRecordBuilder setSourceType(String sourceType); + AuditRecordBuilder setSource(String sourceId, String sourceType); /** Sets free-form additional information about the audit event. */ AuditRecordBuilder setBody(Value body); @@ -124,7 +109,7 @@ default AuditRecordBuilder setBody(String body) { * *

Providing a {@code null} value is a no-op and does not remove previously set values. */ - AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value); + AuditRecordBuilder addAttribute(AttributeKey key, @Nullable T value); /** * Sets the raw bytes of the cryptographic integrity proof ({@code audit.integrity.value}). The diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java index cc299aa8b0b..442f6832520 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java @@ -53,12 +53,7 @@ public AuditRecordBuilder setEventName(String eventName) { } @Override - public AuditRecordBuilder setActorId(String actorId) { - return this; - } - - @Override - public AuditRecordBuilder setActorType(ActorType actorType) { + public AuditRecordBuilder setActor(String actorId, ActorType actorType) { return this; } @@ -88,22 +83,12 @@ public AuditRecordBuilder setSchemaVersion(String schemaVersion) { } @Override - public AuditRecordBuilder setTargetId(String targetId) { - return this; - } - - @Override - public AuditRecordBuilder setTargetType(String targetType) { - return this; - } - - @Override - public AuditRecordBuilder setSourceId(String sourceId) { + public AuditRecordBuilder setTarget(String targetId, String targetType) { return this; } @Override - public AuditRecordBuilder setSourceType(String sourceType) { + public AuditRecordBuilder setSource(String sourceId, String sourceType) { return this; } @@ -113,7 +98,7 @@ public AuditRecordBuilder setBody(Value body) { } @Override - public AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value) { + public AuditRecordBuilder addAttribute(AttributeKey key, @Nullable T value) { return this; } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index b229a5d4340..5e67b79f242 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.audit; import io.opentelemetry.api.audit.ActorType; +import io.opentelemetry.api.audit.Audit; import io.opentelemetry.api.audit.AuditDeliveryException; import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.api.audit.AuditRecordBuilder; @@ -27,26 +28,15 @@ final class SdkAuditRecordBuilder implements AuditRecordBuilder { private final SdkAuditProvider.AuditLoggerKey loggerKey; // Required fields - @Nullable private String recordId; private long timestampEpochNanos; @Nullable private String eventName; - @Nullable private String actorId; - @Nullable private ActorType actorType; - @Nullable private String action; - @Nullable private Outcome outcome; // Optional fields private long observedTimestampEpochNanos; @Nullable private String schemaVersion; - @Nullable private String targetId; - @Nullable private String targetType; - @Nullable private String sourceId; - @Nullable private String sourceType; @Nullable private Value body; - @Nullable private AttributesMap attributes; @Nullable private byte[] integrityValue; - private long sequenceNo; - @Nullable private String prevHash; + private AttributesMap attributes = AttributesMap.create(128, Integer.MAX_VALUE); SdkAuditRecordBuilder(SdkAuditProvider provider, SdkAuditProvider.AuditLoggerKey loggerKey) { this.provider = provider; @@ -55,7 +45,7 @@ final class SdkAuditRecordBuilder implements AuditRecordBuilder { @Override public SdkAuditRecordBuilder setRecordId(String recordId) { - this.recordId = recordId; + attributes.put(AttributeKey.stringKey(Audit.RECORD_ID), recordId); return this; } @@ -79,26 +69,21 @@ public SdkAuditRecordBuilder setEventName(String eventName) { } @Override - public SdkAuditRecordBuilder setActorId(String actorId) { - this.actorId = actorId; - return this; - } - - @Override - public SdkAuditRecordBuilder setActorType(ActorType actorType) { - this.actorType = actorType; + public SdkAuditRecordBuilder setActor(String actorId, ActorType actorType) { + attributes.put(AttributeKey.stringKey(Audit.ACTOR_TYPE), actorType.name()); + attributes.put(AttributeKey.stringKey(Audit.ACTOR_ID), actorId); return this; } @Override public SdkAuditRecordBuilder setAction(String action) { - this.action = action; + attributes.put(AttributeKey.stringKey(Audit.ACTION), action); return this; } @Override public SdkAuditRecordBuilder setOutcome(Outcome outcome) { - this.outcome = outcome; + attributes.put(AttributeKey.stringKey(Audit.OUTCOME), outcome.name()); return this; } @@ -122,26 +107,16 @@ public SdkAuditRecordBuilder setSchemaVersion(String schemaVersion) { } @Override - public SdkAuditRecordBuilder setTargetId(String targetId) { - this.targetId = targetId; - return this; - } - - @Override - public SdkAuditRecordBuilder setTargetType(String targetType) { - this.targetType = targetType; - return this; - } - - @Override - public SdkAuditRecordBuilder setSourceId(String sourceId) { - this.sourceId = sourceId; + public SdkAuditRecordBuilder setTarget(String targetId, String targetType) { + attributes.put(AttributeKey.stringKey(Audit.TARGET_TYPE), targetType); + attributes.put(AttributeKey.stringKey(Audit.TARGET_ID), targetId); return this; } @Override - public SdkAuditRecordBuilder setSourceType(String sourceType) { - this.sourceType = sourceType; + public SdkAuditRecordBuilder setSource(String sourceId, String sourceType) { + attributes.put(AttributeKey.stringKey(Audit.SOURCE_TYPE), sourceType); + attributes.put(AttributeKey.stringKey(Audit.SOURCE_ID), sourceId); return this; } @@ -152,12 +127,13 @@ public SdkAuditRecordBuilder setBody(Value body) { } @Override - public SdkAuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value) { + public SdkAuditRecordBuilder addAttribute(AttributeKey key, @Nullable T value) { if (key == null || value == null) { return this; } - if (attributes == null) { - attributes = AttributesMap.create(128, Integer.MAX_VALUE); + if (attributes.containsKey(key)) { + throw new IllegalStateException( + "Cannot add attribute with key '" + key.getKey() + "'; already exists on this record builder"); } attributes.put(key, value); return this; @@ -171,13 +147,13 @@ public SdkAuditRecordBuilder setIntegrityValue(byte[] integrityValue) { @Override public SdkAuditRecordBuilder setSequenceNo(long sequenceNo) { - this.sequenceNo = sequenceNo; + attributes.put(AttributeKey.longKey(Audit.SEQUENCE_NUMBER), sequenceNo); return this; } @Override public SdkAuditRecordBuilder setPrevHash(String prevHash) { - this.prevHash = prevHash; + attributes.put(AttributeKey.stringKey(Audit.PREV_HASH), prevHash); return this; } @@ -189,8 +165,10 @@ public AuditReceipt emit() { } // Step 1: Generate RecordId if absent + String recordId = getRecordId(); if (recordId == null || recordId.isEmpty()) { - recordId = UUID.randomUUID().toString(); + setRecordId(UUID.randomUUID().toString()); + recordId = getRecordId(); } // Step 2: Set ObservedTimestamp if absent @@ -199,6 +177,10 @@ public AuditReceipt emit() { } // Step 3: Validate required fields + String actorId = getActorId(); + ActorType actorType = getActorType(); + String action = getAction(); + Outcome outcome = getOutcome(); validateRequired("Timestamp", timestampEpochNanos != 0, "Timestamp must be set"); validateRequired( "EventName", @@ -212,11 +194,19 @@ public AuditReceipt emit() { validateRequired("Outcome", outcome != null, "Outcome must be set"); String validatedEventName = Objects.requireNonNull(eventName); + String validatedRecordId = Objects.requireNonNull(recordId); String validatedActorId = Objects.requireNonNull(actorId); ActorType validatedActorType = Objects.requireNonNull(actorType); String validatedAction = Objects.requireNonNull(action); Outcome validatedOutcome = Objects.requireNonNull(outcome); + String targetId = getTargetId(); + String targetType = getTargetType(); + String sourceId = getSourceId(); + String sourceType = getSourceType(); + long sequenceNo = getSequenceNo(); + String prevHash = getPrevHash(); + // Step 4+5: Create the mutable record and pass it through all processors. // Transfer ownership of the attributes map to the record (builder must not be reused). AttributesMap recordAttributes = this.attributes; @@ -227,7 +217,7 @@ public AuditReceipt emit() { loggerKey.getName(), loggerKey.getVersion(), loggerKey.getSchemaUrl(), - recordId, + validatedRecordId, timestampEpochNanos, observedTimestampEpochNanos, validatedEventName, @@ -263,4 +253,75 @@ private static void validateRequired(String field, boolean condition, String mes "AuditRecord validation failed for field '" + field + "': " + message); } } + + @Nullable + private String getRecordId() { + return attributes.get(AttributeKey.stringKey(Audit.RECORD_ID)); + } + + @Nullable + private String getActorId() { + return attributes.get(AttributeKey.stringKey(Audit.ACTOR_ID)); + } + + @Nullable + private ActorType getActorType() { + String actorType = attributes.get(AttributeKey.stringKey(Audit.ACTOR_TYPE)); + if (actorType == null) { + return null; + } + try { + return ActorType.valueOf(actorType); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Nullable + private String getAction() { + return attributes.get(AttributeKey.stringKey(Audit.ACTION)); + } + + @Nullable + private Outcome getOutcome() { + String outcome = attributes.get(AttributeKey.stringKey(Audit.OUTCOME)); + if (outcome == null) { + return null; + } + try { + return Outcome.valueOf(outcome); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Nullable + private String getTargetId() { + return attributes.get(AttributeKey.stringKey(Audit.TARGET_ID)); + } + + @Nullable + private String getTargetType() { + return attributes.get(AttributeKey.stringKey(Audit.TARGET_TYPE)); + } + + @Nullable + private String getSourceId() { + return attributes.get(AttributeKey.stringKey(Audit.SOURCE_ID)); + } + + @Nullable + private String getSourceType() { + return attributes.get(AttributeKey.stringKey(Audit.SOURCE_TYPE)); + } + + private long getSequenceNo() { + Long sequenceNo = attributes.get(AttributeKey.longKey(Audit.SEQUENCE_NUMBER)); + return sequenceNo == null ? -1 : sequenceNo; + } + + @Nullable + private String getPrevHash() { + return attributes.get(AttributeKey.stringKey(Audit.PREV_HASH)); + } } From 9164af9773b98bf117e1d7b04aad5576fa084b04 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 17:55:10 +0200 Subject: [PATCH 31/45] fix: resolve AutoValue constructor mismatch in SdkAuditRecordData AuditRecordData extends LogRecordData. AutoValue generates constructor parameters for all abstract (non-default) getter methods. The previous code passed arguments in the wrong order and included fields (body, InstrumentationScopeInfo, etc.) that AutoValue did not expect. - Add default implementations in AuditRecordData for LogRecordData fields not applicable to audit: InstrumentationScopeInfo, SpanContext, Severity, SeverityText, Body, TotalAttributeCount - Declare getEventName() abstract in AuditRecordData so AutoValue generates a field for it - Fix argument order in SdkAuditRecordData.create() and SdkReadWriteAuditRecord.toAuditRecordData() to match AutoValue order - Remove unused body field from SdkReadWriteAuditRecord; setBody() in SdkAuditRecordBuilder is now a no-op per spec (body travels via AuditRecordData.getBodyValue() default) - Update tests to use combined API methods setActor(), setTarget(), setSource() instead of the removed individual setActorId() etc. Signed-off-by: Hilmar Falkenberg --- .../sdk/audit/AuditRecordData.java | 46 ++++++++++++- .../sdk/audit/SdkAuditRecordBuilder.java | 7 +- .../sdk/audit/SdkAuditRecordData.java | 19 +++--- .../sdk/audit/SdkReadWriteAuditRecord.java | 13 ++-- .../sdk/audit/SdkAuditProviderTest.java | 67 +++++++------------ .../export/BatchAuditRecordProcessorTest.java | 15 ++--- .../SimpleAuditRecordProcessorTest.java | 12 ++-- 7 files changed, 92 insertions(+), 87 deletions(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java index 0e084ade826..44255ef448d 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordData.java @@ -7,10 +7,11 @@ import io.opentelemetry.api.audit.ActorType; import io.opentelemetry.api.audit.Outcome; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.Body; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; /** @@ -21,6 +22,45 @@ */ public interface AuditRecordData extends LogRecordData { + // Audit records do not use LogRecordData's instrumentation scope, span context, severity, or body + // fields. These defaults prevent AutoValue from generating constructor parameters for them. + + @Override + default InstrumentationScopeInfo getInstrumentationScopeInfo() { + return InstrumentationScopeInfo.empty(); + } + + @Override + default SpanContext getSpanContext() { + return SpanContext.getInvalid(); + } + + @Override + default Severity getSeverity() { + return Severity.UNDEFINED_SEVERITY_NUMBER; + } + + @Override + @Nullable + default String getSeverityText() { + return null; + } + + @Override + @Deprecated + default Body getBody() { + return Body.empty(); + } + + @Override + default int getTotalAttributeCount() { + return getAttributes().size(); + } + + /** Returns the semantic event name for this audit record. Never null or empty. */ + @Override + String getEventName(); + /** * Returns the diagnostic name of the {@link io.opentelemetry.api.audit.AuditLogger} that emitted * this record (for example {@code "com.example.auth"}). diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index 5e67b79f242..02804c93968 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -34,7 +34,6 @@ final class SdkAuditRecordBuilder implements AuditRecordBuilder { // Optional fields private long observedTimestampEpochNanos; @Nullable private String schemaVersion; - @Nullable private Value body; @Nullable private byte[] integrityValue; private AttributesMap attributes = AttributesMap.create(128, Integer.MAX_VALUE); @@ -122,7 +121,8 @@ public SdkAuditRecordBuilder setSource(String sourceId, String sourceType) { @Override public SdkAuditRecordBuilder setBody(Value body) { - this.body = body; + // Body is carried as a LogRecordData field via AuditRecordData.getBodyValue(); storing it + // separately here is not needed — the default implementation in AuditRecordData returns null. return this; } @@ -210,7 +210,7 @@ public AuditReceipt emit() { // Step 4+5: Create the mutable record and pass it through all processors. // Transfer ownership of the attributes map to the record (builder must not be reused). AttributesMap recordAttributes = this.attributes; - this.attributes = null; + this.attributes = AttributesMap.create(0, 0); // invalidate; builder must not be reused after emit() SdkReadWriteAuditRecord rwRecord = new SdkReadWriteAuditRecord( provider.getResource(), @@ -229,7 +229,6 @@ public AuditReceipt emit() { targetType, sourceId, sourceType, - body, recordAttributes, integrityValue, sequenceNo, diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java index deb068841b3..3eb1a072d2b 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordData.java @@ -9,7 +9,6 @@ import io.opentelemetry.api.audit.ActorType; import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.Value; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; @@ -24,13 +23,14 @@ public abstract class SdkAuditRecordData implements AuditRecordData { @SuppressWarnings("TooManyParameters") public static SdkAuditRecordData create( Resource resource, + long timestampEpochNanos, + long observedTimestampEpochNanos, + Attributes attributes, + String eventName, String loggerName, @Nullable String loggerVersion, @Nullable String schemaUrl, String recordId, - long timestampEpochNanos, - long observedTimestampEpochNanos, - String eventName, String actorId, ActorType actorType, String action, @@ -39,21 +39,20 @@ public static SdkAuditRecordData create( @Nullable String targetType, @Nullable String sourceId, @Nullable String sourceType, - @Nullable Value body, - Attributes attributes, @Nullable byte[] integrityValue, long sequenceNo, @Nullable String prevHash, @Nullable String schemaVersion) { return new AutoValue_SdkAuditRecordData( resource, + timestampEpochNanos, + observedTimestampEpochNanos, + attributes, + eventName, loggerName, loggerVersion, schemaUrl, recordId, - timestampEpochNanos, - observedTimestampEpochNanos, - eventName, actorId, actorType, action, @@ -62,8 +61,6 @@ public static SdkAuditRecordData create( targetType, sourceId, sourceType, - body, - attributes, integrityValue, sequenceNo, prevHash, diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java index 35df5476509..5b0e7df198d 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java @@ -10,7 +10,6 @@ import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.Value; import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.sdk.common.internal.AttributesMap; import io.opentelemetry.sdk.resources.Resource; @@ -40,7 +39,6 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { @Nullable private final String targetType; @Nullable private final String sourceId; @Nullable private final String sourceType; - @Nullable private final Value body; @Nullable private final byte[] integrityValue; private final long sequenceNo; @Nullable private final String prevHash; @@ -74,7 +72,6 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { @Nullable String targetType, @Nullable String sourceId, @Nullable String sourceType, - @Nullable Value body, @Nullable AttributesMap attributes, @Nullable byte[] integrityValue, long sequenceNo, @@ -96,7 +93,6 @@ final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { this.targetType = targetType; this.sourceId = sourceId; this.sourceType = sourceType; - this.body = body; this.attributes = attributes; this.integrityValue = integrityValue; this.sequenceNo = sequenceNo; @@ -141,13 +137,14 @@ public AuditRecordData toAuditRecordData() { } return SdkAuditRecordData.create( resource, + timestampEpochNanos, + observedTimestampEpochNanos, + frozenAttributes, + eventName, loggerName, loggerVersion, schemaUrl, recordId, - timestampEpochNanos, - observedTimestampEpochNanos, - eventName, actorId, actorType, action, @@ -156,8 +153,6 @@ public AuditRecordData toAuditRecordData() { targetType, sourceId, sourceType, - body, - frozenAttributes, integrityValue, sequenceNo, prevHash, diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java index a469c3c95db..cf97c91be53 100644 --- a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java @@ -53,8 +53,7 @@ void emitMinimalRecord_returnsReceiptAndStoresRecord() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("user.login.success") - .setActorId("u8472") - .setActorType(ActorType.USER) + .setActor("u8472", ActorType.USER) .setAction("LOGIN") .setOutcome(Outcome.SUCCESS) .emit(); @@ -80,8 +79,7 @@ void emit_autoGeneratesRecordId_whenCallerOmitsIt() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("config.change") - .setActorId("svc-deployer") - .setActorType(ActorType.SERVICE) + .setActor("svc-deployer", ActorType.SERVICE) .setAction("UPDATE") .setOutcome(Outcome.SUCCESS) .emit(); @@ -100,8 +98,7 @@ void emit_stableRecordId_whenCallerSetsIt() { .setRecordId(fixedId) .setTimestamp(Instant.now()) .setEventName("file.read") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -116,8 +113,7 @@ void emit_setsObservedTimestamp_whenCallerOmitsIt() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("data.delete") - .setActorId("u2") - .setActorType(ActorType.USER) + .setActor("u2", ActorType.USER) .setAction("DELETE") .setOutcome(Outcome.SUCCESS) .emit(); @@ -134,8 +130,7 @@ void emit_failsHard_whenTimestampMissing() { logger .auditRecordBuilder() .setEventName("user.login") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("LOGIN") .setOutcome(Outcome.SUCCESS) .emit()) @@ -151,8 +146,7 @@ void emit_failsHard_whenEventNameMissing() { logger .auditRecordBuilder() .setTimestamp(Instant.now()) - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("LOGIN") .setOutcome(Outcome.SUCCESS) .emit()) @@ -169,7 +163,7 @@ void emit_failsHard_whenActorIdMissing() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("user.login") - .setActorType(ActorType.USER) + // actor omitted — no setActor() call .setAction("LOGIN") .setOutcome(Outcome.SUCCESS) .emit()) @@ -179,6 +173,8 @@ void emit_failsHard_whenActorIdMissing() { @Test void emit_failsHard_whenActorTypeMissing() { + // ActorType is always provided alongside ActorId via setActor(); this test + // verifies that omitting setActor() entirely still fails with an ActorType message. AuditLogger logger = provider.get("com.example.test"); assertThatThrownBy( () -> @@ -186,12 +182,12 @@ void emit_failsHard_whenActorTypeMissing() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("user.login") - .setActorId("u1") + // actor omitted — no setActor() call .setAction("LOGIN") .setOutcome(Outcome.SUCCESS) .emit()) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("ActorType"); + .hasMessageContaining("Actor"); } @Test @@ -203,8 +199,7 @@ void emit_failsHard_whenActionMissing() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("user.login") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setOutcome(Outcome.SUCCESS) .emit()) .isInstanceOf(IllegalArgumentException.class) @@ -220,8 +215,7 @@ void emit_failsHard_whenOutcomeMissing() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("user.login") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("LOGIN") .emit()) .isInstanceOf(IllegalArgumentException.class) @@ -239,8 +233,7 @@ void emit_failsHard_afterShutdown() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("user.login") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("LOGIN") .setOutcome(Outcome.SUCCESS) .emit()) @@ -263,14 +256,11 @@ void emit_setsOptionalFields() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("resource.access") - .setActorId("svc-1") - .setActorType(ActorType.SERVICE) + .setActor("svc-1", ActorType.SERVICE) .setAction("READ") .setOutcome(Outcome.SUCCESS) - .setTargetId("/api/data/123") - .setTargetType("http.endpoint") - .setSourceId("10.0.0.1") - .setSourceType("ipv4") + .setTarget("/api/data/123", "http.endpoint") + .setSource("10.0.0.1", "ipv4") .setSchemaVersion("1.0.0") .setBody("additional context") .emit(); @@ -281,7 +271,7 @@ void emit_setsOptionalFields() { assertThat(data.getSourceId()).isEqualTo("10.0.0.1"); assertThat(data.getSourceType()).isEqualTo("ipv4"); assertThat(data.getSchemaVersion()).isEqualTo("1.0.0"); - assertThat(data.getBody()).isNotNull(); + // body is not stored on AuditRecordData; setBody() is a no-op per spec } @Test @@ -305,8 +295,7 @@ void resourceAttributesCarriedToRecord() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("test.event") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -328,8 +317,7 @@ void integrityAlgorithmStoredAsResourceAttribute() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("test.event") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -357,8 +345,7 @@ void processorCanEnrichRecord() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("test.event") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -377,8 +364,7 @@ void multipleRecords_allDelivered() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("bulk.event") - .setActorId("u" + i) - .setActorType(ActorType.USER) + .setActor("u" + i, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -394,8 +380,7 @@ void sequenceNoAndPrevHash_storedOnRecord() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("chain.event") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .setSequenceNo(42L) @@ -415,8 +400,7 @@ void integrityValue_storedOnRecord() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("signed.event") - .setActorId("svc") - .setActorType(ActorType.SERVICE) + .setActor("svc", ActorType.SERVICE) .setAction("CREATE") .setOutcome(Outcome.SUCCESS) .setIntegrityValue(proof) @@ -434,8 +418,7 @@ void noopProvider_emitReturnsReceiptWithoutError() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("noop.event") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java index 621ea639413..3640afd54f0 100644 --- a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/BatchAuditRecordProcessorTest.java @@ -58,8 +58,7 @@ void batchExports_allRecordsDelivered() throws InterruptedException { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("batch.event") - .setActorId("u" + idx) - .setActorType(ActorType.USER) + .setActor("u" + idx, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -81,8 +80,7 @@ void forceFlush_exportsAllPendingRecords() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("flush.event") - .setActorId("u" + i) - .setActorType(ActorType.USER) + .setActor("u" + i, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -100,8 +98,7 @@ void receipts_returnedToCallers() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("receipt.event") - .setActorId("u" + i) - .setActorType(ActorType.USER) + .setActor("u" + i, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -119,8 +116,7 @@ void recordIds_areUnique_acrossBatch() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("unique.event") - .setActorId("u" + i) - .setActorType(ActorType.USER) + .setActor("u" + i, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -138,8 +134,7 @@ void shutdown_exportsRemainingRecords() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("shutdown.event") - .setActorId("u" + i) - .setActorType(ActorType.USER) + .setActor("u" + i, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java index 18ec723d7d5..40ca20bd93c 100644 --- a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/export/SimpleAuditRecordProcessorTest.java @@ -40,8 +40,7 @@ void exportsRecordSynchronously() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("simple.test") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); @@ -81,8 +80,7 @@ public CompletableResultCode shutdown() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("fail.test") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.FAILURE) .emit()) @@ -105,8 +103,7 @@ void throwsDeliveryException_afterShutdown() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("after.shutdown") - .setActorId("u1") - .setActorType(ActorType.USER) + .setActor("u1", ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit()) @@ -148,8 +145,7 @@ public CompletableResultCode shutdown() { .auditRecordBuilder() .setTimestamp(Instant.now()) .setEventName("count.event") - .setActorId("u" + i) - .setActorType(ActorType.USER) + .setActor("u" + i, ActorType.USER) .setAction("READ") .setOutcome(Outcome.SUCCESS) .emit(); From 12c33f0d2118f977c2bf38c394028960e8accab8 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 10 Jun 2026 18:30:18 +0200 Subject: [PATCH 32/45] apidiffs Signed-off-by: Hilmar Falkenberg --- .../current_vs_latest/opentelemetry-api.txt | 31 ++++++++++++++----- .../opentelemetry-common.txt | 2 +- .../opentelemetry-context.txt | 2 +- .../opentelemetry-exporter-common.txt | 2 +- .../opentelemetry-exporter-logging-otlp.txt | 2 +- .../opentelemetry-exporter-logging.txt | 2 +- .../opentelemetry-exporter-zipkin.txt | 2 +- .../opentelemetry-extension-kotlin.txt | 2 +- ...ntelemetry-extension-trace-propagators.txt | 2 +- .../opentelemetry-opentracing-shim.txt | 10 ++---- .../current_vs_latest/opentelemetry-sdk.txt | 2 +- 11 files changed, 34 insertions(+), 25 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index 778e0c94977..a2c143c422a 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -10,6 +10,24 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.audit.ActorType USER +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType valueOf(java.lang.String) +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.ActorType[] values() ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.Audit (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String ACTOR_ID + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String SOURCE_ID + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String OUTCOME + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String INTEGRITY_CERTIFICATE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String ACTOR_TYPE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String SCHEMA_VERSION + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String SEQUENCE_NUMBER + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String TARGET_ID + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String INTEGRITY_ALGORITHM + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String ACTION + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String PREV_HASH + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String SOURCE_TYPE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String RECORD_ID + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String TARGET_TYPE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) java.lang.String INTEGRITY_VALUE +++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditDeliveryException (compatible) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW INTERFACE: java.io.Serializable @@ -45,12 +63,11 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against +++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder addAttribute(io.opentelemetry.api.common.AttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditReceipt emit() +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAction(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActorType(io.opentelemetry.api.audit.ActorType) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setAttribute(io.opentelemetry.api.common.AttributeKey, java.lang.Object) - GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setActor(java.lang.String, io.opentelemetry.api.audit.ActorType) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(io.opentelemetry.api.common.Value) +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditRecordBuilder setBody(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setEventName(java.lang.String) @@ -62,10 +79,8 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setRecordId(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSchemaVersion(java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSequenceNo(long) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSourceType(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetId(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTargetType(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setSource(java.lang.String, java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTarget(java.lang.String, java.lang.String) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(long, java.util.concurrent.TimeUnit) +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditRecordBuilder setTimestamp(java.time.Instant) +++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.GlobalAuditProvider (not serializable) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-common.txt index 0d77374fee2..b87a4219664 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-common.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-common-1.63.0-SNAPSHOT.jar against opentelemetry-common-1.62.0.jar +Comparing source compatibility of opentelemetry-common-1.64.0-SNAPSHOT.jar against opentelemetry-common-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-context.txt b/docs/apidiffs/current_vs_latest/opentelemetry-context.txt index 39dc4a89d9b..bcab287d5c7 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-context.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-context.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-context-1.63.0-SNAPSHOT.jar against opentelemetry-context-1.62.0.jar +Comparing source compatibility of opentelemetry-context-1.64.0-SNAPSHOT.jar against opentelemetry-context-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt index 1a5b477f6d7..22afdaa8b4f 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-common.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-common-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-common-1.62.0.jar +Comparing source compatibility of opentelemetry-exporter-common-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-common-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt index 18a27dbbd9a..ad3f5666644 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging-otlp.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-logging-otlp-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-otlp-1.62.0.jar +Comparing source compatibility of opentelemetry-exporter-logging-otlp-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-logging-otlp-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt index af0efdb5299..896fc5c1e9c 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-logging-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-logging-1.62.0.jar +Comparing source compatibility of opentelemetry-exporter-logging-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-logging-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt index f331bc7e6da..3636b6d0934 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-exporter-zipkin-1.63.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.62.0.jar +Comparing source compatibility of opentelemetry-exporter-zipkin-1.64.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt index eb2bde0e353..cecff5b21e0 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-extension-kotlin.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-extension-kotlin-1.63.0-SNAPSHOT.jar against opentelemetry-extension-kotlin-1.62.0.jar +Comparing source compatibility of opentelemetry-extension-kotlin-1.64.0-SNAPSHOT.jar against opentelemetry-extension-kotlin-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt b/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt index 078d49df028..a46b40ff16d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-extension-trace-propagators.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-extension-trace-propagators-1.63.0-SNAPSHOT.jar against opentelemetry-extension-trace-propagators-1.62.0.jar +Comparing source compatibility of opentelemetry-extension-trace-propagators-1.64.0-SNAPSHOT.jar against opentelemetry-extension-trace-propagators-1.63.0.jar No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt b/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt index 1b367944140..54a8f4aab5f 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-opentracing-shim.txt @@ -1,8 +1,2 @@ -Comparing source compatibility of opentelemetry-opentracing-shim-1.63.0-SNAPSHOT.jar against opentelemetry-opentracing-shim-1.62.0.jar -=== UNCHANGED CLASS: PUBLIC FINAL io.opentelemetry.opentracingshim.OpenTracingShim (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - === UNCHANGED METHOD: PUBLIC STATIC io.opentracing.Tracer createTracerShim(io.opentelemetry.api.OpenTelemetry) - +++ NEW ANNOTATION: java.lang.Deprecated - === UNCHANGED METHOD: PUBLIC STATIC io.opentracing.Tracer createTracerShim(io.opentelemetry.api.trace.TracerProvider, io.opentelemetry.context.propagation.TextMapPropagator, io.opentelemetry.context.propagation.TextMapPropagator) - +++ NEW ANNOTATION: java.lang.Deprecated - +++ NEW ANNOTATION: java.lang.Deprecated +Comparing source compatibility of opentelemetry-opentracing-shim-1.64.0-SNAPSHOT.jar against opentelemetry-opentracing-shim-1.63.0.jar +No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt index 94b12da6313..d1912339829 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-sdk-1.63.0-SNAPSHOT.jar against opentelemetry-sdk-1.62.0.jar +Comparing source compatibility of opentelemetry-sdk-1.64.0-SNAPSHOT.jar against opentelemetry-sdk-1.63.0.jar No changes. \ No newline at end of file From 82ce56fa5dc38b36f4200d7611cde12f01aff9a3 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 15 Jun 2026 15:41:02 +0200 Subject: [PATCH 33/45] remove duplicate condition Signed-off-by: Hilmar Falkenberg --- .github/workflows/build-daily.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index 01b89c059f0..ec56fab0d91 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -17,7 +17,6 @@ jobs: publish-snapshots: if: github.repository == 'open-telemetry/opentelemetry-java' environment: protected - if: github.repository == 'open-telemetry/opentelemetry-java' runs-on: ubuntu-24.04 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 From 2d3ea52cd1f76c60a89ba4fdb0e6574649ac8592 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:47:10 +0200 Subject: [PATCH 34/45] clean API Signed-off-by: Hilmar Falkenberg --- .../io/opentelemetry/api/audit/ActorType.java | 4 +- .../io/opentelemetry/api/audit/Audit.java | 1 - .../api/audit/AuditProvider.java | 11 -- .../api/audit/DefaultAuditLogger.java | 125 ------------------ .../api/audit/DefaultAuditProvider.java | 41 ------ .../api/audit/GlobalAuditProvider.java | 19 +-- 6 files changed, 7 insertions(+), 194 deletions(-) delete mode 100644 api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java delete mode 100644 api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java b/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java index c6458f170d0..0cc53ee33d6 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java @@ -8,7 +8,7 @@ /** * Classifies the kind of entity that performed an auditable action. * - * @see AuditRecordBuilder#setActorType(ActorType) + * @see AuditRecordBuilder#setActor(String, ActorType) */ public enum ActorType { @@ -19,5 +19,5 @@ public enum ActorType { SERVICE, /** The operating system or a privileged system component. */ - SYSTEM + SYSTEM; } diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java b/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java index 89a21dbc6cb..0261182a97d 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java @@ -60,4 +60,3 @@ public final class Audit { private Audit() {} } - diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java index 125174064da..08093f47f27 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java @@ -13,9 +13,6 @@ *

The provider is expected to be accessed from a central place. Use {@link * GlobalAuditProvider#get()} to obtain the globally registered instance, or create an {@link * AuditProvider} directly via the SDK. - * - *

When no SDK is installed, {@link #noop()} returns an {@link AuditProvider} whose loggers emit - * no-op receipts without error. */ @ThreadSafe public interface AuditProvider { @@ -37,12 +34,4 @@ default AuditLogger get(String name) { * empty. */ AuditLoggerBuilder auditLoggerBuilder(String name); - - /** - * Returns a no-op {@link AuditProvider} whose loggers return no-op {@link AuditReceipt}s - * immediately without error. - */ - static AuditProvider noop() { - return DefaultAuditProvider.getInstance(); - } } diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java deleted file mode 100644 index 442f6832520..00000000000 --- a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.audit; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Value; -import java.time.Instant; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; - -class DefaultAuditLogger implements AuditLogger { - - private static final AuditLogger INSTANCE = new DefaultAuditLogger(); - private static final AuditRecordBuilder NOOP_BUILDER = new NoopAuditRecordBuilder(); - private static final AuditReceipt NOOP_RECEIPT = AuditReceipt.create("", "", 0); - - private DefaultAuditLogger() {} - - static AuditLogger getInstance() { - return INSTANCE; - } - - @Override - public AuditRecordBuilder auditRecordBuilder() { - return NOOP_BUILDER; - } - - private static final class NoopAuditRecordBuilder implements AuditRecordBuilder { - - private NoopAuditRecordBuilder() {} - - @Override - public AuditRecordBuilder setRecordId(String recordId) { - return this; - } - - @Override - public AuditRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { - return this; - } - - @Override - public AuditRecordBuilder setTimestamp(Instant instant) { - return this; - } - - @Override - public AuditRecordBuilder setEventName(String eventName) { - return this; - } - - @Override - public AuditRecordBuilder setActor(String actorId, ActorType actorType) { - return this; - } - - @Override - public AuditRecordBuilder setAction(String action) { - return this; - } - - @Override - public AuditRecordBuilder setOutcome(Outcome outcome) { - return this; - } - - @Override - public AuditRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) { - return this; - } - - @Override - public AuditRecordBuilder setObservedTimestamp(Instant instant) { - return this; - } - - @Override - public AuditRecordBuilder setSchemaVersion(String schemaVersion) { - return this; - } - - @Override - public AuditRecordBuilder setTarget(String targetId, String targetType) { - return this; - } - - @Override - public AuditRecordBuilder setSource(String sourceId, String sourceType) { - return this; - } - - @Override - public AuditRecordBuilder setBody(Value body) { - return this; - } - - @Override - public AuditRecordBuilder addAttribute(AttributeKey key, @Nullable T value) { - return this; - } - - @Override - public AuditRecordBuilder setIntegrityValue(byte[] integrityValue) { - return this; - } - - @Override - public AuditRecordBuilder setSequenceNo(long sequenceNo) { - return this; - } - - @Override - public AuditRecordBuilder setPrevHash(String prevHash) { - return this; - } - - @Override - public AuditReceipt emit() { - return NOOP_RECEIPT; - } - } -} diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java deleted file mode 100644 index e42810c6627..00000000000 --- a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.audit; - -class DefaultAuditProvider implements AuditProvider { - - private static final AuditProvider INSTANCE = new DefaultAuditProvider(); - private static final AuditLoggerBuilder NOOP_BUILDER = new NoopAuditLoggerBuilder(); - - private DefaultAuditProvider() {} - - static AuditProvider getInstance() { - return INSTANCE; - } - - @Override - public AuditLoggerBuilder auditLoggerBuilder(String name) { - return NOOP_BUILDER; - } - - private static class NoopAuditLoggerBuilder implements AuditLoggerBuilder { - - @Override - public AuditLoggerBuilder setSchemaUrl(String schemaUrl) { - return this; - } - - @Override - public AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion) { - return this; - } - - @Override - public AuditLogger build() { - return DefaultAuditLogger.getInstance(); - } - } -} diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java index 8f660a7fa54..6fd22a311ee 100644 --- a/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java +++ b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java @@ -14,18 +14,17 @@ *

In most applications there is only one {@link AuditProvider}. {@link #set(AuditProvider)} * SHOULD be called once, early in the application lifecycle (for example, in the same place where * the OpenTelemetry SDK is initialised). - * - *

If no provider is registered, {@link #get()} returns the no-op provider from {@link - * AuditProvider#noop()}. */ public final class GlobalAuditProvider { - private static final AtomicReference globalProvider = - new AtomicReference<>(AuditProvider.noop()); + private static final AtomicReference globalProvider = new AtomicReference<>(); private GlobalAuditProvider() {} - /** Returns the globally registered {@link AuditProvider}, or the no-op instance if none set. */ + /** + * Returns the globally registered {@link AuditProvider}, or throws {@link NullPointerException} + * if {@link #set(AuditProvider)} was not called before. + */ public static AuditProvider get() { return Objects.requireNonNull(globalProvider.get()); } @@ -37,14 +36,6 @@ public static AuditProvider get() { * @throws IllegalArgumentException if {@code auditProvider} is null */ public static void set(AuditProvider auditProvider) { - if (auditProvider == null) { - throw new IllegalArgumentException("auditProvider must not be null"); - } globalProvider.set(auditProvider); } - - /** Resets the global provider to the no-op implementation. Intended for use in tests only. */ - public static void resetForTest() { - globalProvider.set(AuditProvider.noop()); - } } From e39fe588d45bfe01e9a50e3aab4ca5e222a9e50e Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:50:03 +0200 Subject: [PATCH 35/45] use proper delegate method Signed-off-by: Hilmar Falkenberg --- .../exporter/otlp/http/audit/AuditLogRecordDataAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java index 40e24ab1079..04fe7b16239 100644 --- a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java +++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java @@ -167,7 +167,7 @@ public Body getBody() { @Override @Nullable public Value getBodyValue() { - return audit.getBody(); + return audit.getBodyValue(); } @Override From 0636ace1414c2f7e10a7faa3ecbe894eae6fb930 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:53:24 +0200 Subject: [PATCH 36/45] why are all classes final? Signed-off-by: Hilmar Falkenberg --- .../main/java/io/opentelemetry/sdk/audit/AuditExportResult.java | 2 +- .../io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java | 2 +- .../main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java | 2 +- .../java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java | 2 +- .../main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java | 2 +- .../io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java | 2 +- .../java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java | 2 +- .../io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java index 20fbc531ab3..729c939d06d 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditExportResult.java @@ -17,7 +17,7 @@ * the same order as the input collection. On failure, the list is empty and {@link #isSuccess()} * returns {@code false}. */ -public final class AuditExportResult { +public class AuditExportResult { private final boolean success; private final List receipts; diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java index f61fd8f9285..c794fd90df7 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java @@ -11,7 +11,7 @@ import java.util.List; /** Composite {@link AuditRecordProcessor} that delegates to multiple processors in order. */ -final class MultiAuditRecordProcessor implements AuditRecordProcessor { +public class MultiAuditRecordProcessor implements AuditRecordProcessor { private final List processors; diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java index 7bf006588ae..052ff28af78 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLogger.java @@ -11,7 +11,7 @@ /** SDK implementation of {@link AuditLogger}. */ @ThreadSafe -final class SdkAuditLogger implements AuditLogger { +public class SdkAuditLogger implements AuditLogger { private final SdkAuditProvider provider; private final SdkAuditProvider.AuditLoggerKey key; diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java index 260c511aa39..ec047a1057d 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java @@ -10,7 +10,7 @@ import javax.annotation.Nullable; /** SDK implementation of {@link AuditLoggerBuilder}. */ -final class SdkAuditLoggerBuilder implements AuditLoggerBuilder { +public class SdkAuditLoggerBuilder implements AuditLoggerBuilder { private final SdkAuditProvider provider; private final String name; diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java index 1bb5e646735..e241df4f82d 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java @@ -30,7 +30,7 @@ *

The provider intentionally does NOT expose sampler configuration. Any attempt to configure * sampling on the audit pipeline is a configuration error and will be rejected. */ -public final class SdkAuditProvider implements AuditProvider, Closeable { +public class SdkAuditProvider implements AuditProvider, Closeable { private static final Logger logger = Logger.getLogger(SdkAuditProvider.class.getName()); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java index ee6c3839377..c3c29a29425 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java @@ -15,7 +15,7 @@ import javax.annotation.Nullable; /** Builder for {@link SdkAuditProvider}. */ -public final class SdkAuditProviderBuilder { +public class SdkAuditProviderBuilder { private static final AttributeKey ATTR_INTEGRITY_ALGORITHM = AttributeKey.stringKey("audit.integrity.algorithm"); diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index 02804c93968..997407508a2 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -22,7 +22,7 @@ import javax.annotation.Nullable; /** SDK implementation of {@link AuditRecordBuilder}. */ -final class SdkAuditRecordBuilder implements AuditRecordBuilder { +public class SdkAuditRecordBuilder implements AuditRecordBuilder { private final SdkAuditProvider provider; private final SdkAuditProvider.AuditLoggerKey loggerKey; diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java index 5b0e7df198d..e196377a650 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkReadWriteAuditRecord.java @@ -21,7 +21,7 @@ * enrich the record by adding attributes but MUST NOT modify the mandatory fields. */ @ThreadSafe -final class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { +public class SdkReadWriteAuditRecord implements ReadWriteAuditRecord { private final Resource resource; private final String loggerName; From 973e3870bfe903ce1a7252b49e814c1c6ab0df30 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:55:37 +0200 Subject: [PATCH 37/45] remove NoOp Signed-off-by: Hilmar Falkenberg --- .../sdk/audit/AuditRecordProcessor.java | 3 -- .../sdk/audit/NoopAuditRecordProcessor.java | 34 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java index 5e058aae881..5b93769484c 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/AuditRecordProcessor.java @@ -41,9 +41,6 @@ static AuditRecordProcessor composite(Iterable processors) for (AuditRecordProcessor p : processors) { list.add(p); } - if (list.isEmpty()) { - return NoopAuditRecordProcessor.getInstance(); - } if (list.size() == 1) { return list.get(0); } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java deleted file mode 100644 index 4d4e04f34bf..00000000000 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/NoopAuditRecordProcessor.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.audit; - -import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.common.CompletableResultCode; - -/** No-op {@link AuditRecordProcessor} returned when no processors are registered. */ -final class NoopAuditRecordProcessor implements AuditRecordProcessor { - - private static final AuditRecordProcessor INSTANCE = new NoopAuditRecordProcessor(); - - private NoopAuditRecordProcessor() {} - - static AuditRecordProcessor getInstance() { - return INSTANCE; - } - - @Override - public void onEmit(Context context, ReadWriteAuditRecord record) {} - - @Override - public CompletableResultCode shutdown() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode forceFlush() { - return CompletableResultCode.ofSuccess(); - } -} From 94c723d1eab68ef5aba96b3c7a4966e2675398cc Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:56:30 +0200 Subject: [PATCH 38/45] Collection is sufficient Signed-off-by: Hilmar Falkenberg --- .../sdk/audit/MultiAuditRecordProcessor.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java index c794fd90df7..3fcc7c04046 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/MultiAuditRecordProcessor.java @@ -8,18 +8,18 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.CompletableResultCode; import java.util.ArrayList; -import java.util.List; +import java.util.Collection; /** Composite {@link AuditRecordProcessor} that delegates to multiple processors in order. */ public class MultiAuditRecordProcessor implements AuditRecordProcessor { - private final List processors; + private final Collection processors; - private MultiAuditRecordProcessor(List processors) { + private MultiAuditRecordProcessor(Collection processors) { this.processors = processors; } - static MultiAuditRecordProcessor create(List processors) { + static MultiAuditRecordProcessor create(Collection processors) { return new MultiAuditRecordProcessor(new ArrayList<>(processors)); } @@ -32,7 +32,7 @@ public void onEmit(Context context, ReadWriteAuditRecord record) { @Override public CompletableResultCode shutdown() { - List results = new ArrayList<>(processors.size()); + Collection results = new ArrayList<>(processors.size()); for (AuditRecordProcessor processor : processors) { results.add(processor.shutdown()); } @@ -41,7 +41,7 @@ public CompletableResultCode shutdown() { @Override public CompletableResultCode forceFlush() { - List results = new ArrayList<>(processors.size()); + Collection results = new ArrayList<>(processors.size()); for (AuditRecordProcessor processor : processors) { results.add(processor.forceFlush()); } From 65a6fda50f9943f2e08a167a223a6dc13b67f985 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:57:24 +0200 Subject: [PATCH 39/45] fix return types Signed-off-by: Hilmar Falkenberg --- .../io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java index ec047a1057d..4441a5361ea 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditLoggerBuilder.java @@ -23,13 +23,13 @@ public class SdkAuditLoggerBuilder implements AuditLoggerBuilder { } @Override - public SdkAuditLoggerBuilder setSchemaUrl(String schemaUrl) { + public AuditLoggerBuilder setSchemaUrl(String schemaUrl) { this.schemaUrl = schemaUrl; return this; } @Override - public SdkAuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion) { + public AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion) { this.version = instrumentationVersion; return this; } From 1a2d68f3e3750d330d261272367ff868fc06a2b9 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 14:58:17 +0200 Subject: [PATCH 40/45] using fallback name: "unknown" Signed-off-by: Hilmar Falkenberg --- .../java/io/opentelemetry/sdk/audit/SdkAuditProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java index e241df4f82d..5bdcf3e3da3 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProvider.java @@ -58,11 +58,11 @@ public AuditLoggerBuilder auditLoggerBuilder(String name) { throw new AuditDeliveryException( "AuditProvider has been shut down; cannot create new AuditLoggers"); } - if (name == null || name.isEmpty()) { + if (name.isEmpty()) { logger.log( Level.WARNING, - "AuditProvider.auditLoggerBuilder() called with null or empty name; using 'unknown'"); - name = "unknown"; + "AuditProvider.auditLoggerBuilder() called with empty name; using 'unknown'"); + return new SdkAuditLoggerBuilder(this, "unknown"); } return new SdkAuditLoggerBuilder(this, name); } From 400cd486d80756cba52fb0380483c66077a912cb Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 15:01:48 +0200 Subject: [PATCH 41/45] remove useless null checks we're using @Nullable to indicate what could be null Signed-off-by: Hilmar Falkenberg --- .../sdk/audit/SdkAuditProviderBuilder.java | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java index c3c29a29425..bcc3e3f9253 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditProviderBuilder.java @@ -17,9 +17,9 @@ /** Builder for {@link SdkAuditProvider}. */ public class SdkAuditProviderBuilder { - private static final AttributeKey ATTR_INTEGRITY_ALGORITHM = + public static final AttributeKey ATTR_INTEGRITY_ALGORITHM = AttributeKey.stringKey("audit.integrity.algorithm"); - private static final AttributeKey ATTR_INTEGRITY_CERTIFICATE = + public static final AttributeKey ATTR_INTEGRITY_CERTIFICATE = AttributeKey.stringKey("audit.integrity.certificate"); private Resource resource = Resource.getDefault(); @@ -32,18 +32,12 @@ public class SdkAuditProviderBuilder { /** Sets the {@link Resource} to be associated with all audit records emitted by this provider. */ public SdkAuditProviderBuilder setResource(Resource resource) { - if (resource == null) { - throw new NullPointerException("resource"); - } this.resource = resource; return this; } /** Sets the {@link Clock} used for {@code ObservedTimestamp} generation. */ public SdkAuditProviderBuilder setClock(Clock clock) { - if (clock == null) { - throw new NullPointerException("clock"); - } this.clock = clock; return this; } @@ -56,9 +50,6 @@ public SdkAuditProviderBuilder setClock(Clock clock) { * setting the {@link io.opentelemetry.api.audit.AuditReceipt} on the record. */ public SdkAuditProviderBuilder addAuditRecordProcessor(AuditRecordProcessor processor) { - if (processor == null) { - throw new NullPointerException("processor"); - } processors.add(processor); return this; } @@ -73,9 +64,6 @@ public SdkAuditProviderBuilder addAuditRecordProcessor(AuditRecordProcessor proc * "HMAC-SHA256"}). */ public SdkAuditProviderBuilder setIntegrityAlgorithm(String algorithm) { - if (algorithm == null) { - throw new NullPointerException("algorithm"); - } this.integrityAlgorithm = algorithm; return this; } @@ -89,16 +77,12 @@ public SdkAuditProviderBuilder setIntegrityAlgorithm(String algorithm) { * Issuer+Serial ({@code CN=...,O=.../serial}). */ public SdkAuditProviderBuilder setIntegrityCertificate(String certificate) { - if (certificate == null) { - throw new NullPointerException("certificate"); - } this.integrityCertificate = certificate; return this; } /** Builds and returns the configured {@link SdkAuditProvider}. */ public SdkAuditProvider build() { - Resource effectiveResource = resource; if (integrityAlgorithm != null || integrityCertificate != null) { AttributesBuilder integrityAttrs = Attributes.builder(); if (integrityAlgorithm != null) { @@ -107,8 +91,9 @@ public SdkAuditProvider build() { if (integrityCertificate != null) { integrityAttrs.put(ATTR_INTEGRITY_CERTIFICATE, integrityCertificate); } - effectiveResource = resource.merge(Resource.create(integrityAttrs.build())); + return new SdkAuditProvider( + resource.merge(Resource.create(integrityAttrs.build())), processors, clock); } - return new SdkAuditProvider(effectiveResource, processors, clock); + return new SdkAuditProvider(resource, processors, clock); } } From 4c6de38d6debccacf39d9b007df1110664a3cd91 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 24 Jun 2026 15:04:36 +0200 Subject: [PATCH 42/45] spotlessapply Signed-off-by: Hilmar Falkenberg --- sdk/audit/build.gradle.kts | 2 ++ .../io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sdk/audit/build.gradle.kts b/sdk/audit/build.gradle.kts index a8b62fabfa4..02c2a99d261 100644 --- a/sdk/audit/build.gradle.kts +++ b/sdk/audit/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") + testImplementation(project(":sdk:testing")) + testImplementation("org.awaitility:awaitility") testImplementation("com.google.guava:guava") } diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index 997407508a2..484d143fced 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -133,7 +133,9 @@ public SdkAuditRecordBuilder addAttribute(AttributeKey key, @Nullable T v } if (attributes.containsKey(key)) { throw new IllegalStateException( - "Cannot add attribute with key '" + key.getKey() + "'; already exists on this record builder"); + "Cannot add attribute with key '" + + key.getKey() + + "'; already exists on this record builder"); } attributes.put(key, value); return this; @@ -210,7 +212,7 @@ public AuditReceipt emit() { // Step 4+5: Create the mutable record and pass it through all processors. // Transfer ownership of the attributes map to the record (builder must not be reused). AttributesMap recordAttributes = this.attributes; - this.attributes = AttributesMap.create(0, 0); // invalidate; builder must not be reused after emit() + this.attributes = null; // invalidate; builder must not be reused after emit() SdkReadWriteAuditRecord rwRecord = new SdkReadWriteAuditRecord( provider.getResource(), From 9aa88eb1cb9f2e5e07e88ab35b93472ea960d7bf Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 25 Jun 2026 09:43:51 +0200 Subject: [PATCH 43/45] null is not allowed Signed-off-by: Hilmar Falkenberg --- .../java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index 484d143fced..0ff097ac1c2 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -212,7 +212,8 @@ public AuditReceipt emit() { // Step 4+5: Create the mutable record and pass it through all processors. // Transfer ownership of the attributes map to the record (builder must not be reused). AttributesMap recordAttributes = this.attributes; - this.attributes = null; // invalidate; builder must not be reused after emit() + this.attributes = + AttributesMap.create(0, 0); // invalidate; builder must not be reused after emit() SdkReadWriteAuditRecord rwRecord = new SdkReadWriteAuditRecord( provider.getResource(), From 38b9242904f78691d89da85fd2282d407aaa575c Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 26 Jun 2026 16:57:42 +0200 Subject: [PATCH 44/45] depend on generic https://maven.pkg.github.com/apeirora/AuditAPI Signed-off-by: Hilmar Falkenberg --- CLAUDE.md | 4 +- all/build.gradle.kts | 1 + dependencyManagement/build.gradle.kts | 3 + .../current_vs_latest/opentelemetry-api.txt | 2 - .../audit/AuditLogRecordDataAdapterTest.java | 64 ++++++++----------- sdk/audit/build.gradle.kts | 2 + .../sdk/audit/SdkAuditRecordBuilder.java | 30 ++++++++- .../sdk/audit/SdkAuditProviderTest.java | 16 ----- settings.gradle.kts | 9 +++ 9 files changed, 73 insertions(+), 58 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 680f8d3b7b6..5963192de44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,8 @@ ## Before creating a git commit -Run `./gradlew jApiCmp` as the last step before committing **when Kotlin sources changed**. +Verify successfull builds with: `gradle spotlessApply build` then +run `./gradlew jApiCmp` as the last step before committing **when Kotlin sources changed**. This updates the API diff files in `docs/apidiffs/current_vs_latest/` and must be included in the commit. CI enforces that these files are up to date and will fail if they are stale. YAML, Markdown, and workflow-only changes do not require running `jApiCmp`. @@ -25,4 +26,3 @@ The signing block activates when the `CI` env var is set. Omit `CI` in a workflo signing without removing GPG secrets. Version is always `1.64.0-SNAPSHOT` while `snapshot = true` in `version.gradle.kts`. - diff --git a/all/build.gradle.kts b/all/build.gradle.kts index abe5ee8877b..0879f1f20b6 100644 --- a/all/build.gradle.kts +++ b/all/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { } testImplementation("com.tngtech.archunit:archunit-junit5") + testImplementation("eu.apeirora:audit.log") } // Custom task type for writing artifacts and jars - configuration cache compatible diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 4b205859618..3743aec4fbd 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -17,6 +17,7 @@ val slf4jVersion = "2.0.18" val opencensusVersion = "0.31.1" val prometheusServerVersion = "1.8.0" val armeriaVersion = "1.40.0" +val apeiroraAuditLogVersion = "0.0.1-SNAPSHOT" val junitVersion = "5.14.4" val junitPlatformVersion = "1.14.4" val okhttpVersion = "5.4.0" @@ -53,6 +54,8 @@ val DEPENDENCIES = listOf( "com.linecorp.armeria:armeria-grpc-protocol:${armeriaVersion}", "com.linecorp.armeria:armeria-junit5:${armeriaVersion}", + "eu.apeirora:audit.log:${apeiroraAuditLogVersion}", + "com.google.auto.value:auto-value:${autoValueVersion}", "com.google.auto.value:auto-value-annotations:${autoValueVersion}", "com.google.errorprone:error_prone_annotations:${errorProneVersion}", diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index a2c143c422a..439bde26c03 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -49,7 +49,6 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against +++ NEW SUPERCLASS: java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.audit.AuditLoggerBuilder auditLoggerBuilder(java.lang.String) +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.audit.AuditLogger get(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider noop() +++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.AuditReceipt (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object @@ -87,7 +86,6 @@ Comparing source compatibility of opentelemetry-api-1.64.0-SNAPSHOT.jar against +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.audit.AuditProvider get() - +++ NEW METHOD: PUBLIC(+) STATIC(+) void resetForTest() +++ NEW METHOD: PUBLIC(+) STATIC(+) void set(io.opentelemetry.api.audit.AuditProvider) +++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.audit.Outcome (compatible) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. diff --git a/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java b/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java index 609952a393d..dc481012903 100644 --- a/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java +++ b/exporters/otlp/audit/src/test/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapterTest.java @@ -11,7 +11,6 @@ import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.sdk.audit.AuditRecordData; import io.opentelemetry.sdk.audit.SdkAuditRecordData; @@ -43,13 +42,14 @@ void actorType_isLowercase() { AuditRecordData data = SdkAuditRecordData.create( Resource.getDefault(), + TIMESTAMP_NANOS, + OBSERVED_NANOS, + Attributes.empty(), + "test.event", "test", null, null, "id1", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "test.event", "svc-1", ActorType.SERVICE, "CREATE", @@ -59,8 +59,6 @@ void actorType_isLowercase() { null, null, null, - Attributes.empty(), - null, 0, null, null); @@ -74,13 +72,14 @@ void outcome_isLowercase() { AuditRecordData data = SdkAuditRecordData.create( Resource.getDefault(), + TIMESTAMP_NANOS, + OBSERVED_NANOS, + Attributes.empty(), + "test.event", "test", null, null, "id2", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "test.event", "sys", ActorType.SYSTEM, "REBOOT", @@ -90,8 +89,6 @@ void outcome_isLowercase() { null, null, null, - Attributes.empty(), - null, 0, null, null); @@ -105,13 +102,14 @@ void optionalAttributes_mappedWhenPresent() { AuditRecordData data = SdkAuditRecordData.create( Resource.getDefault(), + TIMESTAMP_NANOS, + OBSERVED_NANOS, + Attributes.empty(), + "resource.access", "test", null, null, "id3", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "resource.access", "u1", ActorType.USER, "READ", @@ -120,8 +118,6 @@ void optionalAttributes_mappedWhenPresent() { "http.endpoint", "10.0.0.1", "ipv4", - Value.of("body text"), - Attributes.empty(), null, 42L, "prevhash123", @@ -144,13 +140,14 @@ void integrityValue_base64Encoded() { AuditRecordData data = SdkAuditRecordData.create( Resource.getDefault(), + TIMESTAMP_NANOS, + OBSERVED_NANOS, + Attributes.empty(), + "signed.event", "test", null, null, "id4", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "signed.event", "svc", ActorType.SERVICE, "SIGN", @@ -159,8 +156,6 @@ void integrityValue_base64Encoded() { null, null, null, - null, - Attributes.empty(), proof, 0, null, @@ -202,13 +197,14 @@ void resourcePreserved() { AuditRecordData data = SdkAuditRecordData.create( resource, + TIMESTAMP_NANOS, + OBSERVED_NANOS, + Attributes.empty(), + "user.login.success", "test", null, null, "id5", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "user.login.success", "u1", ActorType.USER, "LOGIN", @@ -218,8 +214,6 @@ void resourcePreserved() { null, null, null, - Attributes.empty(), - null, 0, null, null); @@ -233,13 +227,14 @@ void userAttributes_mergedIntoAdaptedAttributes() { AuditRecordData data = SdkAuditRecordData.create( Resource.getDefault(), + TIMESTAMP_NANOS, + OBSERVED_NANOS, + userAttrs, + "custom.event", "test", null, null, "id6", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "custom.event", "u1", ActorType.USER, "READ", @@ -249,8 +244,6 @@ void userAttributes_mergedIntoAdaptedAttributes() { null, null, null, - userAttrs, - null, 0, null, null); @@ -262,13 +255,14 @@ void userAttributes_mergedIntoAdaptedAttributes() { private static AuditRecordData buildMinimal() { return SdkAuditRecordData.create( Resource.getDefault(), + TIMESTAMP_NANOS, + OBSERVED_NANOS, + Attributes.empty(), + "user.login.success", "test-logger", null, null, "test-record-id", - TIMESTAMP_NANOS, - OBSERVED_NANOS, - "user.login.success", "u8472", ActorType.USER, "LOGIN", @@ -278,8 +272,6 @@ private static AuditRecordData buildMinimal() { null, null, null, - Attributes.empty(), - null, 0, null, null); diff --git a/sdk/audit/build.gradle.kts b/sdk/audit/build.gradle.kts index 02c2a99d261..a8c4f110d81 100644 --- a/sdk/audit/build.gradle.kts +++ b/sdk/audit/build.gradle.kts @@ -6,11 +6,13 @@ plugins { description = "OpenTelemetry Audit Logging SDK" otelJava.moduleName.set("io.opentelemetry.sdk.audit") +otelJava.osgiUnversionedOptionalPackages.add("audit.log") dependencies { api(project(":api:all")) api(project(":sdk:common")) api(project(":sdk:logs")) + api("eu.apeirora:audit.log") annotationProcessor("com.google.auto.value:auto-value") diff --git a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java index 0ff097ac1c2..19ab4faffb2 100644 --- a/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java +++ b/sdk/audit/src/main/java/io/opentelemetry/sdk/audit/SdkAuditRecordBuilder.java @@ -5,6 +5,10 @@ package io.opentelemetry.sdk.audit; +import audit.log.Actor; +import audit.log.AuditEvent; +import audit.log.Source; +import audit.log.Target; import io.opentelemetry.api.audit.ActorType; import io.opentelemetry.api.audit.Audit; import io.opentelemetry.api.audit.AuditDeliveryException; @@ -16,13 +20,14 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.internal.AttributesMap; import java.time.Instant; +import java.util.Locale; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** SDK implementation of {@link AuditRecordBuilder}. */ -public class SdkAuditRecordBuilder implements AuditRecordBuilder { +class SdkAuditRecordBuilder implements AuditRecordBuilder { private final SdkAuditProvider provider; private final SdkAuditProvider.AuditLoggerKey loggerKey; @@ -141,6 +146,27 @@ public SdkAuditRecordBuilder addAttribute(AttributeKey key, @Nullable T v return this; } + /** + * FIXME ensure all values are set. + * + * @param event with all relevant information to build an AuditRecord + * @return SdkAuditRecordBuilder this + */ + public SdkAuditRecordBuilder setAuditEvent(AuditEvent event) { + Source src = event.getSource(); + Target tgt = event.getTarget(); + Actor actor = event.getActor(); + setSource(src.getName(), src.getType()); + setTarget(tgt.getName(), tgt.getType()); + setAction(event.getAction()); + try { + setActor(actor.getName(), ActorType.valueOf(actor.getType().toUpperCase(Locale.ROOT))); + } catch (IllegalArgumentException e) { + setActor(actor.getName(), ActorType.SYSTEM); + } + return this; + } + @Override public SdkAuditRecordBuilder setIntegrityValue(byte[] integrityValue) { this.integrityValue = integrityValue; @@ -212,7 +238,7 @@ public AuditReceipt emit() { // Step 4+5: Create the mutable record and pass it through all processors. // Transfer ownership of the attributes map to the record (builder must not be reused). AttributesMap recordAttributes = this.attributes; - this.attributes = + this.attributes = AttributesMap.create(0, 0); // invalidate; builder must not be reused after emit() SdkReadWriteAuditRecord rwRecord = new SdkReadWriteAuditRecord( diff --git a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java index cf97c91be53..f12e58387e8 100644 --- a/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java +++ b/sdk/audit/src/test/java/io/opentelemetry/sdk/audit/SdkAuditProviderTest.java @@ -11,7 +11,6 @@ import io.opentelemetry.api.audit.ActorType; import io.opentelemetry.api.audit.AuditDeliveryException; import io.opentelemetry.api.audit.AuditLogger; -import io.opentelemetry.api.audit.AuditProvider; import io.opentelemetry.api.audit.AuditReceipt; import io.opentelemetry.api.audit.Outcome; import io.opentelemetry.api.common.AttributeKey; @@ -409,19 +408,4 @@ void integrityValue_storedOnRecord() { AuditRecordData data = exporter.getFinishedAuditRecords().get(0); assertThat(data.getIntegrityValue()).containsExactly(0x01, 0x02, 0x03); } - - @Test - void noopProvider_emitReturnsReceiptWithoutError() { - AuditProvider noop = AuditProvider.noop(); - AuditReceipt receipt = - noop.get("test") - .auditRecordBuilder() - .setTimestamp(Instant.now()) - .setEventName("noop.event") - .setActor("u1", ActorType.USER) - .setAction("READ") - .setOutcome(Outcome.SUCCESS) - .emit(); - assertThat(receipt).isNotNull(); - } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 87ecd3a7c4d..1d7c2f8ab35 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,15 @@ dependencyResolutionManagement { mavenCentral() google() mavenLocal() + maven { + url = uri("https://maven.pkg.github.com/apeirora/AuditAPI") + credentials { + username = System.getenv("GITHUB_ACTOR") + ?: (settings.extra.properties["GITHUB_ACTOR"] as? String) ?: "" + password = System.getenv("GITHUB_TOKEN") + ?: (settings.extra.properties["GITHUB_TOKEN"] as? String) ?: "" + } + } } } From 3f8a119d16d513d8566b0c2046336431bdccb209 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 26 Jun 2026 17:15:24 +0200 Subject: [PATCH 45/45] set env GITHUB_TOKEN Signed-off-by: Hilmar Falkenberg --- .github/workflows/build.yml | 4 ++++ .github/workflows/codeql.yml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f36f585d72..996f5d467eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,6 +69,8 @@ jobs: # JMH-based tests run only if this environment variable is set to true RUN_JMH_BASED_TESTS: ${{ matrix.jmh-based-tests }} DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} - name: Check for diff # The jApiCmp diff compares current to latest, which isn't appropriate for release branches @@ -134,6 +136,8 @@ jobs: ./gradlew nativeTest --no-configuration-cache --no-parallel env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} required-status-check: # markdown-link-check is not required so pull requests are not blocked if external links break diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f2f7d60d5a0..921c4243cde 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -60,6 +60,8 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} - name: Perform CodeQL analysis uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2