Skip to content

Commit 02ddff0

Browse files
committed
chore(datastore): Address PR feedback on OTel metrics
1 parent 0707021 commit 02ddff0

File tree

4 files changed

+103
-12
lines changed

4 files changed

+103
-12
lines changed

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOpenTelemetryOptions.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,9 @@ DatastoreOpenTelemetryOptions.Builder setMetricsEnabled(boolean enabled) {
168168
/**
169169
* Sets whether built-in metrics should be exported to Google Cloud Monitoring.
170170
*
171-
* <p>When enabled (the default), client-side metrics are automatically exported to Google Cloud
172-
* Monitoring using the Cloud Monitoring API. This can be disabled to prevent metrics from being
173-
* sent to Cloud Monitoring while still allowing metrics to flow to a custom OpenTelemetry
174-
* backend.
171+
* <p>When enabled, client-side metrics are automatically exported to Google Cloud Monitoring
172+
* using the Cloud Monitoring API. This can be disabled to prevent metrics from being sent to
173+
* Cloud Monitoring while still allowing metrics to flow to a custom OpenTelemetry backend.
175174
*
176175
* @param exportBuiltinMetrics Whether built-in metrics should be exported to Cloud Monitoring.
177176
* @return this builder instance.

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/telemetry/BuiltInDatastoreMetricsProvider.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,8 @@ private Map<String, String> buildClientAttributes() {
8181
* DatastoreCloudMonitoringExporter}. No global or shared state is modified.
8282
*
8383
* <p><b>Lifecycle:</b> The returned instance is owned by the caller. It should be closed by
84-
* calling {@link OpenTelemetrySdk#close()} (or via {@link
84+
* calling {@link io.opentelemetry.sdk.OpenTelemetrySdk#close()} (or via {@link
8585
* OpenTelemetryDatastoreMetricsRecorder#close()}) when the associated Datastore client is closed.
86-
* A JVM shutdown hook is also registered as a last-resort safety net for cases where the caller
87-
* does not explicitly close the client. Note that each call to this method adds a new shutdown
88-
* hook; callers should avoid creating an unbounded number of short-lived clients.
8986
*
9087
* <p>No caching is performed here; callers are responsible for holding the returned instance for
9188
* the lifetime of their Datastore client.
@@ -107,8 +104,6 @@ public OpenTelemetry createOpenTelemetry(
107104
sdkMeterProviderBuilder.setResource(
108105
Resource.create(createResourceAttributes(projectId, databaseId)));
109106
SdkMeterProvider sdkMeterProvider = sdkMeterProviderBuilder.build();
110-
// Ensure cleanup on shutdown.
111-
Runtime.getRuntime().addShutdownHook(new Thread(sdkMeterProvider::close));
112107
return OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
113108
} catch (IOException ex) {
114109
logger.log(

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/telemetry/CompositeDatastoreMetricsRecorder.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.logging.Level;
22+
import java.util.logging.Logger;
2123

2224
/**
2325
* A {@link DatastoreMetricsRecorder} implementation that fans out recording calls to multiple
@@ -28,6 +30,9 @@
2830
*/
2931
class CompositeDatastoreMetricsRecorder implements DatastoreMetricsRecorder {
3032

33+
private static final Logger logger =
34+
Logger.getLogger(CompositeDatastoreMetricsRecorder.class.getName());
35+
3136
private final List<DatastoreMetricsRecorder> recorders;
3237

3338
CompositeDatastoreMetricsRecorder(List<DatastoreMetricsRecorder> recorders) {
@@ -86,8 +91,12 @@ public void recordOperationCount(long count, Map<String, String> attributes) {
8691
*/
8792
@Override
8893
public void close() {
89-
for (DatastoreMetricsRecorder recorder : recorders) {
90-
recorder.close();
94+
for (int i = recorders.size() - 1; i >= 0; i--) {
95+
try {
96+
recorders.get(i).close();
97+
} catch (Exception e) {
98+
logger.log(Level.WARNING, "Failed to close metrics recorder", e);
99+
}
91100
}
92101
}
93102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.datastore.telemetry;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.List;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
import org.junit.runners.JUnit4;
27+
28+
@RunWith(JUnit4.class)
29+
public class CompositeDatastoreMetricsRecorderTest {
30+
31+
@Test
32+
public void testCloseLIFOAndExceptionSafe() {
33+
List<Integer> closeOrder = new ArrayList<>();
34+
DatastoreMetricsRecorder recorder1 = new MockRecorder(1, closeOrder, false);
35+
DatastoreMetricsRecorder recorder2 = new MockRecorder(2, closeOrder, true); // will throw
36+
DatastoreMetricsRecorder recorder3 = new MockRecorder(3, closeOrder, false);
37+
38+
CompositeDatastoreMetricsRecorder composite =
39+
new CompositeDatastoreMetricsRecorder(Arrays.asList(recorder1, recorder2, recorder3));
40+
41+
composite.close();
42+
43+
// LIFO order means 3, then 2, then 1.
44+
// Even though 2 throws, 1 should still be closed.
45+
assertThat(closeOrder).containsExactly(3, 2, 1).inOrder();
46+
}
47+
48+
private static class MockRecorder implements DatastoreMetricsRecorder {
49+
private final int id;
50+
private final List<Integer> closeOrder;
51+
private final boolean throwOnClose;
52+
53+
MockRecorder(int id, List<Integer> closeOrder, boolean throwOnClose) {
54+
this.id = id;
55+
this.closeOrder = closeOrder;
56+
this.throwOnClose = throwOnClose;
57+
}
58+
59+
@Override
60+
public void close() {
61+
closeOrder.add(id);
62+
if (throwOnClose) {
63+
throw new RuntimeException("Mock exception on close");
64+
}
65+
}
66+
67+
@Override
68+
public void recordTransactionLatency(
69+
double latencyMs, java.util.Map<String, String> attributes) {}
70+
71+
@Override
72+
public void recordTransactionAttemptCount(
73+
long count, java.util.Map<String, String> attributes) {}
74+
75+
@Override
76+
public void recordAttemptLatency(double latencyMs, java.util.Map<String, String> attributes) {}
77+
78+
@Override
79+
public void recordAttemptCount(long count, java.util.Map<String, String> attributes) {}
80+
81+
@Override
82+
public void recordOperationLatency(
83+
double latencyMs, java.util.Map<String, String> attributes) {}
84+
85+
@Override
86+
public void recordOperationCount(long count, java.util.Map<String, String> attributes) {}
87+
}
88+
}

0 commit comments

Comments
 (0)