diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java
index 33587b2b4b3..3fc34908cae 100644
--- a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java
+++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java
@@ -35,11 +35,13 @@ class NoSharedInternalCodeTest {
"opentelemetry-exporter-common",
"opentelemetry-exporter-logging",
"opentelemetry-exporter-logging-otlp",
+ "opentelemetry-exporter-otlp-audit",
"opentelemetry-exporter-prometheus",
"opentelemetry-exporter-zipkin",
"opentelemetry-extension-trace-propagators",
"opentelemetry-opencensus-shim",
"opentelemetry-sdk-common",
+ "opentelemetry-sdk-audit",
"opentelemetry-sdk-logs",
"opentelemetry-sdk-metrics",
"opentelemetry-sdk-testing",
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..c6458f170d0
--- /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#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/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..125174064da
--- /dev/null
+++ b/api/all/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/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..547ae61b6c8
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java
@@ -0,0 +1,165 @@
+/*
+ * 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 setActorId(String actorId);
+
+ /** Sets the type of the actor ({@code audit.actor.type}). */
+ AuditRecordBuilder setActorType(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 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 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);
+
+ /** 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 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/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java
new file mode 100644
index 00000000000..cc299aa8b0b
--- /dev/null
+++ b/api/all/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 setActorId(String actorId) {
+ 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 setTargetId(String targetId) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setTargetType(String targetType) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setSourceId(String sourceId) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setSourceType(String sourceType) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setBody(Value> body) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setAttribute(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
new file mode 100644
index 00000000000..e42810c6627
--- /dev/null
+++ b/api/all/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/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..8f660a7fa54
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java
@@ -0,0 +1,50 @@
+/*
+ * 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).
+ *
+ *
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 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) {
+ 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/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/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
index 4883d2787cc..1333e783528 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
@@ -1,2 +1,87 @@
Comparing source compatibility of opentelemetry-api-1.62.0-SNAPSHOT.jar against opentelemetry-api-1.61.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 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()
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 73948123f81..593e270c969 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.62.0-SNAPSHOT.jar against opentelemetry-sdk-extension-autoconfigure-spi-1.61.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/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..40e24ab1079
--- /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.
+ *