Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,16 @@ public class IcebergExceptionMapper implements ExceptionMapper<RuntimeException>

@Override
public Response toResponse(RuntimeException runtimeException) {
getLogger().info("Handling runtimeException {}", runtimeException.getMessage());
LOGGER.info("Handling runtimeException {}", runtimeException.getMessage());

int responseCode = mapExceptionToResponseCode(runtimeException);
getLogger()
.atLevel(responseCode >= 500 ? Level.INFO : Level.DEBUG)
.log("Full RuntimeException", runtimeException);

if (responseCode == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
getLogger().error("Unhandled exception returning INTERNAL_SERVER_ERROR", runtimeException);
if (responseCode == Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
getLoggerForExceptionLogging()
.error("Unhandled exception returning INTERNAL_SERVER_ERROR", runtimeException);
} else {
getLoggerForExceptionLogging()
.atLevel(responseCode > 500 ? Level.INFO : Level.DEBUG)
.log("Full RuntimeException", runtimeException);
}

ErrorResponse icebergErrorResponse =
Expand All @@ -111,7 +112,7 @@ public Response toResponse(RuntimeException runtimeException) {
.entity(icebergErrorResponse)
.type(MediaType.APPLICATION_JSON_TYPE)
.build();
getLogger().debug("Mapped exception to errorResp: {}", errorResp);
LOGGER.debug("Mapped exception to errorResp: {}", errorResp);
return errorResp;
}

Expand Down Expand Up @@ -261,8 +262,12 @@ static Optional<Integer> mapCloudExceptionToResponseCode(Throwable t) {
return Optional.of(Status.INTERNAL_SERVER_ERROR.getStatusCode());
}

/**
* This function is only present for the {@code ExceptionMapperTest} and must only be used once
* during any execution of {@link #toResponse(RuntimeException)}.
*/
@VisibleForTesting
Logger getLogger() {
Logger getLoggerForExceptionLogging() {
return LOGGER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public Response toResponse(JsonProcessingException exception) {
if (exception instanceof JsonGenerationException
|| exception instanceof InvalidDefinitionException) {
long id = ThreadLocalRandom.current().nextLong();
getLogger()
getLoggerForExceptionLogging()
.error(String.format(Locale.ROOT, "Error handling a request: %016x", id), exception);
String message =
String.format(
Expand All @@ -77,8 +77,8 @@ public Response toResponse(JsonProcessingException exception) {
/*
* Otherwise, it's those pesky users.
*/
getLogger().info("Unable to process JSON: {}", exception.getMessage());
getLogger().debug("Full JsonProcessingException", exception);
LOGGER.info("Unable to process JSON: {}", exception.getMessage());
getLoggerForExceptionLogging().debug("Full JsonProcessingException", exception);

String messagePrefix =
switch (exception) {
Expand All @@ -99,8 +99,12 @@ public Response toResponse(JsonProcessingException exception) {
.build();
}

/**
* This function is only present for the {@code ExceptionMapperTest} and must only be used during
* once any execution of {@link #toResponse(JsonProcessingException)}.
*/
@VisibleForTesting
Logger getLogger() {
Logger getLoggerForExceptionLogging() {
return LOGGER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,25 @@
*/
package org.apache.polaris.service.exception;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.iceberg.exceptions.RuntimeIOException;
import org.apache.polaris.core.exceptions.AlreadyExistsException;
import org.apache.polaris.core.exceptions.CommitConflictException;
import org.assertj.core.api.Assertions;
import org.jboss.logmanager.Level;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -67,35 +71,33 @@ public void testFullExceptionIsLogged(

verify(JBOSS_LOGGER)
.logRaw(
argThat(
assertArg(
record -> {
if (record.getLevel() != level) {
return false;
}
assertThat(record.getLevel()).isEqualTo(level);

String message = record.getMessage();
if (message == null) {
return false;
return;
}

Throwable error = record.getThrown();
if (error == null) {
return false;
return;
}

return message.contains(CAUSE)
|| Optional.ofNullable(error.getCause())
.map(Throwable::getMessage)
.orElse("")
.contains(CAUSE);
assertThat(
Optional.ofNullable(error.getCause())
.map(Throwable::getMessage)
.orElse(message))
.contains(CAUSE);
}));
}

@Test
public void testNamespaceException() {
PolarisExceptionMapper mapper = new PolarisExceptionMapper();
Response response = mapper.toResponse(new CommitConflictException("test"));
Assertions.assertThat(response.getStatus()).isEqualTo(409);
assertThat(response.getStatus()).isEqualTo(409);
}

static Stream<Arguments> testFullExceptionIsLogged() {
Expand All @@ -105,21 +107,57 @@ static Stream<Arguments> testFullExceptionIsLogged() {
Arguments.of(
new IcebergExceptionMapper() {
@Override
Logger getLogger() {
Logger getLoggerForExceptionLogging() {
return new Slf4jLogger(JBOSS_LOGGER);
}
},
new RuntimeException(MESSAGE, new RuntimeException(CAUSE)),
Level.ERROR),
Arguments.of(
new IcebergExceptionMapper() {
@Override
Logger getLoggerForExceptionLogging() {
return new Slf4jLogger(JBOSS_LOGGER);
}
},
new RuntimeIOException(new IOException(new RuntimeException(CAUSE)), "%s", MESSAGE),
Level.INFO),
Arguments.of(
new IcebergExceptionMapper() {
@Override
Logger getLoggerForExceptionLogging() {
return new Slf4jLogger(JBOSS_LOGGER);
}
},
new ForbiddenException(MESSAGE, new RuntimeException(CAUSE)),
Level.DEBUG),
Arguments.of(
new IcebergJsonProcessingExceptionMapper() {
@Override
Logger getLogger() {
Logger getLoggerForExceptionLogging() {
return new Slf4jLogger(JBOSS_LOGGER);
}
},
new TestJsonProcessingException(MESSAGE, null, new RuntimeException(CAUSE)),
Level.DEBUG),
Arguments.of(
new IcebergJsonProcessingExceptionMapper() {
@Override
Logger getLoggerForExceptionLogging() {
return new Slf4jLogger(JBOSS_LOGGER);
}
},
new TestJsonProcessingException(MESSAGE, null, new RuntimeException(CAUSE)),
Level.DEBUG),
Arguments.of(
new IcebergJsonProcessingExceptionMapper() {
@Override
Logger getLoggerForExceptionLogging() {
return new Slf4jLogger(JBOSS_LOGGER);
}
},
new JsonGenerationException(MESSAGE, new RuntimeException(CAUSE), null),
Level.ERROR),
Arguments.of(
new PolarisExceptionMapper() {
@Override
Expand Down