This is an automated email from the ASF dual-hosted git repository.
adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 2f0c7a43d Generate Request IDs (if not specified); Return Request ID
as a Header (#2602)
2f0c7a43d is described below
commit 2f0c7a43d446452004ea51196b618de9bdf0e25b
Author: Adnan Hemani <[email protected]>
AuthorDate: Tue Sep 30 03:24:15 2025 -0700
Generate Request IDs (if not specified); Return Request ID as a Header
(#2602)
---
.../polaris/service/config/FilterPriorities.java | 3 +-
.../inmemory/InMemoryBufferEventListener.java | 2 +-
.../polaris/service/logging/LoggingMDCFilter.java | 8 +-
.../RequestIdFilter.java} | 26 ++---
.../service/tracing/RequestIdGenerator.java | 56 ++++++++++
.../service/tracing/RequestIdResponseFilter.java | 46 ++++++++
.../polaris/service/tracing/TracingFilter.java | 3 +-
.../service/tracing/RequestIdGeneratorTest.java | 122 +++++++++++++++++++++
.../service/tracing/RequestIdHeaderTest.java | 122 +++++++++++++++++++++
9 files changed, 360 insertions(+), 28 deletions(-)
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
b/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
index eed2d4353..5ce166409 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
@@ -21,7 +21,8 @@ package org.apache.polaris.service.config;
import jakarta.ws.rs.Priorities;
public final class FilterPriorities {
- public static final int REALM_CONTEXT_FILTER = Priorities.AUTHENTICATION -
100;
+ public static final int REQUEST_ID_FILTER = Priorities.AUTHENTICATION - 101;
+ public static final int REALM_CONTEXT_FILTER = REQUEST_ID_FILTER + 1;
public static final int RATE_LIMITER_FILTER = Priorities.USER;
public static final int MDC_FILTER = REALM_CONTEXT_FILTER + 1;
public static final int TRACING_FILTER = REALM_CONTEXT_FILTER + 2;
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListener.java
b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListener.java
index 6fd88d03f..533e983ae 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListener.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListener.java
@@ -19,7 +19,7 @@
package org.apache.polaris.service.events.listeners.inmemory;
-import static
org.apache.polaris.service.logging.LoggingMDCFilter.REQUEST_ID_KEY;
+import static
org.apache.polaris.service.tracing.RequestIdFilter.REQUEST_ID_KEY;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
b/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
index c3b078ea1..fb0112cbb 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
@@ -19,6 +19,7 @@
package org.apache.polaris.service.logging;
import static
org.apache.polaris.service.context.RealmContextFilter.REALM_CONTEXT_KEY;
+import static
org.apache.polaris.service.tracing.RequestIdFilter.REQUEST_ID_KEY;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
@@ -38,7 +39,6 @@ import org.slf4j.MDC;
public class LoggingMDCFilter implements ContainerRequestFilter {
public static final String REALM_ID_KEY = "realmId";
- public static final String REQUEST_ID_KEY = "requestId";
@Inject LoggingConfiguration loggingConfiguration;
@@ -49,11 +49,7 @@ public class LoggingMDCFilter implements
ContainerRequestFilter {
// Also put the MDC values in the request context for use by other filters
and handlers
loggingConfiguration.mdc().forEach(MDC::put);
loggingConfiguration.mdc().forEach(rc::setProperty);
- var requestId =
rc.getHeaderString(loggingConfiguration.requestIdHeaderName());
- if (requestId != null) {
- MDC.put(REQUEST_ID_KEY, requestId);
- rc.setProperty(REQUEST_ID_KEY, requestId);
- }
+ MDC.put(REQUEST_ID_KEY, (String) rc.getProperty(REQUEST_ID_KEY));
RealmContext realmContext = (RealmContext)
rc.getProperty(REALM_CONTEXT_KEY);
MDC.put(REALM_ID_KEY, realmContext.getRealmIdentifier());
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java
similarity index 60%
copy from
runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
copy to
runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java
index c3b078ea1..973313b9d 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingMDCFilter.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java
@@ -16,9 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.service.logging;
-
-import static
org.apache.polaris.service.context.RealmContextFilter.REALM_CONTEXT_KEY;
+package org.apache.polaris.service.tracing;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
@@ -27,34 +25,26 @@ import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.ext.Provider;
-import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.service.config.FilterPriorities;
-import org.slf4j.MDC;
+import org.apache.polaris.service.logging.LoggingConfiguration;
@PreMatching
@ApplicationScoped
-@Priority(FilterPriorities.MDC_FILTER)
+@Priority(FilterPriorities.REQUEST_ID_FILTER)
@Provider
-public class LoggingMDCFilter implements ContainerRequestFilter {
+public class RequestIdFilter implements ContainerRequestFilter {
- public static final String REALM_ID_KEY = "realmId";
public static final String REQUEST_ID_KEY = "requestId";
@Inject LoggingConfiguration loggingConfiguration;
+ @Inject RequestIdGenerator requestIdGenerator;
@Override
public void filter(ContainerRequestContext rc) {
- // The request scope is active here, so any MDC values set here will be
propagated to
- // threads handling the request.
- // Also put the MDC values in the request context for use by other filters
and handlers
- loggingConfiguration.mdc().forEach(MDC::put);
- loggingConfiguration.mdc().forEach(rc::setProperty);
var requestId =
rc.getHeaderString(loggingConfiguration.requestIdHeaderName());
- if (requestId != null) {
- MDC.put(REQUEST_ID_KEY, requestId);
- rc.setProperty(REQUEST_ID_KEY, requestId);
+ if (requestId == null) {
+ requestId = requestIdGenerator.generateRequestId();
}
- RealmContext realmContext = (RealmContext)
rc.getProperty(REALM_CONTEXT_KEY);
- MDC.put(REALM_ID_KEY, realmContext.getRealmIdentifier());
+ rc.setProperty(REQUEST_ID_KEY, requestId);
}
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdGenerator.java
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdGenerator.java
new file mode 100644
index 000000000..9ddd0b04c
--- /dev/null
+++
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdGenerator.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.polaris.service.tracing;
+
+import com.google.common.annotations.VisibleForTesting;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+
+@ApplicationScoped
+public class RequestIdGenerator {
+ static final Long COUNTER_SOFT_MAX = Long.MAX_VALUE / 2;
+
+ record State(String uuid, long counter) {
+
+ State() {
+ this(UUID.randomUUID().toString(), 1);
+ }
+
+ String requestId() {
+ return String.format("%s_%019d", uuid, counter);
+ }
+
+ State increment() {
+ return counter >= COUNTER_SOFT_MAX ? new State() : new State(uuid,
counter + 1);
+ }
+ }
+
+ final AtomicReference<State> state = new AtomicReference<>(new State());
+
+ public String generateRequestId() {
+ return state.getAndUpdate(State::increment).requestId();
+ }
+
+ @VisibleForTesting
+ public void setCounter(long counter) {
+ state.set(new State(state.get().uuid, counter));
+ }
+}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdResponseFilter.java
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdResponseFilter.java
new file mode 100644
index 000000000..728960182
--- /dev/null
+++
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdResponseFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.polaris.service.tracing;
+
+import static
org.apache.polaris.service.tracing.RequestIdFilter.REQUEST_ID_KEY;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
+import jakarta.ws.rs.ext.Provider;
+import org.apache.polaris.service.logging.LoggingConfiguration;
+
+@ApplicationScoped
+@Provider
+public class RequestIdResponseFilter implements ContainerResponseFilter {
+
+ @Inject LoggingConfiguration loggingConfiguration;
+
+ @Override
+ public void filter(
+ ContainerRequestContext requestContext, ContainerResponseContext
responseContext) {
+ responseContext
+ .getHeaders()
+ .add(
+ loggingConfiguration.requestIdHeaderName(),
requestContext.getProperty(REQUEST_ID_KEY));
+ }
+}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingFilter.java
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingFilter.java
index b4c056235..8b6859ca3 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingFilter.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingFilter.java
@@ -28,7 +28,6 @@ import jakarta.ws.rs.ext.Provider;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.service.config.FilterPriorities;
import org.apache.polaris.service.context.RealmContextFilter;
-import org.apache.polaris.service.logging.LoggingMDCFilter;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@PreMatching
@@ -47,7 +46,7 @@ public class TracingFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext rc) {
if (!sdkDisabled) {
Span span = Span.current();
- String requestId = (String)
rc.getProperty(LoggingMDCFilter.REQUEST_ID_KEY);
+ String requestId = (String)
rc.getProperty(RequestIdFilter.REQUEST_ID_KEY);
if (requestId != null) {
span.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);
}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdGeneratorTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdGeneratorTest.java
new file mode 100644
index 000000000..08edfe481
--- /dev/null
+++
b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdGeneratorTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.polaris.service.tracing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class RequestIdGeneratorTest {
+
+ private RequestIdGenerator requestIdGenerator;
+
+ @BeforeEach
+ void setUp() {
+ requestIdGenerator = new RequestIdGenerator();
+ }
+
+ @Test
+ void testGenerateRequestId_ReturnsValidFormat() {
+ String requestId = requestIdGenerator.generateRequestId();
+
+ assertThat(requestId).isNotNull();
+ assertThat(requestId).matches(this::isValidRequestIdFormat);
+ // First call should increment counter to 1
+ assertThat(extractCounterFromRequestId(requestId)).isEqualTo(1);
+ }
+
+ @Test
+ void testGenerateRequestId_ReturnsUniqueIds() {
+ Set<String> generatedIds = new HashSet<>();
+
+ // Generate multiple request IDs and verify they're all unique
+ for (int i = 0; i < 1000; i++) {
+ String requestId = requestIdGenerator.generateRequestId();
+ assertThat(generatedIds).doesNotContain(requestId);
+ generatedIds.add(requestId);
+ }
+
+ assertThat(generatedIds).hasSize(1000);
+ }
+
+ @Test
+ void testCounterIncrementsSequentially() {
+ // requestIdGenerator.setCounter(0);
+
+ String firstId = requestIdGenerator.generateRequestId();
+ String secondId = requestIdGenerator.generateRequestId();
+ String thirdId = requestIdGenerator.generateRequestId();
+
+ assertThat(extractCounterFromRequestId(firstId)).isEqualTo(1);
+ assertThat(extractCounterFromRequestId(secondId)).isEqualTo(2);
+ assertThat(extractCounterFromRequestId(thirdId)).isEqualTo(3);
+ }
+
+ @Test
+ void testCounterRotationAtSoftMax() {
+ // Set counter close to soft max
+ long softMax = RequestIdGenerator.COUNTER_SOFT_MAX;
+ requestIdGenerator.setCounter(softMax);
+
+ String beforeRotation = requestIdGenerator.generateRequestId();
+ String afterRotation = requestIdGenerator.generateRequestId();
+
+ // The UUID part should be different after rotation
+ String beforeUuidPart = beforeRotation.substring(0,
beforeRotation.lastIndexOf('_'));
+ String afterUuidPart = afterRotation.substring(0,
afterRotation.lastIndexOf('_'));
+ assertNotEquals(beforeUuidPart, afterUuidPart);
+
+ assertThat(extractCounterFromRequestId(beforeRotation)).isEqualTo(softMax);
+ // Counter reset to 1 (after increment from 0)
+ assertThat(extractCounterFromRequestId(afterRotation)).isEqualTo(1);
+ }
+
+ @Test
+ void testSetCounterChangesNextGeneratedId() {
+ requestIdGenerator.setCounter(100);
+
+ String requestId = requestIdGenerator.generateRequestId();
+
+ // Should increment from set value
+ assertThat(extractCounterFromRequestId(requestId)).isEqualTo(100);
+ }
+
+ private boolean isValidRequestIdFormat(String str) {
+ try {
+ String[] requestIdParts = str.split("_");
+ String uuid = requestIdParts[0];
+ String counter = requestIdParts[1];
+ UUID.fromString(uuid);
+ Long.parseLong(counter);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ private long extractCounterFromRequestId(String requestId) {
+ return Long.parseLong(requestId.split("_")[1]);
+ }
+}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdHeaderTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdHeaderTest.java
new file mode 100644
index 000000000..ba64fddcc
--- /dev/null
+++
b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdHeaderTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.service.tracing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.QuarkusTestProfile;
+import io.quarkus.test.junit.TestProfile;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.Response;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.polaris.service.it.env.PolarisApiEndpoints;
+import org.apache.polaris.service.it.env.PolarisClient;
+import org.junit.jupiter.api.Test;
+
+@QuarkusTest
+@TestProfile(RequestIdHeaderTest.Profile.class)
+public class RequestIdHeaderTest {
+ public static class Profile implements QuarkusTestProfile {
+ @Override
+ public Map<String, String> getConfigOverrides() {
+ return Map.of(
+ "polaris.log.request-id-header-name",
+ REQUEST_ID_HEADER,
+ "polaris.realm-context.header-name",
+ REALM_HEADER,
+ "polaris.realm-context.realms",
+ REALM);
+ }
+ }
+
+ private static final String REQUEST_ID_HEADER = "x-test-request-id-random";
+ private static final String REALM_HEADER = "realm";
+ private static final String REALM = "realm1";
+ private static final String CLIENT_ID = "client1";
+ private static final String CLIENT_SECRET = "secret1";
+
+ private static final URI baseUri =
+ URI.create(
+ "http://localhost:"
+ + Objects.requireNonNull(
+ Integer.getInteger("quarkus.http.test-port"),
+ "System property not set correctly:
quarkus.http.test-port"));
+
+ private Response request(Map<String, String> headers) {
+ try (PolarisClient client =
+ PolarisClient.polarisClient(new PolarisApiEndpoints(baseUri, REALM,
headers))) {
+ return client
+ .catalogApiPlain()
+ .request("v1/oauth/tokens")
+ .post(
+ Entity.form(
+ new MultivaluedHashMap<>(
+ Map.of(
+ "grant_type",
+ "client_credentials",
+ "scope",
+ "PRINCIPAL_ROLE:ALL",
+ "client_id",
+ CLIENT_ID,
+ "client_secret",
+ CLIENT_SECRET))));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testRequestIdHeaderSpecified() {
+ String requestId = "pre-requested-request-id";
+ HashMap<String, String> headers =
+ new HashMap<>(Map.of(REALM_HEADER, REALM, REQUEST_ID_HEADER,
requestId));
+ assertThat(sendRequest(headers)).matches(s -> s.equals(requestId));
+ assertThat(sendRequest(headers)).matches(s -> s.equals(requestId));
+
+ String newRequestId = "new-pre-requested-request-id";
+ headers.put(REQUEST_ID_HEADER, newRequestId);
+ assertThat(sendRequest(headers)).matches(s -> s.equals(newRequestId));
+ }
+
+ @Test
+ public void testRequestIdHeaderNotSpecified() {
+ Map<String, String> headers = Map.of(REALM_HEADER, REALM);
+ Set<String> requestIds = new HashSet<>();
+ for (int i = 0; i < 10; i++) {
+ String requestId = sendRequest(headers);
+ assertThat(requestIds).doesNotContain(requestId);
+ requestIds.add(requestId);
+ }
+ }
+
+ private String sendRequest(Map<String, String> headers) {
+ try (Response response = request(headers)) {
+ assertThat(response.getHeaders()).containsKey(REQUEST_ID_HEADER);
+ assertThat(response.getHeaders().get(REQUEST_ID_HEADER)).hasSize(1);
+ return
response.getHeaders().get(REQUEST_ID_HEADER).getFirst().toString();
+ }
+ }
+}