-
Notifications
You must be signed in to change notification settings - Fork 979
Add decorator support for ServiceErrorHandler #6570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughThe pull request refactors the error handling decorator framework to support both ServerErrorHandler and ServiceErrorHandler types uniformly. Classes and interfaces are renamed for broader applicability, new method overloads are introduced, and decorator application is extended to the service-level error handling path. Changes
Poem
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (3)
🧰 Additional context used📓 Path-based instructions (1)**/*.java⚙️ CodeRabbit configuration file
Files:
🧬 Code graph analysis (1)core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
🔇 Additional comments (4)
✏️ Tip: You can disable this entire section by setting Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6570 +/- ##
============================================
- Coverage 74.46% 74.38% -0.08%
- Complexity 22234 23714 +1480
============================================
Files 1963 2128 +165
Lines 82437 88614 +6177
Branches 10764 11587 +823
============================================
+ Hits 61385 65914 +4529
- Misses 15918 17165 +1247
- Partials 5134 5535 +401 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ikhoon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! 👍👍
jrhee17
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, left a minor question
| static ServerErrorHandler decorate(ServerErrorHandler delegate) { | ||
| ServerErrorHandler decorated = delegate; | ||
| for (DecoratingServerErrorHandlerFunction decoratorFunction : decoratorFunctions) { | ||
| for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Since the ServiceErrorHandler is decorated, is there still a need to decorate the ServerErrorHandler?
e.g. the following diff seems to still pass the test:
user@AL02437565 upstream-armeria % git diff
diff --git a/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java b/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
index faa2867cf..a614d3af0 100644
--- a/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
+++ b/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
@@ -35,11 +35,7 @@ final class ErrorHandlerDecorators {
private ErrorHandlerDecorators() {}
static ServerErrorHandler decorate(ServerErrorHandler delegate) {
- ServerErrorHandler decorated = delegate;
- for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) {
- decorated = new DecoratingServerErrorHandler(decorated, decoratorFunction);
- }
- return decorated;
+ return delegate;
}
static ServiceErrorHandler decorate(ServiceErrorHandler delegate) {
user@AL02437565 upstream-armeria %
The concern is that implementors of the default ErrorHandlerDecorators#decoratorFunctions need to be aware that each DecoratingErrorHandlerFunction may be applied twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good finding. I think we need to remove the decorating here:
| final ServerErrorHandler errorHandler = ErrorHandlerDecorators.decorate( |
and add it here instead:
| this.errorHandler = requireNonNull(errorHandler, "errorHandler"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Regardless, I found another edge case where the content preview is not recorded. An HttpResponse can be created directly via renderStatus(...) when it is an AbortedHttpResponse. If a service returns HttpResponse.ofFailure(HttpResponseException(...)) without an error handler, the error response is created using renderStatus(...) directly:
armeria/core/src/main/java/com/linecorp/armeria/server/AbstractHttpResponseSubscriber.java
Lines 275 to 278 in 897b5a0
| } else if (peeled instanceof HttpStatusException) { | |
| final Throwable cause0 = firstNonNull(peeled.getCause(), peeled); | |
| final AggregatedHttpResponse res = toAggregatedHttpResponse((HttpStatusException) peeled); | |
| failAndRespond(cause0, res, Http2Error.CANCEL, false); |
armeria/core/src/main/java/com/linecorp/armeria/server/AbstractHttpResponseHandler.java
Lines 202 to 211 in 897b5a0
| final AggregatedHttpResponse toAggregatedHttpResponse(HttpStatusException cause) { | |
| final HttpStatus status = cause.httpStatus(); | |
| final Throwable cause0 = firstNonNull(cause.getCause(), cause); | |
| final ServiceConfig serviceConfig = reqCtx.config(); | |
| final AggregatedHttpResponse response = serviceConfig.errorHandler() | |
| .renderStatus(reqCtx, req.headers(), status, | |
| null, cause0); | |
| assert response != null; | |
| return response; | |
| } |
Because onServiceException(...) is not called with the returned response, the request log does not complete. My idea is to apply the same decorating logic to the response returned from the delegate’s renderStatus(...).
I’m unsure whether there is any chance the decorator could be applied twice. For example, via renderStatus(...) -> FallbackService -> ContentPreviewingService or CorsService.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, there's definitely an edge case here.
Plus, it looks like we're generating a content preview even when a preceding decorator fails, which we shouldn't be doing.
What do you think about preventing the preview from being generated whenever an exception is raised?
Since we already set the preview to null on an exception in DefaultRequestLog, this seems like the right fix.
armeria/core/src/main/java/com/linecorp/armeria/common/logging/DefaultRequestLog.java
Lines 1471 to 1473 in d068172
| // Will auto-fill response content and its preview if response has failed. | |
| deferredFlags = this.deferredFlags & ~(RESPONSE_CONTENT.flag() | | |
| RESPONSE_CONTENT_PREVIEW.flag()); |
I think we could set the preview to null at these two spots to solve it:
armeria/core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java
Line 178 in 1aed83b
} catch (Throwable t) { armeria/core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java
Line 118 in 0fc9cd1
return null;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plus, it looks like we're generating a content preview even when a preceding decorator fails, which we shouldn't be doing.
Indeed, because content preview generation is done after the response is processed by the recovery step and whether the content preview decorator was actually executed is not considered.
What do you think about preventing the preview from being generated whenever an exception is raised?
If so, should we generate the preview for an aborted response (HttpResponse.ofFailure(...))? I’m a bit confused about what ContentPreviewErrorHandler would do if we choose this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0x1306e6d It is not a push but are you interested in implementing #6577? Otherwise, I will handle it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, let me try if it doesn't bother you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 No rush. Take your time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#6589 has been opened instead. I’ll clean up this PR soon and consolidate the changes into ServiceErrorHandler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, this PR focuses on ServiceErrorHandler only.
minwoox
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
jrhee17
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still organizing my thoughts on https://github.com/line/armeria/pull/6570/files#r2655383994.
As this PR is an independent change, let me approve for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java (1)
203-213: Consider exception type consistency.The annotated service methods now use
IllegalArgumentException(lines 203, 213), while the direct service bindings still useIllegalStateException(lines 66, 72, 103, 121). While this doesn't affect test correctness, using consistent exception types would improve readability and reduce potential confusion.♻️ Optional: Use consistent exception types
If intentional variety is not required for test coverage, consider standardizing to one exception type:
@Get("/aborted/unexpected-exception") public HttpResponse abortedUnexpectedException() { - return HttpResponse.ofFailure(new IllegalArgumentException("Oops!")); + return HttpResponse.ofFailure(new IllegalStateException("Oops!")); } @Get("/throw/unexpected-exception") public HttpResponse throwUnexpectedException() { - throw new IllegalArgumentException("Oops!"); + throw new IllegalStateException("Oops!"); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.javacore/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.javacore/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.javacore/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.javacore/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java
🧬 Code graph analysis (1)
core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java (3)
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (1)
ServerBuilder(171-2569)core/src/main/java/com/linecorp/armeria/server/logging/LoggingService.java (1)
LoggingService(39-88)core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java (1)
ContentPreviewingService(61-183)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: site
- GitHub Check: lint
- GitHub Check: flaky-tests
- GitHub Check: Kubernetes Chaos test
- GitHub Check: Summary
🔇 Additional comments (5)
core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java (1)
174-181: LGTM! Properly finalizes deferred log property on exception.The explicit
responseContentPreview(null)call ensures the deferredRESPONSE_CONTENT_PREVIEWproperty is always completed, even when the service throws an exception. This prevents incomplete logs and aligns with the PR objective to properly handle error responses.core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java (1)
116-120: LGTM! Ensures log completion when previewer is uninitialized.Explicitly setting
responseContentPreview(null)when the previewer is never initialized ensures the deferred log property is always finalized. This handles cases where the response fails before headers are sent, maintaining log consistency across all execution paths.core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java (3)
41-61: LGTM! Clean dual-server test setup.The two
ServerExtensioninstances enable comprehensive testing of both scenarios: with and without error handlers. This structure effectively validates that content previews are properly handled in both configurations, which aligns with the PR objective.
84-123: LGTM! Binding routes effectively test ServiceErrorHandler integration.The binding-based routes with
ServiceErrorHandler(lines 84-123) directly validate the core PR objective: ensuring that error handler decorators now apply toServiceErrorHandlerinstances. The comprehensive coverage of error scenarios (aborted/throw × http-status/unexpected) ensures the decorator integration works correctly.
129-192: LGTM! Comprehensive parameterized test coverage.The three test methods effectively validate:
- Content preview is null when error handlers are configured (both server and service level)
- Content preview is null when no error handlers are present
- Annotated service exception handlers properly record content previews
The inclusion of binding routes in test parameters ensures
ServiceErrorHandlerdecorator integration is properly validated, which is the core objective of this PR.
3e0951b to
8d64d2c
Compare
Motivation: Following the discussion in #6570 (comment), content preview was being generated even when a preceding decorator failed with an exception. This behavior was inconsistent with `DefaultRequestLog`, which sets the preview to null when an exception is present: https://github.com/line/armeria/blob/d068172432163c42908ad5d509262cc4022afb04/core/src/main/java/com/linecorp/armeria/common/logging/DefaultRequestLog.java#L1471-L1473 The content preview should not be recorded when the response flow is interrupted by an exception, as the decorator did not actually process the response content. Modifications: - Set `responseContentPreview(null)` in `ContentPreviewingUtil` when `responseContentPreviewer` is null. - Set `responseContentPreview(null)` in `ContentPreviewingService` when service execution throws an exception. - Updated tests to verify null content preview for various exception scenarios with and without error handlers. Result: - Response content preview is properly set to null when exceptions are raised. - Consistent behavior between decorator-level exceptions and error handler responses.
Motivation:
Following #6269, error handler decorators like
CorsServerErrorHandlerandContentPreviewServerErrorHandlerwere introduced to support decoratingServerErrorHandler. However, these decorators only worked withServerErrorHandlers and did not supportServiceErrorHandlerconfigured viaServiceBindingBuilder.errorHandler(). If aServiceErrorHandleris configured, theServerErrorHandleracts as a fallback, so the error response is returned directly without passing through decorators:armeria/core/src/main/java/com/linecorp/armeria/server/VirtualHostBuilder.java
Lines 1393 to 1395 in 6777284
Modifications:
DecoratingErrorHandlerFunctionto support bothServerErrorHandlerandServiceErrorHandler.ServiceConfigBuilderto automatically decorateServiceErrorHandlerinstances.Result:
ServiceErrorHandler.