Skip to content

fix(rocketmq): log warning when FLAG or DELAY header is not numeric#4330

Merged
uuuyuqi merged 2 commits into
alibaba:2025.1.xfrom
daguimu:fix/rocketmq-delay-flag-header-log
Jul 15, 2026
Merged

fix(rocketmq): log warning when FLAG or DELAY header is not numeric#4330
uuuyuqi merged 2 commits into
alibaba:2025.1.xfrom
daguimu:fix/rocketmq-delay-flag-header-log

Conversation

@daguimu

@daguimu daguimu commented May 14, 2026

Copy link
Copy Markdown
Contributor

Describe what this PR does / why we need it

RocketMQMessageConverterSupport#getAndWrapMessage parses the MQ_FLAG and DELAY_TIME_LEVEL Spring Messaging headers into int via Integer.parseInt. The previous implementation wrapped both parses in a single try { ... } catch (Exception ignored) {}, so any malformed header (e.g. a String value injected via a SpEL expression, a typo, or a non-numeric class) was silently absorbed and the producer fell back to flag=0 and delayLevel=0 with no exception, no log line, and no signal to the caller.

The most user-visible consequence is on the delayed-delivery path: a misconfigured DELAY_TIME_LEVEL (e.g. "abc" from a SpEL that returned a String instead of an int) turns a delayed message into an immediate send and the application has no way of knowing.

Does this pull request fix one issue?

NONE

Describe how you did it

  • Added a class-level SLF4J logger to RocketMQMessageConverterSupport.
  • Extracted the parsing into a parseHeaderAsIntOrDefault(String headerName, @Nullable Object rawValue) helper and parse the two headers independently (flag first, then delayLevel). A malformed value in one header no longer drags the other, well-formed header down to the fallback, and the warning names only the header that actually failed.
  • The helper catches NumberFormatException, logs a warn with the header name, the raw value, and the exception message only (no stack trace, to avoid flooding logs on the per-message producer path), and falls back to 0.
  • The rawValue parameter is annotated @org.jspecify.annotations.Nullable (the support package is @NullMarked; header lookups can return null).

Describe how to verify it

mvn -pl spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq test — all 16 module tests pass locally, including 6 new ones in RocketMQMessageConverterSupportTest:

  • nonNumericDelayTimeLevelHeaderFallsBackToZeroDELAY = "not-a-number"getDelayTimeLevel() == 0.
  • nonNumericFlagHeaderFallsBackToZeroMQ_FLAG = "not-a-number"getFlag() == 0.
  • invalidNumericHeaderDoesNotPropagateException — both headers non-numeric → no exception escapes convertMessage2MQ.
  • nonNumericFlagHeaderLogsWarning — uses OutputCaptureExtension to assert the warning contains the header name and the raw value.
  • validDelayLevelWithNonNumericFlagIsPreservedAndWarnsOnlyFlag — mixed validity: valid DELAY is preserved, warning names only MQ_FLAG.
  • validFlagWithNonNumericDelayLevelIsPreservedAndWarnsOnlyDelayLevel — the symmetric mixed-validity case.

Special notes for reviews

Two intentional behaviour changes relative to the old silent-fallback code, both discussed in review:

  1. Mixed-validity fallback changed (improvement). Previously, one malformed header could force both values to 0 (the parses shared one try block). Now each header is parsed independently, so a well-formed header keeps its value even when the other one is malformed. The mixed-validity tests lock this in.
  2. Catch narrowed from Exception to NumberFormatException (intentional). String.valueOf(rawValue) can in principle throw if a custom header value's toString() throws; such an exception now propagates instead of being silently converted into flag=0/delayLevel=0. This is deliberate: a throwing toString() is a programming error in the producer application, and silently mapping it to "send immediately with flag 0" is exactly the failure mode this PR removes. NumberFormatException (the only expected failure: non-numeric text) is still handled with warn-and-fallback.

For every previously valid header value the output is unchanged; for a message where all malformed headers are non-numeric text (the common case), the fallback output is also unchanged — the log.warn is the only new externally visible behaviour.

