Better error handling and logging
This commit is contained in:
parent
329293cf9a
commit
078ab15a86
9 changed files with 80 additions and 21 deletions
|
@ -174,7 +174,7 @@ public final class CircuitBreaker<T> {
|
||||||
this.state = State.HALF_OPEN;
|
this.state = State.HALF_OPEN;
|
||||||
this.failingCount.set(0);
|
this.failingCount.set(0);
|
||||||
return Result.ofError(new RuntimeException(
|
return Result.ofError(new RuntimeException(
|
||||||
"Set CircuitBeaker to half-open state. Cause: ",
|
"Set CircuitBeaker to half-open state. Cause: " + result.getError().getMessage(),
|
||||||
result.getError()));
|
result.getError()));
|
||||||
} else {
|
} else {
|
||||||
// try again
|
// try again
|
||||||
|
@ -204,7 +204,7 @@ public final class CircuitBreaker<T> {
|
||||||
|
|
||||||
this.state = State.OPEN;
|
this.state = State.OPEN;
|
||||||
return Result.ofError(new RuntimeException(
|
return Result.ofError(new RuntimeException(
|
||||||
"Set CircuitBeaker to open state. Cause: ",
|
"Set CircuitBeaker to open state. Cause: " + result.getError().getMessage(),
|
||||||
result.getError()));
|
result.getError()));
|
||||||
} else {
|
} else {
|
||||||
// on success go to CLOSED state
|
// on success go to CLOSED state
|
||||||
|
|
|
@ -71,4 +71,13 @@ public interface RestService {
|
||||||
EntityType entityType,
|
EntityType entityType,
|
||||||
CallType callType);
|
CallType callType);
|
||||||
|
|
||||||
|
/** Use this to inject the current SEB Server API access RestTemplate to a long living
|
||||||
|
* RestCallBuilder. This is usually used to recover from a disposed API access RestTemplate.
|
||||||
|
*
|
||||||
|
* @param <T> The generic type of RestCallBuilder
|
||||||
|
* @param builder the RestCallBuilder to inject the current RestTemplate into. */
|
||||||
|
default <T> void injectCurrentRestTemplate(final RestCall<T>.RestCallBuilder builder) {
|
||||||
|
builder.withRestTemplate(getWebserviceAPIRestTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
|
||||||
|
|
||||||
|
public class DisposedOAuth2RestTemplateException extends IllegalStateException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -8439656564917103027L;
|
||||||
|
|
||||||
|
public DisposedOAuth2RestTemplateException(final String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -132,7 +132,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
return super.doExecute(url, method, requestCallback, responseExtractor);
|
return super.doExecute(url, method, requestCallback, responseExtractor);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException(
|
throw new DisposedOAuth2RestTemplateException(
|
||||||
"Error: Forbidden execution call on disabled DisposableOAuth2RestTemplate");
|
"Error: Forbidden execution call on disabled DisposableOAuth2RestTemplate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
|
||||||
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
||||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||||
|
|
||||||
|
@ -135,6 +136,7 @@ public class ClientConnectionDetails {
|
||||||
.call()
|
.call()
|
||||||
.get(error -> {
|
.get(error -> {
|
||||||
log.error("Unexpected error while trying to get current client connection data: ", error);
|
log.error("Unexpected error while trying to get current client connection data: ", error);
|
||||||
|
recoverFromDisposedRestTemplate(error);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,4 +236,13 @@ public class ClientConnectionDetails {
|
||||||
pageContext);
|
pageContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void recoverFromDisposedRestTemplate(final Exception error) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Try to recover from disposed OAuth2 rest template...");
|
||||||
|
}
|
||||||
|
if (error instanceof DisposedOAuth2RestTemplateException) {
|
||||||
|
this.pageService.getRestService().injectCurrentRestTemplate(this.restCallBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
|
||||||
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
|
||||||
|
@ -90,8 +91,7 @@ public final class ClientConnectionTable {
|
||||||
private final static LocTextKey CONNECTION_STATUS_TOOLTIP_TEXT_KEY =
|
private final static LocTextKey CONNECTION_STATUS_TOOLTIP_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.monitoring.connection.list.column.status" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
new LocTextKey("sebserver.monitoring.connection.list.column.status" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||||
|
|
||||||
private final WidgetFactory widgetFactory;
|
private final PageService pageService;
|
||||||
private final ResourceService resourceService;
|
|
||||||
private final AsyncRunner asyncRunner;
|
private final AsyncRunner asyncRunner;
|
||||||
private final Exam exam;
|
private final Exam exam;
|
||||||
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
|
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
|
||||||
|
@ -124,12 +124,14 @@ public final class ClientConnectionTable {
|
||||||
final Collection<Indicator> indicators,
|
final Collection<Indicator> indicators,
|
||||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) {
|
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) {
|
||||||
|
|
||||||
this.widgetFactory = pageService.getWidgetFactory();
|
this.pageService = pageService;
|
||||||
this.resourceService = pageService.getResourceService();
|
|
||||||
this.asyncRunner = asyncRunner;
|
this.asyncRunner = asyncRunner;
|
||||||
this.exam = exam;
|
this.exam = exam;
|
||||||
this.restCallBuilder = restCallBuilder;
|
this.restCallBuilder = restCallBuilder;
|
||||||
|
|
||||||
|
final WidgetFactory widgetFactory = pageService.getWidgetFactory();
|
||||||
|
final ResourceService resourceService = pageService.getResourceService();
|
||||||
|
|
||||||
final Display display = tableRoot.getDisplay();
|
final Display display = tableRoot.getDisplay();
|
||||||
this.colorData = new ColorData(display);
|
this.colorData = new ColorData(display);
|
||||||
|
|
||||||
|
@ -143,11 +145,11 @@ public final class ClientConnectionTable {
|
||||||
NUMBER_OF_NONE_INDICATOR_COLUMNS);
|
NUMBER_OF_NONE_INDICATOR_COLUMNS);
|
||||||
|
|
||||||
this.localizedClientConnectionStatusNameFunction =
|
this.localizedClientConnectionStatusNameFunction =
|
||||||
this.resourceService.localizedClientConnectionStatusNameFunction();
|
resourceService.localizedClientConnectionStatusNameFunction();
|
||||||
this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
|
this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
|
||||||
loadStatusFilter();
|
loadStatusFilter();
|
||||||
|
|
||||||
this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL);
|
this.table = widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL);
|
||||||
final GridLayout gridLayout = new GridLayout(3 + indicators.size(), false);
|
final GridLayout gridLayout = new GridLayout(3 + indicators.size(), false);
|
||||||
gridLayout.horizontalSpacing = 100;
|
gridLayout.horizontalSpacing = 100;
|
||||||
gridLayout.marginWidth = 100;
|
gridLayout.marginWidth = 100;
|
||||||
|
@ -161,20 +163,20 @@ public final class ClientConnectionTable {
|
||||||
this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
|
this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
|
||||||
this.table.addListener(SWT.MouseUp, this::notifyTableInfoClick);
|
this.table.addListener(SWT.MouseUp, this::notifyTableInfoClick);
|
||||||
|
|
||||||
this.widgetFactory.tableColumnLocalized(
|
widgetFactory.tableColumnLocalized(
|
||||||
this.table,
|
this.table,
|
||||||
CONNECTION_ID_TEXT_KEY,
|
CONNECTION_ID_TEXT_KEY,
|
||||||
CONNECTION_ID_TOOLTIP_TEXT_KEY);
|
CONNECTION_ID_TOOLTIP_TEXT_KEY);
|
||||||
this.widgetFactory.tableColumnLocalized(
|
widgetFactory.tableColumnLocalized(
|
||||||
this.table,
|
this.table,
|
||||||
CONNECTION_ADDRESS_TEXT_KEY,
|
CONNECTION_ADDRESS_TEXT_KEY,
|
||||||
CONNECTION_ADDRESS_TOOLTIP_TEXT_KEY);
|
CONNECTION_ADDRESS_TOOLTIP_TEXT_KEY);
|
||||||
this.widgetFactory.tableColumnLocalized(
|
widgetFactory.tableColumnLocalized(
|
||||||
this.table,
|
this.table,
|
||||||
CONNECTION_STATUS_TEXT_KEY,
|
CONNECTION_STATUS_TEXT_KEY,
|
||||||
CONNECTION_STATUS_TOOLTIP_TEXT_KEY);
|
CONNECTION_STATUS_TOOLTIP_TEXT_KEY);
|
||||||
for (final Indicator indDef : indicators) {
|
for (final Indicator indDef : indicators) {
|
||||||
final TableColumn tableColumn = this.widgetFactory.tableColumnLocalized(
|
final TableColumn tableColumn = widgetFactory.tableColumnLocalized(
|
||||||
this.table,
|
this.table,
|
||||||
new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.name),
|
new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.name),
|
||||||
new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.type.name));
|
new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.type.name));
|
||||||
|
@ -187,7 +189,7 @@ public final class ClientConnectionTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public WidgetFactory getWidgetFactory() {
|
public WidgetFactory getWidgetFactory() {
|
||||||
return this.widgetFactory;
|
return this.pageService.getWidgetFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
|
@ -325,7 +327,11 @@ public final class ClientConnectionTable {
|
||||||
this.restCallBuilder
|
this.restCallBuilder
|
||||||
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
|
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow()
|
.get(error -> {
|
||||||
|
log.error("Unexpected error while trying to get client connection table data: ", error);
|
||||||
|
recoverFromDisposedRestTemplate(error);
|
||||||
|
return Collections.emptyList();
|
||||||
|
})
|
||||||
.forEach(data -> {
|
.forEach(data -> {
|
||||||
final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
|
final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
|
||||||
data.getConnectionId(),
|
data.getConnectionId(),
|
||||||
|
@ -425,7 +431,7 @@ public final class ClientConnectionTable {
|
||||||
|
|
||||||
private void saveStatusFilter() {
|
private void saveStatusFilter() {
|
||||||
try {
|
try {
|
||||||
this.resourceService
|
this.pageService
|
||||||
.getCurrentUser()
|
.getCurrentUser()
|
||||||
.putAttribute(
|
.putAttribute(
|
||||||
USER_SESSION_STATUS_FILTER_ATTRIBUTE,
|
USER_SESSION_STATUS_FILTER_ATTRIBUTE,
|
||||||
|
@ -440,7 +446,7 @@ public final class ClientConnectionTable {
|
||||||
|
|
||||||
private void loadStatusFilter() {
|
private void loadStatusFilter() {
|
||||||
try {
|
try {
|
||||||
final String attribute = this.resourceService
|
final String attribute = this.pageService
|
||||||
.getCurrentUser()
|
.getCurrentUser()
|
||||||
.getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
|
.getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
|
||||||
this.statusFilter.clear();
|
this.statusFilter.clear();
|
||||||
|
@ -707,4 +713,13 @@ public final class ClientConnectionTable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void recoverFromDisposedRestTemplate(final Exception error) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Try to recover from disposed OAuth2 rest template...");
|
||||||
|
}
|
||||||
|
if (error instanceof DisposedOAuth2RestTemplateException) {
|
||||||
|
this.pageService.getRestService().injectCurrentRestTemplate(this.restCallBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -941,7 +941,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.warn("Failed to try to recover from Moodle quiz restore: ", e.getMessage());
|
log.warn("Failed to try to recover from Moodle quiz restore: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,14 +448,14 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
if (courses == null) {
|
if (courses == null) {
|
||||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||||
Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
logMoodleWarnings(courses.warnings);
|
logMoodleWarnings(courses.warnings);
|
||||||
|
|
||||||
if (courses.courses == null || courses.courses.isEmpty()) {
|
if (courses.courses == null || courses.courses.isEmpty()) {
|
||||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||||
Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return courses.courses;
|
return courses.courses;
|
||||||
|
|
|
@ -324,9 +324,14 @@ class MoodleRestTemplateFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
final String body = response.getBody();
|
final String body = response.getBody();
|
||||||
|
|
||||||
// NOTE: for some unknown reason, Moodles API error responses come with a 200 OK response HTTP Status
|
// NOTE: for some unknown reason, Moodles API error responses come with a 200 OK response HTTP Status
|
||||||
// So this is a special Moodle specific error handling here...
|
// So this is a special Moodle specific error handling here...
|
||||||
if (body.startsWith("{exception")) {
|
if (body.startsWith("{exception") || body.contains("\"exception\":")) {
|
||||||
|
// Reset access token to get new on next call (fix access if token is expired)
|
||||||
|
// TODO find a way to verify token invalidity response from Moodle.
|
||||||
|
// Unfortunately there is not a lot of Moodle documentation for the API error handling around.
|
||||||
|
this.accessToken = null;
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
||||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + body);
|
MoodleRestTemplateFactory.this.lmsSetup + " response: " + body);
|
||||||
|
|
Loading…
Add table
Reference in a new issue