Skip to content
Open
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 @@ -644,6 +644,7 @@ private void initTransactionInternal(BeginTransactionRequest request) {
private final DirectedReadOptions defaultDirectedReadOptions;
private final DecodeMode defaultDecodeMode;
private final Clock clock;
private volatile String cachedRequestTag;

@GuardedBy("lock")
private boolean isValid = true;
Expand Down Expand Up @@ -841,7 +842,22 @@ RequestOptions buildRequestOptions(Options options) {
builder.setClientContext(clientContextBuilder.build());
}
if (getTransactionTag() != null) {
builder.setTransactionTag(getTransactionTag());
// Read-write transactions support transaction-level tags only. We populate the
// transaction tag on the builder if it is non-empty.
if (!getTransactionTag().isEmpty()) {
builder.setTransactionTag(getTransactionTag());
}
} else if (session.getSpanner().getOptions().isAutoTaggingEnabled()
&& builder.getRequestTag().isEmpty()) {
// Read-only contexts (both single-use and multi-use) do not support transaction-level tags.
// We lazily resolve and populate the request tag instead.
if (this.cachedRequestTag == null) {
String autoTag = AutoTagHelper.getAutoTag(session.getSpanner().getOptions());
this.cachedRequestTag = autoTag == null ? "" : autoTag;
}
if (!this.cachedRequestTag.isEmpty()) {
builder.setRequestTag(this.cachedRequestTag);
}
}
return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2026 Google LLC
*
* Licensed 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 com.google.cloud.spanner;

import java.util.List;

/** Helper for Spanner transaction tags. */
final class AutoTagHelper {

/** Maximum allowed character length for resolved tags. */
private static final int MAX_TAG_LENGTH = 50;

/** Ignored packages. */
private static final String[] INTERNAL_PACKAGES;

static {
INTERNAL_PACKAGES =
new String[] {
"java.",
"javax.",
"jdk.",
"sun.",
"io.grpc.",
"com.google.cloud.spanner.",
"com.google.api."
};
}

private AutoTagHelper() {
// prevent instantiation
}

static String getAutoTag(final SpannerOptions options) {
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
Comment thread
sakthivelmanii marked this conversation as resolved.
int tracerLimit = options.getAutoTaggingTracerLimit();
int limit = Math.min(stackTrace.length, tracerLimit);
List<String> targetPackages = options.getAutoTaggingPackages();
boolean hasTarget = targetPackages != null && !targetPackages.isEmpty();

for (int i = 0; i < limit; i++) {
StackTraceElement element = stackTrace[i];
String className = element.getClassName();
if (hasTarget) {
for (String targetPackage : targetPackages) {
if (className.startsWith(targetPackage)) {
return formatTag(className, element.getMethodName());
}
}
} else if (isInternalPackage(className)) {
continue;
} else {
return formatTag(className, element.getMethodName());
}
}
return null;
}

private static boolean isInternalPackage(final String cls) {
for (String internalPackage : INTERNAL_PACKAGES) {
if (cls.startsWith(internalPackage)) {
return true;
}
}
return false;
}

private static String formatTag(final String cls, final String method) {
int lastDot = cls.lastIndexOf('.');
String simpleClassName;
if (lastDot == -1) {
simpleClassName = cls;
} else {
simpleClassName = cls.substring(lastDot + 1);
}
String tag = simpleClassName + "." + method;
if (tag.length() > MAX_TAG_LENGTH) {
tag = tag.substring(tag.length() - MAX_TAG_LENGTH);
}
return tag;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -308,6 +309,9 @@ static GcpChannelPoolOptions mergeWithDefaultChannelPoolOptions(
private final String monitoringHost;
private final TransactionOptions defaultTransactionOptions;
private final RequestOptions.ClientContext clientContext;
private final boolean autoTaggingEnabled;
private final List<String> autoTaggingPackages;
private final int autoTaggingTracerLimit;

enum TracingFramework {
OPEN_CENSUS,
Expand Down Expand Up @@ -993,6 +997,9 @@ protected SpannerOptions(Builder builder) {
monitoringHost = builder.monitoringHost;
defaultTransactionOptions = builder.defaultTransactionOptions;
clientContext = builder.clientContext;
autoTaggingEnabled = builder.autoTaggingEnabled;
autoTaggingPackages = builder.autoTaggingPackages;
autoTaggingTracerLimit = builder.autoTaggingTracerLimit;
}

private String getResolvedUniverseDomain() {
Expand Down Expand Up @@ -1064,6 +1071,14 @@ default boolean isEnableLocationApi() {
return false;
}

default boolean isAutoTaggingDisabled() {
return false;
}

default boolean isAutoTaggingEnabled() {
return false;
}

@Deprecated
@ObsoleteApi(
"This will be removed in an upcoming version without a major version bump. You should use"
Expand Down Expand Up @@ -1168,6 +1183,18 @@ public boolean isEnableLocationApi() {
return Boolean.parseBoolean(System.getenv(EXPERIMENTAL_LOCATION_API_ENV_VAR));
}

@Override
public boolean isAutoTaggingDisabled() {
return Boolean.parseBoolean(System.getenv("SPANNER_DISABLE_AUTO_TAGGING"))
|| Boolean.parseBoolean(System.getProperty("spanner.disable_auto_tagging"));
Comment thread
sakthivelmanii marked this conversation as resolved.
}

@Override
public boolean isAutoTaggingEnabled() {
return Boolean.parseBoolean(System.getenv("SPANNER_ENABLE_AUTO_TAGGING"))
|| Boolean.getBoolean("spanner.enable_auto_tagging");
}

@Override
public String getMonitoringHost() {
return System.getenv(SPANNER_MONITORING_HOST);
Expand Down Expand Up @@ -1256,6 +1283,9 @@ public static class Builder
private boolean usePlainText = false;
private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance();
private RequestOptions.ClientContext clientContext;
private boolean autoTaggingEnabled = false;
private List<String> autoTaggingPackages = Collections.emptyList();
private int autoTaggingTracerLimit = 50;

private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
Expand Down Expand Up @@ -1362,6 +1392,9 @@ protected Builder() {
this.monitoringHost = options.monitoringHost;
this.defaultTransactionOptions = options.defaultTransactionOptions;
this.clientContext = options.clientContext;
this.autoTaggingEnabled = options.autoTaggingEnabled;
this.autoTaggingPackages = options.autoTaggingPackages;
this.autoTaggingTracerLimit = options.autoTaggingTracerLimit;
}

@Override
Expand Down Expand Up @@ -2120,6 +2153,36 @@ public Builder setDefaultClientContext(RequestOptions.ClientContext clientContex
return this;
}

public Builder enableAutoTagging() {
this.autoTaggingEnabled = true;
return this;
}

public Builder disableAutoTagging() {
this.autoTaggingEnabled = false;
return this;
}

public Builder setAutoTaggingPackages(String... autoTaggingPackages) {
this.autoTaggingPackages =
Collections.unmodifiableList(
new ArrayList<>(
java.util.Arrays.asList(Preconditions.checkNotNull(autoTaggingPackages))));
return this;
}

public Builder setAutoTaggingPackages(List<String> autoTaggingPackages) {
this.autoTaggingPackages =
Collections.unmodifiableList(
new ArrayList<>(Preconditions.checkNotNull(autoTaggingPackages)));
return this;
}

public Builder setAutoTaggingTracerLimit(int autoTaggingTracerLimit) {
this.autoTaggingTracerLimit = autoTaggingTracerLimit;
return this;
}

@SuppressWarnings("rawtypes")
@Override
public SpannerOptions build() {
Expand Down Expand Up @@ -2547,6 +2610,25 @@ public TransactionOptions getDefaultTransactionOptions() {
return defaultTransactionOptions;
}

public boolean isAutoTaggingEnabled() {
if (environment.isAutoTaggingDisabled()) {
return false;
}
return autoTaggingEnabled || environment.isAutoTaggingEnabled();
}

public List<String> getAutoTaggingPackages() {
return autoTaggingPackages;
}

public int getAutoTaggingTracerLimit() {
return autoTaggingTracerLimit;
}

public boolean isAutoTaggingDisabled() {
return environment.isAutoTaggingDisabled();
}

@BetaApi
public boolean isUseVirtualThreads() {
return useVirtualThreads;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ public void removeListener(Runnable listener) {
private boolean aborted;

private final Options options;
private volatile String cachedTransactionTag;

/** Default to -1 to indicate not available. */
@GuardedBy("lock")
Expand Down Expand Up @@ -780,6 +781,15 @@ String getTransactionTag() {
if (this.options.hasTag()) {
return this.options.tag();
}
if (session.getSpanner().getOptions().isAutoTaggingEnabled()) {
if (this.cachedTransactionTag == null) {
this.cachedTransactionTag = AutoTagHelper.getAutoTag(session.getSpanner().getOptions());
if (this.cachedTransactionTag == null) {
this.cachedTransactionTag = "";
}
}
return this.cachedTransactionTag;
}
return null;
}

Expand Down
Loading
Loading