Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 75 additions & 6 deletions lib/fluent/plugin/parser_syslog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def initialize
@time_parser_rfc5424 = nil
@space_count_rfc3164 = nil
@space_count_rfc5424 = nil
@time_format_starts_with_space_rfc3164 = false
@skip_space_count_rfc3164 = false
@skip_space_count_rfc5424 = false
@time_parser_rfc5424_without_subseconds = nil
Expand Down Expand Up @@ -123,10 +124,12 @@ class << self

def setup_time_parser_3164(time_fmt)
@time_parser_rfc3164 = time_parser_create(format: time_fmt)
@time_format_starts_with_space_rfc3164 = time_fmt.start_with?(SPLIT_CHAR)
if ['%b %d %H:%M:%S', '%b %d %H:%M:%S.%N'].include?(time_fmt)
@skip_space_count_rfc3164 = true
end
@space_count_rfc3164 = time_fmt.squeeze(' ').count(' ') + 1
@space_count_rfc3164 -= 1 if @time_format_starts_with_space_rfc3164
end

def setup_time_parser_5424(time_fmt)
Expand Down Expand Up @@ -162,6 +165,13 @@ def parse_auto(text, &block)
end

SPLIT_CHAR = ' '.freeze
SPLIT_BYTE = 0x20
DOT_CHAR = '.'.freeze
DOT_BYTE = 0x2e
GREATER_THAN_BYTE = 0x3e
ZERO_BYTE = 0x30
NINE_BYTE = 0x39
RFC3164_PRI_MINIMUM = [nil, nil, nil, 1, 10, 100].freeze

def parse_rfc3164_regex(text, &block)
idx = 0
Expand All @@ -180,12 +190,21 @@ def parse_rfc3164_regex(text, &block)

i = idx - 1
sq = false
first_time_field = true
@space_count_rfc3164.times do
while text[i + 1] == SPLIT_CHAR
if first_time_field
unless @time_format_starts_with_space_rfc3164
idx += 1
i += 1
break
end
end
sq = true
i += 1
end

first_time_field = false
i = text.index(SPLIT_CHAR, i + 1)
end

Expand Down Expand Up @@ -287,14 +306,43 @@ def parse_rfc3164(text, &block)
end
end

byte = text.getbyte(cursor)
space_after_priority = if byte == SPLIT_BYTE
true
elsif @with_priority && text.getbyte(cursor - 1) != GREATER_THAN_BYTE
# String#index returns a character index. Mark
# non-ASCII PRI for the same timestamp lookup.
byte = nil
text[cursor] == SPLIT_CHAR
else
false
end
if space_after_priority
cursor = rfc3164_space_cursor(text, cursor, pri)
unless cursor
yield nil, nil
return
end
end

if @skip_space_count_rfc3164
# header part
time_size = 15 # skip Mmm dd hh:mm:ss
time_end = text[cursor + time_size]
if time_end == SPLIT_CHAR
time_end_index = cursor + time_size
if byte
time_end = text.getbyte(time_end_index)
split_delimiter = SPLIT_BYTE
dot_delimiter = DOT_BYTE
else
time_end = text[time_end_index]
split_delimiter = SPLIT_CHAR
dot_delimiter = DOT_CHAR
end

if time_end == split_delimiter
time_str = text.slice(cursor, time_size)
cursor += 16 # time + ' '
elsif time_end == '.'.freeze
elsif time_end == dot_delimiter
# support subsecond time
i = text.index(SPLIT_CHAR, time_size)
time_str = text.slice(cursor, i - cursor)
Expand All @@ -314,12 +362,12 @@ def parse_rfc3164(text, &block)
i = text.index(SPLIT_CHAR, i + 1)
end

