From a0d48fca36a7b745257a3fbc7269aec1b0925d40 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 27 Oct 2025 18:53:11 +0100 Subject: [PATCH 1/2] Restore original Request ID functionality This PR enacts the action items decided in the following ML thread: https://lists.apache.org/thread/4gmlcn84o7n8d12qnpl7grfbm4zypk7b The action items are: 1. Restore the original functionality for request IDs, change the default header name back to x-request-id 2. Remove RequestIdGenerator and related functionality. 3. Update PolarisEvent, expose request ID if available, expose OTel context if available. 4. Update events table SQL schema: insert request ID if available, insert OTel context if available. Furthermore, this PR also fixes #2913 and adds a small integration test for the JDBC events sink: `InMemoryBufferEventListenerIntegrationTest`. Finally, it modifies the Helm chart to reflect the configuration changes. --- CHANGELOG.md | 18 ++ helm/polaris/README.md | 14 +- helm/polaris/templates/configmap.yaml | 20 +- helm/polaris/tests/configmap_test.yaml | 55 ++++- helm/polaris/values.yaml | 46 ++-- .../relational/jdbc/models/ModelEvent.java | 97 +++++--- .../src/main/resources/h2/schema-v3.sql | 1 + .../src/main/resources/postgres/schema-v3.sql | 1 + .../jdbc/models/ModelEventTest.java | 62 ++--- .../polaris/core/entity/PolarisEvent.java | 12 +- .../src/main/resources/application.properties | 3 +- runtime/service/build.gradle.kts | 1 + .../config/ConfigRelocationInterceptor.java | 53 +++++ .../PolarisPersistenceEventListener.java | 9 +- .../inmemory/InMemoryBufferEventListener.java | 18 ++ .../service/logging/LoggingConfiguration.java | 3 - .../service/logging/LoggingMDCFilter.java | 5 +- .../tracing/DefaultRequestIdGenerator.java | 69 ------ .../service/tracing/RequestIdFilter.java | 53 ++--- ...nerator.java => TracingConfiguration.java} | 27 ++- .../service/tracing/TracingFilter.java | 3 - ...io.smallrye.config.ConfigSourceInterceptor | 20 ++ ...oryBufferEventListenerIntegrationTest.java | 217 ++++++++++++++++++ .../InMemoryBufferEventListenerTestBase.java | 2 +- .../DefaultRequestIdGeneratorTest.java | 83 ------- .../service/tracing/RequestIdFilterTest.java | 48 +--- .../service/tracing/RequestIdHeaderTest.java | 119 ---------- 27 files changed, 578 insertions(+), 481 deletions(-) create mode 100644 runtime/service/src/main/java/org/apache/polaris/service/config/ConfigRelocationInterceptor.java delete mode 100644 runtime/service/src/main/java/org/apache/polaris/service/tracing/DefaultRequestIdGenerator.java rename runtime/service/src/main/java/org/apache/polaris/service/tracing/{RequestIdGenerator.java => TracingConfiguration.java} (67%) create mode 100644 runtime/service/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor create mode 100644 runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java delete mode 100644 runtime/service/src/test/java/org/apache/polaris/service/tracing/DefaultRequestIdGeneratorTest.java delete mode 100644 runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdHeaderTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ee808f8dff..fbb01f0e7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,8 +31,17 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti ### Upgrade notes +- JDBC persistence: a new column has been added to the `events` table to store the OpenTelemetry + context that was active when the event was emitted. This column is nullable. To update your schema + to the latest version, run the following SQL statement: + ```sql + ALTER TABLE POLARIS_SCHEMA.EVENTS ADD COLUMN IF NOT EXISTS otel_context TEXT; + ``` + ### Breaking changes +- The default request ID header name has changed from `Polaris-Request-Id` to `x-request-id`. + ### New Features - Support credential vending for federated catalogs. `ALLOW_FEDERATED_CATALOGS_CREDENTIAL_VENDING` (default: true) was added to toggle this feature. @@ -43,6 +52,15 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti ### Deprecations +* The configuration property `polaris.log.request-id-header-name` is deprecated and has been renamed + to `polaris.tracing.request-id.header-name`; the old name is still supported for backwards + compatibility, but will generate a warning. It will be removed in a future release. +* Helm chart: the `tracing` section has been renamed to `tracing.otel`. The old name is still + supported for backwards compatibility, but will be removed in a future release. +* Helm chart: the `logging.requestIdHeaderName` property is deprecated and has been renamed to + `tracing.requestId.headerName`. The old name is still supported for backwards compatibility, but + will be removed in a future release. + ### Fixes ### Commits diff --git a/helm/polaris/README.md b/helm/polaris/README.md index bbfc8557df..527e6c39de 100644 --- a/helm/polaris/README.md +++ b/helm/polaris/README.md @@ -247,7 +247,7 @@ ct install --namespace polaris --charts ./helm/polaris | livenessProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed. Minimum value is 1. | | livenessProbe.terminationGracePeriodSeconds | int | `30` | Optional duration in seconds the pod needs to terminate gracefully upon probe failure. Minimum value is 1. | | livenessProbe.timeoutSeconds | int | `10` | Number of seconds after which the probe times out. Minimum value is 1. | -| logging | object | `{"categories":{"org.apache.iceberg.rest":"INFO","org.apache.polaris":"INFO"},"console":{"enabled":true,"format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"threshold":"ALL"},"file":{"enabled":false,"fileName":"polaris.log","format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"logsDir":"/deployments/logs","rotation":{"fileSuffix":null,"maxBackupIndex":5,"maxFileSize":"100Mi"},"storage":{"className":"standard","selectorLabels":{},"size":"512Gi"},"threshold":"ALL"},"level":"INFO","mdc":{},"requestIdHeaderName":"Polaris-Request-Id"}` | Logging configuration. | +| logging | object | `{"categories":{"org.apache.iceberg.rest":"INFO","org.apache.polaris":"INFO"},"console":{"enabled":true,"format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"threshold":"ALL"},"file":{"enabled":false,"fileName":"polaris.log","format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"logsDir":"/deployments/logs","rotation":{"fileSuffix":null,"maxBackupIndex":5,"maxFileSize":"100Mi"},"storage":{"className":"standard","selectorLabels":{},"size":"512Gi"},"threshold":"ALL"},"level":"INFO","mdc":{}}` | Logging configuration. | | logging.categories | object | `{"org.apache.iceberg.rest":"INFO","org.apache.polaris":"INFO"}` | Configuration for specific log categories. | | logging.console | object | `{"enabled":true,"format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"threshold":"ALL"}` | Configuration for the console appender. | | logging.console.enabled | bool | `true` | Whether to enable the console appender. | @@ -271,7 +271,6 @@ ct install --namespace polaris --charts ./helm/polaris | logging.file.threshold | string | `"ALL"` | The log level of the file appender. | | logging.level | string | `"INFO"` | The log level of the root category, which is used as the default log level for all categories. | | logging.mdc | object | `{}` | Configuration for MDC (Mapped Diagnostic Context). Values specified here will be added to the log context of all incoming requests and can be used in log patterns. | -| logging.requestIdHeaderName | string | `"Polaris-Request-Id"` | The header name to use for the request ID. | | managementService | object | `{"annotations":{},"clusterIP":"None","externalTrafficPolicy":null,"internalTrafficPolicy":null,"ports":[{"name":"polaris-mgmt","nodePort":null,"port":8182,"protocol":null,"targetPort":null}],"sessionAffinity":null,"trafficDistribution":null,"type":"ClusterIP"}` | Management service settings. These settings are used to configure liveness and readiness probes, and to configure the dedicated headless service that will expose health checks and metrics, e.g. for metrics scraping and service monitoring. | | managementService.annotations | object | `{}` | Annotations to add to the service. | | managementService.clusterIP | string | `"None"` | By default, the management service is headless, i.e. it does not have a cluster IP. This is generally the right option for exposing health checks and metrics, e.g. for metrics scraping and service monitoring. | @@ -368,7 +367,10 @@ ct install --namespace polaris --charts ./helm/polaris | tasks.maxConcurrentTasks | string | `nil` | The maximum number of concurrent tasks that can be executed at the same time. The default is the number of available cores. | | tasks.maxQueuedTasks | string | `nil` | The maximum number of tasks that can be queued up for execution. The default is Integer.MAX_VALUE. | | tolerations | list | `[]` | A list of tolerations to apply to polaris pods. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/. | -| tracing.attributes | object | `{}` | Resource attributes to identify the polaris service among other tracing sources. See https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/#service. If left empty, traces will be attached to a service named "Apache Polaris"; to change this, provide a service.name attribute here. | -| tracing.enabled | bool | `false` | Specifies whether tracing for the polaris server should be enabled. | -| tracing.endpoint | string | `"http://otlp-collector:4317"` | The collector endpoint URL to connect to (required). The endpoint URL must have either the http:// or the https:// scheme. The collector must talk the OpenTelemetry protocol (OTLP) and the port must be its gRPC port (by default 4317). See https://quarkus.io/guides/opentelemetry for more information. | -| tracing.sample | string | `"1.0d"` | Which requests should be sampled. Valid values are: "all", "none", or a ratio between 0.0 and "1.0d" (inclusive). E.g. "0.5d" means that 50% of the requests will be sampled. Note: avoid entering numbers here, always prefer a string representation of the ratio. | +| tracing.otel | object | `{"attributes":{},"enabled":false,"endpoint":"http://otlp-collector:4317","sample":"1.0d"}` | OpenTelemetry configuration. | +| tracing.otel.attributes | object | `{}` | Resource attributes to identify the polaris service among other tracing sources. See https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/#service. If left empty, traces will be attached to a service named "Apache Polaris"; to change this, provide a service.name attribute here. | +| tracing.otel.enabled | bool | `false` | Specifies whether tracing for the Apache Polaris server should be enabled. When this is enabled, then an OpenTelemetry collector endpoint must be configured. | +| tracing.otel.endpoint | string | `"http://otlp-collector:4317"` | The collector endpoint URL to connect to (required). The endpoint URL must have either the http:// or the https:// scheme. The collector must talk the OpenTelemetry protocol (OTLP) and the port must be its gRPC port (by default 4317). See https://quarkus.io/guides/opentelemetry for more information. | +| tracing.otel.sample | string | `"1.0d"` | Which requests should be sampled. Valid values are: "all", "none", or a ratio between 0.0 and "1.0d" (inclusive). E.g. "0.5d" means that 50% of the requests will be sampled. Note: avoid entering numbers here, always prefer a string representation of the ratio. | +| tracing.requestId | object | `{"headerName":"X-Request-ID"}` | Configuration for the request ID filter. | +| tracing.requestId.headerName | string | `"X-Request-ID"` | The name of the header that contains the request ID. | diff --git a/helm/polaris/templates/configmap.yaml b/helm/polaris/templates/configmap.yaml index 3e120765a1..fa1be1545d 100644 --- a/helm/polaris/templates/configmap.yaml +++ b/helm/polaris/templates/configmap.yaml @@ -165,7 +165,6 @@ data: {{- range $k, $v := $categories -}} {{- $_ = set $map (printf "quarkus.log.category.\"%s\".level" $k) $v -}} {{- end -}} - {{- $_ = set $map "polaris.log.request-id-header-name" .Values.logging.requestIdHeaderName -}} {{- $mdc := dict -}} {{- list .Values.logging.mdc "" $mdc | include "polaris.mergeConfigTree" -}} {{- range $k, $v := $mdc -}} @@ -173,20 +172,24 @@ data: {{- end -}} {{- /* Telemetry */ -}} - {{- if .Values.tracing.enabled -}} - {{- $_ = set $map "quarkus.otel.exporter.otlp.endpoint" .Values.tracing.endpoint -}} - {{- if .Values.tracing.attributes -}} + {{- /* TODO remove deprecated configs, keep only tracing.otel.* */ -}} + {{- if (or .Values.tracing.enabled .Values.tracing.otel.enabled) -}} + {{- $_ = set $map "quarkus.otel.exporter.otlp.endpoint" (coalesce .Values.tracing.endpoint .Values.tracing.otel.endpoint) -}} {{- $attributes := dict -}} + {{- if .Values.tracing.otel.attributes -}} + {{- list .Values.tracing.otel.attributes "" $attributes | include "polaris.mergeConfigTree" -}} + {{- end -}} + {{- if .Values.tracing.attributes -}} {{- list .Values.tracing.attributes "" $attributes | include "polaris.mergeConfigTree" -}} + {{- end -}} {{- $i := 0 -}} {{- range $k, $v := $attributes -}} {{- $_ = set $map (printf "quarkus.otel.resource.attributes[%d]" $i) (printf "%s=%s" $k $v) -}} {{- $i = add1 $i -}} {{- end -}} - {{- end -}} - {{- if .Values.tracing.sample -}} - {{- $sample := toString .Values.tracing.sample -}} - {{ if eq $sample "all" -}} + {{- if (or .Values.tracing.sample .Values.tracing.otel.sample) -}} + {{- $sample := toString (coalesce .Values.tracing.sample .Values.tracing.otel.sample) -}} + {{- if eq $sample "all" -}} {{- $_ = set $map "quarkus.otel.traces.sampler" "parentbased_always_on" -}} {{- else if eq $sample "none" -}} {{- $_ = set $map "quarkus.otel.traces.sampler" "always_off" -}} @@ -198,6 +201,7 @@ data: {{- else -}} {{- $_ = set $map "quarkus.otel.sdk.disabled" true -}} {{- end -}} + {{- $_ = set $map "polaris.tracing.request-id.header-name" (coalesce .Values.logging.requestIdHeaderName .Values.tracing.requestId.headerName) -}} {{- /* Metrics */ -}} {{- if .Values.metrics.enabled -}} diff --git a/helm/polaris/tests/configmap_test.yaml b/helm/polaris/tests/configmap_test.yaml index cc0c1354d1..e364f1b70a 100644 --- a/helm/polaris/tests/configmap_test.yaml +++ b/helm/polaris/tests/configmap_test.yaml @@ -292,7 +292,7 @@ tests: - matchRegex: { path: 'data["application.properties"]', pattern: "polaris.log.mdc.\"org.acme\"=foo" } - matchRegex: { path: 'data["application.properties"]', pattern: "polaris.log.mdc.\"org.acme.service\"=foo" } - - it: should include telemetry configuration + - it: should include deprecated telemetry configuration set: tracing: { enabled: true, endpoint: http://custom:4317, attributes: { service.name: custom, foo: bar } } asserts: @@ -300,35 +300,80 @@ tests: - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.resource.attributes\\[\\d\\]=service.name=custom" } - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.resource.attributes\\[\\d\\]=foo=bar" } - - it: should include set sample rate numeric + - it: should include telemetry configuration + set: + tracing.otel: { enabled: true, endpoint: http://custom:4317, attributes: { service.name: custom, foo: bar } } + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.exporter.otlp.endpoint=http://custom:4317" } + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.resource.attributes\\[\\d\\]=service.name=custom" } + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.resource.attributes\\[\\d\\]=foo=bar" } + + - it: should set sample rate numeric (deprecated) set: tracing: { enabled: true, sample: "0.123" } asserts: - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler=parentbased_traceidratio" } - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler.arg=0.123" } - - it: should include set sample rate "all" + - it: should set sample rate numeric + set: + tracing.otel: { enabled: true, sample: "0.123" } + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler=parentbased_traceidratio" } + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler.arg=0.123" } + + - it: should set sample rate "all" (deprecated) set: tracing: { enabled: true, sample: "all" } asserts: - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler=parentbased_always_on" } - - it: should include set sample rate "none" + - it: should set sample rate "all" + set: + tracing.otel: { enabled: true, sample: "all" } + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler=parentbased_always_on" } + + - it: should set sample rate "none" (deprecated) set: tracing: { enabled: true, sample: "none" } asserts: - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler=always_off" } + - it: should set sample rate "none" + set: + tracing.otel: { enabled: true, sample: "none" } + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.traces.sampler=always_off" } + - it: should disable tracing by default asserts: - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.sdk.disabled=true" } - - it: should disable tracing + - it: should disable tracing (deprecated) set: tracing: { enabled: false } asserts: - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.sdk.disabled=true" } + - it: should disable tracing + set: + tracing.otel: { enabled: false } + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "quarkus.otel.sdk.disabled=true" } + + - it: should including request ID header name (deprecated) + set: + logging.requestIdHeaderName: X-Custom-Request-ID + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "polaris.tracing.request-id.header-name=X-Custom-Request-ID" } + + - it: should including request ID header name + set: + tracing.requestId: { headerName: X-Custom-Request-ID } + asserts: + - matchRegex: { path: 'data["application.properties"]', pattern: "polaris.tracing.request-id.header-name=X-Custom-Request-ID" } + - it: should include custom metrics set: metrics: { enabled: true, tags: { app: custom, foo: bar } } diff --git a/helm/polaris/values.yaml b/helm/polaris/values.yaml index 9cecf0a275..f4234dc93d 100644 --- a/helm/polaris/values.yaml +++ b/helm/polaris/values.yaml @@ -392,25 +392,31 @@ extraInitContainers: [] # command: ['sh', '-c', 'echo "hello world"'] tracing: - # -- Specifies whether tracing for the polaris server should be enabled. - enabled: false - # -- The collector endpoint URL to connect to (required). - # The endpoint URL must have either the http:// or the https:// scheme. - # The collector must talk the OpenTelemetry protocol (OTLP) and the port must be its gRPC port (by default 4317). - # See https://quarkus.io/guides/opentelemetry for more information. - endpoint: "http://otlp-collector:4317" - # -- Which requests should be sampled. Valid values are: "all", "none", or a ratio between 0.0 and - # "1.0d" (inclusive). E.g. "0.5d" means that 50% of the requests will be sampled. - # Note: avoid entering numbers here, always prefer a string representation of the ratio. - sample: "1.0d" - # -- Resource attributes to identify the polaris service among other tracing sources. - # See https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/#service. - # If left empty, traces will be attached to a service named "Apache Polaris"; to change this, - # provide a service.name attribute here. - attributes: - {} - # service.name: my-polaris - + # -- OpenTelemetry configuration. + otel: + # -- Specifies whether tracing for the Apache Polaris server should be enabled. When this is enabled, + # then an OpenTelemetry collector endpoint must be configured. + enabled: false + # -- The collector endpoint URL to connect to (required). + # The endpoint URL must have either the http:// or the https:// scheme. + # The collector must talk the OpenTelemetry protocol (OTLP) and the port must be its gRPC port (by default 4317). + # See https://quarkus.io/guides/opentelemetry for more information. + endpoint: "http://otlp-collector:4317" + # -- Which requests should be sampled. Valid values are: "all", "none", or a ratio between 0.0 and + # "1.0d" (inclusive). E.g. "0.5d" means that 50% of the requests will be sampled. + # Note: avoid entering numbers here, always prefer a string representation of the ratio. + sample: "1.0d" + # -- Resource attributes to identify the polaris service among other tracing sources. + # See https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/#service. + # If left empty, traces will be attached to a service named "Apache Polaris"; to change this, + # provide a service.name attribute here. + attributes: + {} + # service.name: my-polaris + # -- Configuration for the request ID filter. + requestId: + # -- The name of the header that contains the request ID. + headerName: X-Request-ID metrics: # -- Specifies whether metrics for the polaris server should be enabled. enabled: true @@ -442,8 +448,6 @@ serviceMonitor: logging: # -- The log level of the root category, which is used as the default log level for all categories. level: INFO - # -- The header name to use for the request ID. - requestIdHeaderName: Polaris-Request-Id # -- Configuration for the console appender. console: # -- Whether to enable the console appender. diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEvent.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEvent.java index 92d01c3e81..90f861d6a2 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEvent.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEvent.java @@ -33,17 +33,48 @@ public interface ModelEvent extends Converter { String TABLE_NAME = "EVENTS"; + String CATALOG_ID = "catalog_id"; + String EVENT_ID = "event_id"; + String REQUEST_ID = "request_id"; + String EVENT_TYPE = "event_type"; + String TIMESTAMP_MS = "timestamp_ms"; + String PRINCIPAL_NAME = "principal_name"; + String RESOURCE_TYPE = "resource_type"; + String RESOURCE_IDENTIFIER = "resource_identifier"; + String OTEL_CONTEXT = "otel_context"; + String ADDITIONAL_PROPERTIES = "additional_properties"; + List ALL_COLUMNS = List.of( - "catalog_id", - "event_id", - "request_id", - "event_type", - "timestamp_ms", - "principal_name", - "resource_type", - "resource_identifier", - "additional_properties"); + CATALOG_ID, + EVENT_ID, + REQUEST_ID, + EVENT_TYPE, + TIMESTAMP_MS, + PRINCIPAL_NAME, + RESOURCE_TYPE, + RESOURCE_IDENTIFIER, + OTEL_CONTEXT, + ADDITIONAL_PROPERTIES); + + /** + * Dummy instance to be used as a Converter when calling #fromResultSet(). + * + *

