Skip to content

Commit

Permalink
Add code to capture caller stack frames
Browse files Browse the repository at this point in the history
  • Loading branch information
bannmann committed Nov 28, 2023
1 parent 5686542 commit d8c3121
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 6 deletions.
22 changes: 21 additions & 1 deletion src/main/java/dev/bannmann/restflow/AbstractRequester.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;

import lombok.RequiredArgsConstructor;

import com.google.common.collect.ImmutableList;
import dev.failsafe.Failsafe;
import dev.failsafe.Policy;

Expand All @@ -20,6 +23,8 @@ abstract class AbstractRequester<B, R> implements Requester<R>
protected final ClientConfig clientConfig;
protected final ConcurrentMap<String, Object> diagnosticsData = new ConcurrentHashMap<>();

private ImmutableList<StackWalker.StackFrame> callerFrames;

@Override
public final CompletableFuture<R> start()
{
Expand All @@ -30,9 +35,24 @@ public final CompletableFuture<R> start()
diagnosticsData.putAll(values);
}

int callerFrameCount = clientConfig.getCallerFrameCount();
if (callerFrameCount > 0)
{
var stackWalker = StackWalker.getInstance(Collections.emptySet(), callerFrameCount);
callerFrames = stackWalker.walk(stream -> captureCallerFrames(callerFrameCount, stream));
}

return send().thenApply(this::extractValue);
}

private ImmutableList<StackWalker.StackFrame> captureCallerFrames(int count, Stream<StackWalker.StackFrame> stream)
{
return stream.dropWhile(stackFrame -> stackFrame.getClassName()
.startsWith(getClass().getPackageName()))
.limit(count)
.collect(ImmutableList.toImmutableList());
}

private CompletableFuture<HttpResponse<B>> send()
{
List<Policy<HttpResponse<?>>> policies = clientConfig.getPolicies();
Expand Down Expand Up @@ -60,7 +80,7 @@ private <T> T addDetailsForLowLevelExceptions(T result, Throwable throwable)
if (throwable != null)
{
String message = String.format("Request to URL %s failed", request.uri());
throw new RequestFailureException(request, message, throwable, diagnosticsData);
throw new RequestFailureException(request, message, throwable, diagnosticsData, callerFrames);
}
return result;
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/dev/bannmann/restflow/ClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ public final class ClientConfig

@Singular
private final List<RequestCustomizer> requestCustomizers;

/**
* The number of caller stack frames to capture when starting a request. The captured frames will be included in
* any {@link RequestException} (or subclass) instance thrown by restflow. This is useful if the application is
* supposed to log intermediate exceptions, which will otherwise not carry any stack information relating to the
* original thread (due to neither being thrown there nor being set as the cause of an exception thrown in that
* thread).
*/
@Builder.Default
private final int callerFrameCount = 5;
}
9 changes: 8 additions & 1 deletion src/main/java/dev/bannmann/restflow/RequestException.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.bannmann.restflow;

import java.net.http.HttpRequest;
import java.util.List;
import java.util.Map;

import lombok.Getter;
Expand All @@ -14,12 +15,18 @@ public abstract class RequestException extends RuntimeException
{
private final transient HttpRequest request;
private final transient Map<String, Object> diagnosticsData;
private final transient List<StackWalker.StackFrame> callerFrames;

protected RequestException(
HttpRequest request, String message, Throwable cause, Map<String, Object> diagnosticsData)
HttpRequest request,
String message,
Throwable cause,
Map<String, Object> diagnosticsData,
List<StackWalker.StackFrame> callerFrames)
{
super(message, cause);
this.request = request;
this.diagnosticsData = diagnosticsData;
this.callerFrames = callerFrames;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.bannmann.restflow;

import java.net.http.HttpRequest;
import java.util.List;
import java.util.Map;

/**
Expand All @@ -11,8 +12,12 @@
public class RequestFailureException extends RequestException
{
public RequestFailureException(
HttpRequest request, String message, Throwable cause, Map<String, Object> diagnosticsData)
HttpRequest request,
String message,
Throwable cause,
Map<String, Object> diagnosticsData,
List<StackWalker.StackFrame> callerFrames)
{
super(request, message, cause, diagnosticsData);
super(request, message, cause, diagnosticsData, callerFrames);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;

import lombok.Builder;
Expand All @@ -27,9 +28,10 @@ public RequestStatusException(
HttpResponse<?> response,
int status,
String body,
Map<String, Object> diagnosticsData)
Map<String, Object> diagnosticsData,
List<StackWalker.StackFrame> callerFrames)
{
super(request, message, cause, diagnosticsData);
super(request, message, cause, diagnosticsData, callerFrames);

this.response = response;
this.status = status;
Expand Down

0 comments on commit d8c3121

Please sign in to comment.