Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.*;
import com.databricks.jdbc.common.util.ValidationUtil;
import com.databricks.jdbc.dbclient.impl.common.TracingUtil;
import com.databricks.jdbc.exception.DatabricksDriverException;
import com.databricks.jdbc.exception.DatabricksParsingException;
import com.databricks.jdbc.exception.DatabricksSQLException;
Expand Down Expand Up @@ -41,6 +42,8 @@ public class DatabricksConnectionContext implements IDatabricksConnectionContext
private DatabricksClientType clientType;
@VisibleForTesting final ImmutableMap<String, String> parameters;
@VisibleForTesting final String connectionUuid;
private String traceId;
private String traceFlags;

private DatabricksConnectionContext(
String connectionURL,
Expand All @@ -58,6 +61,7 @@ private DatabricksConnectionContext(
this.computeResource = buildCompute();
this.connectionUuid = UUID.randomUUID().toString();
this.clientType = getClientTypeFromContext();
initializeTraceContext();
}

private DatabricksConnectionContext(
Expand All @@ -70,6 +74,7 @@ private DatabricksConnectionContext(
this.customHeaders = parseCustomHeaders(parameters);
this.computeResource = null;
this.connectionUuid = UUID.randomUUID().toString();
initializeTraceContext();
}

/**
Expand Down Expand Up @@ -818,6 +823,26 @@ public boolean isRequestTracingEnabled() {
return getParameter(DatabricksJdbcUrlParams.ENABLE_REQUEST_TRACING).equals("1");
}

@Override
public String getTraceParent() {
return getParameter(DatabricksJdbcUrlParams.TRACE_PARENT);
}

@Override
public String getTraceState() {
return getParameter(DatabricksJdbcUrlParams.TRACE_STATE);
}

@Override
public String getTraceId() {
return traceId;
}

@Override
public String getTraceFlags() {
return traceFlags;
}

@Override
public int getHttpConnectionPoolSize() {
return Integer.parseInt(getParameter(DatabricksJdbcUrlParams.HTTP_CONNECTION_POOL_SIZE));
Expand Down Expand Up @@ -942,4 +967,28 @@ private Map<String, String> parseCustomHeaders(ImmutableMap<String, String> para
Collectors.toMap(
entry -> entry.getKey().substring(filterPrefix.length()), Map.Entry::getValue));
}

private void initializeTraceContext() {
if (isRequestTracingEnabled()) {
// Extract or generate trace context
String traceParent = getTraceParent();
if (!isNullOrEmpty(traceParent) && TracingUtil.isValidTraceparent(traceParent)) {
// Use provided trace parent
traceId = TracingUtil.extractTraceId(traceParent);
traceFlags = TracingUtil.extractTraceFlags(traceParent);
LOGGER.debug("Initialized trace context with provided traceparent: {}", traceParent);
} else {
// Generate new trace context
traceId = TracingUtil.generateTraceId();
traceFlags = "01"; // sampled by default
if (!isNullOrEmpty(traceParent)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is nullOrEmpty else-case, why are we checking again?

LOGGER.warn(
"Invalid traceparent header provided: {}. Generated new trace ID.", traceParent);
}
}
} else {
traceId = null;
traceFlags = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,18 @@ public interface IDatabricksConnectionContext {
/** Returns true if request tracing should be enabled. */
boolean isRequestTracingEnabled();

/** Returns the W3C Trace Context traceparent header if provided. */
String getTraceParent();

/** Returns the W3C Trace Context tracestate header if provided. */
String getTraceState();

/** Returns the trace ID for this connection (extracted or generated). */
String getTraceId();

/** Returns the trace flags for this connection. */
String getTraceFlags();

/** Returns maximum number of characters that can be contained in STRING columns. */
int getDefaultStringColumnLength();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public enum DatabricksJdbcUrlParams {
TOKEN_CACHE_PASS_PHRASE("TokenCachePassPhrase", "Pass phrase to use for OAuth U2M Token Cache"),
ENABLE_TOKEN_CACHE("EnableTokenCache", "Enable caching OAuth tokens", "1"),
APPLICATION_NAME("ApplicationName", "Name of application using the driver", ""),
TRACE_PARENT("TraceParent", "W3C Trace Context traceparent header to propagate", ""),
TRACE_STATE("TraceState", "W3C Trace Context tracestate header to propagate", ""),
;

private final String paramName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,146 @@
package com.databricks.jdbc.dbclient.impl.common;

/** Utility class to support request tracing */
import java.security.SecureRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Utility class to support W3C Trace Context request tracing */
public final class TracingUtil {

public static final String TRACE_HEADER = "traceparent";
public static final String TRACE_STATE_HEADER = "tracestate";

private static final String SEED_CHARACTERS = "0123456789abcdef";
private static final int SEED_CHARACTERS_LENGTH = SEED_CHARACTERS.length();
private static final SecureRandom SECURE_RANDOM = new SecureRandom();

// W3C Trace Context format: version-trace-id-parent-id-trace-flags
private static final Pattern TRACEPARENT_PATTERN =
Pattern.compile("^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$");

private static final String VERSION = "00";
private static final String DEFAULT_FLAGS = "01"; // sampled

private TracingUtil() {
// Utility class
}

/**
* Validates a W3C traceparent header format.
*
* @param traceparent The traceparent header to validate
* @return true if valid, false otherwise
*/
public static boolean isValidTraceparent(String traceparent) {
if (traceparent == null || traceparent.isEmpty()) {
return false;
}

Matcher matcher = TRACEPARENT_PATTERN.matcher(traceparent.toLowerCase());
if (!matcher.matches()) {
return false;
}

// Only support version 00 for now
String version = matcher.group(1);
return VERSION.equals(version);
}

/**
* Extracts the trace ID from a valid traceparent header.
*
* @param traceparent The traceparent header
* @return The trace ID or null if invalid
*/
public static String extractTraceId(String traceparent) {
if (!isValidTraceparent(traceparent)) {
return null;
}

public static String getTraceHeader() {
// Construct the string with the specified format
return String.format("00-%s-%s-01", randomSegment(32), randomSegment(16));
Matcher matcher = TRACEPARENT_PATTERN.matcher(traceparent.toLowerCase());
if (matcher.matches()) {
return matcher.group(2);
}
return null;
}

/**
* Extracts the trace flags from a valid traceparent header.
*
* @param traceparent The traceparent header
* @return The trace flags or null if invalid
*/
public static String extractTraceFlags(String traceparent) {
if (!isValidTraceparent(traceparent)) {
return null;
}

Matcher matcher = TRACEPARENT_PATTERN.matcher(traceparent.toLowerCase());
if (matcher.matches()) {
return matcher.group(4);
}
return null;
}

/**
* Generates a new trace ID.
*
* @return A 32-character hex trace ID
*/
public static String generateTraceId() {
return randomSegment(32);
}

/**
* Generates a new span ID.
*
* @return A 16-character hex span ID
*/
public static String generateSpanId() {
return randomSegment(16);
}

/**
* Builds a W3C compliant traceparent header.
*
* @param traceId The trace ID (32 hex characters)
* @param spanId The span ID (16 hex characters)
* @param traceFlags The trace flags (2 hex characters)
* @return The formatted traceparent header
*/
public static String buildTraceparent(String traceId, String spanId, String traceFlags) {
return String.format("%s-%s-%s-%s", VERSION, traceId, spanId, traceFlags);
}

/**
* Generates a complete traceparent header with new IDs.
*
* @return A new traceparent header
*/
public static String generateTraceparent() {
return buildTraceparent(generateTraceId(), generateSpanId(), DEFAULT_FLAGS);
}

/**
* Generates a traceparent header with the given trace ID and flags, and a new span ID.
*
* @param traceId The trace ID to use
* @param traceFlags The trace flags to use
* @return A traceparent header with new span ID
*/
public static String generateTraceparentWithTraceId(String traceId, String traceFlags) {
return buildTraceparent(traceId, generateSpanId(), traceFlags);
}

private static String randomSegment(int length) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
result.append(
SEED_CHARACTERS.charAt((int) Math.floor(Math.random() * SEED_CHARACTERS_LENGTH)));
StringBuilder result = new StringBuilder(length);
byte[] bytes = new byte[length / 2];
SECURE_RANDOM.nextBytes(bytes);

for (byte b : bytes) {
result.append(SEED_CHARACTERS.charAt((b >> 4) & 0xF));
result.append(SEED_CHARACTERS.charAt(b & 0xF));
}

return result.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.databricks.jdbc.common.util.UserAgentManager;
import com.databricks.jdbc.dbclient.IDatabricksHttpClient;
import com.databricks.jdbc.dbclient.impl.common.ConfiguratorUtils;
import com.databricks.jdbc.dbclient.impl.common.TracingUtil;
import com.databricks.jdbc.exception.DatabricksDriverException;
import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.exception.DatabricksRetryHandlerException;
Expand Down Expand Up @@ -51,8 +52,10 @@ public class DatabricksHttpClient implements IDatabricksHttpClient, Closeable {
private final CloseableHttpClient httpClient;
private IdleConnectionEvictor idleConnectionEvictor;
private CloseableHttpAsyncClient asyncClient;
private final IDatabricksConnectionContext connectionContext;

DatabricksHttpClient(IDatabricksConnectionContext connectionContext, HttpClientType type) {
this.connectionContext = connectionContext;
connectionManager = initializeConnectionManager(connectionContext);
httpClient = makeClosableHttpClient(connectionContext, type);
idleConnectionEvictor =
Expand All @@ -65,7 +68,9 @@ public class DatabricksHttpClient implements IDatabricksHttpClient, Closeable {
@VisibleForTesting
DatabricksHttpClient(
CloseableHttpClient testCloseableHttpClient,
PoolingHttpClientConnectionManager testConnectionManager) {
PoolingHttpClientConnectionManager testConnectionManager,
IDatabricksConnectionContext connectionContext) {
this.connectionContext = connectionContext;
httpClient = testCloseableHttpClient;
connectionManager = testConnectionManager;
}
Expand All @@ -88,6 +93,22 @@ public CloseableHttpResponse execute(HttpUriRequest request, boolean supportGzip
if (!isNullOrEmpty(userAgentString) && !request.containsHeader("User-Agent")) {
request.setHeader("User-Agent", userAgentString);
}

// Add W3C Trace Context headers if request tracing is enabled
if (connectionContext.isRequestTracingEnabled() && connectionContext.getTraceId() != null) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit:empty check

String traceHeader =
TracingUtil.generateTraceparentWithTraceId(
connectionContext.getTraceId(), connectionContext.getTraceFlags());
request.setHeader(TracingUtil.TRACE_HEADER, traceHeader);
LOGGER.debug("Added trace header: {}", traceHeader);

// Add tracestate if present
String traceState = connectionContext.getTraceState();
if (traceState != null && !traceState.isEmpty()) {
request.setHeader(TracingUtil.TRACE_STATE_HEADER, traceState);
}
}

return httpClient.execute(request);
} catch (IOException e) {
throwHttpException(e, request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,18 @@ private boolean useCloudFetchForResult(StatementType statementType) {

private Map<String, String> getHeaders(String method) {
Map<String, String> headers = new HashMap<>(JSON_HTTP_HEADERS);
if (connectionContext.isRequestTracingEnabled()) {
String traceHeader = TracingUtil.getTraceHeader();
if (connectionContext.isRequestTracingEnabled() && connectionContext.getTraceId() != null) {
String traceHeader =
TracingUtil.generateTraceparentWithTraceId(
connectionContext.getTraceId(), connectionContext.getTraceFlags());
LOGGER.debug("Tracing header for method {}: [{}]", method, traceHeader);
headers.put(TracingUtil.TRACE_HEADER, traceHeader);

// Add tracestate if present
String traceState = connectionContext.getTraceState();
if (traceState != null && !traceState.isEmpty()) {
Copy link
Copy Markdown
Collaborator

@gopalldb gopalldb Jul 11, 2025

Choose a reason for hiding this comment

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

can use !Strings.isNullOrEmpty

headers.put(TracingUtil.TRACE_STATE_HEADER, traceState);
}
}

// Overriding with URL defined headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,18 @@ public void flush() throws TTransportException {
// Overriding with URL defined headers
this.connectionContext.getCustomHeaders().forEach(request::setHeader);

if (connectionContext.isRequestTracingEnabled()) {
String traceHeader = TracingUtil.getTraceHeader();
if (connectionContext.isRequestTracingEnabled() && connectionContext.getTraceId() != null) {
String traceHeader =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

shall we check for empty also?

TracingUtil.generateTraceparentWithTraceId(
connectionContext.getTraceId(), connectionContext.getTraceFlags());
LOGGER.debug("Thrift tracing header: " + traceHeader);

request.addHeader(TracingUtil.TRACE_HEADER, traceHeader);

// Add tracestate if present
String traceState = connectionContext.getTraceState();
if (traceState != null && !traceState.isEmpty()) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: can use Strings.isNullOrEmpty

request.addHeader(TracingUtil.TRACE_STATE_HEADER, traceState);
}
}

// Set the request entity
Expand Down
Loading
Loading