Visitar URL original
feat(storage): add StorageMetricsContext and StorageErrorTypeMapper f… · googleapis/google-cloud-java@a1393ca · GitHub
Skip to content

Commit a1393ca

Browse files
committed
feat(storage): add StorageMetricsContext and StorageErrorTypeMapper for standard metrics
Introduce package-private StorageMetricsContext to track operation metric state across asynchronous and thread-hopping boundaries (such as ApiFuture callbacks and thread pools). Introduce package-private StorageErrorTypeMapper to provide canonical uppercase error mapping for gRPC status codes, HTTP status codes, network/socket/TLS failures, auth errors, and checksum mismatches per O11y 2026 specification. Add comprehensive unit test suites covering both classes. [Generated-by: AI]
1 parent c21ff47 commit a1393ca

4 files changed

Lines changed: 836 additions & 0 deletions

File tree

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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.storage;
18+
19+
import com.google.api.client.http.HttpResponseException;
20+
import com.google.api.gax.rpc.ApiException;
21+
import com.google.api.gax.rpc.StatusCode;
22+
import io.grpc.Status;
23+
import io.grpc.StatusException;
24+
import io.grpc.StatusRuntimeException;
25+
import java.io.IOException;
26+
import java.net.ConnectException;
27+
import java.net.SocketException;
28+
import java.net.SocketTimeoutException;
29+
import java.net.UnknownHostException;
30+
import java.util.Locale;
31+
import java.util.concurrent.CancellationException;
32+
import java.util.concurrent.CompletionException;
33+
import java.util.concurrent.ExecutionException;
34+
import java.util.concurrent.TimeoutException;
35+
import javax.net.ssl.SSLException;
36+
import javax.net.ssl.SSLHandshakeException;
37+
import org.checkerframework.checker.nullness.qual.Nullable;
38+
39+
/**
40+
* Centralized package-private utility mapping exceptions and status codes to the standard HLD
41+
* uppercase canonical error strings.
42+
*/
43+
final class StorageErrorTypeMapper {
44+
45+
static final String STATUS_OK = "OK";
46+
static final String STATUS_CANCELLED = "CANCELLED";
47+
static final String STATUS_UNKNOWN = "UNKNOWN";
48+
static final String STATUS_INVALID_ARGUMENT = "INVALID_ARGUMENT";
49+
static final String STATUS_DEADLINE_EXCEEDED = "TIMEOUT";
50+
static final String STATUS_NOT_FOUND = "NOT_FOUND";
51+
static final String STATUS_ALREADY_EXISTS = "ALREADY_EXISTS";
52+
static final String STATUS_PERMISSION_DENIED = "PERMISSION_DENIED";
53+
static final String STATUS_RESOURCE_EXHAUSTED = "RESOURCE_EXHAUSTED";
54+
static final String STATUS_FAILED_PRECONDITION = "FAILED_PRECONDITION";
55+
static final String STATUS_ABORTED = "ABORTED";
56+
static final String STATUS_OUT_OF_RANGE = "OUT_OF_RANGE";
57+
static final String STATUS_UNIMPLEMENTED = "UNIMPLEMENTED";
58+
static final String STATUS_INTERNAL = "INTERNAL";
59+
static final String STATUS_UNAVAILABLE = "UNAVAILABLE";
60+
static final String STATUS_DATA_LOSS = "DATA_LOSS";
61+
static final String STATUS_UNAUTHENTICATED = "UNAUTHENTICATED";
62+
63+
static final String STATUS_TIMEOUT = "TIMEOUT";
64+
static final String STATUS_DNS_FAILURE = "DNS_FAILURE";
65+
static final String STATUS_CONNECTION_REFUSED = "CONNECTION_REFUSED";
66+
static final String STATUS_CONNECTION_RESET = "CONNECTION_RESET";
67+
static final String STATUS_TLS_FAILURE = "TLS_FAILURE";
68+
static final String STATUS_CONNECTIVITY = "CONNECTIVITY";
69+
static final String STATUS_AUTHENTICATION_ERROR = "AUTHENTICATION_ERROR";
70+
static final String STATUS_CHECKSUM_MISMATCH = "CHECKSUM_MISMATCH";
71+
72+
private StorageErrorTypeMapper() {}
73+
74+
static String mapGrpcStatus(@Nullable Status status) {
75+
if (status == null) {
76+
return STATUS_UNKNOWN;
77+
}
78+
switch (status.getCode()) {
79+
case OK:
80+
return STATUS_OK;
81+
case CANCELLED:
82+
return STATUS_CANCELLED;
83+
case DEADLINE_EXCEEDED:
84+
return STATUS_TIMEOUT;
85+
case INVALID_ARGUMENT:
86+
return STATUS_INVALID_ARGUMENT;
87+
case NOT_FOUND:
88+
return STATUS_NOT_FOUND;
89+
case ALREADY_EXISTS:
90+
return STATUS_ALREADY_EXISTS;
91+
case PERMISSION_DENIED:
92+
return STATUS_PERMISSION_DENIED;
93+
case RESOURCE_EXHAUSTED:
94+
return STATUS_RESOURCE_EXHAUSTED;
95+
case FAILED_PRECONDITION:
96+
return STATUS_FAILED_PRECONDITION;
97+
case ABORTED:
98+
return STATUS_ABORTED;
99+
case OUT_OF_RANGE:
100+
return STATUS_OUT_OF_RANGE;
101+
case UNIMPLEMENTED:
102+
return STATUS_UNIMPLEMENTED;
103+
case INTERNAL:
104+
return STATUS_INTERNAL;
105+
case UNAVAILABLE:
106+
return STATUS_UNAVAILABLE;
107+
case DATA_LOSS:
108+
return STATUS_DATA_LOSS;
109+
case UNAUTHENTICATED:
110+
return STATUS_UNAUTHENTICATED;
111+
case UNKNOWN:
112+
default:
113+
return STATUS_UNKNOWN;
114+
}
115+
}
116+
117+
static String mapHttpResponseCode(int statusCode) {
118+
if (statusCode >= 200 && statusCode < 300) {
119+
return STATUS_OK;
120+
}
121+
switch (statusCode) {
122+
case 400:
123+
return STATUS_INVALID_ARGUMENT;
124+
case 401:
125+
return STATUS_AUTHENTICATION_ERROR;
126+
case 403:
127+
return STATUS_PERMISSION_DENIED;
128+
case 404:
129+
return STATUS_NOT_FOUND;
130+
case 408:
131+
return STATUS_TIMEOUT;
132+
case 409:
133+
return STATUS_ALREADY_EXISTS;
134+
case 412:
135+
return STATUS_FAILED_PRECONDITION;
136+
case 429:
137+
return STATUS_RESOURCE_EXHAUSTED;
138+
case 500:
139+
return STATUS_INTERNAL;
140+
case 501:
141+
return STATUS_UNIMPLEMENTED;
142+
case 503:
143+
return STATUS_UNAVAILABLE;
144+
case 504:
145+
return STATUS_TIMEOUT;
146+
default:
147+
if (statusCode >= 400 && statusCode < 600) {
148+
return String.valueOf(statusCode);
149+
}
150+
return STATUS_UNKNOWN;
151+
}
152+
}
153+
154+
static String mapThrowable(@Nullable Throwable t) {
155+
if (t == null) {
156+
return STATUS_OK;
157+
}
158+
159+
while ((t instanceof ExecutionException || t instanceof CompletionException)
160+
&& t.getCause() != null) {
161+
t = t.getCause();
162+
}
163+
164+
if (isChecksumMismatch(t)) {
165+
return STATUS_CHECKSUM_MISMATCH;
166+
}
167+
168+
if (t instanceof CancellationException) {
169+
return STATUS_CANCELLED;
170+
}
171+
172+
if (t instanceof StatusRuntimeException) {
173+
return mapGrpcStatus(((StatusRuntimeException) t).getStatus());
174+
}
175+
176+
if (t instanceof StatusException) {
177+
return mapGrpcStatus(((StatusException) t).getStatus());
178+
}
179+
180+
if (t instanceof ApiException) {
181+
ApiException apiException = (ApiException) t;
182+
StatusCode.Code code = apiException.getStatusCode().getCode();
183+
String mapped = mapGaxStatusCode(code);
184+
if (!STATUS_UNKNOWN.equals(mapped)) {
185+
return mapped;
186+
}
187+
}
188+
189+
if (t instanceof HttpResponseException) {
190+
return mapHttpResponseCode(((HttpResponseException) t).getStatusCode());
191+
}
192+
193+
if (t instanceof StorageException) {
194+
StorageException se = (StorageException) t;
195+
if (se.getCode() > 0) {
196+
return mapHttpResponseCode(se.getCode());
197+
}
198+
}
199+
200+
if (isAuthException(t)) {
201+
return STATUS_AUTHENTICATION_ERROR;
202+
}
203+
204+
if (t instanceof SocketTimeoutException || t instanceof TimeoutException) {
205+
return STATUS_TIMEOUT;
206+
}
207+
208+
if (t instanceof UnknownHostException) {
209+
return STATUS_DNS_FAILURE;
210+
}
211+
212+
if (t instanceof ConnectException) {
213+
return STATUS_CONNECTION_REFUSED;
214+
}
215+
216+
if (t instanceof SocketException) {
217+
return STATUS_CONNECTION_RESET;
218+
}
219+
220+
if (t instanceof SSLHandshakeException || t instanceof SSLException) {
221+
return STATUS_TLS_FAILURE;
222+
}
223+
224+
if (t instanceof IOException) {
225+
return STATUS_CONNECTIVITY;
226+
}
227+
228+
if (t.getCause() != null && t.getCause() != t) {
229+
String causeMapping = mapThrowable(t.getCause());
230+
if (!STATUS_UNKNOWN.equals(causeMapping)) {
231+
return causeMapping;
232+
}
233+
}
234+
235+
return STATUS_UNKNOWN;
236+
}
237+
238+
private static boolean isChecksumMismatch(Throwable t) {
239+
if (t instanceof Hasher.ChecksumMismatchException
240+
|| t instanceof Hasher.UncheckedChecksumMismatchException) {
241+
return true;
242+
}
243+
String message = t.getMessage();
244+
if (message != null) {
245+
String lower = message.toLowerCase(Locale.US);
246+
if (lower.contains("mismatch checksum") || lower.contains("checksum mismatch")) {
247+
return true;
248+
}
249+
}
250+
return false;
251+
}
252+
253+
private static boolean isAuthException(Throwable t) {
254+
String className = t.getClass().getName();
255+
if (className.startsWith("com.google.auth")) {
256+
return true;
257+
}
258+
if (t.getClass().getSimpleName().contains("Auth")) {
259+
return true;
260+
}
261+
return false;
262+
}
263+
264+
private static String mapGaxStatusCode(StatusCode.Code code) {
265+
switch (code) {
266+
case OK:
267+
return STATUS_OK;
268+
case CANCELLED:
269+
return STATUS_CANCELLED;
270+
case DEADLINE_EXCEEDED:
271+
return STATUS_TIMEOUT;
272+
case INVALID_ARGUMENT:
273+
return STATUS_INVALID_ARGUMENT;
274+
case NOT_FOUND:
275+
return STATUS_NOT_FOUND;
276+
case ALREADY_EXISTS:
277+
return STATUS_ALREADY_EXISTS;
278+
case PERMISSION_DENIED:
279+
return STATUS_PERMISSION_DENIED;
280+
case RESOURCE_EXHAUSTED:
281+
return STATUS_RESOURCE_EXHAUSTED;
282+
case FAILED_PRECONDITION:
283+
return STATUS_FAILED_PRECONDITION;
284+
case ABORTED:
285+
return STATUS_ABORTED;
286+
case OUT_OF_RANGE:
287+
return STATUS_OUT_OF_RANGE;
288+
case UNIMPLEMENTED:
289+
return STATUS_UNIMPLEMENTED;
290+
case INTERNAL:
291+
return STATUS_INTERNAL;
292+
case UNAVAILABLE:
293+
return STATUS_UNAVAILABLE;
294+
case DATA_LOSS:
295+
return STATUS_DATA_LOSS;
296+
case UNAUTHENTICATED:
297+
return STATUS_UNAUTHENTICATED;
298+
case UNKNOWN:
299+
default:
300+
return STATUS_UNKNOWN;
301+
}
302+
}
303+
}

0 commit comments

Comments
 (0)