time_str = sq ? text.slice(idx, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
time_str = sq ? text.slice(cursor, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
cursor = i + 1
end

i = text.index(SPLIT_CHAR, cursor)
if i.nil?
unless i
yield nil, nil
return
end
Expand All @@ -333,7 +381,7 @@ def parse_rfc3164(text, &block)
i = text.index(SPLIT_CHAR, cursor)

# message part
msg = if i.nil? # for 'only non-space content case'
msg = unless i # for 'only non-space content case'
text.slice(cursor, text.bytesize)
else
if text[i - 1] == ':'.freeze
Expand Down Expand Up @@ -369,6 +417,27 @@ def parse_rfc3164(text, &block)
yield time, record
end

def rfc3164_space_cursor(text, cursor, pri)
if @with_priority
# A nonzero value with the expected decimal width proves that the
# whole PRI is numeric. Leading-zero values take the bytewise path.
minimum = RFC3164_PRI_MINIMUM[cursor]
return unless minimum

unless pri >= minimum
priority_byte = 1
while priority_byte < cursor - 1
byte = text.getbyte(priority_byte)
return unless byte >= ZERO_BYTE && byte <= NINE_BYTE

priority_byte += 1
end
end
end

@time_format_starts_with_space_rfc3164 ? cursor : cursor + 1
end

NILVALUE = '-'.freeze

def parse_rfc5424(text, &block)
Expand Down
72 changes: 72 additions & 0 deletions test/plugin/test_in_syslog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,78 @@ def test_time_format(data)
}
end

data(
'regexp/rfc3164/parser priority' => ['regexp', 'rfc3164', true],
'string/rfc3164/parser priority' => ['string', 'rfc3164', true],
'regexp/auto/parser priority' => ['regexp', 'auto', true],
'string/auto/parser priority' => ['string', 'auto', true],
'regexp/rfc3164/input priority' => ['regexp', 'rfc3164', false],
'string/rfc3164/input priority' => ['string', 'rfc3164', false],
'regexp/auto/input priority' => ['regexp', 'auto', false],
'string/auto/input priority' => ['string', 'auto', false],
)
def test_space_between_rfc3164_priority_and_header(data)
parser_engine, message_format, with_priority = data
d = create_driver([
ipv4_config,
'severity_key severity',
'facility_key facility',
'<parse>',
" parser_engine #{parser_engine}",
" message_format #{message_format}",
" with_priority #{with_priority}",
'</parse>',
].join("\n"))

message = 'Apr 25 16:43:29 PAA-SW1-1 General[procLOG]: main.c(257) 272264 %% Stopping System API application'
d.run(expect_emits: 2) do
u = UDPSocket.new
u.connect('127.0.0.1', @port)
u.send("<14>#{message}", 0)
u.send("<14> #{message}", 0)
end

assert_equal(2, d.events.size)
assert_equal(d.events[0][1], d.events[1][1])
assert_equal(d.events[0][2], d.events[1][2])
d.events.each do |tag, time, record|
assert_equal('syslog.user.info', tag)
assert_equal(event_time('Apr 25 16:43:29', format: '%b %d %H:%M:%S'), time)
assert_equal('user', record['facility'])
assert_equal('info', record['severity'])
assert_equal('PAA-SW1-1', record['host'])
assert_equal('General', record['ident'])
assert_equal('main.c(257) 272264 %% Stopping System API application', record['message'])
end
end

data('regexp' => 'regexp', 'string' => 'string')
def test_space_after_input_owned_priority_preserves_input_priority_syntax(parser_engine)
d = create_driver([
ipv4_config,
'emit_unmatched_lines true',
'<parse>',
" parser_engine #{parser_engine}",
' with_priority false',
'</parse>',
].join("\n"))

messages = [
'<1234>Apr 25 16:43:29 host app: message',
'<1234> Apr 25 16:43:29 host app: message',
'<ab> Apr 25 16:43:29 host app: message',
]
d.run(expect_emits: 3) do
u = UDPSocket.new
u.connect('127.0.0.1', @port)
messages.each { |message| u.send(message, 0) }
end

assert_equal(d.events[0], d.events[1])
assert_equal('syslog.unmatched', d.events[2][0])
assert_equal(messages[2], d.events[2][2]['unmatched_line'])
end

def test_msg_size
d = create_driver
tests = create_test_case
Expand Down
Loading
Loading