@daguimu
daguimu force-pushed the fix/rocketmq-delay-flag-header-log branch from 6f45a40 to ac70625 Compare May 14, 2026 16:01
@uuuyuqi

uuuyuqi commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the fix. The direction looks good to me, but I think there are a few details worth adjusting before merging:

  1. The warning message may be inaccurate in one mixed-validity case.

The code still parses DELAY_TIME_LEVEL before MQ_FLAG. If DELAY_TIME_LEVEL is valid, for example 2, but MQ_FLAG is non-numeric, delayLevel has already been parsed as 2 before the exception is thrown. After the catch block, the message can still keep delayLevel=2, so the log text falling back to flag=0 and delayLevel=0 is not always true and may mislead troubleshooting. Could we either make the message more generic, or parse/log the two headers independently?

  1. The new tests do not verify the main behavior introduced by this PR.

The added tests lock in the existing fallback/no-exception behavior, which is useful, but they would also pass before this change. Since the only new externally visible behavior is the warn log, it would be better to add a log-capture assertion, for example with Spring Boot OutputCaptureExtension, to verify that a malformed header actually emits a warning containing the header name and raw value.

  1. Logging the full exception stack trace on every malformed message may be noisy.

This path can run per produced message. If a producer is misconfigured, log.warn(..., e) may emit a full stack trace repeatedly and flood logs. Since the header names and raw values are already included, perhaps logging the exception message/type without the full stack trace would be enough for diagnosis.

@uuuyuqi uuuyuqi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

RocketMQMessageConverterSupport#getAndWrapMessage swallowed any
Integer.parseInt failure on the MQ_FLAG and DELAY_TIME_LEVEL message
headers with catch (Exception ignored) {}, silently falling back to
flag=0 and delayLevel=0. A producer that set DELAY to a misconfigured
non-numeric value (e.g. via a SpEL expression that produced a String
instead of an int) would have its delayed delivery silently turn into
an immediate send with no log line and no way to know the header was
malformed.

Parse each header independently in a small parseHeaderAsIntOrDefault
helper that warns, naming the offending header and echoing its raw
value, when parsing fails. Parsing both headers in a single shared try
block had two problems: a valid value on the header parsed first (e.g.
a legitimate DELAY) was reported in the warning as having fallen back
to 0 even though it was actually applied, and an exception on the
header parsed first left the other, well-formed header unparsed and
forced it to 0 as well. Independent parsing keeps every well-formed
value, and the warning now names only the header that actually failed.
Only the exception message is logged (not the full stack trace) to
avoid flooding the logs when many messages carry the same bad header,
and the catch is narrowed from Exception to NumberFormatException.

Adds regression tests covering both the fallback behaviour (non-numeric
DELAY and FLAG each stay at 0, conversion never propagates a parse
exception) and the behaviour introduced here: the warning is emitted
for a malformed header (asserted with OutputCaptureExtension), a valid
delay level survives a malformed flag on the same message, and a valid
flag survives a malformed delay level.
@daguimu
daguimu force-pushed the fix/rocketmq-delay-flag-header-log branch from ac70625 to a4e7f72 Compare July 9, 2026 11:57
@uuuyuqi

uuuyuqi commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the update. The latest revision addresses the three points from my previous review:

  • parsing the two headers independently makes the warning accurate and preserves the well-formed header when the other one is malformed;
  • the new OutputCaptureExtension tests verify the warning behavior and the mixed-validity cases;
  • logging only the exception message avoids repeated full stack traces in the producer path.

I found one remaining blocker in the current CI. Integration Testing fails during compilation with:

[NullAway] passing @Nullable parameter `flagObj` where @NonNull is required
[NullAway] passing @Nullable parameter `delayLevelObj` where @NonNull is required

The support package is @NullMarked, while parseHeaderAsIntOrDefault currently declares Object rawValue, even though its Javadoc and null guard say the value may be null. Could you mark it as @Nullable Object rawValue (using org.jspecify.annotations.Nullable) or normalize the values before calling the helper? The test step did not run because compilation failed.

