diff --git a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java index c201924dfbe7..868652fdba4b 100644 --- a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java +++ b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java @@ -306,6 +306,17 @@ public DatabaseClient getDatabaseClient(DatabaseId db) { if (dbClients.containsKey(db)) { return dbClients.get(db); } else { + SpannerOptions opts = getOptions(); + String metricsProject = opts.resolveMetricsProjectId(); + if (opts.isEnableBuiltInMetrics() + && metricsProject != null + && !metricsProject.equals(db.getInstanceId().getProject())) { + logger.log( + Level.INFO, + "Built-in metrics project ''{0}'' differs from DatabaseId project ''{1}''." + + " Use setMetricsProjectId() to override if needed.", + new Object[] {metricsProject, db.getInstanceId().getProject()}); + } if (clientId == null) { clientId = nextDatabaseClientId(db); } diff --git a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 9c542bc52365..277402eb69e0 100644 --- a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -306,6 +306,7 @@ static GcpChannelPoolOptions mergeWithDefaultChannelPoolOptions( private final boolean enableExtendedTracing; private final boolean enableEndToEndTracing; private final String monitoringHost; + private final String metricsProjectId; private final TransactionOptions defaultTransactionOptions; private final RequestOptions.ClientContext clientContext; @@ -991,6 +992,7 @@ protected SpannerOptions(Builder builder) { } enableEndToEndTracing = builder.enableEndToEndTracing; monitoringHost = builder.monitoringHost; + metricsProjectId = builder.metricsProjectId; defaultTransactionOptions = builder.defaultTransactionOptions; clientContext = builder.clientContext; } @@ -1251,6 +1253,7 @@ public static class Builder private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); private boolean enableLocationApi = SpannerOptions.environment.isEnableLocationApi(); private String monitoringHost = SpannerOptions.environment.getMonitoringHost(); + private String metricsProjectId; private SslContext mTLSContext = null; private String experimentalHost = null; private boolean usePlainText = false; @@ -1360,6 +1363,7 @@ protected Builder() { this.enableLocationApi = options.enableLocationApi; this.enableEndToEndTracing = options.enableEndToEndTracing; this.monitoringHost = options.monitoringHost; + this.metricsProjectId = options.metricsProjectId; this.defaultTransactionOptions = options.defaultTransactionOptions; this.clientContext = options.clientContext; } @@ -2033,6 +2037,17 @@ public Builder setMonitoringHost(String monitoringHost) { return this; } + /** + * Sets the GCP project ID for exporting client-side built-in metrics. Defaults to {@link + * SpannerOptions#getProjectId()} when not set. On GKE with shared VPC, the default project may + * resolve to the host project instead of the application project; set this explicitly to avoid + * {@code monitoring.metricWriter} permission errors. + */ + public Builder setMetricsProjectId(String metricsProjectId) { + this.metricsProjectId = metricsProjectId; + return this; + } + /** * Sets whether to enable extended OpenTelemetry tracing. Enabling this option will add the * following additional attributes to the traces that are generated by the client: @@ -2443,14 +2458,14 @@ public ApiTracerFactory getApiTracerFactory() { @InternalApi public OpenTelemetry getBuiltInOpenTelemetry() { return this.builtInMetricsProvider.getOrCreateOpenTelemetry( - this.getProjectId(), getCredentials(), this.monitoringHost, getUniverseDomain()); + this.resolveMetricsProjectId(), getCredentials(), this.monitoringHost, getUniverseDomain()); } public void enablegRPCMetrics(InstantiatingGrpcChannelProvider.Builder channelProviderBuilder) { if (isEnableBuiltInMetrics() && SpannerOptions.environment.isEnableGRPCBuiltInMetrics()) { this.builtInMetricsProvider.enableGrpcMetrics( channelProviderBuilder, - this.getProjectId(), + this.resolveMetricsProjectId(), getCredentials(), this.monitoringHost, getUniverseDomain()); @@ -2499,7 +2514,7 @@ private ApiTracerFactory getDefaultApiTracerFactory() { private ApiTracerFactory createMetricsApiTracerFactory() { OpenTelemetry openTelemetry = this.builtInMetricsProvider.getOrCreateOpenTelemetry( - this.getProjectId(), getCredentials(), this.monitoringHost, getUniverseDomain()); + this.resolveMetricsProjectId(), getCredentials(), this.monitoringHost, getUniverseDomain()); return openTelemetry != null ? new BuiltInMetricsTracerFactory( @@ -2543,6 +2558,19 @@ String getMonitoringHost() { return monitoringHost; } + /** + * Returns the GCP project ID for client-side metrics export. Returns the explicit value if set + * via {@link Builder#setMetricsProjectId}, otherwise falls back to {@link #getProjectId()}. + */ + String resolveMetricsProjectId() { + return metricsProjectId != null ? metricsProjectId : getProjectId(); + } + + /** Returns the explicitly configured metrics project ID, or null if not set. */ + public String getMetricsProjectId() { + return metricsProjectId; + } + public TransactionOptions getDefaultTransactionOptions() { return defaultTransactionOptions; }