Skip to content

Commit 4870467

Browse files
authored
Add ability to show stack traces in danger alerts (#215)
* Add ability to show stack traces in danger alerts * (Slightly) improve error handling
1 parent fb860ca commit 4870467

File tree

5 files changed

+121
-25
lines changed

5 files changed

+121
-25
lines changed

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/cluster/ClusterConfigController.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
package org.sourcelab.kafka.webview.ui.controller.configuration.cluster;
2626

27-
import org.apache.kafka.common.KafkaException;
2827
import org.slf4j.Logger;
2928
import org.slf4j.LoggerFactory;
3029
import org.sourcelab.kafka.webview.ui.controller.BaseController;
@@ -421,12 +420,14 @@ public String testCluster(@PathVariable final Long id, final RedirectAttributes
421420
redirectAttributes.addFlashAttribute("FlashMessage", FlashMessage.newSuccess("Cluster configuration is valid!"));
422421
}
423422
} catch (final Exception e) {
424-
String reason = e.getMessage();
425-
if (e instanceof KafkaException && e.getCause() != null) {
426-
reason = e.getCause().getMessage();
427-
}
423+
// Collect all reasons.
424+
final String reason = e.getMessage();
425+
428426
// Set error msg
429-
redirectAttributes.addFlashAttribute("FlashMessage", FlashMessage.newDanger("Error connecting to cluster: " + reason));
427+
redirectAttributes.addFlashAttribute(
428+
"FlashMessage",
429+
FlashMessage.newDanger("Error connecting to cluster: " + reason, e)
430+
);
430431

431432
// Mark as invalid
432433
cluster.setValid(false);

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/KafkaOperations.java

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.apache.kafka.clients.admin.TopicListing;
4242
import org.apache.kafka.clients.consumer.KafkaConsumer;
4343
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
44+
import org.apache.kafka.common.KafkaException;
4445
import org.apache.kafka.common.Node;
4546
import org.apache.kafka.common.TopicPartitionInfo;
4647
import org.apache.kafka.common.config.ConfigResource;
@@ -114,7 +115,9 @@ public TopicList getAvailableTopics() {
114115
);
115116
}
116117
return new TopicList(topicListings);
117-
} catch (InterruptedException | ExecutionException e) {
118+
} catch (final ExecutionException e) {
119+
throw handleExecutionException(e);
120+
} catch (final InterruptedException e) {
118121
// TODO Handle
119122
throw new RuntimeException(e.getMessage(), e);
120123
}
@@ -128,12 +131,13 @@ public NodeList getClusterNodes() {
128131

129132
try {
130133
final Collection<Node> nodes = adminClient.describeCluster().nodes().get();
131-
for (final Node node: nodes) {
134+
for (final Node node : nodes) {
132135
nodeDetails.add(new NodeDetails(node.id(), node.host(), node.port(), node.rack()));
133136
}
134137
return new NodeList(nodeDetails);
135-
} catch (InterruptedException | ExecutionException e) {
136-
// TODO Handle
138+
} catch (final ExecutionException e) {
139+
throw handleExecutionException(e);
140+
} catch (final InterruptedException e) {
137141
throw new RuntimeException(e.getMessage(), e);
138142
}
139143
}
@@ -201,7 +205,9 @@ public Map<String, TopicDetails> getTopicDetails(final Collection<String> topics
201205
}
202206
// Return it
203207
return results;
204-
} catch (final InterruptedException | ExecutionException exception) {
208+
} catch (final ExecutionException e) {
209+
throw handleExecutionException(e);
210+
} catch (final InterruptedException exception) {
205211
// TODO Handle this
206212
throw new RuntimeException(exception.getMessage(), exception);
207213
}
@@ -252,7 +258,9 @@ public boolean createTopic(final CreateTopic createTopic) {
252258

253259
// return true?
254260
return true;
255-
} catch (final InterruptedException | ExecutionException exception) {
261+
} catch (final ExecutionException e) {
262+
throw handleExecutionException(e);
263+
} catch (final InterruptedException exception) {
256264
// TODO Handle this
257265
throw new RuntimeException(exception.getMessage(), exception);
258266
}
@@ -287,7 +295,9 @@ public TopicConfig alterTopicConfig(final String topic, final Map<String, String
287295

288296
// Lets return updated topic details
289297
return getTopicConfig(topic);
290-
} catch (final InterruptedException | ExecutionException exception) {
298+
} catch (final ExecutionException e) {
299+
throw handleExecutionException(e);
300+
} catch (final InterruptedException exception) {
291301
// TODO Handle this
292302
throw new RuntimeException(exception.getMessage(), exception);
293303
}
@@ -307,7 +317,9 @@ public boolean removeTopic(final String topic) {
307317

308318
// return true?
309319
return true;
310-
} catch (final InterruptedException | ExecutionException exception) {
320+
} catch (final ExecutionException e) {
321+
throw handleExecutionException(e);
322+
} catch (final InterruptedException exception) {
311323
// TODO Handle this
312324
throw new RuntimeException(exception.getMessage(), exception);
313325
}
@@ -340,8 +352,9 @@ public List<ConsumerGroupIdentifier> listConsumers() {
340352

341353
// return immutable list.
342354
return Collections.unmodifiableList(consumerIds);
343-
344-
} catch (final InterruptedException | ExecutionException e) {
355+
} catch (final ExecutionException e) {
356+
throw handleExecutionException(e);
357+
} catch (final InterruptedException e) {
345358
throw new RuntimeException(e.getMessage(), e);
346359
}
347360
}
@@ -358,7 +371,9 @@ public boolean removeConsumerGroup(final String id) {
358371
try {
359372
request.all().get();
360373
return true;
361-
} catch (InterruptedException | ExecutionException e) {
374+
} catch (final ExecutionException e) {
375+
throw handleExecutionException(e);
376+
} catch (InterruptedException e) {
362377
// TODO Handle this
363378
throw new RuntimeException(e.getMessage(), e);
364379
}
@@ -444,7 +459,9 @@ public List<ConsumerGroupDetails> getConsumerGroupDetails(final List<String> con
444459

445460
// Return immutable list.
446461
return Collections.unmodifiableList(consumerGroupDetails);
447-
} catch (final InterruptedException | ExecutionException e) {
462+
} catch (final ExecutionException e) {
463+
throw handleExecutionException(e);
464+
} catch (final InterruptedException e) {
448465
throw new RuntimeException(e.getMessage(), e);
449466
}
450467
}
@@ -476,7 +493,9 @@ public ConsumerGroupOffsets getConsumerGroupOffsets(final String consumerGroupId
476493
}
477494

