Skip to content

Commit 4d3f093

Browse files
committed
chore: add x-goog-request-id insertion into *Exception
Allows users to examine and report the requestId in any thrown exceptions.
1 parent f0f072b commit 4d3f093

12 files changed

+136
-33
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AdminRequestsPerMinuteExceededException.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@ public class AdminRequestsPerMinuteExceededException extends SpannerException {
3232
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
3333
AdminRequestsPerMinuteExceededException(
3434
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
35-
this(token, message, cause, null);
35+
this(token, message, cause, null, null);
3636
}
3737

3838
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
3939
AdminRequestsPerMinuteExceededException(
4040
DoNotConstructDirectly token,
4141
@Nullable String message,
4242
@Nullable Throwable cause,
43-
@Nullable ApiException apiException) {
44-
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause, apiException);
43+
@Nullable ApiException apiException,
44+
@Nullable XGoogSpannerRequestId reqId) {
45+
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause, apiException, reqId);
4546
}
4647
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseNotFoundException.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class DatabaseNotFoundException extends ResourceNotFoundException {
3535
@Nullable String message,
3636
ResourceInfo resourceInfo,
3737
@Nullable Throwable cause) {
38-
this(token, message, resourceInfo, cause, null);
38+
this(token, message, resourceInfo, cause, null, null);
3939
}
4040

4141
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
@@ -44,7 +44,8 @@ public class DatabaseNotFoundException extends ResourceNotFoundException {
4444
@Nullable String message,
4545
ResourceInfo resourceInfo,
4646
@Nullable Throwable cause,
47-
@Nullable ApiException apiException) {
48-
super(token, message, resourceInfo, cause, apiException);
47+
@Nullable ApiException apiException,
48+
@Nullable XGoogSpannerRequestId reqId) {
49+
super(token, message, resourceInfo, cause, apiException, reqId);
4950
}
5051
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceNotFoundException.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ public class InstanceNotFoundException extends ResourceNotFoundException {
3535
@Nullable String message,
3636
ResourceInfo resourceInfo,
3737
@Nullable Throwable cause) {
38-
this(token, message, resourceInfo, cause, null);
38+
this(token, message, resourceInfo, cause, null, null);
3939
}
4040
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
4141
InstanceNotFoundException(
4242
DoNotConstructDirectly token,
4343
@Nullable String message,
4444
ResourceInfo resourceInfo,
4545
@Nullable Throwable cause,
46-
@Nullable ApiException apiException) {
47-
super(token, message, resourceInfo, cause, apiException);
46+
@Nullable ApiException apiException,
47+
@Nullable XGoogSpannerRequestId reqId) {
48+
super(token, message, resourceInfo, cause, apiException, reqId);
4849
}
4950
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/MissingDefaultSequenceKindException.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ public class MissingDefaultSequenceKindException extends SpannerException {
3737
ErrorCode errorCode,
3838
String message,
3939
Throwable cause,
40-
@Nullable ApiException apiException) {
41-
super(token, errorCode, /*retryable = */ false, message, cause, apiException);
40+
@Nullable ApiException apiException,
41+
@Nullable XGoogSpannerRequestId reqId) {
42+
super(token, errorCode, /*retryable = */ false, message, cause, apiException, reqId);
4243
}
4344

4445
static boolean isMissingDefaultSequenceKindException(Throwable cause) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Operation.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private static <R, M> Operation<R, M> failed(
8989
SpannerRpc rpc, String name, Status status, M metadata, Parser<R, M> parser, ApiClock clock) {
9090
SpannerException e =
9191
SpannerExceptionFactory.newSpannerException(
92-
ErrorCode.fromRpcStatus(status), status.getMessage(), null);
92+
ErrorCode.fromRpcStatus(status), status.getMessage(), (Throwable) (null));
9393
return new Operation<>(rpc, name, metadata, null, e, true, parser, clock);
9494
}
9595

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java