FIXME: fromResultSet() is a factory method and should be static or moved to a factory class. + */ + ModelEvent CONVERTER = + ImmutableModelEvent.builder() + .catalogId("") + .eventId("") + .requestId("") + .eventType("") + .timestampMs(0L) + .principalName("") + .resourceType(PolarisEvent.ResourceType.CATALOG) + .resourceIdentifier("") + .openTelemetryContext("") + .additionalProperties("") + .build(); // catalog id String getCatalogId(); @@ -71,6 +102,10 @@ public interface ModelEvent extends Converter { // Which resource was operated on String getResourceIdentifier(); + // OpenTelemetry context that was active when this event was emitted + @Nullable + String getOpenTelemetryContext(); + // Additional parameters that were not earlier recorded String getAdditionalProperties(); @@ -78,15 +113,16 @@ public interface ModelEvent extends Converter { default PolarisEvent fromResultSet(ResultSet rs) throws SQLException { var modelEvent = ImmutableModelEvent.builder() - .catalogId(rs.getString("catalog_id")) - .eventId(rs.getString("event_id")) - .requestId(rs.getString("request_id")) - .eventType(rs.getString("event_type")) - .timestampMs(rs.getLong("timestamp_ms")) - .principalName(rs.getString("actor")) - .resourceType(PolarisEvent.ResourceType.valueOf(rs.getString("resource_type"))) - .resourceIdentifier(rs.getString("resource_identifier")) - .additionalProperties(rs.getString("additional_properties")) + .catalogId(rs.getString(CATALOG_ID)) + .eventId(rs.getString(EVENT_ID)) + .requestId(rs.getString(REQUEST_ID)) + .eventType(rs.getString(EVENT_TYPE)) + .timestampMs(rs.getLong(TIMESTAMP_MS)) + .principalName(rs.getString(PRINCIPAL_NAME)) + .resourceType(PolarisEvent.ResourceType.valueOf(rs.getString(RESOURCE_TYPE))) + .resourceIdentifier(rs.getString(RESOURCE_IDENTIFIER)) + .openTelemetryContext(rs.getString(OTEL_CONTEXT)) + .additionalProperties(rs.getString(ADDITIONAL_PROPERTIES)) .build(); return toEvent(modelEvent); } @@ -94,18 +130,19 @@ default PolarisEvent fromResultSet(ResultSet rs) throws SQLException { @Override default Map toMap(DatabaseType databaseType) { Map map = new LinkedHashMap<>(); - map.put("catalog_id", getCatalogId()); - map.put("event_id", getEventId()); - map.put("request_id", getRequestId()); - map.put("event_type", getEventType()); - map.put("timestamp_ms", getTimestampMs()); - map.put("principal_name", getPrincipalName()); - map.put("resource_type", getResourceType().toString()); - map.put("resource_identifier", getResourceIdentifier()); + map.put(CATALOG_ID, getCatalogId()); + map.put(EVENT_ID, getEventId()); + map.put(REQUEST_ID, getRequestId()); + map.put(EVENT_TYPE, getEventType()); + map.put(TIMESTAMP_MS, getTimestampMs()); + map.put(PRINCIPAL_NAME, getPrincipalName()); + map.put(RESOURCE_TYPE, getResourceType().toString()); + map.put(RESOURCE_IDENTIFIER, getResourceIdentifier()); + map.put(OTEL_CONTEXT, getOpenTelemetryContext()); if (databaseType.equals(DatabaseType.POSTGRES)) { - map.put("additional_properties", toJsonbPGobject(getAdditionalProperties())); + map.put(ADDITIONAL_PROPERTIES, toJsonbPGobject(getAdditionalProperties())); } else { - map.put("additional_properties", getAdditionalProperties()); + map.put(ADDITIONAL_PROPERTIES, getAdditionalProperties()); } return map; } @@ -122,6 +159,7 @@ static ModelEvent fromEvent(PolarisEvent event) { .principalName(event.getPrincipalName()) .resourceType(event.getResourceType()) .resourceIdentifier(event.getResourceIdentifier()) + .openTelemetryContext(event.getOpenTelemetryContext()) .additionalProperties(event.getAdditionalProperties()) .build(); } @@ -138,7 +176,8 @@ static PolarisEvent toEvent(ModelEvent model) { model.getTimestampMs(), model.getPrincipalName(), model.getResourceType(), - model.getResourceIdentifier()); + model.getResourceIdentifier(), + model.getOpenTelemetryContext()); polarisEvent.setAdditionalProperties(model.getAdditionalProperties()); return polarisEvent; } diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v3.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v3.sql index 3fb7749a3d..c22354e5a7 100644 --- a/persistence/relational-jdbc/src/main/resources/h2/schema-v3.sql +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v3.sql @@ -131,6 +131,7 @@ CREATE TABLE IF NOT EXISTS events ( principal_name TEXT, resource_type TEXT NOT NULL, resource_identifier TEXT NOT NULL, + otel_context TEXT, additional_properties TEXT NOT NULL, PRIMARY KEY (event_id) ); \ No newline at end of file diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v3.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v3.sql index 96897f5106..9e667633c0 100644 --- a/persistence/relational-jdbc/src/main/resources/postgres/schema-v3.sql +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v3.sql @@ -131,6 +131,7 @@ CREATE TABLE IF NOT EXISTS events ( principal_name TEXT, resource_type TEXT NOT NULL, resource_identifier TEXT NOT NULL, + otel_context TEXT, additional_properties JSONB NOT NULL DEFAULT '{}'::JSONB, PRIMARY KEY (event_id) ); diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEventTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEventTest.java index fa78de0885..8b9e09b809 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEventTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEventTest.java @@ -19,6 +19,16 @@ package org.apache.polaris.persistence.relational.jdbc.models; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.ADDITIONAL_PROPERTIES; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.CATALOG_ID; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.EVENT_ID; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.EVENT_TYPE; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.OTEL_CONTEXT; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.PRINCIPAL_NAME; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.REQUEST_ID; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.RESOURCE_IDENTIFIER; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.RESOURCE_TYPE; +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.TIMESTAMP_MS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; @@ -33,18 +43,6 @@ import org.postgresql.util.PGobject; public class ModelEventTest { - // Column names - private static final String CATALOG_ID = "catalog_id"; - private static final String EVENT_ID = "event_id"; - private static final String REQUEST_ID = "request_id"; - private static final String EVENT_TYPE = "event_type"; - private static final String TIMESTAMP_MS = "timestamp_ms"; - private static final String ACTOR = "actor"; - private static final String PRINCIPAL_NAME = "principal_name"; - private static final String RESOURCE_TYPE = "resource_type"; - private static final String RESOURCE_IDENTIFIER = "resource_identifier"; - private static final String ADDITIONAL_PROPERTIES = "additional_properties"; - // Test data values private static final String TEST_CATALOG_ID = "test-catalog"; private static final String TEST_EVENT_ID = "event-123"; @@ -56,6 +54,7 @@ public class ModelEventTest { PolarisEvent.ResourceType.TABLE; private static final String TEST_RESOURCE_TYPE_STRING = "TABLE"; private static final String TEST_RESOURCE_IDENTIFIER = "test-table"; + private static final String TEST_OTEL_CONTEXT = "test-opentelemetry-context"; private static final String EMPTY_JSON = "{}"; private static final String TEST_JSON = "{\"key\":\"value\"}"; @@ -74,27 +73,14 @@ public void testFromResultSet() throws SQLException { when(mockResultSet.getString(REQUEST_ID)).thenReturn(TEST_REQUEST_ID); when(mockResultSet.getString(EVENT_TYPE)).thenReturn(TEST_EVENT_TYPE); when(mockResultSet.getLong(TIMESTAMP_MS)).thenReturn(TEST_TIMESTAMP_MS); - when(mockResultSet.getString(ACTOR)).thenReturn(TEST_USER); + when(mockResultSet.getString(PRINCIPAL_NAME)).thenReturn(TEST_USER); when(mockResultSet.getString(RESOURCE_TYPE)).thenReturn(TEST_RESOURCE_TYPE_STRING); when(mockResultSet.getString(RESOURCE_IDENTIFIER)).thenReturn(TEST_RESOURCE_IDENTIFIER); + when(mockResultSet.getString(OTEL_CONTEXT)).thenReturn(TEST_OTEL_CONTEXT); when(mockResultSet.getString(ADDITIONAL_PROPERTIES)).thenReturn(EMPTY_JSON); - // Create a concrete implementation of ModelEvent for testing - ModelEvent modelEvent = - ImmutableModelEvent.builder() - .catalogId(DUMMY) - .eventId(DUMMY) - .requestId(DUMMY) - .eventType(DUMMY) - .timestampMs(DUMMY_TIMESTAMP) - .principalName(DUMMY) - .resourceType(DUMMY_RESOURCE_TYPE) - .resourceIdentifier(DUMMY) - .additionalProperties(EMPTY_JSON) - .build(); - // Act - PolarisEvent result = modelEvent.fromResultSet(mockResultSet); + PolarisEvent result = ModelEvent.CONVERTER.fromResultSet(mockResultSet); // Assert assertEquals(TEST_CATALOG_ID, result.getCatalogId()); @@ -105,6 +91,7 @@ public void testFromResultSet() throws SQLException { assertEquals(TEST_USER, result.getPrincipalName()); assertEquals(TEST_RESOURCE_TYPE, result.getResourceType()); assertEquals(TEST_RESOURCE_IDENTIFIER, result.getResourceIdentifier()); + assertEquals(TEST_OTEL_CONTEXT, result.getOpenTelemetryContext()); assertEquals(EMPTY_JSON, result.getAdditionalProperties()); } @@ -121,6 +108,7 @@ public void testToMapWithH2DatabaseType() { .principalName(TEST_USER) .resourceType(TEST_RESOURCE_TYPE) .resourceIdentifier(TEST_RESOURCE_IDENTIFIER) + .openTelemetryContext(TEST_OTEL_CONTEXT) .additionalProperties(TEST_JSON) .build(); @@ -136,6 +124,7 @@ public void testToMapWithH2DatabaseType() { assertEquals(TEST_USER, resultMap.get(PRINCIPAL_NAME)); assertEquals(TEST_RESOURCE_TYPE_STRING, resultMap.get(RESOURCE_TYPE)); assertEquals(TEST_RESOURCE_IDENTIFIER, resultMap.get(RESOURCE_IDENTIFIER)); + assertEquals(TEST_OTEL_CONTEXT, resultMap.get(OTEL_CONTEXT)); assertEquals(TEST_JSON, resultMap.get(ADDITIONAL_PROPERTIES)); } @@ -152,6 +141,7 @@ public void testToMapWithPostgresType() { .principalName(TEST_USER) .resourceType(TEST_RESOURCE_TYPE) .resourceIdentifier(TEST_RESOURCE_IDENTIFIER) + .openTelemetryContext(TEST_OTEL_CONTEXT) .additionalProperties(TEST_JSON) .build(); @@ -167,6 +157,7 @@ public void testToMapWithPostgresType() { assertEquals(TEST_USER, resultMap.get(PRINCIPAL_NAME)); assertEquals(TEST_RESOURCE_TYPE_STRING, resultMap.get(RESOURCE_TYPE)); assertEquals(TEST_RESOURCE_IDENTIFIER, resultMap.get(RESOURCE_IDENTIFIER)); + assertEquals(TEST_OTEL_CONTEXT, resultMap.get(OTEL_CONTEXT)); // For PostgreSQL, the additional properties should be a PGobject of type "jsonb" PGobject pgObject = (PGobject) resultMap.get(ADDITIONAL_PROPERTIES); @@ -195,7 +186,8 @@ public void testFromEvent() { TEST_TIMESTAMP_MS, TEST_USER, TEST_RESOURCE_TYPE, - TEST_RESOURCE_IDENTIFIER); + TEST_RESOURCE_IDENTIFIER, + TEST_OTEL_CONTEXT); polarisEvent.setAdditionalProperties(TEST_JSON); // Act @@ -210,18 +202,10 @@ public void testFromEvent() { assertEquals(TEST_USER, result.getPrincipalName()); assertEquals(TEST_RESOURCE_TYPE, result.getResourceType()); assertEquals(TEST_RESOURCE_IDENTIFIER, result.getResourceIdentifier()); + assertEquals(TEST_OTEL_CONTEXT, result.getOpenTelemetryContext()); assertEquals(TEST_JSON, result.getAdditionalProperties()); } - @Test - public void testToEventWithNullInput() { - // Act - PolarisEvent result = ModelEvent.toEvent(null); - - // Assert - assertNull(result); - } - @Test public void testToEvent() { // Arrange @@ -235,6 +219,7 @@ public void testToEvent() { .principalName(TEST_USER) .resourceType(TEST_RESOURCE_TYPE) .resourceIdentifier(TEST_RESOURCE_IDENTIFIER) + .openTelemetryContext(TEST_OTEL_CONTEXT) .additionalProperties(TEST_JSON) .build(); @@ -250,6 +235,7 @@ public void testToEvent() { assertEquals(TEST_USER, result.getPrincipalName()); assertEquals(TEST_RESOURCE_TYPE, result.getResourceType()); assertEquals(TEST_RESOURCE_IDENTIFIER, result.getResourceIdentifier()); + assertEquals(TEST_OTEL_CONTEXT, result.getOpenTelemetryContext()); assertEquals(TEST_JSON, result.getAdditionalProperties()); } } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEvent.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEvent.java index 0ac1ea84a4..4bb305da59 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEvent.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEvent.java @@ -56,6 +56,9 @@ public class PolarisEvent { // Which resource was operated on private final String resourceIdentifier; + // OpenTelemetry context that was active when this event was emitted + @Nullable private final String openTelemetryContext; + // Additional parameters that were not earlier recorded private String additionalProperties; @@ -93,6 +96,11 @@ public String getResourceIdentifier() { return resourceIdentifier; } + @Nullable + public String getOpenTelemetryContext() { + return openTelemetryContext; + } + public String getAdditionalProperties() { return additionalProperties != null ? additionalProperties : EMPTY_MAP_STRING; } @@ -105,7 +113,8 @@ public PolarisEvent( long timestampMs, @Nullable String principalName, ResourceType resourceType, - String resourceIdentifier) { + String resourceIdentifier, + @Nullable String openTelemetryContext) { this.catalogId = catalogId; this.id = id; this.requestId = requestId; @@ -114,6 +123,7 @@ public PolarisEvent( this.principalName = principalName; this.resourceType = resourceType; this.resourceIdentifier = resourceIdentifier; + this.openTelemetryContext = openTelemetryContext; } @JsonIgnore diff --git a/runtime/defaults/src/main/resources/application.properties b/runtime/defaults/src/main/resources/application.properties index 0358eb4b50..3ea4b7d42d 100644 --- a/runtime/defaults/src/main/resources/application.properties +++ b/runtime/defaults/src/main/resources/application.properties @@ -146,10 +146,11 @@ 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 +polaris.tracing.request-id.header-name=X-Request-ID + polaris.metrics.tags.application=Polaris # polaris.metrics.tags.service=polaris # polaris.metrics.tags.environment=prod diff --git a/runtime/service/build.gradle.kts b/runtime/service/build.gradle.kts index 7b41c3d10e..034a9ffee7 100644 --- a/runtime/service/build.gradle.kts +++ b/runtime/service/build.gradle.kts @@ -114,6 +114,7 @@ dependencies { } testImplementation(project(":polaris-api-management-model")) + testImplementation(project(":polaris-relational-jdbc")) testImplementation(project(":polaris-minio-testcontainer")) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ConfigRelocationInterceptor.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ConfigRelocationInterceptor.java new file mode 100644 index 0000000000..be6b552af6 --- /dev/null +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ConfigRelocationInterceptor.java @@ -0,0 +1,53 @@ +/* + * 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 java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConfigRelocationInterceptor extends RelocateConfigSourceInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigRelocationInterceptor.class); + + public ConfigRelocationInterceptor() { + super(new RelocationChecker()::checkForRelocation); + } + + private static class RelocationChecker { + + private final Map relocations = + Map.of("polaris.log.request-id-header-name", "polaris.tracing.request-id.header-name"); + + private String checkForRelocation(String name) { + String relocated = relocations.get(name); + if (relocated != null) { + warnOnRelocatedProperty(name, relocated); + return relocated; + } + return name; + } + + private void warnOnRelocatedProperty(String name, String replacement) { + LOGGER.warn("Property '{}' is deprecated, use '{}' instead", name, replacement); + } + } +} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java index f2f0c960a8..254ec95333 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java @@ -45,7 +45,8 @@ public void onAfterCreateTable(IcebergRestCatalogEvents.AfterCreateTableEvent ev contextSpecificInformation.timestamp(), contextSpecificInformation.principalName(), PolarisEvent.ResourceType.TABLE, - TableIdentifier.of(event.namespace(), event.tableName()).toString()); + TableIdentifier.of(event.namespace(), event.tableName()).toString(), + getOpenTelemetryContext()); Map additionalParameters = Map.of( "table-uuid", @@ -68,7 +69,8 @@ public void onAfterCreateCatalog(CatalogsServiceEvents.AfterCreateCatalogEvent e contextSpecificInformation.timestamp(), contextSpecificInformation.principalName(), PolarisEvent.ResourceType.CATALOG, - event.catalog().getName()); + event.catalog().getName(), + getOpenTelemetryContext()); processEvent(polarisEvent); } @@ -79,5 +81,8 @@ public record ContextSpecificInformation(long timestamp, @Nullable String princi @Nullable protected abstract String getRequestId(); + @Nullable + protected abstract String getOpenTelemetryContext(); + protected abstract void processEvent(PolarisEvent event); } 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 533e983ae5..02c6627c79 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 @@ -25,6 +25,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.RemovalCause; import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.trace.Span; import io.smallrye.common.annotation.Identifier; import io.smallrye.mutiny.infrastructure.Infrastructure; import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor; @@ -97,6 +98,23 @@ protected String getRequestId() { return (String) requestContext.getProperty(REQUEST_ID_KEY); } + @Nullable + @Override + protected String getOpenTelemetryContext() { + Span span = Span.current(); + if (span.getSpanContext().isValid()) { + // construct a string from the span context according to W3C Trace Context proposal + // https://www.w3.org/TR/trace-context/ + return "00-" + + span.getSpanContext().getTraceId() + + "-" + + span.getSpanContext().getSpanId() + + "-" + + span.getSpanContext().getTraceFlags().asHex(); + } + return null; + } + @PreDestroy public void shutdown() { processors.asMap().values().forEach(UnicastProcessor::onComplete); diff --git a/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingConfiguration.java b/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingConfiguration.java index 26c0a1efe4..ce89ffc6be 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingConfiguration.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/logging/LoggingConfiguration.java @@ -24,9 +24,6 @@ @ConfigMapping(prefix = "polaris.log") public interface LoggingConfiguration { - /** The name of the header that contains the request ID. */ - String requestIdHeaderName(); - /** Additional MDC values to include in the log context. */ Map mdc(); } 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 fb0112cbb6..400129caf7 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 @@ -49,7 +49,10 @@ 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); - MDC.put(REQUEST_ID_KEY, (String) rc.getProperty(REQUEST_ID_KEY)); + String requestId = (String) rc.getProperty(REQUEST_ID_KEY); + if (requestId != null) { + MDC.put(REQUEST_ID_KEY, requestId); + } 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/tracing/DefaultRequestIdGenerator.java b/runtime/service/src/main/java/org/apache/polaris/service/tracing/DefaultRequestIdGenerator.java deleted file mode 100644 index 47f17291fa..0000000000 --- a/runtime/service/src/main/java/org/apache/polaris/service/tracing/DefaultRequestIdGenerator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 io.smallrye.mutiny.Uni; -import jakarta.annotation.Nonnull; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.ws.rs.container.ContainerRequestContext; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Default implementation of {@link RequestIdGenerator}, striking a balance between randomness and - * performance. - * - *