Please also update the PR description, which still describes the previous implementation. In particular, it says the code keeps a shared broad catch (Exception), preserves delay-first parse order, changes only observability, adds three tests, and verifies the warning manually. The current patch parses the headers independently (flag first), catches NumberFormatException, changes the mixed-validity fallback behavior, adds six tests, and verifies logging with OutputCaptureExtension.

One small compatibility note: narrowing the catch from Exception to NumberFormatException means an exception thrown by a custom header value whose toString() fails would now propagate instead of falling back to zero. Please either preserve the previous broad fallback semantics or document that this behavior change is intentional.

Once CI is green and the description is updated, my previous review concerns look resolved.

The support package is @NullMarked, but parseHeaderAsIntOrDefault
declared its rawValue parameter as non-null Object while receiving
possibly-null values from MessageHeaders#get, failing NullAway checks:

  [NullAway] passing @nullable parameter 'flagObj' where @nonnull is required
  [NullAway] passing @nullable parameter 'delayLevelObj' where @nonnull is required

Annotate the parameter with org.jspecify.annotations.Nullable to match
the existing null guard and Javadoc.

@uuuyuqi uuuyuqi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@uuuyuqi
uuuyuqi merged commit f6182c9 into alibaba:2025.1.x Jul 15, 2026
3 checks passed
@daguimu
daguimu deleted the fix/rocketmq-delay-flag-header-log branch July 15, 2026 02:04
@uuuyuqi

uuuyuqi commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the update and fix. The same silent catch (Exception ignored) {} still exists on the 2025.0.x branch, so I think this change should be backported there as well.

Could you please open a follow-up PR targeting 2025.0.x and reference this PR as the source? That branch does not currently use @NullMarked, NullAway, or JSpecify, so the backport should keep the independent header parsing, warning behavior, and regression tests, but omit the org.jspecify.annotations.Nullable import and annotation.

uuuyuqi pushed a commit that referenced this pull request Jul 15, 2026
…2025.0.x backport of #4330) (#4361)

Backport #4330 to the 2025.0.x branch.

RocketMQ message conversion silently ignored non-numeric MQ_FLAG and DELAY_TIME_LEVEL headers, which could cause a delayed message to be sent immediately without any diagnostic.

Parse the headers independently, warn for the offending header without a stack trace, and fall back to 0 on NumberFormatException. Preserve a valid header when the other value is malformed, while allowing unexpected
conversion failures to propagate.

Add regression tests for fallback behavior, warning output, and mixed-validity cases. Omit the JSpecify @nullable annotation because this branch does not use @NullMarked, NullAway, or JSpecify.
jaimyjie pushed a commit to jaimyjie/spring-cloud-alibaba that referenced this pull request Jul 16, 2026
…2025.0.x backport of alibaba#4330) (alibaba#4361)

Backport alibaba#4330 to the 2025.0.x branch.

RocketMQ message conversion silently ignored non-numeric MQ_FLAG and DELAY_TIME_LEVEL headers, which could cause a delayed message to be sent immediately without any diagnostic.

Parse the headers independently, warn for the offending header without a stack trace, and fall back to 0 on NumberFormatException. Preserve a valid header when the other value is malformed, while allowing unexpected
conversion failures to propagate.

Add regression tests for fallback behavior, warning output, and mixed-validity cases. Omit the JSpecify @nullable annotation because this branch does not use @NullMarked, NullAway, or JSpecify.
jaimyjie pushed a commit to jaimyjie/spring-cloud-alibaba that referenced this pull request Jul 16, 2026
RocketMQ message conversion silently ignored non-numeric MQ_FLAG and DELAY_TIME_LEVEL headers, which could cause a delayed message to be sent immediately without any diagnostic.

Parse the two headers independently, log a warning for the offending header without a stack trace, and fall back to 0 on NumberFormatException. Preserve a valid header when the other value is malformed, while allowing unexpected conversion failures to propagate.

Mark nullable header lookups with JSpecify @nullable and add tests for fallback behavior, warning output, and mixed-validity cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants