Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jakarta.ws.rs.Priorities;

public final class FilterPriorities {
public static final int REQUEST_ID_FILTER = Priorities.AUTHENTICATION - 101;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: -> REALM_CONTEXT_FILTER - 1 to make it more readable?

public static final int REALM_CONTEXT_FILTER = Priorities.AUTHENTICATION - 100;
public static final int RATE_LIMITER_FILTER = Priorities.USER;
public static final int MDC_FILTER = REALM_CONTEXT_FILTER + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,7 +39,6 @@
public class LoggingMDCFilter implements ContainerRequestFilter {

public static final String REALM_ID_KEY = "realmId";
public static final String REQUEST_ID_KEY = "requestId";

@Inject LoggingConfiguration loggingConfiguration;

Expand All @@ -49,11 +49,7 @@ public void filter(ContainerRequestContext rc) {
// 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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
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 java.util.UUID;
import org.apache.polaris.service.config.FilterPriorities;
import org.apache.polaris.service.logging.LoggingConfiguration;

@PreMatching
@ApplicationScoped
@Priority(FilterPriorities.REQUEST_ID_FILTER)
@Provider
public class RequestIdFilter implements ContainerRequestFilter {

public static final String REQUEST_ID_KEY = "requestId";

@Inject LoggingConfiguration loggingConfiguration;

@Override
public void filter(ContainerRequestContext rc) {
var requestId = rc.getHeaderString(loggingConfiguration.requestIdHeaderName());
Comment thread
adutra marked this conversation as resolved.
if (requestId == null) {
requestId = UUID.randomUUID().toString();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is exhausting the randomness pool a concern? @snazy : WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Also: UUID.randomUUID() is in theory a blocking call (happens only when the entropy source is empty though), and therefore should be avoided in filters.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the request ID does not have to be a UUID (I'm a bit out-of-date on this) it may be worth using a per-node (or per-thread) UUID (allocated one per restart) plus a simple counter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few suggestions:

  1. Use org.apache.polaris.ids.impl.SnowflakeIdGeneratorImpl#idToTimeUuid
  2. Use a simple AtomicLong counter

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also UUID v7 may be worth considering (optional, for follow-up): https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7

@adnanhemani adnanhemani Sep 25, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the RequestIdGenerator, which is a very, very simple ID generator - since we don't require the level of complexity that has been made in the SnowflakeIdGenerator. It's just a quick implementation of @dimas-b's suggestion above:

If the request ID does not have to be a UUID (I'm a bit out-of-date on this) it may be worth using a per-node (or per-thread) UUID (allocated one per restart) plus a simple counter.

I'm very hesitant to introduce a complete dependency on the NoSql Persistence for the Service module and so creating the RequestIdGenerator is my minor effort to avoid doing that. I'm sure there may be a better reason for introducing this dependency in the future, but I don't want to sidetrack the simple goal (and simple requirements) of this PR with whether we should introduce this dependency or not.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure that UUID.randomUUID() is blocking call and in what chance will it exhaust the system entropy?

I've never got a satisfying answer for that question 😄

Most commenters stress the fact that dev/urandom never blocks.

This is certainly true, but doesn't address the fact that the entropy pool might get exhausted at some point, in which case your UUIDs will have very poor randomness.

Furthermore, the UUID.randomUUID() call is explicitly considered blocking by BlockHound:

reactor/BlockHound#157

I think that this is due to the fact that new SecureRandom() does some magic to select the random numbers provider, and even if the default provider doesn't block (it plugs into /dev/urandom), you still can configure your JVM with a different provider that could block.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here’s a post that shows how to work around BlockHound: https://stackoverflow.com/a/75687886/933856.

That said, we probably shouldn’t make decisions based on a single anecdote or assumptions about JVM configurations. What if a user runs Polaris on their own JVM and it breaks? That scenario is very likely. And what if a future JVM introduces a breaking change?

Do we need to worry about that now? Probably not. It feels like a premature optimization at this stage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quarkus doesn't use BlockHound – I cited BlockHound as an example. But Quarkus does have a mechanism to detect blocking calls in non-blocking contexts, we already had the problem with RealmContextFilter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I posted that link to demonstrate that people disagree with BlockHound on whether it is blocking call. Does Quarkus consider also UUID.randomUUID() as a blocking-call? If not, we got another reason to optimize it later.

}
rc.setProperty(REQUEST_ID_KEY, requestId);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
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
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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.admin;

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.Map;
import java.util.Objects;
import java.util.UUID;
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.bootstrap.credentials",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right. There is no Quarkus configuration named polaris.bootstrap.credentials.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's odd - I found it from a different test and the README as well.

I've removed it and the test still works 🤔 I can investigate this later as this is not the point of this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README is correct, but the other test is wrong indeed.

String.format("%s,%s,%s", REALM, CLIENT_ID, CLIENT_SECRET),
"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";
Map<String, String> headers = Map.of(REALM_HEADER, REALM, REQUEST_ID_HEADER, requestId);
try (Response response = request(headers)) {
assertThat(response.getHeaders()).containsKey(REQUEST_ID_HEADER);
assertThat(response.getHeaders().get(REQUEST_ID_HEADER)).hasSize(1);
assertThat(response.getHeaders().get(REQUEST_ID_HEADER)).allMatch(s -> s.equals(requestId));
}
}

@Test
public void testRequestIdHeaderNotSpecified() {
Map<String, String> headers = Map.of(REALM_HEADER, REALM);
try (Response response = request(headers)) {
assertThat(response.getHeaders()).containsKey(REQUEST_ID_HEADER);
assertThat(response.getHeaders().get(REQUEST_ID_HEADER)).hasSize(1);
assertThat(response.getHeaders().get(REQUEST_ID_HEADER))
.allMatch(s -> isValidUUID(s.toString()));
}
}

private boolean isValidUUID(String str) {
try {
UUID.fromString(str);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
}