+43-1
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ void appendToOptions(Options options) {
535535
private RpcLockHint lockHint;
536536
private Boolean lastStatement;
537537
private IsolationLevel isolationLevel;
538+
private XGoogSpannerRequestId reqId;
538539

539540
// Construction is via factory methods below.
540541
private Options() {}
@@ -599,6 +600,14 @@ String filter() {
599600
return filter;
600601
}
601602

603+
boolean hasReqId() {
604+
return reqId != null;
605+
}
606+
607+
XGoogSpannerRequestId reqId() {
608+
return reqId;
609+
}
610+
602611
boolean hasPriority() {
603612
return priority != null;
604613
}
@@ -756,6 +765,9 @@ public String toString() {
756765
if (isolationLevel != null) {
757766
b.append("isolationLevel: ").append(isolationLevel).append(' ');
758767
}
768+
if (reqId != null) {
769+
b.append("requestId: ").append(reqId.toString());
770+
}
759771
return b.toString();
760772
}
761773

@@ -798,7 +810,8 @@ public boolean equals(Object o) {
798810
&& Objects.equals(orderBy(), that.orderBy())
799811
&& Objects.equals(isLastStatement(), that.isLastStatement())
800812
&& Objects.equals(lockHint(), that.lockHint())
801-
&& Objects.equals(isolationLevel(), that.isolationLevel());
813+
&& Objects.equals(isolationLevel(), that.isolationLevel())
814+
&& Objects.equals(reqId(), that.reqId());
802815
}
803816

804817
@Override
@@ -867,6 +880,9 @@ public int hashCode() {
867880
if (isolationLevel != null) {
868881
result = 31 * result + isolationLevel.hashCode();
869882
}
883+
if (reqId != null) {
884+
result = 31 * result + reqId.hashCode();
885+
}
870886
return result;
871887
}
872888

@@ -1052,4 +1068,30 @@ public boolean equals(Object o) {
10521068
return o instanceof LastStatementUpdateOption;
10531069
}
10541070
}
1071+
1072+
static final class RequestIdOption extends InternalOption
1073+
implements TransactionOption, UpdateOption {
1074+
private final XGoogSpannerRequestId reqId;
1075+
1076+
RequestIdOption(XGoogSpannerRequestId reqId) {
1077+
this.reqId = reqId;
1078+
}
1079+
1080+
@Override
1081+
void appendToOptions(Options options) {
1082+
options.reqId = this.reqId;
1083+
}
1084+
1085+
@Override
1086+
public int hashCode() {
1087+
return RequestIdOption.class.hashCode();
1088+
}
1089+
1090+
@Override
1091+
public boolean equals(Object o) {
1092+
// TODO: Examine why the precedent for LastStatementUpdateOption
1093+
// does not check against the actual value.
1094+
return o instanceof RequestIdOption;
1095+
}
1096+
}
10551097
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionNotFoundException.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class SessionNotFoundException extends ResourceNotFoundException {
3535
@Nullable String message,
3636
ResourceInfo resourceInfo,
3737
@Nullable Throwable cause) {
38-
this(token, message, resourceInfo, cause, null);
38+
this(token, message, resourceInfo, cause, null, null);
3939
}
4040

4141
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
@@ -44,7 +44,8 @@ public class SessionNotFoundException extends ResourceNotFoundException {
4444
@Nullable String message,
4545
ResourceInfo resourceInfo,
4646
@Nullable Throwable cause,
47-
@Nullable ApiException apiException) {
48-
super(token, message, resourceInfo, cause, apiException);
47+
@Nullable ApiException apiException,
48+
@Nullable XGoogSpannerRequestId reqId) {
49+
super(token, message, resourceInfo, cause, apiException, reqId);
4950
}
5051
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java

+4
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,10 @@ PooledSession get(final boolean eligibleForLongRunning) {
15681568
throw SpannerExceptionFactory.propagateInterrupt(e);
15691569
}
15701570
}
1571+
1572+
public int getChannel() {
1573+
return get().getChannel();
1574+
}
15711575
}
15721576

15731577
interface CachedSession extends Session {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ public abstract static class ResourceNotFoundException extends SpannerException
4040
@Nullable String message,
4141
ResourceInfo resourceInfo,
4242
@Nullable Throwable cause,
43-
@Nullable ApiException apiException) {
44-
super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause, apiException);
43+
@Nullable ApiException apiException,
44+
@Nullable XGoogSpannerRequestId reqId) {
45+
super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause, apiException, reqId);
4546
this.resourceInfo = resourceInfo;
4647
}
4748

@@ -56,6 +57,7 @@ public String getResourceName() {
5657

5758
private final ErrorCode code;
5859
private final ApiException apiException;
60+
private final XGoogSpannerRequestId requestId;
5961

6062
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
6163
SpannerException(
@@ -75,19 +77,39 @@ public String getResourceName() {
7577
@Nullable String message,
7678
@Nullable Throwable cause,
7779
@Nullable ApiException apiException) {
80+
this(token, code, retryable, message, cause, apiException, null);
81+
}
82+
83+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
84+
SpannerException(
85+
DoNotConstructDirectly token,
86+
ErrorCode code,
87+
boolean retryable,
88+
@Nullable String message,
89+
@Nullable Throwable cause,
90+
@Nullable ApiException apiException,
91+
@Nullable XGoogSpannerRequestId requestId) {
7892
super(message, cause, code.getCode(), retryable);
7993
if (token != DoNotConstructDirectly.ALLOWED) {
8094
throw new AssertionError("Do not construct directly: use SpannerExceptionFactory");
8195
}
8296
this.code = Preconditions.checkNotNull(code);
8397
this.apiException = apiException;
98+
this.requestId = requestId;
8499
}
85100

86101
/** Returns the error code associated with this exception. */
87102
public ErrorCode getErrorCode() {
88103
return code;
89104
}
90105

106+
public String getRequestId() {
107+
if (requestId == null) {
108+
return "";
109+
}
110+
return requestId.toString();
111+
}
112+
91113
enum DoNotConstructDirectly {
92114
ALLOWED
93115
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java

+29-13
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ public final class SpannerExceptionFactory {
5858
ProtoUtils.keyForProto(ErrorInfo.getDefaultInstance());
5959

6060
public static SpannerException newSpannerException(ErrorCode code, @Nullable String message) {
61-
return newSpannerException(code, message, null);
61+
return newSpannerException(code, message, (XGoogSpannerRequestId) (null));
62+
}
63+
64+
public static SpannerException newSpannerException(
65+
ErrorCode code, @Nullable String message, @Nullable XGoogSpannerRequestId reqId) {
66+
return newSpannerExceptionPreformatted(
67+
code, formatMessage(code, message), (Throwable) (null), (ApiException) (null), reqId);
6268
}
6369

6470
public static SpannerException newSpannerException(
6571
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
66-
return newSpannerExceptionPreformatted(code, formatMessage(code, message), cause);
72+
return newSpannerExceptionPreformatted(code, formatMessage(code, message), cause, null, null);
6773
}
6874

6975
public static SpannerException propagateInterrupt(InterruptedException e) {
@@ -115,12 +121,17 @@ public static SpannerException newSpannerException(Throwable cause) {
115121
return newSpannerException(null, cause);
116122
}
117123

124+
public static SpannerException newSpannerException(
125+
Throwable cause, XGoogSpannerRequestId requestId) {
126+
return newSpannerExceptionPreformatted(null, null, cause, null, requestId);
127+
}
128+
118129
public static SpannerBatchUpdateException newSpannerBatchUpdateException(
119130
ErrorCode code, String message, long[] updateCounts) {
120131
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
121132
SpannerException cause = null;
122133
if (isTransactionMutationLimitException(code, message)) {
123-
cause = new TransactionMutationLimitExceededException(token, code, message, null, null);
134+
cause = new TransactionMutationLimitExceededException(token, code, message, null, null, null);
124135
}
125136
return new SpannerBatchUpdateException(token, code, message, updateCounts, cause);
126137
}
@@ -305,7 +316,8 @@ static SpannerException newSpannerExceptionPreformatted(
305316
ErrorCode code,
306317
@Nullable String message,
307318
@Nullable Throwable cause,
308-
@Nullable ApiException apiException) {
319+
@Nullable ApiException apiException,
320+
@Nullable XGoogSpannerRequestId reqId) {
309321
// This is the one place in the codebase that is allowed to call constructors directly.
310322
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
311323
switch (code) {
@@ -319,41 +331,44 @@ static SpannerException newSpannerExceptionPreformatted(
319331
&& AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_VALUE.equals(
320332
info.getMetadataMap()
321333
.get(AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_KEY))) {
322-
return new AdminRequestsPerMinuteExceededException(token, message, cause, apiException);
334+
return new AdminRequestsPerMinuteExceededException(
335+
token, message, cause, apiException, reqId);
323336
}
324337
case NOT_FOUND:
325338
ResourceInfo resourceInfo = extractResourceInfo(cause);
326339
if (resourceInfo != null) {
327340
switch (resourceInfo.getResourceType()) {
328341
case SESSION_RESOURCE_TYPE:
329342
return new SessionNotFoundException(
330-
token, message, resourceInfo, cause, apiException);
343+
token, message, resourceInfo, cause, apiException, reqId);
331344
case DATABASE_RESOURCE_TYPE:
332345
return new DatabaseNotFoundException(
333-
token, message, resourceInfo, cause, apiException);
346+
token, message, resourceInfo, cause, apiException, reqId);
334347
case INSTANCE_RESOURCE_TYPE:
335348
return new InstanceNotFoundException(
336-
token, message, resourceInfo, cause, apiException);
349+
token, message, resourceInfo, cause, apiException, reqId);
337350
}
338351
}
339352
case INVALID_ARGUMENT:
340353
if (isTransactionMutationLimitException(cause)) {
341354
return new TransactionMutationLimitExceededException(
342-
token, code, message, cause, apiException);
355+
token, code, message, cause, apiException, reqId);
343356
}
344357
if (isMissingDefaultSequenceKindException(apiException)) {
345-
return new MissingDefaultSequenceKindException(token, code, message, cause, apiException);
358+
return new MissingDefaultSequenceKindException(
359+
token, code, message, cause, apiException, reqId);
346360
}
347361
// Fall through to the default.
348362
default:
349363
return new SpannerException(
350-
token, code, isRetryable(code, cause), message, cause, apiException);
364+
token, code, isRetryable(code, cause), message, cause, apiException, reqId);
351365
}
352366
}
353367

354368
static SpannerException newSpannerExceptionPreformatted(
355369
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
356-
return newSpannerExceptionPreformatted(code, message, cause, null);
370+
return newSpannerExceptionPreformatted(
371+
code, message, cause, null, (XGoogSpannerRequestId) (null));
357372
}
358373

359374
private static SpannerException fromApiException(ApiException exception) {
@@ -371,7 +386,8 @@ private static SpannerException fromApiException(ApiException exception) {
371386
errorCode,
372387
formatMessage(errorCode, exception.getMessage()),
373388
exception.getCause(),
374-
exception);
389+
exception,
390+
null);
375391
}
376392

377393
private static boolean isRetryable(ErrorCode code, @Nullable Throwable cause) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionMutationLimitExceededException.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ public class TransactionMutationLimitExceededException extends SpannerException
3434
ErrorCode errorCode,
3535
String message,
3636
Throwable cause,
37-
@Nullable ApiException apiException) {
38-
super(token, errorCode, /*retryable = */ false, message, cause, apiException);
37+
@Nullable ApiException apiException,
38+
@Nullable XGoogSpannerRequestId reqId) {
39+
super(token, errorCode, /*retryable = */ false, message, cause, apiException, reqId);
3940
}
4041

4142
static boolean isTransactionMutationLimitException(ErrorCode code, String message) {

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerExceptionFactoryTest.java

+13
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,17 @@ private Metadata createResourceTypeMetadata(String resourceType, String resource
250250

251251
return trailers;
252252
}
253+
254+
@Test
255+
public void withRequestId() {
256+
XGoogSpannerRequestId reqIdIn = XGoogSpannerRequestId.of(1, 2, 3, 4);
257+
Status status = Status.fromCodeValue(Status.Code.ABORTED.value());
258+
Exception exc = new StatusRuntimeException(status);
259+
SpannerException spannerExceptionWithReqId =
260+
SpannerExceptionFactory.newSpannerException(exc, reqIdIn);
261+
assertThat(spannerExceptionWithReqId.getRequestId()).isEqualTo(reqIdIn.toString());
262+
SpannerException spannerExceptionWithoutReqId =
263+
SpannerExceptionFactory.newSpannerException(exc);
264+
assertThat(spannerExceptionWithoutReqId.getRequestId()).isEqualTo("");
265+
}
253266
}

0 commit comments

Comments
 (0)