The IDs generated by this generator are of the form: {@code UUID_COUNTER}. The UUID part is - * randomly generated at startup, and the counter is incremented for each request. - * - *

In the unlikely event that the counter overflows, a new UUID is generated and the counter is - * reset to 1. - */ -@ApplicationScoped -public class DefaultRequestIdGenerator implements RequestIdGenerator { - - record RequestId(UUID uuid, long counter) { - - RequestId() { - this(UUID.randomUUID(), 1); - } - - @Override - @Nonnull - public String toString() { - return String.format("%s_%019d", uuid(), counter()); - } - - RequestId increment() { - return counter == Long.MAX_VALUE ? new RequestId() : new RequestId(uuid, counter + 1); - } - } - - final AtomicReference state = new AtomicReference<>(new RequestId()); - - @Override - public Uni generateRequestId(ContainerRequestContext requestContext) { - return Uni.createFrom().item(nextRequestId().toString()); - } - - RequestId nextRequestId() { - return state.getAndUpdate(RequestId::increment); - } -} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java index 460a732331..63b5ac9df7 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java @@ -18,60 +18,43 @@ */ package org.apache.polaris.service.tracing; -import io.smallrye.mutiny.Uni; import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerResponseContext; -import jakarta.ws.rs.core.MediaType; -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; +/** + * Filter that handles request IDs for tracing. + * + *

See Envoy + * Tracing + * + * @see Heroku - HTTP Request + * IDs + */ public class RequestIdFilter { public static final String REQUEST_ID_KEY = "requestId"; - private static final Logger LOGGER = LoggerFactory.getLogger(RequestIdFilter.class); - - @Inject LoggingConfiguration loggingConfiguration; - @Inject RequestIdGenerator requestIdGenerator; + @Inject TracingConfiguration tracingConfiguration; @ServerRequestFilter(preMatching = true, priority = FilterPriorities.REQUEST_ID_FILTER) - public Uni assignRequestId(ContainerRequestContext rc) { - var requestId = rc.getHeaderString(loggingConfiguration.requestIdHeaderName()); - return (requestId != null - ? Uni.createFrom().item(requestId) - : requestIdGenerator.generateRequestId(rc)) - .onItem() - .invoke(id -> rc.setProperty(REQUEST_ID_KEY, id)) - .onItemOrFailure() - .transform((id, error) -> error == null ? null : errorResponse(error)); + public void extractRequestId(ContainerRequestContext rc) { + String requestId = rc.getHeaderString(tracingConfiguration.requestId().headerName()); + if (requestId != null) { + rc.setProperty(REQUEST_ID_KEY, requestId); + } } @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); + if (requestId != null) { + response.getHeaders().add(tracingConfiguration.requestId().headerName(), requestId); } } - - private static Response errorResponse(Throwable error) { - LOGGER.error("Failed to generate request ID", error); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .type(MediaType.APPLICATION_JSON_TYPE) - .entity( - ErrorResponse.builder() - .responseCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) - .withMessage("Request ID generation failed") - .withType("RequestIdGenerationError") - .build()) - .build(); - } } 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/TracingConfiguration.java similarity index 67% rename from runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdGenerator.java rename to runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingConfiguration.java index 4264f0e381..35b8f9b55d 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdGenerator.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingConfiguration.java @@ -19,20 +19,19 @@ package org.apache.polaris.service.tracing; -import io.smallrye.mutiny.Uni; -import jakarta.ws.rs.container.ContainerRequestContext; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; -/** - * A generator for request IDs. - * - * @see RequestIdFilter - */ -public interface RequestIdGenerator { +@ConfigMapping(prefix = "polaris.tracing") +public interface TracingConfiguration { + + RequestId requestId(); + + /** Configuration for the request ID filter. */ + interface RequestId { - /** - * Generates a new request ID. IDs must be fast to generate and unique. - * - * @param requestContext The JAX-RS request context - */ - Uni generateRequestId(ContainerRequestContext requestContext); + /** The name of the header that contains the request ID. */ + @WithDefault("x-request-id") + String headerName(); + } } 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 d794317d47..bd7915afe8 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 @@ -36,7 +36,6 @@ @Provider public class TracingFilter implements ContainerRequestFilter { - public static final String REQUEST_ID_ATTRIBUTE = "polaris.request.id"; public static final String REALM_ID_ATTRIBUTE = "polaris.realm.id"; @ConfigProperty(name = "quarkus.otel.sdk.disabled") @@ -46,8 +45,6 @@ public class TracingFilter implements ContainerRequestFilter { public void filter(ContainerRequestContext rc) { if (!sdkDisabled) { Span span = Span.current(); - String requestId = (String) rc.getProperty(RequestIdFilter.REQUEST_ID_KEY); - span.setAttribute(REQUEST_ID_ATTRIBUTE, requestId); RealmContext realmContext = (RealmContext) rc.getProperty(RealmContextFilter.REALM_CONTEXT_KEY); span.setAttribute(REALM_ID_ATTRIBUTE, realmContext.getRealmIdentifier()); diff --git a/runtime/service/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor b/runtime/service/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor new file mode 100644 index 0000000000..6e17ae8a92 --- /dev/null +++ b/runtime/service/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor @@ -0,0 +1,20 @@ +# +# 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. +# + +org.apache.polaris.service.config.ConfigRelocationInterceptor diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java new file mode 100644 index 0000000000..2956a9d9d6 --- /dev/null +++ b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java @@ -0,0 +1,217 @@ +/* + * 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.events.listeners.inmemory; + +import static org.apache.polaris.persistence.relational.jdbc.models.ModelEvent.CONVERTER; +import static org.apache.polaris.service.it.env.PolarisClient.polarisClient; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import javax.sql.DataSource; +import org.apache.iceberg.PartitionSpec; +import org.apache.iceberg.Schema; +import org.apache.iceberg.SortOrder; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.SessionCatalog; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.rest.RESTSessionCatalog; +import org.apache.iceberg.rest.auth.OAuth2Properties; +import org.apache.iceberg.types.Types; +import org.apache.polaris.core.admin.model.Catalog; +import org.apache.polaris.core.admin.model.CatalogProperties; +import org.apache.polaris.core.admin.model.FileStorageConfigInfo; +import org.apache.polaris.core.admin.model.PolarisCatalog; +import org.apache.polaris.core.admin.model.StorageConfigInfo; +import org.apache.polaris.core.entity.PolarisEvent; +import org.apache.polaris.service.it.env.ClientPrincipal; +import org.apache.polaris.service.it.env.IntegrationTestsHelper; +import org.apache.polaris.service.it.env.PolarisApiEndpoints; +import org.apache.polaris.service.it.env.PolarisClient; +import org.apache.polaris.service.it.env.RestApi; +import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +@QuarkusTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestProfile(InMemoryBufferEventListenerIntegrationTest.Profile.class) +@ExtendWith(PolarisIntegrationTestExtension.class) +class InMemoryBufferEventListenerIntegrationTest { + + public static class Profile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return ImmutableMap.builder() + .put("polaris.persistence.type", "relational-jdbc") + .put("polaris.persistence.auto-bootstrap-types", "relational-jdbc") + .put("quarkus.datasource.db-kind", "h2") + .put("quarkus.otel.sdk.disabled", "false") + .put( + "quarkus.datasource.jdbc.url", + "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE") + .put("polaris.event-listener.type", "persistence-in-memory-buffer") + .put("polaris.event-listener.persistence-in-memory-buffer.buffer-time", "100ms") + .put("polaris.features.\"ALLOW_INSECURE_STORAGE_TYPES\"", "true") + .put("polaris.features.\"SUPPORTED_CATALOG_STORAGE_TYPES\"", "[\"FILE\",\"S3\"]") + .put("polaris.readiness.ignore-severe-issues", "true") + .build(); + } + } + + private RestApi managementApi; + private PolarisApiEndpoints endpoints; + private PolarisClient client; + private String realm; + private String authToken; + private URI baseLocation; + + @Inject Instance dataSource; + + @BeforeAll + public void setup( + PolarisApiEndpoints apiEndpoints, ClientPrincipal adminCredentials, @TempDir Path tempDir) { + endpoints = apiEndpoints; + client = polarisClient(endpoints); + realm = endpoints.realmId(); + authToken = client.obtainToken(adminCredentials.credentials()); + managementApi = client.managementApi(authToken); + baseLocation = IntegrationTestsHelper.getTemporaryDirectory(tempDir).resolve(realm + "/"); + } + + @Test + void testCreateCatalogAndTable() throws IOException { + + String catalogName = client.newEntityName("testCreateCatalogAndTable"); + + Catalog catalog = + PolarisCatalog.builder() + .setName(catalogName) + .setType(Catalog.TypeEnum.INTERNAL) + .setProperties(CatalogProperties.builder("file:///tmp/").build()) + .setStorageConfigInfo( + FileStorageConfigInfo.builder() + .setStorageType(StorageConfigInfo.StorageTypeEnum.FILE) + .setAllowedLocations(List.of(baseLocation.toString())) + .build()) + .build(); + + try (Response response = + managementApi + .request("v1/catalogs") + .header("x-request-id", "12345") + .post(Entity.json(catalog))) { + assertThat(response).returns(Response.Status.CREATED.getStatusCode(), Response::getStatus); + } + + try (RESTSessionCatalog sessionCatalog = new RESTSessionCatalog()) { + + sessionCatalog.initialize( + "polaris_catalog_test", + ImmutableMap.builder() + .put("uri", endpoints.catalogApiEndpoint().toString()) + .put(OAuth2Properties.TOKEN, authToken) + .put("warehouse", catalogName) + .putAll(endpoints.extraHeaders("header.")) + .put("header.x-request-id", "456789") + .build()); + + SessionCatalog.SessionContext sessionContext = SessionCatalog.SessionContext.createEmpty(); + Namespace ns = Namespace.of("db1"); + sessionCatalog.createNamespace(sessionContext, ns); + + sessionCatalog + .buildTable( + sessionContext, + TableIdentifier.of(ns, "t1"), + new Schema( + List.of(Types.NestedField.required(1, "theField", Types.StringType.get())))) + .withSortOrder(SortOrder.unsorted()) + .withPartitionSpec(PartitionSpec.unpartitioned()) + .create(); + } + + String query = + "SELECT * FROM polaris_schema.events WHERE realm_id = '" + + realm + + "' ORDER BY timestamp_ms"; + + List events = + await() + .atMost(Duration.ofSeconds(10)) + .until( + () -> { + ImmutableList.Builder e = ImmutableList.builder(); + try (Connection connection = dataSource.get().getConnection(); + Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(query)) { + while (rs.next()) { + PolarisEvent event = CONVERTER.fromResultSet(rs); + e.add(event); + } + } + return e.build(); + }, + e -> e.size() >= 2); + + // FIXME: check before events when they get persisted + + PolarisEvent e1 = events.getFirst(); + assertThat(e1.getCatalogId()).isEqualTo(catalogName); + assertThat(e1.getResourceType()).isEqualTo(PolarisEvent.ResourceType.CATALOG); + assertThat(e1.getResourceIdentifier()).isEqualTo(catalogName); + assertThat(e1.getEventType()).isEqualTo("AfterCreateCatalogEvent"); + assertThat(e1.getAdditionalProperties()).isEqualTo("{}"); + assertThat(e1.getPrincipalName()).isEqualTo("root"); + assertThat(e1.getRequestId()).isEqualTo("12345"); + assertThat(e1.getOpenTelemetryContext()).matches("00-[0-9a-f]{32}-[0-9a-f]{16}-0\\d"); + + PolarisEvent e2 = events.getLast(); + assertThat(e2.getCatalogId()).isEqualTo(catalogName); + assertThat(e2.getResourceType()).isEqualTo(PolarisEvent.ResourceType.TABLE); + assertThat(e2.getResourceIdentifier()).isEqualTo("db1.t1"); + assertThat(e2.getEventType()).isEqualTo("AfterCreateTableEvent"); + assertThat(e2.getAdditionalProperties()).contains("table-uuid"); + assertThat(e2.getPrincipalName()).isEqualTo("root"); + assertThat(e2.getRequestId()).isEqualTo("456789"); + assertThat(e2.getOpenTelemetryContext()).matches("00-[0-9a-f]{32}-[0-9a-f]{16}-0\\d"); + } +} diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerTestBase.java b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerTestBase.java index d86fb44e8a..e42cffc088 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerTestBase.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerTestBase.java @@ -112,6 +112,6 @@ void assertRows(String realmId, int expected) { static PolarisEvent event() { String id = UUID.randomUUID().toString(); - return new PolarisEvent("test", id, null, "test", 0, null, CATALOG, "test"); + return new PolarisEvent("test", id, null, "test", 0, null, CATALOG, "test", null); } } diff --git a/runtime/service/src/test/java/org/apache/polaris/service/tracing/DefaultRequestIdGeneratorTest.java b/runtime/service/src/test/java/org/apache/polaris/service/tracing/DefaultRequestIdGeneratorTest.java deleted file mode 100644 index 13dc54baed..0000000000 --- a/runtime/service/src/test/java/org/apache/polaris/service/tracing/DefaultRequestIdGeneratorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.apache.polaris.service.tracing.DefaultRequestIdGenerator.RequestId; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class DefaultRequestIdGeneratorTest { - - private DefaultRequestIdGenerator requestIdGenerator; - - @BeforeEach - void setUp() { - requestIdGenerator = new DefaultRequestIdGenerator(); - } - - @Test - void testGeneratesUniqueIds() { - Set generatedIds = new ConcurrentSkipListSet<>(); - try (ExecutorService executor = Executors.newFixedThreadPool(10)) { - for (int i = 0; i < 1000; i++) { - executor.execute(() -> generatedIds.add(requestIdGenerator.nextRequestId().toString())); - } - } - assertThat(generatedIds).hasSize(1000); - } - - @Test - void testCounterIncrementsSequentially() { - assertThat(requestIdGenerator.nextRequestId().counter()).isEqualTo(1L); - assertThat(requestIdGenerator.nextRequestId().counter()).isEqualTo(2L); - assertThat(requestIdGenerator.nextRequestId().counter()).isEqualTo(3L); - } - - @Test - void testCounterRotationAtMax() { - requestIdGenerator.state.set(new RequestId(UUID.randomUUID(), Long.MAX_VALUE)); - - var beforeRotation = requestIdGenerator.nextRequestId(); - var afterRotation = requestIdGenerator.nextRequestId(); - - // The UUID should be different after rotation - assertThat(beforeRotation.uuid()).isNotEqualTo(afterRotation.uuid()); - - // The counter should be reset to 1 after rotation - assertThat(beforeRotation.counter()).isEqualTo(Long.MAX_VALUE); - assertThat(afterRotation.counter()).isEqualTo(1L); - } - - @Test - void testRequestIdToString() { - var uuid = UUID.randomUUID(); - assertThat(new RequestId(uuid, 1L).toString()).isEqualTo(uuid + "_0000000000000000001"); - assertThat(new RequestId(uuid, 12345L).toString()).isEqualTo(uuid + "_0000000000000012345"); - assertThat(new RequestId(uuid, Long.MAX_VALUE).toString()) - .isEqualTo(uuid + "_9223372036854775807"); - } -} diff --git a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java index ddcf5894ba..8b0dfb131f 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java @@ -20,77 +20,41 @@ package org.apache.polaris.service.tracing; import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.anything; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.hamcrest.CoreMatchers.nullValue; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.mockito.InjectSpy; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; -import io.smallrye.mutiny.Uni; import org.apache.polaris.service.catalog.api.IcebergRestOAuth2Api; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; @QuarkusTest @TestHTTPEndpoint(IcebergRestOAuth2Api.class) -@SuppressWarnings("UastIncorrectHttpHeaderInspection") public class RequestIdFilterTest { - @InjectSpy RequestIdGenerator requestIdGenerator; - - @BeforeEach - void resetMocks() { - Mockito.reset(requestIdGenerator); - } - @Test - void testSuccessWithGeneratedRequestId() { + void testNoRequestId() { givenTokenRequest() .when() .post() .then() .statusCode(200) .body(containsString("access_token")) - .header("Polaris-Request-Id", anything()); - verify(requestIdGenerator, times(1)).generateRequestId(any()); + .header("x-request-id", nullValue()); } @Test - void testSuccessWithCustomRequestId() { + void testWithRequestId() { givenTokenRequest() - .header("Polaris-Request-Id", "custom-request-id") + .header("x-request-id", "custom-request-id") .when() .post() .then() .statusCode(200) .body(containsString("access_token")) - .header("Polaris-Request-Id", "custom-request-id"); - verify(requestIdGenerator, never()).generateRequestId(any()); - } - - @Test - void testError() { - doReturn(Uni.createFrom().failure(new RuntimeException("test error"))) - .when(requestIdGenerator) - .generateRequestId(any()); - givenTokenRequest() - .when() - .post() - .then() - .statusCode(500) - .body("error.message", is("Request ID generation failed")) - .body("error.type", is("RequestIdGenerationError")) - .body("error.code", is(500)); - verify(requestIdGenerator, times(1)).generateRequestId(any()); + .header("x-request-id", "custom-request-id"); } private static RequestSpecification givenTokenRequest() { 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 deleted file mode 100644 index 8a653476df..0000000000 --- a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdHeaderTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.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 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 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 headers = Map.of(REALM_HEADER, REALM, REQUEST_ID_HEADER, requestId); - assertThat(sendRequest(headers)).isEqualTo(requestId); - assertThat(sendRequest(headers)).isEqualTo(requestId); - - String newRequestId = "new-pre-requested-request-id"; - headers = Map.of(REALM_HEADER, REALM, REQUEST_ID_HEADER, newRequestId); - assertThat(sendRequest(headers)).isEqualTo(newRequestId); - } - - @Test - public void testRequestIdHeaderNotSpecified() { - Map headers = Map.of(REALM_HEADER, REALM); - Set requestIds = new HashSet<>(); - for (int i = 0; i < 10; i++) { - requestIds.add(sendRequest(headers)); - } - assertThat(requestIds).hasSize(10); - } - - private String sendRequest(Map 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(); - } - } -} From c20669677663a6d2301f080246f28899704b8219 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 29 Oct 2025 12:53:32 +0100 Subject: [PATCH 2/2] update documentation + normalize case --- CHANGELOG.md | 2 +- getting-started/telemetry/README.md | 6 +++--- .../service/tracing/TracingConfiguration.java | 2 +- ...oryBufferEventListenerIntegrationTest.java | 4 ++-- .../service/tracing/RequestIdFilterTest.java | 6 +++--- .../in-dev/unreleased/configuration.md | 4 ++-- .../using-polaris/telemetry-tools.md | 6 +++--- site/content/in-dev/unreleased/helm.md | 19 +++++++++++++------ site/content/in-dev/unreleased/telemetry.md | 6 ++---- 9 files changed, 30 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb01f0e7a..dd5d2fc13a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti ### Breaking changes -- The default request ID header name has changed from `Polaris-Request-Id` to `x-request-id`. +- The default request ID header name has changed from `Polaris-Request-Id` to `X-Request-ID`. ### New Features diff --git a/getting-started/telemetry/README.md b/getting-started/telemetry/README.md index c1bb392013..88fc1cbe4e 100644 --- a/getting-started/telemetry/README.md +++ b/getting-started/telemetry/README.md @@ -49,15 +49,15 @@ This example requires `jq` to be installed on your machine. ``` 4. Then, use the access token in the Authorization header when accessing Polaris; you can also test - the `Polaris-Request-Id` header; you should see it in all logs and traces: + the `X-Request-ID` header; you should see it in all logs and traces: ```shell curl -v 'http://localhost:8181/api/management/v1/principal-roles' \ -H "Authorization: Bearer $POLARIS_TOKEN" \ - -H "Polaris-Request-Id: 1234" + -H "X-Request-ID: 1234" curl -v 'http://localhost:8181/api/catalog/v1/config?warehouse=quickstart_catalog' \ -H "Authorization: Bearer $POLARIS_TOKEN" \ - -H "Polaris-Request-Id: 5678" + -H "X-Request-ID: 5678" ``` 5. Access the following services: diff --git a/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingConfiguration.java b/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingConfiguration.java index 35b8f9b55d..41aaaec697 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingConfiguration.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/tracing/TracingConfiguration.java @@ -31,7 +31,7 @@ public interface TracingConfiguration { interface RequestId { /** The name of the header that contains the request ID. */ - @WithDefault("x-request-id") + @WithDefault("X-Request-ID") String headerName(); } } diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java index 2956a9d9d6..cd690115f8 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java @@ -137,7 +137,7 @@ void testCreateCatalogAndTable() throws IOException { try (Response response = managementApi .request("v1/catalogs") - .header("x-request-id", "12345") + .header("X-Request-ID", "12345") .post(Entity.json(catalog))) { assertThat(response).returns(Response.Status.CREATED.getStatusCode(), Response::getStatus); } @@ -151,7 +151,7 @@ void testCreateCatalogAndTable() throws IOException { .put(OAuth2Properties.TOKEN, authToken) .put("warehouse", catalogName) .putAll(endpoints.extraHeaders("header.")) - .put("header.x-request-id", "456789") + .put("header.X-Request-ID", "456789") .build()); SessionCatalog.SessionContext sessionContext = SessionCatalog.SessionContext.createEmpty(); diff --git a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java index 8b0dfb131f..4e96d93f4f 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/tracing/RequestIdFilterTest.java @@ -42,19 +42,19 @@ void testNoRequestId() { .then() .statusCode(200) .body(containsString("access_token")) - .header("x-request-id", nullValue()); + .header("X-Request-ID", nullValue()); } @Test void testWithRequestId() { givenTokenRequest() - .header("x-request-id", "custom-request-id") + .header("X-Request-ID", "custom-request-id") .when() .post() .then() .statusCode(200) .body(containsString("access_token")) - .header("x-request-id", "custom-request-id"); + .header("X-Request-ID", "custom-request-id"); } private static RequestSpecification givenTokenRequest() { diff --git a/site/content/in-dev/unreleased/configuration.md b/site/content/in-dev/unreleased/configuration.md index 5728361ada..d5b5f5ac3d 100644 --- a/site/content/in-dev/unreleased/configuration.md +++ b/site/content/in-dev/unreleased/configuration.md @@ -153,14 +153,14 @@ read-only mode, as Polaris only reads the configuration file once, at startup. | `polaris.storage.aws.secret-key` | `secretKey` | Define the AWS S3 secret key. If unset, the default credential provider chain will be used. | | `polaris.storage.gcp.token` | `token` | Define the Google Cloud Storage token. If unset, the default credential provider chain will be used. | | `polaris.storage.gcp.lifespan` | `PT1H` | Define the Google Cloud Storage lifespan type. If unset, the default credential provider chain will be used. | -| `polaris.log.request-id-header-name` | `Polaris-Request-Id` | Define the header name to match request ID in the log. | +| `polaris.tracing.request-id.header-name` | `X-Request-ID` | Define the header name to use for the request ID. | | `polaris.log.mdc.aid` | `polaris` | Define the log context (e.g. MDC) AID. | | `polaris.log.mdc.sid` | `polaris-service` | Define the log context (e.g. MDC) SID. | | `polaris.rate-limiter.filter.type` | `no-op` | Define the Polaris rate limiter. Supported values are `no-op`, `token-bucket`. | | `polaris.rate-limiter.token-bucket.type` | `default` | Define the token bucket rate limiter. | | `polaris.rate-limiter.token-bucket.requests-per-second` | `9999` | Define the number of requests per second for the token bucket rate limiter. | | `polaris.rate-limiter.token-bucket.window` | `PT10S` | Define the window type for the token bucket rate limiter. | -| `polaris.metrics.tags.=` | `application=Polaris` | Define arbitrary metric tags to include in every request. | +| `polaris.metrics.tags.=` | `application=Polaris` | Define arbitrary metric tags to include in every request. | | `polaris.metrics.realm-id-tag.api-metrics-enabled` | `false` | Whether to enable the `realm_id` metric tag in API metrics. | | `polaris.metrics.realm-id-tag.http-metrics-enabled` | `false` | Whether to enable the `realm_id` metric tag in HTTP request metrics. | | `polaris.metrics.realm-id-tag.http-metrics-max-cardinality` | `100` | The maximum cardinality for the `realm_id` tag in HTTP request metrics. | diff --git a/site/content/in-dev/unreleased/getting-started/using-polaris/telemetry-tools.md b/site/content/in-dev/unreleased/getting-started/using-polaris/telemetry-tools.md index b6a9e8f8eb..ce6490297d 100644 --- a/site/content/in-dev/unreleased/getting-started/using-polaris/telemetry-tools.md +++ b/site/content/in-dev/unreleased/getting-started/using-polaris/telemetry-tools.md @@ -53,15 +53,15 @@ This example requires `jq` to be installed on your machine. ``` 4. Then, use the access token in the Authorization header when accessing Polaris; you can also test - the `Polaris-Request-Id` header; you should see it in all logs and traces: + the `X-Request-ID` header; you should see it in all logs and traces: ```shell curl -v 'http://localhost:8181/api/management/v1/principal-roles' \ -H "Authorization: Bearer $POLARIS_TOKEN" \ - -H "Polaris-Request-Id: 1234" + -H "X-Request-ID: 1234" curl -v 'http://localhost:8181/api/catalog/v1/config?warehouse=quickstart_catalog' \ -H "Authorization: Bearer $POLARIS_TOKEN" \ - -H "Polaris-Request-Id: 5678" + -H "X-Request-ID: 5678" ``` 5. Access the following services: diff --git a/site/content/in-dev/unreleased/helm.md b/site/content/in-dev/unreleased/helm.md index ef82e8e675..1936fe4d68 100644 --- a/site/content/in-dev/unreleased/helm.md +++ b/site/content/in-dev/unreleased/helm.md @@ -249,7 +249,7 @@ ct install --namespace polaris --charts ./helm/polaris | livenessProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed. Minimum value is 1. | | livenessProbe.terminationGracePeriodSeconds | int | `30` | Optional duration in seconds the pod needs to terminate gracefully upon probe failure. Minimum value is 1. | | livenessProbe.timeoutSeconds | int | `10` | Number of seconds after which the probe times out. Minimum value is 1. | -| logging | object | `{"categories":{"org.apache.iceberg.rest":"INFO","org.apache.polaris":"INFO"},"console":{"enabled":true,"format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"threshold":"ALL"},"file":{"enabled":false,"fileName":"polaris.log","format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"logsDir":"/deployments/logs","rotation":{"fileSuffix":null,"maxBackupIndex":5,"maxFileSize":"100Mi"},"storage":{"className":"standard","selectorLabels":{},"size":"512Gi"},"threshold":"ALL"},"level":"INFO","mdc":{},"requestIdHeaderName":"Polaris-Request-Id"}` | Logging configuration. | +| logging | object | `{"categories":{"org.apache.iceberg.rest":"INFO","org.apache.polaris":"INFO"},"console":{"enabled":true,"format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"threshold":"ALL"},"file":{"enabled":false,"fileName":"polaris.log","format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"logsDir":"/deployments/logs","rotation":{"fileSuffix":null,"maxBackupIndex":5,"maxFileSize":"100Mi"},"storage":{"className":"standard","selectorLabels":{},"size":"512Gi"},"threshold":"ALL"},"level":"INFO","mdc":{}}` | Logging configuration. | | logging.categories | object | `{"org.apache.iceberg.rest":"INFO","org.apache.polaris":"INFO"}` | Configuration for specific log categories. | | logging.console | object | `{"enabled":true,"format":"%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] [%X{requestId},%X{realmId}] [%X{traceId},%X{parentId},%X{spanId},%X{sampled}] (%t) %s%e%n","json":false,"threshold":"ALL"}` | Configuration for the console appender. | | logging.console.enabled | bool | `true` | Whether to enable the console appender. | @@ -273,7 +273,6 @@ ct install --namespace polaris --charts ./helm/polaris | logging.file.threshold | string | `"ALL"` | The log level of the file appender. | | logging.level | string | `"INFO"` | The log level of the root category, which is used as the default log level for all categories. | | logging.mdc | object | `{}` | Configuration for MDC (Mapped Diagnostic Context). Values specified here will be added to the log context of all incoming requests and can be used in log patterns. | -| logging.requestIdHeaderName | string | `"Polaris-Request-Id"` | The header name to use for the request ID. | | managementService | object | `{"annotations":{},"clusterIP":"None","externalTrafficPolicy":null,"internalTrafficPolicy":null,"ports":[{"name":"polaris-mgmt","nodePort":null,"port":8182,"protocol":null,"targetPort":null}],"sessionAffinity":null,"trafficDistribution":null,"type":"ClusterIP"}` | Management service settings. These settings are used to configure liveness and readiness probes, and to configure the dedicated headless service that will expose health checks and metrics, e.g. for metrics scraping and service monitoring. | | managementService.annotations | object | `{}` | Annotations to add to the service. | | managementService.clusterIP | string | `"None"` | By default, the management service is headless, i.e. it does not have a cluster IP. This is generally the right option for exposing health checks and metrics, e.g. for metrics scraping and service monitoring. | @@ -312,6 +311,11 @@ ct install --namespace polaris --charts ./helm/polaris | persistence.relationalJdbc.secret.username | string | `"username"` | The secret key holding the database username for authentication | | persistence.type | string | `"in-memory"` | The type of persistence to use. Two built-in types are supported: in-memory and relational-jdbc. The eclipse-link type is also supported but is deprecated. | | podAnnotations | object | `{}` | Annotations to apply to polaris pods. | +| podDisruptionBudget | object | `{"annotations":{},"enabled":false,"maxUnavailable":null,"minAvailable":null}` | Pod disruption budget settings. | +| podDisruptionBudget.annotations | object | `{}` | Annotations to add to the pod disruption budget. | +| podDisruptionBudget.enabled | bool | `false` | Specifies whether a pod disruption budget should be created. | +| podDisruptionBudget.maxUnavailable | string | `nil` | The maximum number of pods that can be unavailable during disruptions. Can be an absolute number (ex: 5) or a percentage of desired pods (ex: 50%). IMPORTANT: Cannot be used simultaneously with minAvailable. | +| podDisruptionBudget.minAvailable | string | `nil` | The minimum number of pods that should remain available during disruptions. Can be an absolute number (ex: 5) or a percentage of desired pods (ex: 50%). IMPORTANT: Cannot be used simultaneously with maxUnavailable. | | podLabels | object | `{}` | Additional Labels to apply to polaris pods. | | podSecurityContext | object | `{"fsGroup":10001,"seccompProfile":{"type":"RuntimeDefault"}}` | Security context for the polaris pod. See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/. | | podSecurityContext.fsGroup | int | `10001` | GID 10001 is compatible with Polaris OSS default images; change this if you are using a different image. | @@ -365,7 +369,10 @@ ct install --namespace polaris --charts ./helm/polaris | tasks.maxConcurrentTasks | string | `nil` | The maximum number of concurrent tasks that can be executed at the same time. The default is the number of available cores. | | tasks.maxQueuedTasks | string | `nil` | The maximum number of tasks that can be queued up for execution. The default is Integer.MAX_VALUE. | | tolerations | list | `[]` | A list of tolerations to apply to polaris pods. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/. | -| tracing.attributes | object | `{}` | Resource attributes to identify the polaris service among other tracing sources. See https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/#service. If left empty, traces will be attached to a service named "Apache Polaris"; to change this, provide a service.name attribute here. | -| tracing.enabled | bool | `false` | Specifies whether tracing for the polaris server should be enabled. | -| tracing.endpoint | string | `"http://otlp-collector:4317"` | The collector endpoint URL to connect to (required). The endpoint URL must have either the http:// or the https:// scheme. The collector must talk the OpenTelemetry protocol (OTLP) and the port must be its gRPC port (by default 4317). See https://quarkus.io/guides/opentelemetry for more information. | -| tracing.sample | string | `"1.0d"` | Which requests should be sampled. Valid values are: "all", "none", or a ratio between 0.0 and "1.0d" (inclusive). E.g. "0.5d" means that 50% of the requests will be sampled. Note: avoid entering numbers here, always prefer a string representation of the ratio. | +| tracing.otel | object | `{"attributes":{},"enabled":false,"endpoint":"http://otlp-collector:4317","sample":"1.0d"}` | OpenTelemetry configuration. | +| tracing.otel.attributes | object | `{}` | Resource attributes to identify the polaris service among other tracing sources. See https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/#service. If left empty, traces will be attached to a service named "Apache Polaris"; to change this, provide a service.name attribute here. | +| tracing.otel.enabled | bool | `false` | Specifies whether tracing for the Apache Polaris server should be enabled. When this is enabled, then an OpenTelemetry collector endpoint must be configured. | +| tracing.otel.endpoint | string | `"http://otlp-collector:4317"` | The collector endpoint URL to connect to (required). The endpoint URL must have either the http:// or the https:// scheme. The collector must talk the OpenTelemetry protocol (OTLP) and the port must be its gRPC port (by default 4317). See https://quarkus.io/guides/opentelemetry for more information. | +| tracing.otel.sample | string | `"1.0d"` | Which requests should be sampled. Valid values are: "all", "none", or a ratio between 0.0 and "1.0d" (inclusive). E.g. "0.5d" means that 50% of the requests will be sampled. Note: avoid entering numbers here, always prefer a string representation of the ratio. | +| tracing.requestId | object | `{"headerName":"X-Request-ID"}` | Configuration for the request ID filter. | +| tracing.requestId.headerName | string | `"X-Request-ID"` | The name of the header that contains the request ID. | diff --git a/site/content/in-dev/unreleased/telemetry.md b/site/content/in-dev/unreleased/telemetry.md index 8bf8df03c3..b4a889cfa5 100644 --- a/site/content/in-dev/unreleased/telemetry.md +++ b/site/content/in-dev/unreleased/telemetry.md @@ -110,10 +110,8 @@ quarkus.otel.resource.attributes[0]=service.name=Polaris quarkus.otel.resource.attributes[1]=deployment.environment=dev ``` -Finally, two additional span attributes are added to all request parent spans: +Finally, one additional span attribute is added to all request parent spans: -- `polaris.request.id`: The unique identifier of the request, if set by the caller through the - `Polaris-Request-Id` header. - `polaris.realm`: The unique identifier of the realm. Always set (unless the request failed because of a realm resolution error). @@ -168,7 +166,7 @@ Polaris uses Mapped Diagnostic Context (MDC) to enrich log messages with additio following MDC keys are available: - `requestId`: The unique identifier of the request, if set by the caller through the - `Polaris-Request-Id` header. + `X-Request-ID` header. - `realmId`: The unique identifier of the realm. Always set. - `traceId`: The unique identifier of the trace. Present if tracing is enabled and the message is originating from a traced context.