Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,31 @@
@InternalApi
public class LoggingUtils {

private static boolean loggingEnabled = isLoggingEnabled();
static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";

private static boolean loggingEnabled = checkLoggingEnabled(GOOGLE_SDK_JAVA_LOGGING);

/**
* Returns whether client-side logging is enabled.
*
* @return true if logging is enabled, false otherwise.
*/
static boolean isLoggingEnabled() {
String enableLogging = System.getenv(GOOGLE_SDK_JAVA_LOGGING);
return loggingEnabled;
}

/**
* Sets whether client-side logging is enabled. Visible for testing.
*
* @param enabled true to enable logging, false to disable.
*/
@com.google.common.annotations.VisibleForTesting
static void setLoggingEnabled(boolean enabled) {
loggingEnabled = enabled;
}

private static boolean checkLoggingEnabled(String envVar) {
String enableLogging = System.getenv(envVar);
return "true".equalsIgnoreCase(enableLogging);
}

Expand Down Expand Up @@ -126,6 +146,22 @@ public static <RespT> void logRequest(
}
}

/**
* Logs an actionable error message with structured context at a specific log level.
*
* @param logContext A map containing the structured logging context (e.g., RPC service, method,
* error details).
* @param loggerProvider The provider used to obtain the logger.
* @param message The human-readable error message.
*/
public static void logActionableError(
Map<String, Object> logContext, LoggerProvider loggerProvider, String message) {
if (loggingEnabled) {
org.slf4j.Logger logger = loggerProvider.getLogger();
Slf4jUtils.log(logger, org.slf4j.event.Level.DEBUG, logContext, message);
}
}

public static void executeWithTryCatch(ThrowingRunnable action) {
try {
action.run();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import com.google.api.gax.tracing.ApiTracerContext;
import com.google.api.gax.tracing.ApiTracerFactory;
import com.google.api.gax.tracing.BaseApiTracerFactory;
import com.google.api.gax.tracing.SpanTracerFactory;
import com.google.auth.ApiKeyCredentials;
import com.google.auth.CredentialTypeForMetrics;
import com.google.auth.Credentials;
Expand Down Expand Up @@ -278,8 +277,13 @@ public static ClientContext create(StubSettings settings) throws IOException {
.setLibraryMetadata(settings.getLibraryMetadata())
.build();
ApiTracerFactory apiTracerFactory = settings.getTracerFactory();
if (apiTracerFactory instanceof SpanTracerFactory) {
apiTracerFactory = apiTracerFactory.withContext(apiTracerContext);
apiTracerFactory = apiTracerFactory.withContext(apiTracerContext);
if (!(apiTracerFactory instanceof com.google.api.gax.tracing.CompositeTracerFactory)) {
com.google.api.gax.tracing.ApiTracerFactory loggingTracerFactory =
new com.google.api.gax.tracing.LoggingTracerFactory().withContext(apiTracerContext);
apiTracerFactory =
new com.google.api.gax.tracing.CompositeTracerFactory(
apiTracerFactory, loggingTracerFactory);
}

return newBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright 2026 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.api.gax.tracing;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import java.util.List;

/** A composite {@link ApiTracer} that delegates to a list of {@link ApiTracer}s. */
@BetaApi
@InternalApi
public class CompositeTracer implements ApiTracer {
private final List<ApiTracer> tracers;

public CompositeTracer(List<ApiTracer> tracers) {
this.tracers = tracers;
}

public List<ApiTracer> getTracers() {
return tracers;
}

@Override
public Scope inScope() {
// Returning a scope that closes all sub-scopes
final Scope[] scopes = new Scope[tracers.size()];
for (int i = 0; i < tracers.size(); i++) {
scopes[i] = tracers.get(i).inScope();
}
return () -> {
for (int i = scopes.length - 1; i >= 0; i--) {
Scope scope = scopes[i];
if (scope != null) {
scope.close();
}
}
};
}

@Override
public void operationSucceeded() {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).operationSucceeded();
}
}

@Override
public void operationCancelled() {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).operationCancelled();
}
}

@Override
public void operationFailed(Throwable error) {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).operationFailed(error);
}
}

