fluentd 1.19.3 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +46 -0
- data/CHANGELOG.md +120 -0
- data/GithubWorkflow.md +1 -1
- data/MAINTAINERS.md +2 -2
- data/README.md +3 -2
- data/Rakefile +2 -1
- data/SECURITY.md +26 -1
- data/lib/fluent/command/cat.rb +3 -3
- data/lib/fluent/compat/exec_util.rb +20 -5
- data/lib/fluent/config/basic_parser.rb +0 -1
- data/lib/fluent/config/literal_parser.rb +22 -10
- data/lib/fluent/config/types.rb +10 -2
- data/lib/fluent/config/yaml_parser/loader.rb +12 -1
- data/lib/fluent/config/yaml_parser/parser.rb +14 -13
- data/lib/fluent/configurable.rb +1 -0
- data/lib/fluent/daemon.rb +2 -1
- data/lib/fluent/env.rb +1 -0
- data/lib/fluent/event.rb +37 -0
- data/lib/fluent/load.rb +0 -1
- data/lib/fluent/log.rb +2 -2
- data/lib/fluent/plugin/base.rb +7 -2
- data/lib/fluent/plugin/buf_file.rb +3 -3
- data/lib/fluent/plugin/buf_file_single.rb +6 -2
- data/lib/fluent/plugin/buffer/chunk.rb +14 -6
- data/lib/fluent/plugin/buffer/file_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer.rb +125 -41
- data/lib/fluent/plugin/compressable.rb +2 -2
- data/lib/fluent/plugin/extractor.rb +17 -6
- data/lib/fluent/plugin/filter_record_transformer.rb +2 -1
- data/lib/fluent/plugin/formatter_out_file.rb +3 -4
- data/lib/fluent/plugin/in_exec.rb +6 -4
- data/lib/fluent/plugin/in_forward.rb +39 -29
- data/lib/fluent/plugin/in_http.rb +83 -2
- data/lib/fluent/plugin/in_monitor_agent.rb +2 -1
- data/lib/fluent/plugin/in_object_space.rb +2 -2
- data/lib/fluent/plugin/in_sample.rb +2 -1
- data/lib/fluent/plugin/in_syslog.rb +72 -6
- data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
- data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
- data/lib/fluent/plugin/in_tail/group_watch.rb +13 -13
- data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
- data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
- data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
- data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
- data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
- data/lib/fluent/plugin/in_tail.rb +324 -815
- data/lib/fluent/plugin/in_unix.rb +5 -3
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_file.rb +10 -4
- data/lib/fluent/plugin/out_forward/connection_manager.rb +18 -0
- data/lib/fluent/plugin/out_forward.rb +21 -2
- data/lib/fluent/plugin/out_http.rb +0 -10
- data/lib/fluent/plugin/output.rb +81 -31
- data/lib/fluent/plugin/parser_csv.rb +7 -2
- data/lib/fluent/plugin/parser_json.rb +25 -12
- data/lib/fluent/plugin/parser_ltsv.rb +2 -4
- data/lib/fluent/plugin/parser_msgpack.rb +7 -6
- data/lib/fluent/plugin/parser_syslog.rb +55 -8
- data/lib/fluent/plugin/parser_tsv.rb +6 -1
- data/lib/fluent/plugin/sd_file.rb +2 -1
- data/lib/fluent/plugin/storage_local.rb +2 -2
- data/lib/fluent/plugin_helper/http_server/server.rb +29 -13
- data/lib/fluent/plugin_helper/http_server.rb +2 -1
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/supervisor.rb +32 -6
- data/lib/fluent/system_config.rb +5 -2
- data/lib/fluent/time.rb +8 -7
- data/lib/fluent/version.rb +1 -1
- data/templates/new_gem/.gitignore +8 -0
- metadata +41 -21
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
#
|
|
16
16
|
|
|
17
17
|
require 'fluent/plugin/input'
|
|
18
|
-
require '
|
|
18
|
+
require 'json'
|
|
19
19
|
|
|
20
20
|
module Fluent::Plugin
|
|
21
21
|
class ExecInput < Fluent::Plugin::Input
|
|
@@ -43,6 +43,8 @@ module Fluent::Plugin
|
|
|
43
43
|
config_param :tag, :string, default: nil
|
|
44
44
|
desc 'The interval time between periodic program runs.'
|
|
45
45
|
config_param :run_interval, :time, default: nil
|
|
46
|
+
desc 'Command (program) execution timeout.'
|
|
47
|
+
config_param :command_timeout, :time, default: nil
|
|
46
48
|
desc 'The default block size to read if parser requires partial read.'
|
|
47
49
|
config_param :read_block_size, :size, default: 10240 # 10k
|
|
48
50
|
desc 'The encoding to receive the result of the command, especially for non-ascii characters.'
|
|
@@ -86,9 +88,9 @@ module Fluent::Plugin
|
|
|
86
88
|
options[:external_encoding] = @encoding if @encoding
|
|
87
89
|
|
|
88
90
|
if @run_interval
|
|
89
|
-
child_process_execute(:exec_input, @command, interval: @run_interval, **options, &method(:run))
|
|
91
|
+
child_process_execute(:exec_input, @command, interval: @run_interval, wait_timeout: @command_timeout, **options, &method(:run))
|
|
90
92
|
else
|
|
91
|
-
child_process_execute(:exec_input, @command, immediate: true, **options, &method(:run))
|
|
93
|
+
child_process_execute(:exec_input, @command, immediate: true, wait_timeout: @command_timeout, **options, &method(:run))
|
|
92
94
|
end
|
|
93
95
|
end
|
|
94
96
|
|
|
@@ -115,7 +117,7 @@ module Fluent::Plugin
|
|
|
115
117
|
time ||= extract_time_from_record(record) || Fluent::EventTime.now
|
|
116
118
|
router.emit(tag, time, record)
|
|
117
119
|
rescue => e
|
|
118
|
-
log.error "exec failed to emit", tag: tag, record:
|
|
120
|
+
log.error "exec failed to emit", tag: tag, record: JSON.generate(record), error: e
|
|
119
121
|
router.emit_error_event(tag, time, record, e) if tag && time && record
|
|
120
122
|
end
|
|
121
123
|
end
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
#
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
require 'fluent/env'
|
|
18
19
|
require 'fluent/plugin/input'
|
|
19
20
|
require 'fluent/msgpack_factory'
|
|
20
|
-
require '
|
|
21
|
+
require 'json'
|
|
21
22
|
require 'digest'
|
|
22
23
|
require 'securerandom'
|
|
23
24
|
|
|
@@ -31,6 +32,12 @@ module Fluent::Plugin
|
|
|
31
32
|
helpers :server
|
|
32
33
|
|
|
33
34
|
LISTEN_PORT = 24224
|
|
35
|
+
OPTION_ACK = 'ack'.freeze
|
|
36
|
+
OPTION_CHUNK = 'chunk'.freeze
|
|
37
|
+
OPTION_COMPRESSED = 'compressed'.freeze
|
|
38
|
+
OPTION_FLUENT_SIGNAL = 'fluent_signal'.freeze
|
|
39
|
+
OPTION_SIZE = 'size'.freeze
|
|
40
|
+
OPTION_TEXT = 'text'.freeze
|
|
34
41
|
|
|
35
42
|
desc 'The port to listen to.'
|
|
36
43
|
config_param :port, :integer, default: LISTEN_PORT
|
|
@@ -250,13 +257,15 @@ module Fluent::Plugin
|
|
|
250
257
|
unless feeder
|
|
251
258
|
first = data[0]
|
|
252
259
|
if first == '{' || first == '[' # json
|
|
253
|
-
parser =
|
|
254
|
-
parser.on_parse_complete = ->(obj){
|
|
255
|
-
block.call(obj, bytes, serializer)
|
|
256
|
-
bytes = 0
|
|
257
|
-
}
|
|
260
|
+
parser = JSON::ResumableParser.new(**Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
258
261
|
serializer = :to_json.to_proc
|
|
259
|
-
feeder = ->(d){
|
|
262
|
+
feeder = ->(d){
|
|
263
|
+
parser << d
|
|
264
|
+
while parser.parse
|
|
265
|
+
block.call(parser.value, bytes, serializer)
|
|
266
|
+
bytes = 0
|
|
267
|
+
end
|
|
268
|
+
}
|
|
260
269
|
else # msgpack
|
|
261
270
|
parser = Fluent::MessagePackFactory.msgpack_unpacker
|
|
262
271
|
serializer = :to_msgpack.to_proc
|
|
@@ -275,8 +284,8 @@ module Fluent::Plugin
|
|
|
275
284
|
end
|
|
276
285
|
|
|
277
286
|
def response(option)
|
|
278
|
-
if option && option[
|
|
279
|
-
return {
|
|
287
|
+
if option && option[OPTION_CHUNK]
|
|
288
|
+
return { OPTION_ACK => option[OPTION_CHUNK] }
|
|
280
289
|
end
|
|
281
290
|
nil
|
|
282
291
|
end
|
|
@@ -310,14 +319,19 @@ module Fluent::Plugin
|
|
|
310
319
|
when String
|
|
311
320
|
# PackedForward
|
|
312
321
|
option = msg[2] || {}
|
|
313
|
-
size = option[
|
|
322
|
+
size = option[OPTION_SIZE] || 0
|
|
323
|
+
compressed = option[OPTION_COMPRESSED]
|
|
314
324
|
|
|
315
|
-
if
|
|
316
|
-
es = Fluent::CompressedMessagePackEventStream.new(entries, nil, size.to_i, compress:
|
|
325
|
+
if compressed && compressed != OPTION_TEXT
|
|
326
|
+
es = Fluent::CompressedMessagePackEventStream.new(entries, nil, size.to_i, compress: compressed.to_sym, decompression_size_limit: @decompression_size_limit)
|
|
317
327
|
else
|
|
318
328
|
es = Fluent::MessagePackEventStream.new(entries, nil, size.to_i)
|
|
319
329
|
end
|
|
320
|
-
|
|
330
|
+
if @skip_invalid_event
|
|
331
|
+
es = normalize_event_stream(tag, es, conn.remote_host)
|
|
332
|
+
elsif option[OPTION_FLUENT_SIGNAL] == 0
|
|
333
|
+
es = Fluent::MetadataTimeEventStream.new(es)
|
|
334
|
+
end
|
|
321
335
|
if @enable_field_injection
|
|
322
336
|
es = add_source_info(es, conn)
|
|
323
337
|
end
|
|
@@ -325,19 +339,7 @@ module Fluent::Plugin
|
|
|
325
339
|
|
|
326
340
|
when Array
|
|
327
341
|
# Forward
|
|
328
|
-
es =
|
|
329
|
-
check_and_skip_invalid_event(tag, entries, conn.remote_host)
|
|
330
|
-
else
|
|
331
|
-
es = Fluent::MultiEventStream.new
|
|
332
|
-
entries.each { |e|
|
|
333
|
-
record = e[1]
|
|
334
|
-
next if record.nil?
|
|
335
|
-
time = e[0]
|
|
336
|
-
time = Fluent::EventTime.now if time.nil? || time.to_i == 0 # `to_i == 0` for empty EventTime
|
|
337
|
-
es.add(time, record)
|
|
338
|
-
}
|
|
339
|
-
es
|
|
340
|
-
end
|
|
342
|
+
es = normalize_event_stream(tag, entries, conn.remote_host)
|
|
341
343
|
if @enable_field_injection
|
|
342
344
|
es = add_source_info(es, conn)
|
|
343
345
|
end
|
|
@@ -346,7 +348,7 @@ module Fluent::Plugin
|
|
|
346
348
|
|
|
347
349
|
else
|
|
348
350
|
# Message
|
|
349
|
-
time = msg[1]
|
|
351
|
+
time = extract_event_time(msg[1])
|
|
350
352
|
record = msg[2]
|
|
351
353
|
if @skip_invalid_event && invalid_event?(tag, time, record)
|
|
352
354
|
log.warn "got invalid event and drop it:", host: conn.remote_host, tag: tag, time: time, record: record
|
|
@@ -367,21 +369,29 @@ module Fluent::Plugin
|
|
|
367
369
|
end
|
|
368
370
|
|
|
369
371
|
def invalid_event?(tag, time, record)
|
|
372
|
+
time = extract_event_time(time)
|
|
370
373
|
!((time.is_a?(Integer) || time.is_a?(::Fluent::EventTime)) && record.is_a?(Hash) && tag.is_a?(String))
|
|
371
374
|
end
|
|
372
375
|
|
|
373
|
-
def
|
|
376
|
+
def normalize_event_stream(tag, es, remote_host)
|
|
374
377
|
new_es = Fluent::MultiEventStream.new
|
|
375
378
|
es.each { |time, record|
|
|
376
|
-
|
|
379
|
+
time = extract_event_time(time)
|
|
380
|
+
if @skip_invalid_event && invalid_event?(tag, time, record)
|
|
377
381
|
log.warn "skip invalid event:", host: remote_host, tag: tag, time: time, record: record
|
|
378
382
|
next
|
|
379
383
|
end
|
|
384
|
+
next if record.nil?
|
|
385
|
+
time = Fluent::EventTime.now if time.nil? || time.to_i == 0 # `to_i == 0` for empty EventTime
|
|
380
386
|
new_es.add(time, record)
|
|
381
387
|
}
|
|
382
388
|
new_es
|
|
383
389
|
end
|
|
384
390
|
|
|
391
|
+
def extract_event_time(time)
|
|
392
|
+
time.is_a?(Array) ? time[0] : time
|
|
393
|
+
end
|
|
394
|
+
|
|
385
395
|
def add_source_info(es, conn)
|
|
386
396
|
new_es = Fluent::MultiEventStream.new
|
|
387
397
|
if @source_address_key && @source_hostname_key
|
|
@@ -24,6 +24,8 @@ require 'webrick/httputils'
|
|
|
24
24
|
require 'uri'
|
|
25
25
|
require 'socket'
|
|
26
26
|
require 'json'
|
|
27
|
+
require 'ipaddr'
|
|
28
|
+
require 'openssl'
|
|
27
29
|
|
|
28
30
|
module Fluent::Plugin
|
|
29
31
|
class InHttpParser < Parser
|
|
@@ -90,6 +92,22 @@ module Fluent::Plugin
|
|
|
90
92
|
desc "Add prefix to incoming tag"
|
|
91
93
|
config_param :add_tag_prefix, :string, default: nil
|
|
92
94
|
|
|
95
|
+
config_section :auth, required: false, multi: false do
|
|
96
|
+
desc 'The method for HTTP authentication'
|
|
97
|
+
config_param :method, :enum, list: [:basic], default: :basic
|
|
98
|
+
desc 'The username for basic authentication'
|
|
99
|
+
config_param :username, :string
|
|
100
|
+
desc 'The password for basic authentication'
|
|
101
|
+
config_param :password, :string, secret: true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
config_section :security, required: false, multi: false do
|
|
105
|
+
config_section :client, param_name: :clients, required: false, multi: true do
|
|
106
|
+
desc 'Address or network of the client, IPv4 or IPv6. e.g. 192.168.1.10, 192.168.0.0/16, fd00::/8'
|
|
107
|
+
config_param :network, :string
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
93
111
|
config_section :parse do
|
|
94
112
|
config_set_default :@type, 'in_http'
|
|
95
113
|
end
|
|
@@ -100,6 +118,7 @@ module Fluent::Plugin
|
|
|
100
118
|
super
|
|
101
119
|
|
|
102
120
|
@km = nil
|
|
121
|
+
@nodes = []
|
|
103
122
|
@format_name = nil
|
|
104
123
|
@parser_time_key = nil
|
|
105
124
|
|
|
@@ -127,6 +146,8 @@ module Fluent::Plugin
|
|
|
127
146
|
|
|
128
147
|
raise Fluent::ConfigError, "'add_tag_prefix' parameter must not be empty" if @add_tag_prefix && @add_tag_prefix.empty?
|
|
129
148
|
|
|
149
|
+
configure_security if @security
|
|
150
|
+
|
|
130
151
|
m = if @parser_configs.first['@type'] == 'in_http'
|
|
131
152
|
@parser_msgpack = parser_create(usage: 'parser_in_http_msgpack', type: 'msgpack')
|
|
132
153
|
@parser_msgpack.time_key = nil
|
|
@@ -260,11 +281,33 @@ module Fluent::Plugin
|
|
|
260
281
|
|
|
261
282
|
private
|
|
262
283
|
|
|
284
|
+
def configure_security
|
|
285
|
+
@nodes = @security.clients.map do |client|
|
|
286
|
+
begin
|
|
287
|
+
IPAddr.new(client.network)
|
|
288
|
+
rescue ArgumentError
|
|
289
|
+
raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def allowed_client?(address)
|
|
295
|
+
return true if @nodes.empty?
|
|
296
|
+
@nodes.any? { |node| node.include?(address) rescue false }
|
|
297
|
+
end
|
|
298
|
+
|
|
263
299
|
def on_server_connect(conn)
|
|
300
|
+
unless allowed_client?(conn.remote_addr)
|
|
301
|
+
log.warn "client address does not match any allowed network", address: conn.remote_addr
|
|
302
|
+
conn.data {|_data| }
|
|
303
|
+
conn.close
|
|
304
|
+
return
|
|
305
|
+
end
|
|
306
|
+
|
|
264
307
|
handler = Handler.new(conn, @km, method(:on_request),
|
|
265
308
|
@body_size_limit, @decompression_size_limit, @format_name, log,
|
|
266
309
|
@cors_allow_origins, @cors_allow_credentials,
|
|
267
|
-
@add_query_params)
|
|
310
|
+
@add_query_params, @auth)
|
|
268
311
|
|
|
269
312
|
conn.on(:data) do |data|
|
|
270
313
|
handler.on_read(data)
|
|
@@ -347,7 +390,7 @@ module Fluent::Plugin
|
|
|
347
390
|
attr_reader :content_type
|
|
348
391
|
|
|
349
392
|
def initialize(io, km, callback, body_size_limit, decompression_size_limit, format_name, log,
|
|
350
|
-
cors_allow_origins, cors_allow_credentials, add_query_params)
|
|
393
|
+
cors_allow_origins, cors_allow_credentials, add_query_params, auth = nil)
|
|
351
394
|
@io = io
|
|
352
395
|
@km = km
|
|
353
396
|
@callback = callback
|
|
@@ -360,6 +403,8 @@ module Fluent::Plugin
|
|
|
360
403
|
@cors_allow_credentials = cors_allow_credentials
|
|
361
404
|
@idle = 0
|
|
362
405
|
@add_query_params = add_query_params
|
|
406
|
+
@auth = auth
|
|
407
|
+
@authorization = nil
|
|
363
408
|
@km.add(self)
|
|
364
409
|
|
|
365
410
|
@remote_port, @remote_addr = io.remote_port, io.remote_addr
|
|
@@ -399,6 +444,7 @@ module Fluent::Plugin
|
|
|
399
444
|
@env = {}
|
|
400
445
|
@content_type = ""
|
|
401
446
|
@content_encoding = ""
|
|
447
|
+
@authorization = nil
|
|
402
448
|
headers.each_pair {|k,v|
|
|
403
449
|
@env["HTTP_#{k.tr('-','_').upcase}"] = v
|
|
404
450
|
case k
|
|
@@ -426,8 +472,22 @@ module Fluent::Plugin
|
|
|
426
472
|
@access_control_request_method = v
|
|
427
473
|
when /\AAccess-Control-Request-Headers\Z/i
|
|
428
474
|
@access_control_request_headers = v
|
|
475
|
+
when /\AAuthorization\Z/i
|
|
476
|
+
@authorization = v.is_a?(Array) ? v.first : v
|
|
429
477
|
end
|
|
430
478
|
}
|
|
479
|
+
|
|
480
|
+
if @auth
|
|
481
|
+
# Never let the credential reach a record built by add_http_headers.
|
|
482
|
+
@env.delete("HTTP_AUTHORIZATION")
|
|
483
|
+
|
|
484
|
+
unless preflight_request? || authenticate
|
|
485
|
+
@log.warn "authentication failed", address: @remote_addr
|
|
486
|
+
send_response_and_close(RES_401_STATUS, {'WWW-Authenticate' => AUTH_CHALLENGE}, "")
|
|
487
|
+
return
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
431
491
|
if expect
|
|
432
492
|
if expect == '100-continue'.freeze
|
|
433
493
|
if !size || size < @body_size_limit
|
|
@@ -442,6 +502,8 @@ module Fluent::Plugin
|
|
|
442
502
|
end
|
|
443
503
|
|
|
444
504
|
def on_body(chunk)
|
|
505
|
+
return if closing?
|
|
506
|
+
|
|
445
507
|
if @body.bytesize + chunk.bytesize > @body_size_limit
|
|
446
508
|
unless closing?
|
|
447
509
|
send_response_and_close("413 Request Entity Too Large", {}, "Too large")
|
|
@@ -452,6 +514,8 @@ module Fluent::Plugin
|
|
|
452
514
|
end
|
|
453
515
|
|
|
454
516
|
RES_200_STATUS = "200 OK".freeze
|
|
517
|
+
RES_401_STATUS = "401 Unauthorized".freeze
|
|
518
|
+
AUTH_CHALLENGE = 'Basic realm="fluentd"'.freeze
|
|
455
519
|
RES_403_STATUS = "403 Forbidden".freeze
|
|
456
520
|
|
|
457
521
|
# Azure App Service sends GET requests for health checking purpose.
|
|
@@ -651,6 +715,23 @@ module Fluent::Plugin
|
|
|
651
715
|
def parse_query(query)
|
|
652
716
|
query.nil? ? {} : Hash[URI.decode_www_form(query, Encoding::ASCII_8BIT)]
|
|
653
717
|
end
|
|
718
|
+
|
|
719
|
+
# Browsers never attach Authorization to a CORS preflight.
|
|
720
|
+
def preflight_request?
|
|
721
|
+
@parser.http_method == 'OPTIONS'.freeze
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def authenticate
|
|
725
|
+
scheme, credentials = @authorization.to_s.split(' ', 2)
|
|
726
|
+
return false unless scheme == 'Basic'.freeze
|
|
727
|
+
|
|
728
|
+
username, password = credentials.to_s.unpack1('m').to_s.split(':', 2)
|
|
729
|
+
username_matched = OpenSSL.secure_compare(username.to_s, @auth.username)
|
|
730
|
+
password_matched = OpenSSL.secure_compare(password.to_s, @auth.password)
|
|
731
|
+
username_matched && password_matched
|
|
732
|
+
rescue ArgumentError
|
|
733
|
+
false
|
|
734
|
+
end
|
|
654
735
|
end
|
|
655
736
|
end
|
|
656
737
|
end
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
require 'json'
|
|
18
18
|
|
|
19
|
+
require 'fluent/env'
|
|
19
20
|
require 'fluent/config/types'
|
|
20
21
|
require 'fluent/plugin/input'
|
|
21
22
|
require 'fluent/plugin/output'
|
|
@@ -101,7 +102,7 @@ module Fluent::Plugin
|
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
def render_ltsv(obj, code: 200)
|
|
104
|
-
normalized = JSON.parse(obj.to_json)
|
|
105
|
+
normalized = JSON.parse(obj.to_json, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
105
106
|
text = ''
|
|
106
107
|
normalized.each do |hash|
|
|
107
108
|
row = []
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
#
|
|
16
16
|
|
|
17
|
-
require '
|
|
17
|
+
require 'json'
|
|
18
18
|
|
|
19
19
|
require 'fluent/plugin/input'
|
|
20
20
|
|
|
@@ -86,7 +86,7 @@ module Fluent::Plugin
|
|
|
86
86
|
|
|
87
87
|
router.emit(@tag, now, record)
|
|
88
88
|
rescue => e
|
|
89
|
-
log.error "object space failed to emit", error: e, tag: @tag, record:
|
|
89
|
+
log.error "object space failed to emit", error: e, tag: @tag, record: JSON.generate(record)
|
|
90
90
|
log.error_backtrace
|
|
91
91
|
end
|
|
92
92
|
end
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
require 'json'
|
|
18
18
|
|
|
19
|
+
require 'fluent/env'
|
|
19
20
|
require 'fluent/plugin/input'
|
|
20
21
|
require 'fluent/config/error'
|
|
21
22
|
|
|
@@ -44,7 +45,7 @@ module Fluent::Plugin
|
|
|
44
45
|
desc "The sample data to be generated. An array of JSON hashes or a single JSON hash."
|
|
45
46
|
config_param :sample, alias: :dummy, default: [{"message" => "sample"}] do |val|
|
|
46
47
|
begin
|
|
47
|
-
parsed = JSON.parse(val)
|
|
48
|
+
parsed = JSON.parse(val, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
48
49
|
rescue JSON::ParserError => ex
|
|
49
50
|
# Fluent::ConfigParseError, "got incomplete JSON" will be raised
|
|
50
51
|
# at literal_parser.rb with --use-v1-config, but I had to
|
|
@@ -18,7 +18,7 @@ require 'fluent/plugin/input'
|
|
|
18
18
|
require 'fluent/config/error'
|
|
19
19
|
require 'fluent/plugin/parser'
|
|
20
20
|
|
|
21
|
-
require '
|
|
21
|
+
require 'json'
|
|
22
22
|
|
|
23
23
|
module Fluent::Plugin
|
|
24
24
|
class SyslogInput < Input
|
|
@@ -98,8 +98,8 @@ module Fluent::Plugin
|
|
|
98
98
|
desc 'The field name of the facility.'
|
|
99
99
|
config_param :facility_key, :string, default: nil
|
|
100
100
|
|
|
101
|
-
desc "The max bytes of message"
|
|
102
|
-
config_param :message_length_limit, :size, default:
|
|
101
|
+
desc "The max bytes of message. The messages exceeding this limit are dropped (UDP, TCP/TLS with traditional frame_type) or the connection is closed (TCP/TLS with octet_count frame_type)."
|
|
102
|
+
config_param :message_length_limit, :size, default: 8192
|
|
103
103
|
|
|
104
104
|
config_param :blocking_timeout, :time, default: 0.5
|
|
105
105
|
|
|
@@ -190,29 +190,95 @@ module Fluent::Plugin
|
|
|
190
190
|
resolve_name: @resolve_hostname,
|
|
191
191
|
send_keepalive_packet: @send_keepalive_packet
|
|
192
192
|
) do |conn|
|
|
193
|
+
discard_till_next_delimiter = false
|
|
193
194
|
conn.data do |data|
|
|
194
195
|
buffer = conn.buffer
|
|
195
196
|
buffer << data
|
|
196
197
|
pos = 0
|
|
197
198
|
if octet_count_frame
|
|
199
|
+
# Once an octet-counted stream contains an untrustworthy frame header, the stream
|
|
200
|
+
# can't be resynchronized. So close the connection instead of skipping bytes.
|
|
201
|
+
close_connection = false
|
|
198
202
|
while idx = buffer.index(delimiter, pos)
|
|
199
|
-
|
|
203
|
+
begin
|
|
204
|
+
num = Integer(buffer[pos..idx])
|
|
205
|
+
rescue ArgumentError
|
|
206
|
+
log.info "The message length is not a number, closing connection"
|
|
207
|
+
log.on_debug { log.debug "The message length is not a number:", head: buffer[pos, 32].dump }
|
|
208
|
+
close_connection = true
|
|
209
|
+
break
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if num < 0
|
|
213
|
+
log.info "The message length is negative, closing connection:", size: num
|
|
214
|
+
close_connection = true
|
|
215
|
+
break
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if num > @message_length_limit
|
|
219
|
+
log.info "The message length exceeds 'message_length_limit', closing connection:", limit: @message_length_limit, size: num
|
|
220
|
+
close_connection = true
|
|
221
|
+
break
|
|
222
|
+
end
|
|
223
|
+
|
|
200
224
|
msg = buffer[idx + delimiter_size, num]
|
|
201
225
|
if msg.size != num
|
|
226
|
+
# The frame is not fully received yet.
|
|
202
227
|
break
|
|
203
228
|
end
|
|
204
229
|
|
|
205
230
|
pos = idx + delimiter_size + num
|
|
206
231
|
message_handler(msg, conn)
|
|
207
232
|
end
|
|
233
|
+
buffer.slice!(0, pos) if pos > 0
|
|
234
|
+
|
|
235
|
+
# If the remaining buffer has no delimiter, no message length can be parsed from it.
|
|
236
|
+
# Since a valid frame header is much shorter than the limit, such a buffer never
|
|
237
|
+
# becomes a valid frame. (Note that a partially received valid frame always has a
|
|
238
|
+
# delimiter after its length, so this doesn't drop it even if it slightly exceeds
|
|
239
|
+
# the limit with its length header.)
|
|
240
|
+
if !close_connection && !buffer.index(delimiter) && buffer.bytesize > @message_length_limit
|
|
241
|
+
log.info "The buffer size exceeds 'message_length_limit', closing connection:", limit: @message_length_limit, size: buffer.bytesize
|
|
242
|
+
log.on_debug { log.debug "The buffer size exceeds 'message_length_limit':", head: buffer[...32] }
|
|
243
|
+
close_connection = true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if close_connection
|
|
247
|
+
buffer.clear
|
|
248
|
+
conn.close
|
|
249
|
+
next
|
|
250
|
+
end
|
|
208
251
|
else
|
|
209
252
|
while idx = buffer.index(delimiter, pos)
|
|
210
253
|
msg = buffer[pos...idx]
|
|
211
254
|
pos = idx + delimiter_size
|
|
255
|
+
|
|
256
|
+
if discard_till_next_delimiter
|
|
257
|
+
discard_till_next_delimiter = false
|
|
258
|
+
next
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if msg.bytesize > @message_length_limit
|
|
262
|
+
log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize
|
|
263
|
+
log.on_debug { log.debug "The received data is larger than 'message_length_limit':", head: msg[...32] }
|
|
264
|
+
next
|
|
265
|
+
end
|
|
266
|
+
|
|
212
267
|
message_handler(msg, conn)
|
|
213
268
|
end
|
|
269
|
+
buffer.slice!(0, pos) if pos > 0
|
|
270
|
+
|
|
271
|
+
# If the buffer size exceeds the limit here, it means that the next message will
|
|
272
|
+
# definitely exceed the limit. So we should clear the buffer here. Otherwise, it will
|
|
273
|
+
# keep storing useless data until the next delimiter comes.
|
|
274
|
+
if buffer.bytesize > @message_length_limit
|
|
275
|
+
log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buffer.bytesize
|
|
276
|
+
log.on_debug { log.debug "The buffer size exceeds 'message_length_limit':", head: buffer[...32] }
|
|
277
|
+
buffer.clear
|
|
278
|
+
# We should discard the subsequent data until the next delimiter comes.
|
|
279
|
+
discard_till_next_delimiter = true
|
|
280
|
+
end
|
|
214
281
|
end
|
|
215
|
-
buffer.slice!(0, pos) if pos > 0
|
|
216
282
|
end
|
|
217
283
|
end
|
|
218
284
|
end
|
|
@@ -274,7 +340,7 @@ module Fluent::Plugin
|
|
|
274
340
|
def emit(tag, time, record)
|
|
275
341
|
router.emit(tag, time, record)
|
|
276
342
|
rescue => e
|
|
277
|
-
log.error "syslog failed to emit", error: e, tag: tag, record:
|
|
343
|
+
log.error "syslog failed to emit", error: e, tag: tag, record: JSON.generate(record)
|
|
278
344
|
end
|
|
279
345
|
end
|
|
280
346
|
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Fluentd
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
require 'fluent/plugin/input'
|
|
18
|
+
require 'fluent/clock'
|
|
19
|
+
|
|
20
|
+
module Fluent::Plugin
|
|
21
|
+
class TailInput < Fluent::Plugin::Input
|
|
22
|
+
module Compatibility
|
|
23
|
+
# Keep these methods as compatibility entry points for plugins which
|
|
24
|
+
# inherit TailInput and override line processing methods. TailInput is a
|
|
25
|
+
# built-in plugin, not a public inheritance API, and these methods may be
|
|
26
|
+
# removed in a future release.
|
|
27
|
+
def flush_buffer(tw, buf)
|
|
28
|
+
@line_feeder.flush_buffer(tw, buf)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Feeds lines through the per-file feed, or through LineFeeder for watchers
|
|
32
|
+
# created by plugins using the deprecated LineBufferTimerFlusher.
|
|
33
|
+
#
|
|
34
|
+
# @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
|
|
35
|
+
def receive_lines(lines, tail_watcher)
|
|
36
|
+
return async_receive_lines(lines, tail_watcher) if @worker_pool
|
|
37
|
+
|
|
38
|
+
file_feed = tail_watcher.file_feed
|
|
39
|
+
return file_feed.feed_lines(lines, tail_watcher) if file_feed.respond_to?(:feed_lines)
|
|
40
|
+
|
|
41
|
+
@line_feeder.feed_lines(lines, tail_watcher)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def convert_line_to_event(line, es, tail_watcher)
|
|
45
|
+
@line_feeder.convert_line_to_event(line, es, tail_watcher)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_singleline(lines, tail_watcher)
|
|
49
|
+
@line_feeder.parse_singleline(lines, tail_watcher)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# TailInput's default #parse_multilines path starts the flush timer before
|
|
53
|
+
# parsing. An override that skips super must call
|
|
54
|
+
# tail_watcher.line_buffer_timer_flusher.reset_timer when needed.
|
|
55
|
+
def parse_multilines(lines, tail_watcher)
|
|
56
|
+
@line_feeder.parse_multilines(lines, tail_watcher)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class TailWatcher
|
|
61
|
+
# The object which keeps the line buffer of the multiline mode of the file:
|
|
62
|
+
# the FileFeed built by LineFeeder#new_file_feed, or the deprecated
|
|
63
|
+
# LineBufferTimerFlusher which a plugin overriding TailInput#setup_watcher
|
|
64
|
+
# passes to keep working without changes.
|
|
65
|
+
def line_buffer_timer_flusher
|
|
66
|
+
@file_feed
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Kept for compatibility with the plugins which build it by themselves and
|
|
70
|
+
# pass it to TailWatcher in the overridden TailInput#setup_watcher.
|
|
71
|
+
# LineFeeder#feed_lines feeds the lines of a watcher which keeps it, and
|
|
72
|
+
# LineFeeder::FileFeed built by LineFeeder#new_file_feed replaces it.
|
|
73
|
+
class LineBufferTimerFlusher
|
|
74
|
+
attr_accessor :line_buffer
|
|
75
|
+
|
|
76
|
+
def initialize(log, flush_interval, &flush_method)
|
|
77
|
+
@log = log
|
|
78
|
+
@flush_interval = flush_interval
|
|
79
|
+
@flush_method = flush_method
|
|
80
|
+
@start = nil
|
|
81
|
+
@line_buffer = nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def on_notify(tw)
|
|
85
|
+
unless @start && @flush_method
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if Fluent::Clock.now - @start >= @flush_interval
|
|
90
|
+
@flush_method.call(tw, @line_buffer) if @line_buffer
|
|
91
|
+
@line_buffer = nil
|
|
92
|
+
@start = nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def close(tw)
|
|
97
|
+
return unless @line_buffer
|
|
98
|
+
|
|
99
|
+
@flush_method.call(tw, @line_buffer)
|
|
100
|
+
@line_buffer = nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def reset_timer
|
|
104
|
+
return unless @flush_interval
|
|
105
|
+
|
|
106
|
+
@start = Fluent::Clock.now
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|