-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(bigquery-jdbc): implement custom OTel SpanExporter credentials injector and authentication bypass #13263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: jdbc/feature-branch-otel
Are you sure you want to change the base?
Changes from all commits
6405ccd
9780ed9
8103651
dba7f5a
172dd9d
02ca2c5
701d549
3e8ca63
1ffc421
69b119b
7349805
aef912c
a8a51a8
2be5968
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,12 +29,17 @@ | |
| import io.opentelemetry.api.trace.Tracer; | ||
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.Scope; | ||
| import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; | ||
| import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; | ||
| import io.opentelemetry.sdk.OpenTelemetrySdk; | ||
| import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; | ||
| import java.io.IOException; | ||
| import java.net.URI; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.sql.SQLException; | ||
| import java.util.Collection; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.Callable; | ||
|
|
@@ -65,6 +70,11 @@ public class BigQueryJdbcOpenTelemetry { | |
| private static final String OTLP_ENDPOINT_VALUE = "https://telemetry.googleapis.com:443"; | ||
| private static final String EXPORTER_NONE = "none"; | ||
| private static final String EXPORTER_OTLP = "otlp"; | ||
| private static final String OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT = | ||
| "otel.span.attribute.value.length.limit"; | ||
| private static final String OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = | ||
| "otel.attribute.value.length.limit"; | ||
| private static final String DEFAULT_ATTRIBUTE_LENGTH_LIMIT = "32768"; | ||
| private static final BigQueryJdbcCustomLogger LOG = | ||
| new BigQueryJdbcCustomLogger("BigQueryJdbcOpenTelemetry"); | ||
|
|
||
|
|
@@ -216,6 +226,23 @@ private static Credentials resolveCredentialsFromString(String credsString) { | |
| BigQueryJdbcOpenTelemetry.class.getName()); | ||
| } | ||
|
|
||
| private static Map<String, String> getAuthHeaders(Credentials credentials) { | ||
| try { | ||
| Map<String, List<String>> metadata = | ||
| credentials.getRequestMetadata(URI.create(OTLP_ENDPOINT_VALUE)); | ||
| Map<String, String> headers = new HashMap<>(); | ||
| metadata.forEach( | ||
| (headerKey, headerValues) -> { | ||
| if (!headerValues.isEmpty()) { | ||
| headers.put(headerKey, headerValues.get(0)); | ||
| } | ||
| }); | ||
| return headers; | ||
| } catch (IOException e) { | ||
| throw new RuntimeException("Failed to get auth headers", e); | ||
| } | ||
| } | ||
|
|
||
| public static TelemetryConfig getConnectionConfig(String connectionId) { | ||
| return connectionConfigs.get(connectionId); | ||
| } | ||
|
|
@@ -265,13 +292,9 @@ public static OpenTelemetry getOpenTelemetry( | |
| key, | ||
| k -> { | ||
| Map<String, String> props = new HashMap<>(); | ||
| Credentials credentials = null; | ||
| if (gcpTelemetryCredentials != null) { | ||
| byte[] credsBytes = gcpTelemetryCredentials.getBytes(StandardCharsets.UTF_8); | ||
| if (BigQueryJdbcOAuthUtility.isJson(credsBytes)) { | ||
| props.put(CREDENTIALS_JSON, gcpTelemetryCredentials); | ||
| } else { | ||
| props.put(CREDENTIALS_PATH, gcpTelemetryCredentials); | ||
| } | ||
| credentials = resolveCredentialsFromString(gcpTelemetryCredentials); | ||
| } | ||
|
|
||
| if (enableGcpTraceExporter) { | ||
|
|
@@ -290,8 +313,41 @@ public static OpenTelemetry getOpenTelemetry( | |
| props.put(GOOGLE_CLOUD_PROJECT, gcpTelemetryProjectId); | ||
| } | ||
|
|
||
| AutoConfiguredOpenTelemetrySdk autoConfigured = | ||
| AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> props).build(); | ||
| // Set safe, generous default limits on attribute value lengths (32KB) to protect | ||
| // customers from GCP Cloud Trace 64KB span ingestion failures when logging massive | ||
| // exception stack traces or database schema metadata. | ||
| // Respect any existing user configuration overrides. | ||
| if (!props.containsKey(OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT)) { | ||
| props.put(OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, DEFAULT_ATTRIBUTE_LENGTH_LIMIT); | ||
| } | ||
| if (!props.containsKey(OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT)) { | ||
| props.put(OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, DEFAULT_ATTRIBUTE_LENGTH_LIMIT); | ||
| } | ||
|
|
||
| final Credentials finalCreds = credentials; | ||
| AutoConfiguredOpenTelemetrySdk autoConfigured; | ||
|
|
||
| if (finalCreds != null) { | ||
| autoConfigured = | ||
| AutoConfiguredOpenTelemetrySdk.builder() | ||
| .addPropertiesSupplier(() -> props) | ||
| .addSpanExporterCustomizer( | ||
| (spanExporter, configProperties) -> { | ||
| if (spanExporter instanceof OtlpHttpSpanExporter) { | ||
| return ((OtlpHttpSpanExporter) spanExporter) | ||
| .toBuilder().setHeaders(() -> getAuthHeaders(finalCreds)).build(); | ||
| } | ||
| if (spanExporter instanceof OtlpGrpcSpanExporter) { | ||
| return ((OtlpGrpcSpanExporter) spanExporter) | ||
| .toBuilder().setHeaders(() -> getAuthHeaders(finalCreds)).build(); | ||
| } | ||
| return spanExporter; | ||
| }) | ||
| .build(); | ||
| } else { | ||
| autoConfigured = | ||
| AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> props).build(); | ||
| } | ||
|
Comment on lines
+327
to
+350
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The AutoConfiguredOpenTelemetrySdk builder logic is duplicated between the if and else blocks. This can be refactored to use a single builder chain, which improves maintainability and reduces code duplication. When refactoring, ensure that any null checks or validation steps are not redundant with preceding logic. final Credentials finalCreds = credentials;
AutoConfiguredOpenTelemetrySdk autoConfigured =
AutoConfiguredOpenTelemetrySdk.builder()
.addPropertiesSupplier(() -> props)
.addSpanExporterCustomizer(
(spanExporter, configProperties) -> {
if (finalCreds != null) {
if (spanExporter instanceof OtlpHttpSpanExporter) {
return ((OtlpHttpSpanExporter) spanExporter)
.toBuilder().setHeaders(() -> getAuthHeaders(finalCreds)).build();
}
if (spanExporter instanceof OtlpGrpcSpanExporter) {
return ((OtlpGrpcSpanExporter) spanExporter)
.toBuilder().setHeaders(() -> getAuthHeaders(finalCreds)).build();
}
}
return spanExporter;
})
.build();References
|
||
|
|
||
| OpenTelemetrySdk sdk = autoConfigured.getOpenTelemetrySdk(); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throwing a RuntimeException inside the header supplier may cause the background exporter thread to terminate unexpectedly if an error occurs during credential metadata retrieval (e.g., a network failure during token refresh). It is generally safer to log the error and return an empty map, allowing the exporter to handle the failure through standard OTLP response codes (like 401 Unauthorized) rather than crashing the thread.