@Override
public void connectionSelected(String id) {
for (ApiTracer tracer : tracers) {
tracer.connectionSelected(id);
}
}

@Override
@SuppressWarnings("deprecation")
public void attemptStarted(int attemptNumber) {
for (ApiTracer tracer : tracers) {
tracer.attemptStarted(attemptNumber);
}
}

@Override
public void attemptStarted(Object request, int attemptNumber) {
for (ApiTracer tracer : tracers) {
tracer.attemptStarted(request, attemptNumber);
}
}

@Override
public void attemptSucceeded() {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).attemptSucceeded();
}
}

@Override
public void attemptCancelled() {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).attemptCancelled();
}
}

@Override
@SuppressWarnings("deprecation")
public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).attemptFailed(error, delay);
}
}

@Override
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).attemptFailedDuration(error, delay);
}
}

@Override
public void attemptFailedRetriesExhausted(Throwable error) {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).attemptFailedRetriesExhausted(error);
}
}

@Override
public void attemptPermanentFailure(Throwable error) {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).attemptPermanentFailure(error);
}
}

@Override
public void lroStartFailed(Throwable error) {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).lroStartFailed(error);
}
}

@Override
public void lroStartSucceeded() {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).lroStartSucceeded();
}
}

@Override
public void responseReceived() {
for (int i = tracers.size() - 1; i >= 0; i--) {
tracers.get(i).responseReceived();
}
}

@Override
public void requestSent() {
for (ApiTracer tracer : tracers) {
tracer.requestSent();
}
}

@Override
public void batchRequestSent(long elementCount, long requestSize) {
for (ApiTracer tracer : tracers) {
tracer.batchRequestSent(elementCount, requestSize);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2026 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.api.gax.tracing;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* A composite {@link ApiTracerFactory} that creates a {@link CompositeTracer} containing multiple
* {@link ApiTracer} instances.
*/
@BetaApi
@InternalApi
public class CompositeTracerFactory implements ApiTracerFactory {
private final List<ApiTracerFactory> factories;
private final ApiTracerContext apiTracerContext;

public CompositeTracerFactory(ApiTracerFactory... factories) {
this(Arrays.asList(factories), ApiTracerContext.empty());
}

public CompositeTracerFactory(List<ApiTracerFactory> factories) {
this(factories, ApiTracerContext.empty());
}

private CompositeTracerFactory(
List<ApiTracerFactory> factories, ApiTracerContext apiTracerContext) {
this.factories = factories;
this.apiTracerContext = apiTracerContext;
}

@Override
public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) {
List<ApiTracer> tracers = new ArrayList<>(factories.size());
for (int i = 0; i < factories.size(); i++) {
ApiTracer subParent = getSubParent(parent, i);
tracers.add(factories.get(i).newTracer(subParent, spanName, operationType));
}
return new CompositeTracer(tracers);
}

@Override
public ApiTracer newTracer(ApiTracer parent, ApiTracerContext context) {
List<ApiTracer> tracers = new ArrayList<>(factories.size());
for (int i = 0; i < factories.size(); i++) {
ApiTracer subParent = getSubParent(parent, i);
tracers.add(factories.get(i).newTracer(subParent, context));
}
return new CompositeTracer(tracers);
}

private ApiTracer getSubParent(ApiTracer parent, int index) {
if (parent instanceof CompositeTracer) {
CompositeTracer compositeParent = (CompositeTracer) parent;
if (index < compositeParent.getTracers().size()) {
return compositeParent.getTracers().get(index);
}
}
return parent;
}

@Override
public ApiTracerContext getApiTracerContext() {
return apiTracerContext;
}

@Override
public ApiTracerFactory withContext(ApiTracerContext context) {
List<ApiTracerFactory> updatedFactories = new ArrayList<>(factories.size());
for (ApiTracerFactory factory : factories) {
updatedFactories.add(factory.withContext(context));
}
return new CompositeTracerFactory(updatedFactories, apiTracerContext.merge(context));
}
}
Loading
Loading