Skip to content

Conversation

@0x1306e6d
Copy link
Contributor

Motivation:

Following #6269, error handler decorators like CorsServerErrorHandler and ContentPreviewServerErrorHandler were introduced to support decorating ServerErrorHandler. However, these decorators only worked with ServerErrorHandlers and did not support ServiceErrorHandler configured via ServiceBindingBuilder.errorHandler(). If a ServiceErrorHandler is configured, the ServerErrorHandler acts as a fallback, so the error response is returned directly without passing through decorators:

final ServiceErrorHandler serviceErrorHandler = serverErrorHandler.asServiceErrorHandler();
final ServiceErrorHandler defaultErrorHandler =
errorHandler != null ? errorHandler.orElse(serviceErrorHandler) : serviceErrorHandler;

Modifications:

  • Extended DecoratingErrorHandlerFunction to support both ServerErrorHandler and ServiceErrorHandler.
  • Updated ServiceConfigBuilder to automatically decorate ServiceErrorHandler instances.

Result:

  • Error response content preview and CORS headers are now available for services configured with ServiceErrorHandler.

@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

Generalized error-handling decorators: removed the "Server" prefix from interfaces/classes, added overloads to let decorators wrap both ServerErrorHandler and ServiceErrorHandler, extracted CORS and content-preview logic into helper methods, and applied the new decorator wiring across builders, configs, and tests.

Changes

Cohort / File(s) Summary
Error Handler Interface Generalization
core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java
Renamed interface from DecoratingServerErrorHandlerFunctionDecoratingErrorHandlerFunction and added onServiceException(ServiceErrorHandler delegate, ServiceRequestContext ctx, Throwable cause) while retaining the ServerErrorHandler variant.
Error Handler Decorators Infrastructure
core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
Renamed ServerErrorHandlerDecoratorsErrorHandlerDecorators. Switched internal types to DecoratingErrorHandlerFunction, added decorate(ServiceErrorHandler) and a new inner DecoratingServiceErrorHandler that routes onServiceException through the decorator.
CORS Error Handler
core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java
Renamed from CorsServerErrorHandler, now implements DecoratingErrorHandlerFunction. Extracted CORS header logic into maybeSetCorsHeaders(ServiceRequestContext ctx) and added a ServiceErrorHandler overload that invokes the helper before delegating.
Content Preview Error Handler
core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java
Renamed from ContentPreviewServerErrorHandler, now implements DecoratingErrorHandlerFunction. Extracted preview setup to maybeSetUpResponseContentPreviewer(...) and added a ServiceErrorHandler overload delegating through that helper.
Builder & Config Integration
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java, core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java, core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java
Replaced references to server-only decorators with ErrorHandlerDecorators.decorate(...) so default and configured error handlers are wrapped by the new decorator mechanism.
Content Preview Logging & Service
core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java, core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java
Ensure response content preview is explicitly logged as null when the previewer is uninitialized or when an exception occurs, finalizing the log's responseContentPreview in error paths.
Tests
core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java
Added server variants and binding routes to exercise ServiceErrorHandler propagation, updated test setup to toggle global handler, and adjusted expectations and routes to validate the new decorator behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • trustin
  • ikhoon
  • minwoox

Poem

🐰 I nibble code and tidy trails,

Decorators hop on service scales,
Helpers hide the fiddly bits,
Headers, previews — tidy fits,
A rabbit cheers these cleaner rails! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding decorator support for ServiceErrorHandler, which is the core focus of the PR.
Description check ✅ Passed The description clearly explains the motivation, modifications, and result. It directly relates to the changeset and provides context for why the changes were needed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

❌ Patch coverage is 97.29730% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.38%. Comparing base (8150425) to head (3e0951b).
⚠️ Report is 316 commits behind head on main.

Files with missing lines Patch % Lines
.../com/linecorp/armeria/server/CorsErrorHandler.java 90.90% 0 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍👍

@ikhoon ikhoon added the defect label Dec 30, 2025
@ikhoon ikhoon added this to the 1.35.0 milestone Dec 30, 2025
Copy link
Contributor

@jrhee17 jrhee17 left a 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) {
Copy link
Contributor

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.

Copy link
Contributor

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");

Copy link
Contributor Author

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:

} else if (peeled instanceof HttpStatusException) {
final Throwable cause0 = firstNonNull(peeled.getCause(), peeled);
final AggregatedHttpResponse res = toAggregatedHttpResponse((HttpStatusException) peeled);
failAndRespond(cause0, res, Http2Error.CANCEL, false);

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.

Copy link
Contributor

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.

// 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:

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to hear that. I agree that #6577 is the ultimate goal for addressing all of these issues.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0x1306e6d So, could you fix this PR to always complete the request log?

Copy link
Contributor Author

@0x1306e6d 0x1306e6d Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand what 'always complete the request log' means. Could you please provide more details? If you meant setting the content preview to null for failed responses, I’m unsure how to retrieve the failed response preview until #6577 is available.

Copy link
Contributor

@minwoox minwoox Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you meant setting the content preview to null for failed responses,

Yeah, that's what I meant. 😉
I initially thought we should prioritize the completeness of the request log, even if it meant disabling the content preview on failed responses. The idea was to ship this fix before #6577.
However, we already released 1.35.0 and Since #6577 will likely be included in the next release, we can probably wait for that.

So if you don't want to apply the workaround for this PR, I think we can just close this PR and implement #6577.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I added a commit to ensure the termination of RequestLog, and #6577 will provide the fundamental fix for the content preview.

One question I have is that the RequestLog termination may have different characteristics from the original PR. Also, CorsService requires a ServiceErrorHandler decorator. Shouldn’t we separate these into different PRs for better management?

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Contributor

@jrhee17 jrhee17 left a 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

@minwoox minwoox modified the milestones: 1.35.0, 1.36.0 Jan 5, 2026
Copy link

@coderabbitai coderabbitai bot left a 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 use IllegalStateException (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

📥 Commits

Reviewing files that changed from the base of the PR and between d417af7 and 3e0951b.

📒 Files selected for processing (3)
  • core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java
  • core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java
  • core/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 in site/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 @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .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.java
  • core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java
  • core/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 deferred RESPONSE_CONTENT_PREVIEW property 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 ServerExtension instances 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 to ServiceErrorHandler instances. 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:

  1. Content preview is null when error handlers are configured (both server and service level)
  2. Content preview is null when no error handlers are present
  3. Annotated service exception handlers properly record content previews

The inclusion of binding routes in test parameters ensures ServiceErrorHandler decorator integration is properly validated, which is the core objective of this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants