Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7082247
feat: introduce minimal implementation of OTel tracing
diegomarquezp Feb 4, 2026
bfe6a6d
chore: avoid new public methods
diegomarquezp Feb 5, 2026
ea6cb18
feat: add api tracer context
diegomarquezp Feb 5, 2026
97bec08
Merge remote-tracking branch 'origin/main' into observability/initial…
diegomarquezp Feb 5, 2026
097f701
chore: concise comment
diegomarquezp Feb 5, 2026
f547e5a
fix: use internal span kind for operations
diegomarquezp Feb 5, 2026
b7b9e31
chore: remove unnecessary inScope implementation
diegomarquezp Feb 5, 2026
556c84c
Revert "chore: remove unnecessary inScope implementation"
diegomarquezp Feb 5, 2026
0359b7d
test: add test for inScope()
diegomarquezp Feb 5, 2026
402cb89
Merge remote-tracking branch 'origin/main' into observability/initial…
diegomarquezp Feb 5, 2026
420278b
chore: use suggested server address resolution impl
diegomarquezp Feb 5, 2026
6de9fb6
fix: use concurrent hash map
diegomarquezp Feb 5, 2026
a61dacf
chore: remove default impl for startSpan with parent
diegomarquezp Feb 5, 2026
b000d52
chore: add opentelemery-context to tests
diegomarquezp Feb 5, 2026
9ce0c34
test: increase coverage for TracingTracerTest
diegomarquezp Feb 5, 2026
9c6c737
deps: include opentelemetry context in gax
diegomarquezp Feb 5, 2026
e77de58
chore: simplify and remove error handling
diegomarquezp Feb 9, 2026
8f07f61
chore: review refactor
diegomarquezp Feb 10, 2026
2cfcd68
chore: make TracingTracerFactory(recorder, opAtts, atAtts) package pr…
diegomarquezp Feb 10, 2026
2415c6b
chore: remove unnecessary inScope
diegomarquezp Feb 10, 2026
0f5e9b5
chore: rename startSpan to createSpan
diegomarquezp Feb 10, 2026
0c3bd22
chore: use server address instead of endpoint context
diegomarquezp Feb 10, 2026
bdeb291
chore: rename classes
diegomarquezp Feb 10, 2026
5c07a3c
chore: add javadoc for tracer
diegomarquezp Feb 10, 2026
ff1d45d
chore: rename to TraceSpan, improve javadocs
diegomarquezp Feb 10, 2026
043ce62
chore: format
diegomarquezp Feb 10, 2026
dbc5f41
Update gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentr…
diegomarquezp Feb 10, 2026
b4bce0d
chore: handle ipv6 in endpoint context
diegomarquezp Feb 10, 2026
6e95cf4
Merge branch 'observability/initial-tracing-impl' of https://github.c…
diegomarquezp Feb 10, 2026
e873904
chore: add language tests
diegomarquezp Feb 10, 2026
68943b0
chore: AppCentricTracer to implement interface
diegomarquezp Feb 11, 2026
ba3c080
feat(obs): generate gapic.properties file with repo property
diegomarquezp Feb 12, 2026
caead0d
feat: implement repo handling in gax
diegomarquezp Feb 12, 2026
58f72d2
test: add tests for repo property
diegomarquezp Feb 12, 2026
5a5d9d6
chore: speed up image building
diegomarquezp Feb 12, 2026
015aa88
build: introduce repo property to generation config
diegomarquezp Feb 12, 2026
0907e93
chore: update showcase module
diegomarquezp Feb 12, 2026
cd4d4d0
chore: revert operation implementation
diegomarquezp Feb 12, 2026
5b76e40
chore: extract common span attributes to separate class
diegomarquezp Feb 12, 2026
c783d68
chore: remove unused var
diegomarquezp Feb 12, 2026
1eeb197
Merge branch 'observability/initial-tracing-impl' into observability/…
diegomarquezp Feb 12, 2026
d2bd03c
chore(obs): use `Span` instead of `AppCentric` prefix for tracing
diegomarquezp Feb 19, 2026
13f9bb3
Merge branch 'observability/rename-to-span' into observability/tracin…
diegomarquezp Feb 19, 2026
beee741
chore: use ApiTracerContext for this
diegomarquezp Feb 19, 2026
c2acaaf
feat: add ApiTracerContext client specific subclass generation
diegomarquezp Feb 19, 2026
c60da4c
chore: update goldens
diegomarquezp Feb 19, 2026
e3be9f4
chore: fix ApiTraceContext subclass generation
diegomarquezp Feb 20, 2026
da35f97
chore: regenerate showcase
diegomarquezp Feb 20, 2026
2983e30
chore: fix default var in generate_library.sh
diegomarquezp Feb 20, 2026
f0e17ac
chore: infer server address directly from StubSettings
diegomarquezp Feb 20, 2026
55e4be1
chore: adapt to stubsettings inferring server address
diegomarquezp Feb 20, 2026
4aa17c4
chore: revert changes and pass server address from ClientContext
diegomarquezp Feb 20, 2026
079d1f5
chore: update goldens
diegomarquezp Feb 20, 2026
8601b76
chore: generate libraries at Fri Feb 20 18:31:23 UTC 2026
cloud-java-bot Feb 20, 2026
a0fcd28
chore: regenerate showcase
diegomarquezp Feb 20, 2026
4b6e9ae
Merge branch 'observability/tracing-attr/repo' of https://github.com/…
diegomarquezp Feb 20, 2026
c413fbf
chore: fix type hint
diegomarquezp Feb 20, 2026
63e6ee6
chore: optimize for reliability scanner
diegomarquezp Feb 20, 2026
bac45b3
chore: delete old class
diegomarquezp Feb 20, 2026
f3f14c9
chore: simplify apitracercontext
diegomarquezp Feb 20, 2026
2357560
chore: simplify apitracercontext
diegomarquezp Feb 20, 2026
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 @@ -30,7 +30,7 @@ RUN cat /java-formatter-version
RUN V=$(cat /java-formatter-version) && curl -o "/google-java-format.jar" "https://maven-central.storage-download.googleapis.com/maven2/com/google/googlejavaformat/google-java-format/${V}/google-java-format-${V}-all-deps.jar"

