Repository navigation
Conversation
Before: the info logs for message_length_limit have the head of the received bytes as is. When the head has binary bytes, fluentd's own log event can not be made into JSON, and "failed to emit fluentd's log event" (JSON::GeneratorError) happens with <label @FLUENT_LOG>. After: the info logs show only the limit and the size. The head is logged at debug level like in_syslog, and it is escaped so that the log event can be made into JSON. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kentaro Hayashi <hayashi@clear-code.com>
Before: the debug logs for message_length_limit have the head of the received bytes as is. When the head has binary bytes, fluentd's own log event can not be made into JSON, and "failed to emit fluentd's log event" (JSON::GeneratorError) happens with <label @FLUENT_LOG>. After: the head is escaped, the same as the debug log for a message length that is not a number. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kentaro Hayashi <hayashi@clear-code.com>
Before: when message_length_limit is set and a client sends data without a delimiter, an info log is written each time the buffer goes over the limit. The data until the next delimiter fills the buffer again, so the log repeats, and how often depends on the read size. After: the buffer clear is logged only once for one message. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kentaro Hayashi <hayashi@clear-code.com>
Before: with the traditional frame, when a client sends data without a delimiter, an info log is written each time the buffer goes over message_length_limit. The data until the next delimiter fills the buffer again, so the log repeats, and how often depends on the read size. After: the buffer clear is logged only once for one message, the same as in_tcp. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kentaro Hayashi <hayashi@clear-code.com>
Watson1978
left a comment
There was a problem hiding this comment.
Thanks for this. Logging the head only at debug with String#dump looks good to me.
I found two cases where the new "log only once" check hides a clear that master logs. Please see the inline comments.
| if !@message_length_limit.nil? && @message_length_limit < buf.bytesize | ||
| log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32] | ||
| # The rest of a long message fills the buffer again on each read, so log only the first clear. | ||
| unless discard_till_next_delimiter |
There was a problem hiding this comment.
MUST: discard_till_next_delimiter is a local variable of start, so all connections share it. This check now uses it to skip the log, so a clear on one connection can hide a clear on another connection.
For example, with message_length_limit 12 and delimiter ";", and each step received in a separate read:
- Connection A sends
"a" * 13and closes without a delimiter. - Connection B sends
"b" * 13. - Connection B sends
";ok;".
On master, both clears are logged. With this PR, only A's clear is logged. B's 13 bytes are dropped with no log (ok is still emitted). The same happens while A is still open, and in the single_emit path with <extract> and tag_key (line 164). Also, a delimiter on another connection resets the flag, so one long message on A can be logged more than once.
in_syslog keeps the flag per connection. in_tcp can do the same. For TCP and TLS, server_create and server_create_connection use the same connection setup, and server_create just registers the block with conn.data. So, in both paths, the flag can move into the connection block like this, and the declaration in start can be removed:
server_create_connection(:in_tcp_server_batch_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |conn|
discard_till_next_delimiter = false
conn.data do |data|
# the current body of the block
end
endI tried this locally. The existing tests passed, and the cases above were fixed. It also fixes an existing bug: on master, a valid line on B can be dropped because A set the flag.
Could you make this change, and add a test with two connections?
| log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buffer.bytesize | ||
| log.on_debug { log.debug "The buffer size exceeds 'message_length_limit':", head: buffer[...32] } | ||
| # The rest of a long message fills the buffer again on each read, so log only the first clear. | ||
| unless discard_till_next_delimiter |
There was a problem hiding this comment.
SHOULD (non-blocking, a follow-up PR is fine): With a delimiter of two or more bytes, such as "\r\n", the buffer.clear below can cut the delimiter in half. Then the next message boundary is missed, and the flag stays true until an intact delimiter is received. With this PR, the clears of other long messages before that are not logged.
For example, with message_length_limit 64 and delimiter "\r\n", send three 65-byte messages, each followed by "\r\n", so that each read ends right after the "\r". On master, three clears are logged. With this PR, only the first one is logged. in_tcp has the same problem in both paths.
The missed boundary itself is an existing bug. On master, the valid message after it is dropped too. In this example, keeping up to delimiter_size - 1 trailing bytes, instead of clearing all of them, fixed both. The existing tests passed:
buffer.slice!(0, buffer.bytesize - (delimiter_size - 1))In in_tcp, it is buf.slice!(0, buf.bytesize - (del_size - 1)) in both paths. If you fix it here, please add a test that splits "\r\n" between reads, and checks that each long message is logged once and the next valid message is emitted.
Which issue(s) this PR fixes:
Fixes #
What this PR does / why we need it:
This PR fixes two problems with the logs for
message_length_limitin in_tcp and in_syslog.The info log for a buffer clear repeats.
Before: when
message_length_limitis set and a client sends data without a delimiter, an info log is written each time the buffer goesover the limit. The data until the next delimiter fills the buffer again, so the log repeats, and how often depends on the read size. For ex
ample, sending 64MiB without a newline to in_tcp with
message_length_limit 4096wrote about 4,200 info lines.After: the buffer clear is logged only once for one message.
The head of the received data breaks fluentd's own log events.
Before: the logs have the head of the received bytes as is. in_tcp writes it at info level, and in_syslog writes it at debug level. When
the head has binary bytes, fluentd's own log event can not be made into JSON, and
failed to emit fluentd's log event(JSON::GeneratorErro r) happens with<label @FLUENT_LOG>.After: the info logs of in_tcp show only the limit and the size, like in_syslog. The head is logged at debug level, and it is escaped wit
h
String#dumplike the existing debug log of in_syslog for a message length that is not a number.The tests wait for the log instead of sleeping where it matters, so they do not depend on how the server splits the reads. All the new tests
fail without the change to
lib/.Docs Changes:
Release Note:
in_tcp, in_syslog: log a buffer clear for
message_length_limitonly once per message, and log the head of the received data only at debuglevel as an escaped string