3131
3232import com .google .api .gax .rpc .ApiException ;
3333import com .google .api .gax .rpc .StatusCode ;
34+ import com .google .common .base .Strings ;
3435import io .opentelemetry .api .common .Attributes ;
3536import io .opentelemetry .api .common .AttributesBuilder ;
37+ import java .util .Arrays ;
38+ import java .util .Collections ;
39+ import java .util .List ;
3640import java .util .Map ;
3741import java .util .concurrent .CancellationException ;
3842import javax .annotation .Nullable ;
@@ -56,6 +60,38 @@ public String toString() {
5660 }
5761 }
5862
63+ private static final List <Class <? extends Throwable >> CLIENT_TIMEOUT_CLASSES =
64+ Arrays .asList (
65+ java .util .concurrent .TimeoutException .class , java .net .SocketTimeoutException .class );
66+ private static final List <String > CLIENT_TIMEOUT_NAMES =
67+ Collections .singletonList ("WatchdogTimeoutException" );
68+
69+ private static final List <Class <? extends Throwable >> CLIENT_CONNECTION_ERROR_CLASSES =
70+ Arrays .asList (
71+ java .net .ConnectException .class ,
72+ java .net .UnknownHostException .class ,
73+ java .nio .channels .UnresolvedAddressException .class );
74+ private static final List <String > CLIENT_CONNECTION_ERROR_NAMES =
75+ Collections .singletonList ("ConnectException" );
76+
77+ private static final List <String > CLIENT_AUTH_ERROR_SUBSTRINGS =
78+ Arrays .asList ("CredentialsException" , "AuthenticationException" );
79+
80+ private static final List <String > CLIENT_RESPONSE_DECODE_ERROR_SUBSTRINGS =
81+ Arrays .asList ("ProtocolBufferParsingException" , "DecodeException" );
82+
83+ private static final List <String > CLIENT_REDIRECT_ERROR_SUBSTRINGS =
84+ Collections .singletonList ("RedirectException" );
85+
86+ private static final List <String > CLIENT_REQUEST_BODY_ERROR_SUBSTRINGS =
87+ Collections .singletonList ("RequestBodyException" );
88+
89+ private static final List <String > CLIENT_REQUEST_ERROR_SUBSTRINGS =
90+ Collections .singletonList ("RequestException" );
91+
92+ private static final List <String > CLIENT_UNKNOWN_ERROR_SUBSTRINGS =
93+ Collections .singletonList ("UnknownClientException" );
94+
5995 /**
6096 * Extracts a low-cardinality string representing the specific classification of the error to be
6197 * used in the {@link ObservabilityAttributes#ERROR_TYPE_ATTRIBUTE} attribute.
@@ -107,76 +143,104 @@ static String extractErrorType(@Nullable Throwable error) {
107143 }
108144
109145 if (error instanceof ApiException ) {
110- ApiException apiException = (ApiException ) error ;
111-
112- // 1. Check for ErrorInfo.reason
113- String reason = apiException .getReason ();
114- if (reason != null && !reason .isEmpty ()) {
115- return reason ;
146+ String errorType = extractFromApiException ((ApiException ) error );
147+ if (errorType != null ) {
148+ return errorType ;
116149 }
150+ }
117151
118- // 2. Specific Server Error Code
119- if (apiException .getStatusCode () != null ) {
120- Object transportCode = apiException .getStatusCode ().getTransportCode ();
121- if (transportCode instanceof Integer ) {
122- // HTTP Status Code
123- return String .valueOf (transportCode );
124- } else if (apiException .getStatusCode ().getCode () != null ) {
125- // gRPC Status Code name
126- return apiException .getStatusCode ().getCode ().name ();
127- }
128- }
152+ String clientError = getClientSideError (error );
153+ if (clientError != null ) {
154+ return clientError ;
129155 }
130156
131- // 3. Client-Side Network/Operational Errors
157+ // 4. Language-specific error type fallback
132158 String exceptionName = error .getClass ().getSimpleName ();
159+ if (exceptionName != null && !exceptionName .isEmpty ()) {
160+ return exceptionName ;
161+ }
133162
134- if (error instanceof java .util .concurrent .TimeoutException
135- || error instanceof java .net .SocketTimeoutException
136- || exceptionName .equals ("WatchdogTimeoutException" )) {
137- return ErrorType .CLIENT_TIMEOUT .toString ();
163+ // 5. Internal Fallback
164+ return ErrorType .INTERNAL .toString ();
165+ }
166+
167+ @ Nullable
168+ private static String extractFromApiException (ApiException apiException ) {
169+ // 1. Check for ErrorInfo.reason
170+ String reason = apiException .getReason ();
171+ if (!Strings .isNullOrEmpty (reason )) {
172+ return reason ;
173+ }
174+
175+ // 2. Specific Server Error Code
176+ if (apiException .getStatusCode () != null ) {
177+ Object transportCode = apiException .getStatusCode ().getTransportCode ();
178+ if (transportCode instanceof Integer ) {
179+ // HTTP Status Code
180+ return String .valueOf (transportCode );
181+ } else if (apiException .getStatusCode ().getCode () != null ) {
182+ // gRPC Status Code name
183+ return apiException .getStatusCode ().getCode ().name ();
184+ }
138185 }
186+ return null ;
187+ }
139188
140- if (error instanceof java .net .ConnectException
141- || error instanceof java .net .UnknownHostException
142- || error instanceof java .nio .channels .UnresolvedAddressException
143- || exceptionName .equals ("ConnectException" )) {
189+ @ Nullable
190+ private static String getClientSideError (Throwable error ) {
191+ if (isInstanceof (error , CLIENT_TIMEOUT_CLASSES )) {
192+ return ErrorType .CLIENT_TIMEOUT .toString ();
193+ }
194+ if (isInstanceof (error , CLIENT_CONNECTION_ERROR_CLASSES )) {
144195 return ErrorType .CLIENT_CONNECTION_ERROR .toString ();
145196 }
146197
147- if (exceptionName .contains ("CredentialsException" )
148- || exceptionName .contains ("AuthenticationException" )) {
198+ String exceptionName = error .getClass ().getSimpleName ();
199+
200+ if (CLIENT_TIMEOUT_NAMES .contains (exceptionName )) {
201+ return ErrorType .CLIENT_TIMEOUT .toString ();
202+ }
203+ if (CLIENT_CONNECTION_ERROR_NAMES .contains (exceptionName )) {
204+ return ErrorType .CLIENT_CONNECTION_ERROR .toString ();
205+ }
206+ if (nameContains (exceptionName , CLIENT_AUTH_ERROR_SUBSTRINGS )) {
149207 return ErrorType .CLIENT_AUTHENTICATION_ERROR .toString ();
150208 }
151-
152- if (exceptionName .contains ("ProtocolBufferParsingException" )
153- || exceptionName .contains ("DecodeException" )) {
209+ if (nameContains (exceptionName , CLIENT_RESPONSE_DECODE_ERROR_SUBSTRINGS )) {
154210 return ErrorType .CLIENT_RESPONSE_DECODE_ERROR .toString ();
155211 }
156-
157- if (exceptionName .contains ("RedirectException" )) {
212+ if (nameContains (exceptionName , CLIENT_REDIRECT_ERROR_SUBSTRINGS )) {
158213 return ErrorType .CLIENT_REDIRECT_ERROR .toString ();
159214 }
160-
161- if (exceptionName .contains ("RequestBodyException" )) {
215+ if (nameContains (exceptionName , CLIENT_REQUEST_BODY_ERROR_SUBSTRINGS )) {
162216 return ErrorType .CLIENT_REQUEST_BODY_ERROR .toString ();
163217 }
164-
165- if (exceptionName .contains ("RequestException" )) {
218+ if (nameContains (exceptionName , CLIENT_REQUEST_ERROR_SUBSTRINGS )) {
166219 return ErrorType .CLIENT_REQUEST_ERROR .toString ();
167220 }
168-
169- if (exceptionName .contains ("UnknownClientException" )) {
221+ if (nameContains (exceptionName , CLIENT_UNKNOWN_ERROR_SUBSTRINGS )) {
170222 return ErrorType .CLIENT_UNKNOWN_ERROR .toString ();
171223 }
172224
173- // 4. Language-specific error type fallback
174- if (exceptionName != null && !exceptionName .isEmpty ()) {
175- return exceptionName ;
225+ return null ;
226+ }
227+
228+ private static boolean isInstanceof (Throwable error , List <Class <? extends Throwable >> classes ) {
229+ for (Class <? extends Throwable > clazz : classes ) {
230+ if (clazz .isInstance (error )) {
231+ return true ;
232+ }
176233 }
234+ return false ;
235+ }
177236
178- // 5. Internal Fallback
179- return ErrorType .INTERNAL .toString ();
237+ private static boolean nameContains (String name , List <String > substrings ) {
238+ for (String sub : substrings ) {
239+ if (name .contains (sub )) {
240+ return true ;
241+ }
242+ }
243+ return false ;
180244 }
181245
182246 /** Function to extract the status of the error as a string */
0 commit comments