478495
return builder.build();
479-
} catch (final InterruptedException | ExecutionException e) {
496+
} catch (final ExecutionException e) {
497+
throw handleExecutionException(e);
498+
} catch (final InterruptedException e) {
480499
throw new RuntimeException(e.getMessage(), e);
481500
}
482501
}
@@ -573,12 +592,21 @@ private List<ConfigItem> describeResource(final ConfigResource configResource) {
573592
);
574593
}
575594
return configItems;
576-
} catch (InterruptedException | ExecutionException e) {
595+
} catch (final ExecutionException e) {
596+
throw handleExecutionException(e);
597+
} catch (InterruptedException e) {
577598
// TODO Handle this
578599
throw new RuntimeException(e.getMessage(), e);
579600
}
580601
}
581602

603+
private RuntimeException handleExecutionException(final ExecutionException e) {
604+
if (e.getCause() != null && e.getCause() instanceof RuntimeException) {
605+
return (RuntimeException) e.getCause();
606+
}
607+
return new RuntimeException(e.getMessage(), e);
608+
}
609+
582610
/**
583611
* Close out the Client.
584612
*/

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/dto/ApiErrorResponse.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
package org.sourcelab.kafka.webview.ui.manager.kafka.dto;
2626

27+
import net.bytebuddy.implementation.bytecode.Throw;
28+
2729
import java.util.ArrayList;
2830
import java.util.Arrays;
2931
import java.util.List;
@@ -77,7 +79,7 @@ public String toString() {
7779
* @param exception exception.
7880
* @return Array of ApiErrorCauses.
7981
*/
80-
public static ApiErrorCause[] buildCauseList(final Exception exception) {
82+
public static ApiErrorCause[] buildCauseList(final Throwable exception) {
8183
if (exception == null) {
8284
return new ApiErrorCause[0];
8385
}

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/FlashMessage.java

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,30 @@
2424

2525
package org.sourcelab.kafka.webview.ui.manager.ui;
2626

27+
import org.sourcelab.kafka.webview.ui.manager.kafka.dto.ApiErrorCause;
28+
import org.sourcelab.kafka.webview.ui.manager.kafka.dto.ApiErrorResponse;
29+
30+
import java.util.ArrayList;
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Objects;
34+
2735
/**
2836
* Represents an Alert Message.
2937
*/
3038
public class FlashMessage {
3139
private final String type;
3240
private final String message;
41+
private final List<String> details;
3342

3443
private FlashMessage(final String type, final String message) {
35-
this.type = type;
36-
this.message = message;
44+
this(type, message, Collections.emptyList());
45+
}
46+
47+
private FlashMessage(final String type, final String message, final List<String> details) {
48+
this.type = Objects.requireNonNull(type);
49+
this.message = Objects.requireNonNull(message);
50+
this.details = Objects.requireNonNull(Collections.unmodifiableList(new ArrayList<>(details)));
3751
}
3852

3953
public String getType() {
@@ -60,6 +74,14 @@ public boolean isDanger() {
6074
return "danger".equals(getType());
6175
}
6276

77+
public boolean hasDetails() {
78+
return !details.isEmpty();
79+
}
80+
81+
public List<String> getDetails() {
82+
return details;
83+
}
84+
6385
public static FlashMessage newSuccess(final String message) {
6486
return new FlashMessage("success", message);
6587
}
@@ -76,6 +98,38 @@ public static FlashMessage newDanger(final String message) {
7698
return new FlashMessage("danger", message);
7799
}
78100

101+
/**
102+
* Create a new Danger alert with stack trace.
103+
* @param message Message to display.
104+
* @param details Additional details about the error.
105+
* @return FlashMessage instance.
106+
*/
107+
public static FlashMessage newDanger(final String message, final List<String> details) {
108+
return new FlashMessage("danger", message, details);
109+
}
110+
111+
/**
112+
* Create a new Danger alert with stack trace.
113+
* @param message Message to display.
114+
* @param cause Underlying exception.
115+
* @return FlashMessage instance.
116+
*/
117+
public static FlashMessage newDanger(final String message, final Throwable cause) {
118+
final List<String> reasons = convertExceptionToDetails(cause);
119+
120+
return new FlashMessage("danger", message, reasons);
121+
}
122+
123+
private static List<String> convertExceptionToDetails(final Throwable throwable) {
124+
final ApiErrorCause[] causes = ApiErrorResponse.buildCauseList(throwable);
125+
final List<String> reasons = new ArrayList<>();
126+
127+
for (final ApiErrorCause cause : causes) {
128+
reasons.add(cause.getType() + " thrown at " + cause.getMethod() + " -> " + cause.getMessage());
129+
}
130+
return reasons;
131+
}
132+
79133
@Override
80134
public String toString() {
81135
return "FlashMessage{"

kafka-webview-ui/src/main/resources/templates/fragments/alert.html

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@
44
<body>
55

66
<!-- Handles showing Alerts -->
7-
<div th:if="${FlashMessage != null}" class="alert alert-dismissable" th:fragment="alert (message)" th:classappend="'alert-' + ${message.getType()}">
7+
<div th:if="${message != null}" class="alert alert-dismissable" th:fragment="alert (message)" th:classappend="'alert-' + ${message.getType()}">
88
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
9-
<span th:text="${message.getMessage()}"></span>
9+
<span th:text="${message.getMessage()}">
10+
</span>
11+
<span th:if="${message.hasDetails()}">
12+
(<a data-toggle="collapse" href="#stackTrace" role="button" aria-expanded="false" aria-controls="stackTrace">show more</a>)
13+
</span>
14+
<div th:if="${message.hasDetails()}" class="collapse" id="stackTrace" >
15+
<ol>
16+
<li th:each="detail : ${message.getDetails()}" th:text="${detail}">
17+
</li>
18+
</ol>
19+
</div>
20+
1021
</div>
1122

1223
</body>

0 commit comments

Comments
 (0)