-
Notifications
You must be signed in to change notification settings - Fork 14
Include Exception Properties at FailureDetails #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
f4c3777
17f1ec2
e8fc02a
7f69d92
11a3961
04fe9b9
6b1c474
4563e96
af58fa2
3ea28a1
0598526
2c0e3fb
2029997
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,16 @@ | |
| // Licensed under the MIT License. | ||
| package com.microsoft.durabletask; | ||
|
|
||
| import com.google.protobuf.NullValue; | ||
| import com.google.protobuf.StringValue; | ||
| import com.google.protobuf.Value; | ||
| import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.TaskFailureDetails; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.Nullable; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Class that represents the details of a task failure. | ||
|
|
@@ -20,29 +25,50 @@ public final class FailureDetails { | |
| private final String errorMessage; | ||
| private final String stackTrace; | ||
| private final boolean isNonRetriable; | ||
| private final FailureDetails innerFailure; | ||
| private final Map<String, Object> properties; | ||
|
|
||
| FailureDetails( | ||
| String errorType, | ||
| @Nullable String errorMessage, | ||
| @Nullable String errorDetails, | ||
| boolean isNonRetriable) { | ||
| this(errorType, errorMessage, errorDetails, isNonRetriable, null, null); | ||
| } | ||
|
|
||
| FailureDetails( | ||
| String errorType, | ||
| @Nullable String errorMessage, | ||
| @Nullable String errorDetails, | ||
| boolean isNonRetriable, | ||
| @Nullable FailureDetails innerFailure, | ||
| @Nullable Map<String, Object> properties) { | ||
| this.errorType = errorType; | ||
| this.stackTrace = errorDetails; | ||
|
|
||
| // Error message can be null for things like NullPointerException but the gRPC contract doesn't allow null | ||
| this.errorMessage = errorMessage != null ? errorMessage : ""; | ||
| this.isNonRetriable = isNonRetriable; | ||
| this.innerFailure = innerFailure; | ||
| this.properties = properties != null ? Collections.unmodifiableMap(new HashMap<>(properties)) : null; | ||
| } | ||
|
|
||
| FailureDetails(Exception exception) { | ||
| this(exception.getClass().getName(), exception.getMessage(), getFullStackTrace(exception), false); | ||
| this(exception.getClass().getName(), | ||
| exception.getMessage(), | ||
| getFullStackTrace(exception), | ||
| false, | ||
| exception.getCause() != null ? fromExceptionRecursive(exception.getCause()) : null, | ||
| null); | ||
| } | ||
|
|
||
| FailureDetails(TaskFailureDetails proto) { | ||
| this(proto.getErrorType(), | ||
| proto.getErrorMessage(), | ||
| proto.getStackTrace().getValue(), | ||
| proto.getIsNonRetriable()); | ||
| proto.getIsNonRetriable(), | ||
| proto.hasInnerFailure() ? new FailureDetails(proto.getInnerFailure()) : null, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a depth limit for this recursive call too? |
||
| convertProtoProperties(proto.getPropertiesMap())); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -86,6 +112,28 @@ public boolean isNonRetriable() { | |
| return this.isNonRetriable; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the inner failure that caused this failure, or {@code null} if there is no inner cause. | ||
| * | ||
| * @return the inner {@code FailureDetails} or {@code null} | ||
| */ | ||
| @Nullable | ||
| public FailureDetails getInnerFailure() { | ||
| return this.innerFailure; | ||
| } | ||
|
|
||
| /** | ||
| * Gets additional properties associated with the exception, or {@code null} if no properties are available. | ||
| * <p> | ||
| * The returned map is unmodifiable. | ||
| * | ||
| * @return an unmodifiable map of property names to values, or {@code null} | ||
| */ | ||
| @Nullable | ||
| public Map<String, Object> getProperties() { | ||
|
github-code-quality[bot] marked this conversation as resolved.
Fixed
github-code-quality[bot] marked this conversation as resolved.
Fixed
github-code-quality[bot] marked this conversation as resolved.
Fixed
|
||
| return this.properties; | ||
| } | ||
|
|
||
| /** | ||
| * Returns {@code true} if the task failure was provided by the specified exception type, otherwise {@code false}. | ||
| * <p> | ||
|
|
@@ -112,6 +160,11 @@ public boolean isCausedBy(Class<? extends Exception> exceptionClass) { | |
| } | ||
| } | ||
|
Comment on lines
165
to
176
|
||
|
|
||
| @Override | ||
| public String toString() { | ||
| return this.errorType + ": " + this.errorMessage; | ||
| } | ||
|
|
||
| static String getFullStackTrace(Throwable e) { | ||
| StackTraceElement[] elements = e.getStackTrace(); | ||
|
|
||
|
|
@@ -124,10 +177,88 @@ static String getFullStackTrace(Throwable e) { | |
| } | ||
|
|
||
| TaskFailureDetails toProto() { | ||
| return TaskFailureDetails.newBuilder() | ||
| TaskFailureDetails.Builder builder = TaskFailureDetails.newBuilder() | ||
| .setErrorType(this.getErrorType()) | ||
| .setErrorMessage(this.getErrorMessage()) | ||
| .setStackTrace(StringValue.of(this.getStackTrace() != null ? this.getStackTrace() : "")) | ||
| .build(); | ||
| .setIsNonRetriable(this.isNonRetriable); | ||
|
|
||
| if (this.innerFailure != null) { | ||
| builder.setInnerFailure(this.innerFailure.toProto()); | ||
| } | ||
|
|
||
| if (this.properties != null) { | ||
| builder.putAllProperties(convertToProtoProperties(this.properties)); | ||
| } | ||
|
|
||
| return builder.build(); | ||
| } | ||
|
|
||
| @Nullable | ||
| private static FailureDetails fromExceptionRecursive(@Nullable Throwable exception) { | ||
| if (exception == null) { | ||
| return null; | ||
| } | ||
| return new FailureDetails( | ||
| exception.getClass().getName(), | ||
| exception.getMessage(), | ||
| getFullStackTrace(exception), | ||
| false, | ||
| exception.getCause() != null ? fromExceptionRecursive(exception.getCause()) : null, | ||
| null); | ||
| } | ||
|
|
||
| @Nullable | ||
| private static Map<String, Object> convertProtoProperties(Map<String, Value> protoProperties) { | ||
| if (protoProperties == null || protoProperties.isEmpty()) { | ||
| return null; | ||
| } | ||
|
|
||
| Map<String, Object> result = new HashMap<>(); | ||
| for (Map.Entry<String, Value> entry : protoProperties.entrySet()) { | ||
| result.put(entry.getKey(), convertProtoValue(entry.getValue())); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| @Nullable | ||
| private static Object convertProtoValue(Value value) { | ||
| if (value == null) { | ||
| return null; | ||
| } | ||
| switch (value.getKindCase()) { | ||
| case NULL_VALUE: | ||
| return null; | ||
| case NUMBER_VALUE: | ||
| return value.getNumberValue(); | ||
| case STRING_VALUE: | ||
| return value.getStringValue(); | ||
| case BOOL_VALUE: | ||
| return value.getBoolValue(); | ||
| default: | ||
| return value.toString(); | ||
| } | ||
|
nytian marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private static Map<String, Value> convertToProtoProperties(Map<String, Object> properties) { | ||
| Map<String, Value> result = new HashMap<>(); | ||
| for (Map.Entry<String, Object> entry : properties.entrySet()) { | ||
| result.put(entry.getKey(), convertToProtoValue(entry.getValue())); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| private static Value convertToProtoValue(@Nullable Object obj) { | ||
| if (obj == null) { | ||
| return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); | ||
| } else if (obj instanceof Number) { | ||
| return Value.newBuilder().setNumberValue(((Number) obj).doubleValue()).build(); | ||
| } else if (obj instanceof Boolean) { | ||
| return Value.newBuilder().setBoolValue((Boolean) obj).build(); | ||
| } else if (obj instanceof String) { | ||
| return Value.newBuilder().setStringValue((String) obj).build(); | ||
| } else { | ||
| return Value.newBuilder().setStringValue(obj.toString()).build(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
FailureDetails(Exception exception)used anywhere?