-
Notifications
You must be signed in to change notification settings - Fork 320
Rename request ID to correlation ID #2757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,7 +89,8 @@ quarkus.oidc.tenant-enabled=false | |
| # quarkus.oidc.idp1.auth-server-url=https://auth.example.com/realms/polaris2 | ||
| # quarkus.oidc.idp1.client-id=polaris2 | ||
|
|
||
| # quarkus.otel.sdk.disabled is set to `true` by default to avoid spuriour errors about | ||
| # OpenTelemetry settings. | ||
| # quarkus.otel.sdk.disabled is set to `true` by default to avoid spurious errors about | ||
| # trace collector connections being impossible to establish. This setting can be enabled | ||
| # at runtime after configuring other OTel properties for proper trace data collection. | ||
| quarkus.otel.sdk.disabled=true | ||
|
|
@@ -106,6 +107,9 @@ quarkus.fault-tolerance.global.timeout.enabled=false | |
| # quarkus.fault-tolerance.global.timeout.unit=minutes | ||
| # quarkus.fault-tolerance.global.timeout.value=10 | ||
|
|
||
| polaris.correlation-id.header-name=Polaris-Request-Id | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't change the value of this setting as this would be a breaking change, but |
||
| polaris.correlation-id.generator.type=default | ||
|
|
||
| polaris.realm-context.type=default | ||
| polaris.realm-context.realms=POLARIS | ||
| polaris.realm-context.header-name=Polaris-Realm | ||
|
|
@@ -146,7 +150,6 @@ polaris.event-listener.type=no-op | |
| # polaris.event-listener.aws-cloudwatch.region=us-east-1 | ||
| # polaris.event-listener.aws-cloudwatch.synchronous-mode=false | ||
|
|
||
| polaris.log.request-id-header-name=Polaris-Request-Id | ||
| # polaris.log.mdc.aid=polaris | ||
| # polaris.log.mdc.sid=polaris-service | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* | ||
| * 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.config; | ||
|
|
||
| import io.smallrye.config.RelocateConfigSourceInterceptor; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class ConfigRelocationInterceptor extends RelocateConfigSourceInterceptor { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 for adding this! |
||
|
|
||
| private static final Logger LOGGER = LoggerFactory.getLogger(ConfigRelocationInterceptor.class); | ||
|
|
||
| public ConfigRelocationInterceptor() { | ||
| super(ConfigRelocationInterceptor::applyRelocations); | ||
| } | ||
|
|
||
| private static String applyRelocations(String name) { | ||
| if (name.equals("polaris.log.request-id-header-name")) { | ||
| String replacement = "polaris.correlation-id.header-name"; | ||
| LOGGER.warn("Property '{}' is deprecated, use '{}' instead", name, replacement); | ||
| return replacement; | ||
| } | ||
| return name; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* | ||
| * 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.correlation; | ||
|
|
||
| import io.smallrye.config.ConfigMapping; | ||
|
|
||
| @ConfigMapping(prefix = "polaris.correlation-id") | ||
| public interface CorrelationIdConfiguration { | ||
|
|
||
| /** | ||
| * The name of the header that contains the correlation ID. | ||
| * | ||
| * <p>If a request does not contain this header, it will be assigned a new correlation ID | ||
| * generated using the configured {@link #generator()}. | ||
| * | ||
| * <p>All responses will include the correlation ID in this header. | ||
| */ | ||
| String headerName(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems we prefer in Polaris to have a default declared in Do you prefer the other way around? I don't have a strong preference here :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't mind either. |
||
|
|
||
| /** | ||
| * The correlation ID generator to use, when a request does not contain the {@link #headerName()}. | ||
| */ | ||
| Generator generator(); | ||
|
|
||
| interface Generator { | ||
|
|
||
| /** | ||
| * The type of the correlation ID generator. Must be a registered {@link CorrelationIdGenerator} | ||
| * identifier. | ||
| */ | ||
| String type(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,7 @@ | |
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.apache.polaris.service.tracing; | ||
| package org.apache.polaris.service.correlation; | ||
|
|
||
| import io.smallrye.mutiny.Uni; | ||
| import jakarta.inject.Inject; | ||
|
|
@@ -26,39 +26,38 @@ | |
| import jakarta.ws.rs.core.Response; | ||
| import org.apache.iceberg.rest.responses.ErrorResponse; | ||
| import org.apache.polaris.service.config.FilterPriorities; | ||
| import org.apache.polaris.service.logging.LoggingConfiguration; | ||
| import org.jboss.resteasy.reactive.server.ServerRequestFilter; | ||
| import org.jboss.resteasy.reactive.server.ServerResponseFilter; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class RequestIdFilter { | ||
| public class CorrelationIdFilter { | ||
|
|
||
| public static final String REQUEST_ID_KEY = "requestId"; | ||
| public static final String CORRELATION_ID_KEY = "requestId"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, |
||
|
|
||
| private static final Logger LOGGER = LoggerFactory.getLogger(RequestIdFilter.class); | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(CorrelationIdFilter.class); | ||
|
|
||
| @Inject LoggingConfiguration loggingConfiguration; | ||
| @Inject RequestIdGenerator requestIdGenerator; | ||
| @Inject CorrelationIdConfiguration correlationIdConfiguration; | ||
| @Inject CorrelationIdGenerator correlationIdGenerator; | ||
|
|
||
| @ServerRequestFilter(preMatching = true, priority = FilterPriorities.REQUEST_ID_FILTER) | ||
| @ServerRequestFilter(preMatching = true, priority = FilterPriorities.CORRELATION_ID_FILTER) | ||
| public Uni<Response> assignRequestId(ContainerRequestContext rc) { | ||
| var requestId = rc.getHeaderString(loggingConfiguration.requestIdHeaderName()); | ||
| return (requestId != null | ||
| ? Uni.createFrom().item(requestId) | ||
| : requestIdGenerator.generateRequestId(rc)) | ||
| var correlationId = rc.getHeaderString(correlationIdConfiguration.headerName()); | ||
| return (correlationId != null | ||
| ? Uni.createFrom().item(correlationId) | ||
| : correlationIdGenerator.generateCorrelationId(rc)) | ||
| .onItem() | ||
| .invoke(id -> rc.setProperty(REQUEST_ID_KEY, id)) | ||
| .invoke(id -> rc.setProperty(CORRELATION_ID_KEY, id)) | ||
| .onItemOrFailure() | ||
| .transform((id, error) -> error == null ? null : errorResponse(error)); | ||
| } | ||
|
|
||
| @ServerResponseFilter | ||
| public void addResponseHeader( | ||
| ContainerRequestContext request, ContainerResponseContext response) { | ||
| String requestId = (String) request.getProperty(REQUEST_ID_KEY); | ||
| if (requestId != null) { // can be null if request ID generation fails | ||
| response.getHeaders().add(loggingConfiguration.requestIdHeaderName(), requestId); | ||
| String correlationId = (String) request.getProperty(CORRELATION_ID_KEY); | ||
| if (correlationId != null) { // can be null if request ID generation fails | ||
| response.getHeaders().add(correlationIdConfiguration.headerName(), correlationId); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for coming late to this PR, but I wonder if we may want to support standard OTel context propagation techniques instead 🤔
https://opentelemetry.io/docs/concepts/context-propagation/
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already support that :-) In Quarkus, W3C Trace Context propagation is enabled by default:
https://quarkus.io/guides/opentelemetry-tracing#propagators
IOW, Polaris correlation ID can be considered a simplified alternative to OTel context propagation; but the former doesn't preclude the latter. You can perfectly combine both techniques together.
Polaris correlation ID has one small advantage though: it is included in Polaris events, while OTel context is not. So the only way to correlate a Polaris event with an OTel trace is to first locate the trace using the correlation ID from the event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting... I think we should propagate OTel context into events. OTel has a concept of "detached" links... This is certainly beyond this scope of this PR, though 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For ref: I think Span kinds of Producer (Polaris side) and Consumer (event receiver) would fit this kind of context propagation.
https://opentelemetry.io/docs/concepts/signals/traces/#producer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but the choice to use a specific
SpanKinddepends on the event receiver and what it does with events.A span with
SpanKind.PRODUCERwould be the right thing to do if the event is being sent to a message queue.But if the event is being sent via HTTP or gRPC to a remote system, a span with
SpanKind.CLIENTwould be more appropriate.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍 I was actually thinking of embedding an outgoing OTel context into the event itself. This way regardless of the event delivery technology, consumers may want to perform async tasks that actually related to the event data, so in that case they would have the option of linking a
CONSUMERspan to the Polaris Event'sPRODUCERspan.... just an idea.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'm not sure I fully understand your example. I'd imagine that, if some event listener wants to send the event to a remote system and propagate OTel context, it would leverage the OTel SDK and do something like this:
The created span would have the current request span as its parent, then the OTel magic would kick in when the appropriate propagator is invoked.
We might not even have to do this btw, some message queue clients probably already add a PRODUCER span transparently whenever they produce a record to the message broker.
The
CONSUMERspan kind would have to be created on the receiving side (the message broker) for this to work, but I bet this is the case for most popular message queues as they have the required instrumentation already.Would that example solve your problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question how context propagation happens...
sendEventToMessageQueuewill probably use some integration code that takes the context and passes it to the message queue as a "header" of sorts.If out intention is to correlate with Polaris events using only Polaris "specs", we could put the outgoing context into the event as an attribute.