# Compile and install packages
RUN mvn install -B -ntp -DskipTests -Dclirr.skip -Dcheckstyle.skip
RUN mvn install -B -ntp -T 1.5C -DskipTests -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip
RUN cp "/root/.m2/repository/com/google/api/gapic-generator-java/${DOCKER_GAPIC_GENERATOR_VERSION}/gapic-generator-java-${DOCKER_GAPIC_GENERATOR_VERSION}.jar" \
"./gapic-generator-java.jar"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.api.generator.engine.ast.ClassDefinition;
import com.google.api.generator.engine.ast.CommentStatement;
import com.google.api.generator.gapic.composer.comment.CommentComposer;
import com.google.api.generator.gapic.composer.common.ServiceApiTracerContextClassComposer;
import com.google.api.generator.gapic.composer.grpc.GrpcServiceCallableFactoryClassComposer;
import com.google.api.generator.gapic.composer.grpc.GrpcServiceStubClassComposer;
import com.google.api.generator.gapic.composer.grpc.MockServiceClassComposer;
Expand Down Expand Up @@ -86,6 +87,7 @@ public static List<GapicClass> generateStubClasses(GapicContext context) {
.services()
.forEach(
s -> {
clazzes.add(ServiceApiTracerContextClassComposer.instance().generate(context, s));
if (context.transport() == Transport.REST) {
clazzes.add(
com.google.api.generator.gapic.composer.rest.ServiceStubClassComposer.instance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.api.gax.rpc.UnaryCallSettings;
import com.google.api.gax.rpc.UnaryCallable;
import com.google.api.gax.tracing.ApiTracerContext;
import com.google.api.generator.engine.ast.AnnotationNode;
import com.google.api.generator.engine.ast.AnonymousClassExpr;
import com.google.api.generator.engine.ast.AssignmentExpr;
Expand Down Expand Up @@ -1053,6 +1054,7 @@ private List<MethodDefinition> createClassMethods(
SettingsCommentComposer.NEW_BUILDER_METHOD_COMMENT));
javaMethods.addAll(createBuilderHelperMethods(service, typeStore));
javaMethods.add(createClassConstructor(service, methodSettingsMemberVarExprs, typeStore));
javaMethods.add(createGetApiTracerContextMethod(service, typeStore));
return javaMethods;
}

Expand Down Expand Up @@ -2096,6 +2098,31 @@ private static MethodDefinition createNestedClassBuildMethod(
.build();
}

private MethodDefinition createGetApiTracerContextMethod(Service service, TypeStore typeStore) {
TypeNode returnType = FIXED_TYPESTORE.get("ApiTracerContext");
VariableExpr serverAddressVarExpr =
VariableExpr.withVariable(
Variable.builder().setType(TypeNode.STRING).setName("serverAddress").build());

TypeNode serviceApiTracerContextType =
typeStore.get(ClassNames.getServiceApiTracerContextClassName(service));

return MethodDefinition.builder()
.setIsOverride(true)
.setScope(ScopeNode.PROTECTED)
.setReturnType(returnType)
.setName("getApiTracerContext")
.setArguments(serverAddressVarExpr.toBuilder().setIsDecl(true).build())
.setReturnExpr(
MethodInvocationExpr.builder()
.setStaticReferenceType(serviceApiTracerContextType)
.setMethodName("create")
.setArguments(serverAddressVarExpr)
.setReturnType(returnType)
.build())
.build();
}

private static TypeStore createStaticTypes() {
List<Class<?>> concreteClazzes =
Arrays.asList(
Expand Down Expand Up @@ -2143,7 +2170,8 @@ private static TypeStore createStaticTypes() {
StubSettings.class,
TransportChannelProvider.class,
UnaryCallSettings.class,
UnaryCallable.class);
UnaryCallable.class,
ApiTracerContext.class);
return new TypeStore(concreteClazzes);
}

Expand Down Expand Up @@ -2175,6 +2203,8 @@ private TypeStore createDynamicTypes(Service service, String pakkage) {
true,
ClassNames.getServiceClientClassName(service));

typeStore.put(pakkage, ClassNames.getServiceApiTracerContextClassName(service));

return typeStore;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* 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.api.generator.gapic.composer.common;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.api.gax.tracing.ApiTracerContext;
import com.google.api.generator.engine.ast.AnnotationNode;
import com.google.api.generator.engine.ast.AssignmentExpr;
import com.google.api.generator.engine.ast.ClassDefinition;
import com.google.api.generator.engine.ast.Expr;
import com.google.api.generator.engine.ast.ExprStatement;
import com.google.api.generator.engine.ast.MethodDefinition;
import com.google.api.generator.engine.ast.ScopeNode;
import com.google.api.generator.engine.ast.Statement;
import com.google.api.generator.engine.ast.StringObjectValue;
import com.google.api.generator.engine.ast.ThisObjectValue;
import com.google.api.generator.engine.ast.TypeNode;
import com.google.api.generator.engine.ast.ValueExpr;
import com.google.api.generator.engine.ast.Variable;
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.gapic.composer.store.TypeStore;
import com.google.api.generator.gapic.composer.utils.ClassNames;
import com.google.api.generator.gapic.model.GapicClass;
import com.google.api.generator.gapic.model.GapicContext;
import com.google.api.generator.gapic.model.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Generated;
import javax.annotation.Nullable;

/**
* Composes a service-specific implementation of {@link ApiTracerContext}.
*
* <p>The generated class is used to provide client-library-specific attributes (like the repository
* URL) to {@link com.google.api.gax.tracing.ApiTracer}s.
*/
public class ServiceApiTracerContextClassComposer implements ClassComposer {
private static final ServiceApiTracerContextClassComposer INSTANCE =
new ServiceApiTracerContextClassComposer();

private static final TypeStore FIXED_TYPESTORE = createStaticTypes();

private ServiceApiTracerContextClassComposer() {}

public static ServiceApiTracerContextClassComposer instance() {
return INSTANCE;
}

@Override
public GapicClass generate(GapicContext context, Service service) {
String pakkage = String.format("%s.stub", service.pakkage());
String className = ClassNames.getServiceApiTracerContextClassName(service);
TypeStore typeStore = createDynamicTypes(service, pakkage);

ClassDefinition classDef =
ClassDefinition.builder()
.setPackageString(pakkage)
.setAnnotations(createClassAnnotations())
.setScope(ScopeNode.PUBLIC)
.setName(className)
.setExtendsType(FIXED_TYPESTORE.get("ApiTracerContext"))
.setStatements(createClassStatements())
.setMethods(createClassMethods(service, context, typeStore))
.build();

return GapicClass.create(GapicClass.Kind.STUB, classDef);
}

private static List<AnnotationNode> createClassAnnotations() {
return Arrays.asList(
AnnotationNode.withType(FIXED_TYPESTORE.get("InternalApi")),
AnnotationNode.withType(FIXED_TYPESTORE.get("BetaApi")),
AnnotationNode.builder()
.setType(FIXED_TYPESTORE.get("Generated"))
.setDescription("by gapic-generator-java")
.build());
}

private static List<Statement> createClassStatements() {
VariableExpr serverAddressVarExpr =
VariableExpr.withVariable(
Variable.builder().setType(TypeNode.STRING).setName("serverAddress").build());
return Arrays.asList(
ExprStatement.withExpr(
serverAddressVarExpr.toBuilder()
.setIsDecl(true)
.setScope(ScopeNode.PRIVATE)
.setIsFinal(true)
.build()));
}

private static List<MethodDefinition> createClassMethods(
Service service, GapicContext context, TypeStore typeStore) {
List<MethodDefinition> methods = new ArrayList<>();
methods.add(createConstructor(service, typeStore));
methods.add(createCreateMethod(service, typeStore));
methods.add(createGetServerAddressMethod());
methods.add(createGetRepoMethod(context));
return methods;
}

private static MethodDefinition createConstructor(Service service, TypeStore typeStore) {
TypeNode thisType = typeStore.get(ClassNames.getServiceApiTracerContextClassName(service));
VariableExpr serverAddressVarExpr =
VariableExpr.withVariable(
Variable.builder().setType(TypeNode.STRING).setName("serverAddress").build());

VariableExpr thisServerAddressVarExpr =
VariableExpr.builder()
.setVariable(serverAddressVarExpr.variable())
.setExprReferenceExpr(ValueExpr.withValue(ThisObjectValue.withType(thisType)))
.build();

return MethodDefinition.constructorBuilder()
.setScope(ScopeNode.PRIVATE)
.setReturnType(thisType)
.setArguments(serverAddressVarExpr.toBuilder().setIsDecl(true).build())
.setBody(
Arrays.asList(
ExprStatement.withExpr(
AssignmentExpr.builder()
.setVariableExpr(thisServerAddressVarExpr)
.setValueExpr(serverAddressVarExpr)
.build())))
.build();
}

private static MethodDefinition createCreateMethod(Service service, TypeStore typeStore) {
TypeNode thisType = typeStore.get(ClassNames.getServiceApiTracerContextClassName(service));
VariableExpr serverAddressVarExpr =
VariableExpr.withVariable(
Variable.builder().setType(TypeNode.STRING).setName("serverAddress").build());

return MethodDefinition.builder()
.setScope(ScopeNode.PUBLIC)
.setIsStatic(true)
.setReturnType(thisType)
.setName("create")
.setArguments(serverAddressVarExpr.toBuilder().setIsDecl(true).build())
.setReturnExpr(
com.google.api.generator.engine.ast.NewObjectExpr.builder()
.setType(thisType)
.setArguments(serverAddressVarExpr)
.build())
.build();
}

private static MethodDefinition createGetServerAddressMethod() {
return MethodDefinition.builder()
.setIsOverride(true)
.setScope(ScopeNode.PUBLIC)
.setReturnType(TypeNode.STRING)
.setName("getServerAddress")
.setReturnExpr(
VariableExpr.withVariable(
Variable.builder().setType(TypeNode.STRING).setName("serverAddress").build()))
.build();
}

private static MethodDefinition createGetRepoMethod(GapicContext context) {
Expr returnExpr = ValueExpr.createNullExpr();
if (context.repo().isPresent()) {
returnExpr = ValueExpr.withValue(StringObjectValue.withValue(context.repo().get()));
}
return MethodDefinition.builder()
.setIsOverride(true)
.setScope(ScopeNode.PUBLIC)
.setReturnType(TypeNode.STRING)
.setName("getRepo")
.setReturnExpr(returnExpr)
.build();
}

private static TypeStore createStaticTypes() {
return new TypeStore(
Arrays.asList(
BetaApi.class,
InternalApi.class,
Generated.class,
ApiTracerContext.class,
Nullable.class));
}

private TypeStore createDynamicTypes(Service service, String pakkage) {
TypeStore typeStore = new TypeStore();
typeStore.put(pakkage, ClassNames.getServiceApiTracerContextClassName(service));
return typeStore;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class ClassNames {
private static final String TRANSPORT_SERVICE_STUB_CLASS_NAME_PATTERN = "%s%sStub";
private static final String TRANSPORT_SERVICE_CALLABLE_FACTORY_CLASS_NAME_PATTERN =
"%s%sCallableFactory";
private static final String SERVICE_API_TRACER_CONTEXT_CLASS_NAME_PATTERN = "%sApiTracerContext";

private final List<String> transportPrefixes;

Expand Down Expand Up @@ -116,6 +117,12 @@ public static String getMockServiceImplClassName(Service service) {
return String.format(MOCK_SERVICE_IMPL_CLASS_NAME_PATTERN, service.name());
}

public static String getServiceApiTracerContextClassName(Service service) {
return String.format(
SERVICE_API_TRACER_CONTEXT_CLASS_NAME_PATTERN,
monolithBackwardsCompatibleName(service.name()));
}

private static String monolithBackwardsCompatibleName(String rawServiceName) {
return rawServiceName.startsWith("IAMCredentials")
? rawServiceName.replace("IAM", "Iam")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,14 @@ static GapicMetadata defaultGapicMetadata() {

public abstract Transport transport();

public abstract Optional<String> repo();

public static Builder builder() {
return new AutoValue_GapicContext.Builder()
.setMixinServices(Collections.emptyList())
.setGapicMetadataEnabled(false)
.setRestNumericEnumsEnabled(false);
.setRestNumericEnumsEnabled(false)
.setRepo(Optional.empty());
}

@AutoValue.Builder
Expand Down Expand Up @@ -130,6 +133,8 @@ public Builder setHelperResourceNames(Set<ResourceName> helperResourceNames) {

public abstract Builder setTransport(Transport transport);

public abstract Builder setRepo(Optional<String> repo);

abstract ImmutableMap<String, ResourceName> resourceNames();

abstract ImmutableMap<String, ResourceName> helperResourceNames();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public static GapicContext parse(CodeGeneratorRequest request) {
Optional<GapicLanguageSettings> languageSettingsOpt =
GapicLanguageSettingsParser.parse(gapicYamlConfigPathOpt);
Optional<String> transportOpt = PluginArgumentParser.parseTransport(request);
Optional<String> repoOpt = PluginArgumentParser.parseRepo(request);

boolean willGenerateMetadata = PluginArgumentParser.hasMetadataFlag(request);
boolean willGenerateNumericEnum = PluginArgumentParser.hasNumericEnumFlag(request);
Expand Down Expand Up @@ -253,6 +254,7 @@ public static GapicContext parse(CodeGeneratorRequest request) {
.setServiceYamlProto(serviceYamlProtoOpt.orElse(null))
.setTransport(transport)
.setRestNumericEnumsEnabled(willGenerateNumericEnum)
.setRepo(repoOpt)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class PluginArgumentParser {
@VisibleForTesting static final String KEY_NUMERIC_ENUM = "rest-numeric-enums";
@VisibleForTesting static final String KEY_SERVICE_YAML_CONFIG = "api-service-config";
@VisibleForTesting static final String KEY_TRANSPORT = "transport";
@VisibleForTesting static final String KEY_REPO = "repo";

private static final String JSON_FILE_ENDING = "grpc_service_config.json";
private static final String GAPIC_YAML_FILE_ENDING = "gapic.yaml";
Expand All @@ -53,6 +54,10 @@ static Optional<String> parseTransport(CodeGeneratorRequest request) {
return parseConfigArgument(request.getParameter(), KEY_TRANSPORT);
}

static Optional<String> parseRepo(CodeGeneratorRequest request) {
return parseConfigArgument(request.getParameter(), KEY_REPO);
}
Comment on lines +57 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The parseRepo method uses parseConfigArgument, which parses plugin arguments by splitting the input string by commas. If the repo value contains a comma, it will be split, potentially allowing an attacker to inject additional plugin flags or options (e.g., injecting ,metadata to enable metadata generation).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that's how the argument parser will work. If repo contains a comma then what's after the comma will be ignored and treated as a sperate flag not handled by the generator.


static boolean hasMetadataFlag(CodeGeneratorRequest request) {
return hasFlag(request.getParameter(), KEY_METADATA);
}
Expand Down
Loading
Loading