From 7c960ad7e9666983a6ced67e25181ee81be307fb Mon Sep 17 00:00:00 2001 From: yuxndjhdh <148313905+yuxndjhdh@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:39:23 +0800 Subject: [PATCH] Log unknown exceptions in FallbackHandler FallbackHandler.handleException only logged when logger.isDebugEnabled() was true before invoking logger.error(...). Gating an ERROR-level log behind a DEBUG check inverts the usual convention: when nothing else handles the exception the fallback branch silently swallowed the causing exception at the default (INFO) logging level. Log unconditionally at ERROR so operators always see genuinely unhandled server exceptions; the returned Status is unchanged and the logger can still be silenced through normal logging configuration. Closes: gh-426 Signed-off-by: yuxndjhdh <148313905+yuxndjhdh@users.noreply.github.com> --- .../GrpcExceptionHandlerInterceptor.java | 4 +- .../GrpcExceptionHandlerInterceptorTests.java | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java index 7be47993..0e6ada0a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java @@ -186,9 +186,7 @@ static class FallbackHandler implements GrpcExceptionHandler { public @Nullable StatusException handleException(Throwable exception) { StatusException status = this.exceptionHandler.handleException(exception); if (status == null) { - if (logger.isDebugEnabled()) { - logger.error("Unknown exception", exception); - } + logger.error("Unknown exception", exception); return Status.fromThrowable(exception).asException(); } return status; diff --git a/spring-grpc-core/src/test/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptorTests.java b/spring-grpc-core/src/test/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptorTests.java index 3b0dafe5..6c60d555 100644 --- a/spring-grpc-core/src/test/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptorTests.java +++ b/spring-grpc-core/src/test/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptorTests.java @@ -22,6 +22,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -54,6 +60,43 @@ void testNullStatusHandled() { .isNotNull(); } + @Test + void unknownExceptionIsLoggedWhenNoHandlerMatches() { + java.util.logging.Logger logger = java.util.logging.Logger + .getLogger("org.springframework.grpc.server.exception.GrpcExceptionHandlerInterceptor$FallbackHandler"); + List records = new ArrayList<>(); + Handler handler = new Handler() { + @Override + public void publish(LogRecord record) { + records.add(record); + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + }; + Level previousLevel = logger.getLevel(); + boolean useParentHandlers = logger.getUseParentHandlers(); + logger.setLevel(Level.ALL); + logger.setUseParentHandlers(false); + logger.addHandler(handler); + try { + new FallbackHandler(exception -> null).handleException(new RuntimeException("Test exception")); + } + finally { + logger.removeHandler(handler); + logger.setUseParentHandlers(useParentHandlers); + logger.setLevel(previousLevel); + } + assertThat(records).anySatisfy((record) -> { + assertThat(record.getMessage()).isEqualTo("Unknown exception"); + }); + } + @Test void propagatesTrailersFromStatusExceptionWhenStartCallThrows() { Status statusWithDetails = Status.newBuilder()