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
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')
}}
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
diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml
new file mode 100644
index 00000000000..0c8a34c1d02
--- /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 publishAllPublicationsToGithubPackagesRepository
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_ACTOR: ${{ github.actor }}
+ DISABLE_REMOTE_BUILD_CACHE: true
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 }}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000000..5963192de44
--- /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
+
+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`.
+
+## 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`.
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.
diff --git a/README.md b/README.md
index b4d88aa43ea..0485c9013db 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-java?ref=badge_shield&issueType=security)
[](https://scorecard.dev/viewer/?uri=github.com/open-telemetry/opentelemetry-java)
[](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/io/opentelemetry/java/README.md)
+[](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.
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/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/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/ActorType.java b/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java
new file mode 100644
index 00000000000..0cc53ee33d6
--- /dev/null
+++ b/api/all/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#setActor(String, 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/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..0261182a97d
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/Audit.java
@@ -0,0 +1,62 @@
+/*
+ * 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() {}
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java
new file mode 100644
index 00000000000..8b30f2c9009
--- /dev/null
+++ b/api/all/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/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java
new file mode 100644
index 00000000000..e345e56e7f9
--- /dev/null
+++ b/api/all/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/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java
new file mode 100644
index 00000000000..b2ea7437c76
--- /dev/null
+++ b/api/all/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/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java
new file mode 100644
index 00000000000..08093f47f27
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+@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);
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java
new file mode 100644
index 00000000000..74ec7a4c3b6
--- /dev/null
+++ b/api/all/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/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java
new file mode 100644
index 00000000000..88e207d0bae
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java
@@ -0,0 +1,150 @@
+/*
+ * 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 ({@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);
+
+ /**
+ * 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 ({@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 ({@code audit.actor.id}).
+ *
+ *
If the actor cannot be determined, set to a sentinel such as {@code "anonymous"}.
+ */
+ AuditRecordBuilder setActor(String actorId, ActorType actorType);
+
+ /**
+ * 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 ({@code audit.outcome}). */
+ 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 ({@code audit.schema.version}), e.g. {@code
+ * "1.0.0"}.
+ */
+ AuditRecordBuilder setSchemaVersion(String schemaVersion);
+
+ /**
+ * Sets the identifier of the resource acted upon ({@code audit.target.id}), e.g. a file path,
+ * REST endpoint, or database table name.
+ */
+ 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 setSource(String sourceId, String sourceType);
+
+ /** 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 addAttribute(AttributeKey key, @Nullable T value);
+
+ /**
+ * 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 setIntegrityValue(byte[] integrityValue);
+
+ /**
+ * 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 audit.prev.hash} 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/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java
new file mode 100644
index 00000000000..6fd22a311ee
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import java.util.Objects;
+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).
+ */
+public final class GlobalAuditProvider {
+
+ private static final AtomicReference globalProvider = new AtomicReference<>();
+
+ private GlobalAuditProvider() {}
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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) {
+ globalProvider.set(auditProvider);
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java b/api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java
new file mode 100644
index 00000000000..7f9e891d8f7
--- /dev/null
+++ b/api/all/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/all/src/main/java/io/opentelemetry/api/audit/package-info.java b/api/all/src/main/java/io/opentelemetry/api/audit/package-info.java
new file mode 100644
index 00000000000..67bbe297c0b
--- /dev/null
+++ b/api/all/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/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/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)
diff --git a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts
index 0a6a100fe15..ea18c7c2d7f 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()
@@ -66,9 +66,28 @@ publishing {
}
}
}
+
+ // 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 && project.version.toString().endsWith("-SNAPSHOT")) {
+ 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) {
+// 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"])
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",
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 d55eea5331d..439bde26c03 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
@@ -1,2 +1,100 @@
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.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
+ +++ 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 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.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 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)
+ +++ 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 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)
+ +++ 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 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/docs/github-packages-snapshot.md b/docs/github-packages-snapshot.md
new file mode 100644
index 00000000000..15301d440be
--- /dev/null
+++ b/docs/github-packages-snapshot.md
@@ -0,0 +1,126 @@
+# 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.
+
+### 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: ./gradlew build
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_ACTOR: ${{ github.actor }}
+```
+
+### 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 }}
+```
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/audit/build.gradle.kts b/exporters/otlp/audit/build.gradle.kts
new file mode 100644
index 00000000000..ddd1ee333de
--- /dev/null
+++ b/exporters/otlp/audit/build.gradle.kts
@@ -0,0 +1,19 @@
+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:all"))
+ implementation(project(":exporters:otlp:common"))
+ implementation(project(":exporters:sender:okhttp"))
+
+ testImplementation(project(":sdk:audit"))
+ testImplementation(project(":sdk:logs"))
+}
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..04fe7b16239
--- /dev/null
+++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java
@@ -0,0 +1,187 @@
+/*
+ * 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.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;
+
+/**
+ * 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 {@code audit.*} keys.
+ *