fluentd 1.4.2 → 1.5.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +9 -5
- data/CHANGELOG.md +25 -0
- data/README.md +5 -0
- data/example/in_http.conf +3 -1
- data/lib/fluent/command/fluentd.rb +1 -1
- data/lib/fluent/log.rb +1 -1
- data/lib/fluent/plugin/in_forward.rb +11 -0
- data/lib/fluent/plugin/in_syslog.rb +4 -2
- data/lib/fluent/plugin/out_exec_filter.rb +1 -0
- data/lib/fluent/plugin/out_forward.rb +231 -16
- data/lib/fluent/plugin/parser_json.rb +7 -1
- data/lib/fluent/supervisor.rb +1 -1
- data/lib/fluent/version.rb +1 -1
- data/test/plugin/test_in_forward.rb +64 -0
- data/test/plugin/test_out_exec_filter.rb +6 -0
- data/test/plugin/test_out_forward.rb +209 -0
- data/test/plugin/test_parser_json.rb +24 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ba10a2d9e94456e95d304989cd7487771c3dc71
|
4
|
+
data.tar.gz: ce09baf07ad2c3af4fdea5665c6c57a4387e433a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '084c5d106ce8f806f3d52798a7a45498155d5a78c87eefcb6ee0163a79d4cccab42dac9c4e86c90ba644975f06ca0359fc5afa8f65c273f00841312491f7efee'
|
7
|
+
data.tar.gz: 166d9c9a41e3e6b9ae6b3881655be16348ab8d562e4890fe0d27202431a94f2fd37f203b928915b4272dc9da975c2932bb2c449fca69553227eec6f8d18df5db
|
data/.travis.yml
CHANGED
@@ -11,11 +11,15 @@ matrix:
|
|
11
11
|
os: linux
|
12
12
|
- rvm: 2.2.10
|
13
13
|
os: linux
|
14
|
-
- rvm: 2.
|
14
|
+
- rvm: 2.2.10
|
15
|
+
os: linux-ppc64le
|
16
|
+
- rvm: 2.4.6
|
15
17
|
os: linux
|
16
|
-
- rvm: 2.
|
18
|
+
- rvm: 2.4.6
|
19
|
+
os: linux-ppc64le
|
20
|
+
- rvm: 2.5.5
|
17
21
|
os: linux
|
18
|
-
- rvm: 2.6.
|
22
|
+
- rvm: 2.6.3
|
19
23
|
os: linux
|
20
24
|
- rvm: ruby-head
|
21
25
|
os: linux
|
@@ -28,7 +32,7 @@ matrix:
|
|
28
32
|
# - rvm: 2.3.3
|
29
33
|
# os: osx
|
30
34
|
# osx_image: xcode8.2 # OSX 10.12
|
31
|
-
- rvm: 2.4.
|
35
|
+
- rvm: 2.4.6
|
32
36
|
os: osx
|
33
37
|
osx_image: xcode8.3 # OSX 10.12
|
34
38
|
- rvm: ruby-head
|
@@ -40,7 +44,7 @@ matrix:
|
|
40
44
|
- rvm: 2.1.10
|
41
45
|
os: osx
|
42
46
|
osx_image: xcode8.3
|
43
|
-
- rvm: 2.4.
|
47
|
+
- rvm: 2.4.6
|
44
48
|
os: osx
|
45
49
|
osx_image: xcode8.3
|
46
50
|
- rvm: ruby-head
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
# v1.5
|
2
|
+
|
3
|
+
## Pre release
|
4
|
+
|
5
|
+
### New feature
|
6
|
+
|
7
|
+
* out_forward: Support keepalive feature
|
8
|
+
https://github.com/fluent/fluentd/pull/2393
|
9
|
+
|
10
|
+
### Enhancement
|
11
|
+
|
12
|
+
* in_syslog: Add delimiter parameter
|
13
|
+
https://github.com/fluent/fluentd/pull/2378
|
14
|
+
* in_forward: Add tag/add_tag_prefix parameters
|
15
|
+
https://github.com/fluent/fluentd/pull/2396
|
16
|
+
* parser_json: Add stream_buffer_size parameter for yajl
|
17
|
+
https://github.com/fluent/fluentd/pull/2381
|
18
|
+
* command: Add deprecated message to show-plugin-config option
|
19
|
+
https://github.com/fluent/fluentd/pull/2401
|
20
|
+
|
21
|
+
### Bug fixes
|
22
|
+
|
23
|
+
* out_forward: Don't use SO_LINGER on SSL/TLS WinSock
|
24
|
+
https://github.com/fluent/fluentd/pull/2398
|
25
|
+
|
1
26
|
# v1.4
|
2
27
|
|
3
28
|
## Release v1.4.2 - 2019/04/02
|
data/README.md
CHANGED
@@ -30,6 +30,11 @@ Mobile/Web Application Logging | Fluentd can function as middleware to enable as
|
|
30
30
|
|
31
31
|
## Development
|
32
32
|
|
33
|
+
### Branch
|
34
|
+
|
35
|
+
- master: For v1 development.
|
36
|
+
- v0.12: For v0.12. This is security maintenance mode. Only security fix is accepted.
|
37
|
+
|
33
38
|
### Prerequisites
|
34
39
|
|
35
40
|
- Ruby 2.1 or later
|
data/example/in_http.conf
CHANGED
@@ -40,7 +40,7 @@ op.on('--dry-run', "Check fluentd setup is correct or not", TrueClass) {|b|
|
|
40
40
|
opts[:dry_run] = b
|
41
41
|
}
|
42
42
|
|
43
|
-
op.on('--show-plugin-config=PLUGIN', "Show PLUGIN configuration and exit(ex: input:dummy)") {|plugin|
|
43
|
+
op.on('--show-plugin-config=PLUGIN', "[DEPRECATED] Show PLUGIN configuration and exit(ex: input:dummy)") {|plugin|
|
44
44
|
opts[:show_plugin_config] = plugin
|
45
45
|
}
|
46
46
|
|
data/lib/fluent/log.rb
CHANGED
@@ -148,10 +148,10 @@ module Fluent
|
|
148
148
|
end
|
149
149
|
|
150
150
|
attr_reader :format
|
151
|
+
attr_reader :time_format
|
151
152
|
attr_accessor :log_event_enabled
|
152
153
|
attr_accessor :out
|
153
154
|
attr_accessor :level
|
154
|
-
attr_accessor :time_format
|
155
155
|
attr_accessor :optional_header, :optional_attrs
|
156
156
|
|
157
157
|
def logdev=(logdev)
|
@@ -62,6 +62,11 @@ module Fluent::Plugin
|
|
62
62
|
desc "The field name of the client's hostname."
|
63
63
|
config_param :source_hostname_key, :string, default: nil
|
64
64
|
|
65
|
+
desc "New tag instead of incoming tag"
|
66
|
+
config_param :tag, :string, default: nil
|
67
|
+
desc "Add prefix to incoming tag"
|
68
|
+
config_param :add_tag_prefix, :string, default: nil
|
69
|
+
|
65
70
|
config_section :security, required: false, multi: false do
|
66
71
|
desc 'The hostname'
|
67
72
|
config_param :self_hostname, :string
|
@@ -106,6 +111,9 @@ module Fluent::Plugin
|
|
106
111
|
end
|
107
112
|
@enable_field_injection = @source_address_key || @source_hostname_key
|
108
113
|
|
114
|
+
raise Fluent::ConfigError, "'tag' parameter must not be empty" if @tag && @tag.empty?
|
115
|
+
raise Fluent::ConfigError, "'add_tag_prefix' parameter must not be empty" if @add_tag_prefix && @add_tag_prefix.empty?
|
116
|
+
|
109
117
|
if @security
|
110
118
|
if @security.user_auth && @security.users.empty?
|
111
119
|
raise Fluent::ConfigError, "<user> sections required if user_auth enabled"
|
@@ -293,6 +301,9 @@ module Fluent::Plugin
|
|
293
301
|
log.warn "Input chunk size is larger than 'chunk_size_warn_limit':", tag: tag, host: conn.remote_host, limit: @chunk_size_warn_limit, size: chunk_size
|
294
302
|
end
|
295
303
|
|
304
|
+
tag = @tag.dup if @tag
|
305
|
+
tag = "#{@add_tag_prefix}.#{tag}" if @add_tag_prefix
|
306
|
+
|
296
307
|
case entries
|
297
308
|
when String
|
298
309
|
# PackedForward
|
@@ -99,6 +99,9 @@ module Fluent::Plugin
|
|
99
99
|
|
100
100
|
config_param :blocking_timeout, :time, default: 0.5
|
101
101
|
|
102
|
+
desc 'The delimiter value "\n"'
|
103
|
+
config_param :delimiter, :string, default: "\n" # syslog family add "\n" to each message
|
104
|
+
|
102
105
|
config_section :parse do
|
103
106
|
config_set_default :@type, DEFAULT_PARSER
|
104
107
|
config_param :with_priority, :bool, default: true
|
@@ -156,8 +159,7 @@ module Fluent::Plugin
|
|
156
159
|
def start_tcp_server
|
157
160
|
octet_count_frame = @frame_type == :octet_count
|
158
161
|
|
159
|
-
|
160
|
-
delimiter = octet_count_frame ? " " : "\n"
|
162
|
+
delimiter = octet_count_frame ? " " : @delimiter
|
161
163
|
delimiter_size = delimiter.size
|
162
164
|
server_create_connection(:in_syslog_tcp_server, @port, bind: @bind, resolve_name: @resolve_hostname) do |conn|
|
163
165
|
conn.data do |data|
|
@@ -36,7 +36,6 @@ module Fluent::Plugin
|
|
36
36
|
desc 'The transport protocol.'
|
37
37
|
config_param :transport, :enum, list: [:tcp, :tls], default: :tcp
|
38
38
|
# TODO: TLS session cache/tickets
|
39
|
-
# TODO: Connection keepalive
|
40
39
|
|
41
40
|
desc 'The timeout time when sending event logs.'
|
42
41
|
config_param :send_timeout, :time, default: 60
|
@@ -103,6 +102,10 @@ module Fluent::Plugin
|
|
103
102
|
config_param :tls_client_private_key_path, :string, default: nil
|
104
103
|
desc 'The client private key passphrase for TLS.'
|
105
104
|
config_param :tls_client_private_key_passphrase, :string, default: nil, secret: true
|
105
|
+
desc "Enable keepalive connection."
|
106
|
+
config_param :keepalive, :bool, default: false
|
107
|
+
desc "Expired time of keepalive. Default value is nil, which means to keep connection as long as possible"
|
108
|
+
config_param :keepalive_timeout, :time, default: nil
|
106
109
|
|
107
110
|
config_section :security, required: false, multi: false do
|
108
111
|
desc 'The hostname'
|
@@ -151,6 +154,7 @@ module Fluent::Plugin
|
|
151
154
|
@usock = nil
|
152
155
|
@sock_ack_waiting = nil
|
153
156
|
@sock_ack_waiting_mutex = nil
|
157
|
+
@keep_alive_watcher_interval = 5 # TODO
|
154
158
|
end
|
155
159
|
|
156
160
|
def configure(conf)
|
@@ -201,9 +205,9 @@ module Fluent::Plugin
|
|
201
205
|
|
202
206
|
log.info "adding forwarding server '#{name}'", host: server.host, port: server.port, weight: server.weight, plugin_id: plugin_id
|
203
207
|
if @heartbeat_type == :none
|
204
|
-
@nodes << NoneHeartbeatNode.new(self, server, failure: failure)
|
208
|
+
@nodes << NoneHeartbeatNode.new(self, server, failure: failure, keepalive: @keepalive, keepalive_timeout: @keepalive_timeout)
|
205
209
|
else
|
206
|
-
node = Node.new(self, server, failure: failure)
|
210
|
+
node = Node.new(self, server, failure: failure, keepalive: @keepalive, keepalive_timeout: @keepalive_timeout)
|
207
211
|
begin
|
208
212
|
node.validate_host_resolution!
|
209
213
|
rescue => e
|
@@ -227,6 +231,10 @@ module Fluent::Plugin
|
|
227
231
|
raise Fluent::ConfigError, "forward output plugin requires at least one <server> is required"
|
228
232
|
end
|
229
233
|
|
234
|
+
if !@keepalive && @keepalive_timeout
|
235
|
+
log.warn('The value of keepalive_timeout is ignored. if you want to use keepalive, please add `keepalive true` to your conf.')
|
236
|
+
end
|
237
|
+
|
230
238
|
raise Fluent::ConfigError, "ack_response_timeout must be a positive integer" if @ack_response_timeout < 1
|
231
239
|
end
|
232
240
|
|
@@ -279,6 +287,10 @@ module Fluent::Plugin
|
|
279
287
|
end
|
280
288
|
end
|
281
289
|
end
|
290
|
+
|
291
|
+
if @keepalive && @keepalive_timeout
|
292
|
+
timer_execute(:out_forward_keep_alived_socket_watcher, @keep_alive_watcher_interval, &method(:on_purge_obsolete_socks))
|
293
|
+
end
|
282
294
|
end
|
283
295
|
|
284
296
|
def close
|
@@ -286,6 +298,10 @@ module Fluent::Plugin
|
|
286
298
|
# close socket and ignore errors: this socket will not be used anyway.
|
287
299
|
@usock.close rescue nil
|
288
300
|
end
|
301
|
+
|
302
|
+
if @keepalive && @keepalive_timeout
|
303
|
+
@nodes.each(&:clear)
|
304
|
+
end
|
289
305
|
super
|
290
306
|
end
|
291
307
|
|
@@ -354,7 +370,10 @@ module Fluent::Plugin
|
|
354
370
|
cert_path: @tls_client_cert_path,
|
355
371
|
private_key_path: @tls_client_private_key_path,
|
356
372
|
private_key_passphrase: @tls_client_private_key_passphrase,
|
357
|
-
|
373
|
+
|
374
|
+
# Enabling SO_LINGER causes data loss on Windows
|
375
|
+
# https://github.com/fluent/fluentd/issues/1968
|
376
|
+
linger_timeout: Fluent.windows? ? nil : @send_timeout,
|
358
377
|
send_timeout: @send_timeout,
|
359
378
|
recv_timeout: @ack_response_timeout,
|
360
379
|
&block
|
@@ -450,6 +469,10 @@ module Fluent::Plugin
|
|
450
469
|
end
|
451
470
|
end
|
452
471
|
|
472
|
+
def on_purge_obsolete_socks
|
473
|
+
@nodes.each(&:purge_obsolete_socks)
|
474
|
+
end
|
475
|
+
|
453
476
|
# return chunk id to be committed
|
454
477
|
def read_ack_from_sock(sock, unpacker)
|
455
478
|
begin
|
@@ -484,8 +507,13 @@ module Fluent::Plugin
|
|
484
507
|
log.error "unexpected error while receiving ack message", error: e
|
485
508
|
log.error_backtrace
|
486
509
|
ensure
|
487
|
-
|
488
|
-
|
510
|
+
if @keepalive
|
511
|
+
info.node.socket_cache.dec_ref_by_value(info.sock)
|
512
|
+
else
|
513
|
+
info.sock.close_write rescue nil
|
514
|
+
info.sock.close rescue nil
|
515
|
+
end
|
516
|
+
|
489
517
|
@sock_ack_waiting_mutex.synchronize do
|
490
518
|
@sock_ack_waiting.delete(info)
|
491
519
|
end
|
@@ -513,6 +541,9 @@ module Fluent::Plugin
|
|
513
541
|
# (2) the node does support sending response but responses have not arrived for some reasons.
|
514
542
|
log.warn "no response from node. regard it as unavailable.", host: info.node.host, port: info.node.port
|
515
543
|
info.node.disable!
|
544
|
+
if @keepalive
|
545
|
+
info.node.socket_cache.revoke_by_value(info.sock)
|
546
|
+
end
|
516
547
|
info.sock.close rescue nil
|
517
548
|
rollback_write(info.chunk_id, update_retry: false)
|
518
549
|
else
|
@@ -538,7 +569,142 @@ module Fluent::Plugin
|
|
538
569
|
end
|
539
570
|
|
540
571
|
class Node
|
541
|
-
|
572
|
+
class SocketCache
|
573
|
+
TimedSocket = Struct.new(:timeout, :sock, :ref)
|
574
|
+
|
575
|
+
def initialize(timeout, log)
|
576
|
+
@log = log
|
577
|
+
@timeout = timeout
|
578
|
+
@active_socks = {}
|
579
|
+
@inactive_socks = {}
|
580
|
+
@mutex = Mutex.new
|
581
|
+
end
|
582
|
+
|
583
|
+
def revoke(key = Thread.current.object_id)
|
584
|
+
@mutex.synchronize do
|
585
|
+
if @active_socks[key]
|
586
|
+
@inactive_socks[key] = @active_socks.delete(key)
|
587
|
+
@inactive_socks[key].ref = 0
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
def clear
|
593
|
+
@mutex.synchronize do
|
594
|
+
@inactive_socks.values.each do |s|
|
595
|
+
s.sock.close rescue nil
|
596
|
+
end
|
597
|
+
@inactive_socks.clear
|
598
|
+
|
599
|
+
@active_socks.values.each do |s|
|
600
|
+
s.sock.close rescue nil
|
601
|
+
end
|
602
|
+
@active_socks.clear
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
def purge_obsolete_socks
|
607
|
+
@mutex.synchronize do
|
608
|
+
@inactive_socks.keys.each do |k|
|
609
|
+
# 0 means sockets stored in this class received all acks
|
610
|
+
if @inactive_socks[k].ref <= 0
|
611
|
+
s = @inactive_socks.delete(k)
|
612
|
+
s.sock.close rescue nil
|
613
|
+
@log.debug("purged obsolete socket #{s.sock}")
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
@active_socks.keys.each do |k|
|
618
|
+
if expired?(k) && @active_socks[k].ref <= 0
|
619
|
+
@inactive_socks[k] = @active_socks.delete(k)
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
# We expect that `yield` returns a unique object in this class
|
626
|
+
def fetch_or(key = Thread.current.object_id)
|
627
|
+
@mutex.synchronize do
|
628
|
+
unless @active_socks[key]
|
629
|
+
@active_socks[key] = TimedSocket.new(timeout, yield, 1)
|
630
|
+
@log.debug("connect new socket #{@active_socks[key]}")
|
631
|
+
return @active_socks[key].sock
|
632
|
+
end
|
633
|
+
|
634
|
+
if expired?(key)
|
635
|
+
# Do not close this socket here in case of it will be used by other place (e.g. wait for receiving ack)
|
636
|
+
@inactive_socks[key] = @active_socks.delete(key)
|
637
|
+
@log.debug("connection #{@inactive_socks[key]} is expired. reconnecting...")
|
638
|
+
@active_socks[key] = TimedSocket.new(timeout, yield, 0)
|
639
|
+
end
|
640
|
+
|
641
|
+
@active_socks[key].ref += 1;
|
642
|
+
@active_socks[key].sock
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
def dec_ref(key = Thread.current.object_id)
|
647
|
+
@mutex.synchronize do
|
648
|
+
if @active_socks[key]
|
649
|
+
@active_socks[key].ref -= 1
|
650
|
+
elsif @inactive_socks[key]
|
651
|
+
@inactive_socks[key].ref -= 1
|
652
|
+
else
|
653
|
+
@log.warn("Not found key for dec_ref: #{key}")
|
654
|
+
end
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
# This method is expected to be called in class which doesn't call #inc_ref
|
659
|
+
def dec_ref_by_value(val)
|
660
|
+
@mutex.synchronize do
|
661
|
+
sock = @active_socks.detect { |_, v| v.sock == val }
|
662
|
+
if sock
|
663
|
+
key = sock.first
|
664
|
+
@active_socks[key].ref -= 1
|
665
|
+
return
|
666
|
+
end
|
667
|
+
|
668
|
+
sock = @inactive_socks.detect { |_, v| v.sock == val }
|
669
|
+
if sock
|
670
|
+
key = sock.first
|
671
|
+
@inactive_socks[key].ref -= 1
|
672
|
+
return
|
673
|
+
else
|
674
|
+
@log.warn("Not found key for dec_ref_by_value: #{key}")
|
675
|
+
end
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
# This method is expected to be called in class which doesn't call #fetch_or
|
680
|
+
def revoke_by_value(val)
|
681
|
+
@mutex.synchronize do
|
682
|
+
sock = @active_socks.detect { |_, v| v.sock == val }
|
683
|
+
if sock
|
684
|
+
key = sock.first
|
685
|
+
@inactive_socks[key] = @active_socks.delete(key)
|
686
|
+
@inactive_socks[key].ref = 0
|
687
|
+
else
|
688
|
+
@log.debug("Not found for revoke_by_value :#{val}")
|
689
|
+
end
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
private
|
694
|
+
|
695
|
+
def timeout
|
696
|
+
@timeout && Time.now + @timeout
|
697
|
+
end
|
698
|
+
|
699
|
+
# This method is thread unsafe
|
700
|
+
def expired?(key = Thread.current.object_id)
|
701
|
+
@active_socks[key].timeout ? @active_socks[key].timeout < Time.now : false
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
# @param keepalive [Bool]
|
706
|
+
# @param keepalive_timeout [Integer | nil]
|
707
|
+
def initialize(sender, server, failure:, keepalive: false, keepalive_timeout: nil)
|
542
708
|
@sender = sender
|
543
709
|
@log = sender.log
|
544
710
|
@compress = sender.compress
|
@@ -571,6 +737,11 @@ module Fluent::Plugin
|
|
571
737
|
@resolved_host = nil
|
572
738
|
@resolved_time = 0
|
573
739
|
@resolved_once = false
|
740
|
+
|
741
|
+
@keepalive = keepalive
|
742
|
+
if @keepalive
|
743
|
+
@socket_cache = SocketCache.new(keepalive_timeout, @log)
|
744
|
+
end
|
574
745
|
end
|
575
746
|
|
576
747
|
attr_accessor :usock
|
@@ -578,6 +749,7 @@ module Fluent::Plugin
|
|
578
749
|
attr_reader :name, :host, :port, :weight, :standby, :state
|
579
750
|
attr_reader :sockaddr # used by on_heartbeat
|
580
751
|
attr_reader :failure, :available # for test
|
752
|
+
attr_reader :socket_cache # for ack
|
581
753
|
|
582
754
|
RequestInfo = Struct.new(:state, :shared_key_nonce, :auth)
|
583
755
|
|
@@ -598,15 +770,12 @@ module Fluent::Plugin
|
|
598
770
|
end
|
599
771
|
|
600
772
|
def verify_connection
|
601
|
-
|
602
|
-
begin
|
773
|
+
connect do |sock|
|
603
774
|
ri = RequestInfo.new(@sender.security ? :helo : :established)
|
604
775
|
if ri.state != :established
|
605
776
|
establish_connection(sock, ri)
|
606
777
|
raise if ri.state != :established
|
607
778
|
end
|
608
|
-
ensure
|
609
|
-
sock.close
|
610
779
|
end
|
611
780
|
end
|
612
781
|
|
@@ -672,11 +841,16 @@ module Fluent::Plugin
|
|
672
841
|
end
|
673
842
|
|
674
843
|
def send_data(tag, chunk)
|
675
|
-
sock =
|
844
|
+
sock = connect
|
845
|
+
|
676
846
|
begin
|
677
847
|
send_data_actual(sock, tag, chunk)
|
678
848
|
rescue
|
679
|
-
|
849
|
+
if @keepalive
|
850
|
+
@socket_cache.revoke
|
851
|
+
else
|
852
|
+
sock.close rescue nil
|
853
|
+
end
|
680
854
|
raise
|
681
855
|
end
|
682
856
|
|
@@ -684,12 +858,27 @@ module Fluent::Plugin
|
|
684
858
|
return sock # to read ACK from socket
|
685
859
|
end
|
686
860
|
|
687
|
-
|
688
|
-
|
861
|
+
if @keepalive
|
862
|
+
@socket_cache.dec_ref
|
863
|
+
else
|
864
|
+
sock.close_write rescue nil
|
865
|
+
sock.close rescue nil
|
866
|
+
end
|
689
867
|
heartbeat(false)
|
690
868
|
nil
|
691
869
|
end
|
692
870
|
|
871
|
+
def clear
|
872
|
+
@keepalive && @socket_cache.clear
|
873
|
+
end
|
874
|
+
|
875
|
+
def purge_obsolete_socks
|
876
|
+
unless @keepalive
|
877
|
+
raise "Don not call this method without keepalive option"
|
878
|
+
end
|
879
|
+
@socket_cache.purge_obsolete_socks
|
880
|
+
end
|
881
|
+
|
693
882
|
# FORWARD_TCP_HEARTBEAT_DATA = FORWARD_HEADER + ''.to_msgpack + [].to_msgpack
|
694
883
|
def send_heartbeat
|
695
884
|
begin
|
@@ -705,7 +894,7 @@ module Fluent::Plugin
|
|
705
894
|
|
706
895
|
case @sender.heartbeat_type
|
707
896
|
when :transport
|
708
|
-
|
897
|
+
connect(dest_addr) do |sock|
|
709
898
|
## don't send any data to not cause a compatibility problem
|
710
899
|
# sock.write FORWARD_TCP_HEARTBEAT_DATA
|
711
900
|
|
@@ -880,6 +1069,32 @@ module Fluent::Plugin
|
|
880
1069
|
raise "BUG: unknown session state: #{ri.state}"
|
881
1070
|
end
|
882
1071
|
end
|
1072
|
+
|
1073
|
+
private
|
1074
|
+
|
1075
|
+
def connect(host = nil)
|
1076
|
+
sock = if @keepalive
|
1077
|
+
@socket_cache.fetch_or { @sender.create_transfer_socket(host || resolved_host, port, @hostname) }
|
1078
|
+
else
|
1079
|
+
@log.debug('connect new socket')
|
1080
|
+
@sender.create_transfer_socket(host || resolved_host, port, @hostname)
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
if block_given?
|
1084
|
+
begin
|
1085
|
+
yield(sock)
|
1086
|
+
rescue
|
1087
|
+
@socket_cache.revoke(sock) if @keepalive
|
1088
|
+
raise
|
1089
|
+
else
|
1090
|
+
@socket_cache.dec_ref(sock) if @keepalive
|
1091
|
+
ensure
|
1092
|
+
sock.close unless @keepalive
|
1093
|
+
end
|
1094
|
+
else
|
1095
|
+
sock
|
1096
|
+
end
|
1097
|
+
end
|
883
1098
|
end
|
884
1099
|
|
885
1100
|
# Override Node to disable heartbeat
|
@@ -30,6 +30,12 @@ module Fluent
|
|
30
30
|
desc 'Set JSON parser'
|
31
31
|
config_param :json_parser, :enum, list: [:oj, :yajl, :json], default: :oj
|
32
32
|
|
33
|
+
# The Yajl library defines a default buffer size of 8KiB when parsing
|
34
|
+
# from IO streams, so maintain this for backwards-compatibility.
|
35
|
+
# https://www.rubydoc.info/github/brianmario/yajl-ruby/Yajl%2FParser:parse
|
36
|
+
desc 'Set the buffer size that Yajl will use when parsing streaming input'
|
37
|
+
config_param :stream_buffer_size, :integer, default: 8192
|
38
|
+
|
33
39
|
config_set_default :time_type, :float
|
34
40
|
|
35
41
|
def configure(conf)
|
@@ -81,7 +87,7 @@ module Fluent
|
|
81
87
|
y.on_parse_complete = ->(record){
|
82
88
|
block.call(parse_time(record), record)
|
83
89
|
}
|
84
|
-
y.parse(io)
|
90
|
+
y.parse(io, @stream_buffer_size)
|
85
91
|
end
|
86
92
|
end
|
87
93
|
end
|
data/lib/fluent/supervisor.rb
CHANGED
@@ -588,7 +588,7 @@ module Fluent
|
|
588
588
|
|
589
589
|
def show_plugin_config
|
590
590
|
name, type = @show_plugin_config.split(":") # input:tail
|
591
|
-
$log.info "Use fluent-plugin-config-format --format=txt #{name} #{type}"
|
591
|
+
$log.info "show_plugin_config option is deprecated. Use fluent-plugin-config-format --format=txt #{name} #{type}"
|
592
592
|
exit 0
|
593
593
|
end
|
594
594
|
|
data/lib/fluent/version.rb
CHANGED
@@ -82,6 +82,14 @@ class ForwardInputTest < Test::Unit::TestCase
|
|
82
82
|
assert_equal 1, d.instance.security.clients.size
|
83
83
|
end
|
84
84
|
|
85
|
+
data(tag: "tag",
|
86
|
+
add_tag_prefix: "add_tag_prefix")
|
87
|
+
test 'tag parameters' do |data|
|
88
|
+
assert_raise(Fluent::ConfigError.new("'#{data}' parameter must not be empty")) {
|
89
|
+
create_driver(CONFIG + "#{data} ''")
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
85
93
|
test 'send_keepalive_packet is disabled by default' do
|
86
94
|
@d = d = create_driver(CONFIG_AUTH)
|
87
95
|
assert_false d.instance.send_keepalive_packet
|
@@ -269,6 +277,34 @@ class ForwardInputTest < Test::Unit::TestCase
|
|
269
277
|
assert_equal(records, d.events)
|
270
278
|
end
|
271
279
|
|
280
|
+
data(tag: {
|
281
|
+
param: "tag new_tag",
|
282
|
+
result: "new_tag"
|
283
|
+
},
|
284
|
+
add_tag_prefix: {
|
285
|
+
param: "add_tag_prefix new_prefix",
|
286
|
+
result: "new_prefix.tag1"
|
287
|
+
})
|
288
|
+
test 'tag parameters' do |data|
|
289
|
+
@d = create_driver(CONFIG + data[:param])
|
290
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
291
|
+
options = {auth: false}
|
292
|
+
|
293
|
+
records = [
|
294
|
+
["tag1", time, {"a"=>1}],
|
295
|
+
["tag1", time, {"a"=>2}],
|
296
|
+
]
|
297
|
+
|
298
|
+
@d.run(expect_records: records.length, timeout: 20) do
|
299
|
+
entries = []
|
300
|
+
records.each {|tag, _time, record|
|
301
|
+
entries << [_time, record]
|
302
|
+
}
|
303
|
+
send_data packer.write(["tag1", entries]).to_s, **options
|
304
|
+
end
|
305
|
+
assert_equal(data[:result], @d.events[0][0])
|
306
|
+
end
|
307
|
+
|
272
308
|
data(tcp: {
|
273
309
|
config: CONFIG,
|
274
310
|
options: {
|
@@ -378,6 +414,34 @@ class ForwardInputTest < Test::Unit::TestCase
|
|
378
414
|
assert_equal(records, d.events)
|
379
415
|
end
|
380
416
|
|
417
|
+
data(tag: {
|
418
|
+
param: "tag new_tag",
|
419
|
+
result: "new_tag"
|
420
|
+
},
|
421
|
+
add_tag_prefix: {
|
422
|
+
param: "add_tag_prefix new_prefix",
|
423
|
+
result: "new_prefix.tag1"
|
424
|
+
})
|
425
|
+
test 'tag parameters' do |data|
|
426
|
+
@d = create_driver(CONFIG + data[:param])
|
427
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
428
|
+
options = {auth: false}
|
429
|
+
|
430
|
+
records = [
|
431
|
+
["tag1", time, {"a"=>1}],
|
432
|
+
["tag1", time, {"a"=>2}],
|
433
|
+
]
|
434
|
+
|
435
|
+
@d.run(expect_records: records.length, timeout: 20) do
|
436
|
+
entries = ''
|
437
|
+
records.each {|_tag, _time, record|
|
438
|
+
packer(entries).write([_time, record]).flush
|
439
|
+
}
|
440
|
+
send_data packer.write(["tag1", entries]).to_s, **options
|
441
|
+
end
|
442
|
+
assert_equal(data[:result], @d.events[0][0])
|
443
|
+
end
|
444
|
+
|
381
445
|
data(tcp: {
|
382
446
|
config: CONFIG,
|
383
447
|
options: {
|
@@ -328,6 +328,7 @@ class ExecFilterOutputTest < Test::Unit::TestCase
|
|
328
328
|
</format>
|
329
329
|
<parse>
|
330
330
|
@type json
|
331
|
+
stream_buffer_size 1
|
331
332
|
</parse>
|
332
333
|
<extract>
|
333
334
|
tag_key tag
|
@@ -338,6 +339,7 @@ class ExecFilterOutputTest < Test::Unit::TestCase
|
|
338
339
|
command cat
|
339
340
|
in_keys message
|
340
341
|
out_format json
|
342
|
+
out_stream_buffer_size 1
|
341
343
|
time_key time
|
342
344
|
tag_key tag
|
343
345
|
]
|
@@ -372,6 +374,7 @@ class ExecFilterOutputTest < Test::Unit::TestCase
|
|
372
374
|
</format>
|
373
375
|
<parse>
|
374
376
|
@type json
|
377
|
+
stream_buffer_size 1
|
375
378
|
</parse>
|
376
379
|
<extract>
|
377
380
|
tag_key tag
|
@@ -382,6 +385,7 @@ class ExecFilterOutputTest < Test::Unit::TestCase
|
|
382
385
|
command cat
|
383
386
|
in_keys message
|
384
387
|
out_format json
|
388
|
+
out_stream_buffer_size 1
|
385
389
|
time_key time
|
386
390
|
tag_key tag
|
387
391
|
]
|
@@ -414,6 +418,7 @@ class ExecFilterOutputTest < Test::Unit::TestCase
|
|
414
418
|
</format>
|
415
419
|
<parse>
|
416
420
|
@type json
|
421
|
+
stream_buffer_size 1
|
417
422
|
</parse>
|
418
423
|
<extract>
|
419
424
|
tag_key tag
|
@@ -426,6 +431,7 @@ class ExecFilterOutputTest < Test::Unit::TestCase
|
|
426
431
|
command cat
|
427
432
|
in_keys message
|
428
433
|
out_format json
|
434
|
+
out_stream_buffer_size 1
|
429
435
|
time_key time
|
430
436
|
time_format %d/%b/%Y %H:%M:%S.%N %z
|
431
437
|
tag_key tag
|
@@ -920,4 +920,213 @@ EOL
|
|
920
920
|
assert_equal(['test', time, records[1]], events[1])
|
921
921
|
end
|
922
922
|
end
|
923
|
+
|
924
|
+
test 'Create new connection per send_data' do
|
925
|
+
target_input_driver = create_target_input_driver(conf: TARGET_CONFIG)
|
926
|
+
output_conf = CONFIG
|
927
|
+
d = create_driver(output_conf)
|
928
|
+
d.instance_start
|
929
|
+
|
930
|
+
begin
|
931
|
+
chunk = Fluent::Plugin::Buffer::MemoryChunk.new(Fluent::Plugin::Buffer::Metadata.new(nil, nil, nil))
|
932
|
+
mock.proxy(d.instance).create_transfer_socket(TARGET_HOST, TARGET_PORT, 'test') { |sock| mock(sock).close.once; sock }.twice
|
933
|
+
|
934
|
+
target_input_driver.run(timeout: 15) do
|
935
|
+
d.run(shutdown: false) do
|
936
|
+
node = d.instance.nodes.first
|
937
|
+
2.times do
|
938
|
+
node.send_data('test', chunk) rescue nil
|
939
|
+
end
|
940
|
+
end
|
941
|
+
end
|
942
|
+
ensure
|
943
|
+
d.instance_shutdown
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
sub_test_case 'keepalive' do
|
948
|
+
test 'Do not create connection per send_data' do
|
949
|
+
target_input_driver = create_target_input_driver(conf: TARGET_CONFIG)
|
950
|
+
output_conf = CONFIG + %[
|
951
|
+
keepalive true
|
952
|
+
keepalive_timeout 2
|
953
|
+
]
|
954
|
+
d = create_driver(output_conf)
|
955
|
+
d.instance_start
|
956
|
+
|
957
|
+
begin
|
958
|
+
chunk = Fluent::Plugin::Buffer::MemoryChunk.new(Fluent::Plugin::Buffer::Metadata.new(nil, nil, nil))
|
959
|
+
mock.proxy(d.instance).create_transfer_socket(TARGET_HOST, TARGET_PORT, 'test') { |sock| mock(sock).close.once; sock }.once
|
960
|
+
|
961
|
+
target_input_driver.run(timeout: 15) do
|
962
|
+
d.run(shutdown: false) do
|
963
|
+
node = d.instance.nodes.first
|
964
|
+
2.times do
|
965
|
+
node.send_data('test', chunk) rescue nil
|
966
|
+
end
|
967
|
+
end
|
968
|
+
end
|
969
|
+
ensure
|
970
|
+
d.instance_shutdown
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
sub_test_case 'with require_ack_response' do
|
975
|
+
test 'Do not create connection per send_data' do
|
976
|
+
target_input_driver = create_target_input_driver(conf: TARGET_CONFIG)
|
977
|
+
output_conf = CONFIG + %[
|
978
|
+
require_ack_response true
|
979
|
+
keepalive true
|
980
|
+
keepalive_timeout 2
|
981
|
+
]
|
982
|
+
d = create_driver(output_conf)
|
983
|
+
d.instance_start
|
984
|
+
|
985
|
+
begin
|
986
|
+
chunk = Fluent::Plugin::Buffer::MemoryChunk.new(Fluent::Plugin::Buffer::Metadata.new(nil, nil, nil))
|
987
|
+
mock.proxy(d.instance).create_transfer_socket(TARGET_HOST, TARGET_PORT, 'test') { |sock| mock(sock).close.once; sock }.once
|
988
|
+
|
989
|
+
target_input_driver.run(timeout: 15) do
|
990
|
+
d.run(shutdown: false) do
|
991
|
+
node = d.instance.nodes.first
|
992
|
+
2.times do
|
993
|
+
node.send_data('test', chunk) rescue nil
|
994
|
+
end
|
995
|
+
end
|
996
|
+
end
|
997
|
+
ensure
|
998
|
+
d.instance_shutdown
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
sub_test_case 'SocketCache' do
|
1005
|
+
sub_test_case 'fetch_or' do
|
1006
|
+
test 'when gived key does not exist' do
|
1007
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1008
|
+
sock = mock!.open { 1 }.subject
|
1009
|
+
assert_equal(1, c.fetch_or { sock.open })
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
test 'when given key exists' do
|
1013
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1014
|
+
assert_equal(1, c.fetch_or { 1 })
|
1015
|
+
|
1016
|
+
sock = dont_allow(mock!).open
|
1017
|
+
assert_equal(1, c.fetch_or { sock.open })
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
test "when given key's value was expired" do
|
1021
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(0, Logger.new(nil))
|
1022
|
+
assert_equal(1, c.fetch_or { 1 })
|
1023
|
+
|
1024
|
+
sock = mock!.open { 1 }.subject
|
1025
|
+
assert_equal(1, c.fetch_or { sock.open })
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
test 'revoke' do
|
1030
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1031
|
+
c.fetch_or { 1 }
|
1032
|
+
c.revoke
|
1033
|
+
|
1034
|
+
sock = mock!.open { 1 }.subject
|
1035
|
+
assert_equal(1, c.fetch_or { sock.open })
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
test 'revoke_by_value' do
|
1039
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1040
|
+
c.fetch_or { 1 }
|
1041
|
+
c.revoke_by_value(1)
|
1042
|
+
|
1043
|
+
sock = mock!.open { 1 }.subject
|
1044
|
+
assert_equal(1, c.fetch_or { sock.open })
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
sub_test_case 'dec_ref' do
|
1048
|
+
test 'when value exists in active_socks' do
|
1049
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1050
|
+
c.fetch_or { 1 }
|
1051
|
+
c.dec_ref
|
1052
|
+
|
1053
|
+
assert_equal(0, c.instance_variable_get(:@active_socks)[Thread.current.object_id].ref)
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
test 'when value exists in inactive_socks' do
|
1057
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1058
|
+
c.fetch_or { 1 }
|
1059
|
+
c.revoke
|
1060
|
+
c.dec_ref
|
1061
|
+
assert_equal(-1, c.instance_variable_get(:@inactive_socks)[Thread.current.object_id].ref)
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
sub_test_case 'dec_ref_by_value' do
|
1066
|
+
test 'when value exists in active_socks' do
|
1067
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1068
|
+
c.fetch_or { 1 }
|
1069
|
+
c.dec_ref_by_value(1)
|
1070
|
+
|
1071
|
+
assert_equal(0, c.instance_variable_get(:@active_socks)[Thread.current.object_id].ref)
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
test 'when value exists in inactive_socks' do
|
1075
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1076
|
+
c.fetch_or { 1 }
|
1077
|
+
c.revoke
|
1078
|
+
c.dec_ref_by_value(1)
|
1079
|
+
assert_equal(-1, c.instance_variable_get(:@inactive_socks)[Thread.current.object_id].ref)
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
sub_test_case 'clear' do
|
1084
|
+
test 'when value is in active_socks' do
|
1085
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1086
|
+
m = mock!.close { 'closed' }.subject
|
1087
|
+
c.fetch_or { m }
|
1088
|
+
assert_true(!c.instance_variable_get(:@active_socks).empty?)
|
1089
|
+
|
1090
|
+
c.clear
|
1091
|
+
assert_true(c.instance_variable_get(:@active_socks).empty?)
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
test 'when value is in inactive_socks' do
|
1095
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1096
|
+
m = mock!.close { 'closed' }.subject
|
1097
|
+
c.fetch_or { m }
|
1098
|
+
c.revoke
|
1099
|
+
assert_true(!c.instance_variable_get(:@inactive_socks).empty?)
|
1100
|
+
|
1101
|
+
c.clear
|
1102
|
+
assert_true(c.instance_variable_get(:@active_socks).empty?)
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
sub_test_case 'purge_obsolete_socks' do
|
1107
|
+
test 'delete key in inactive_socks' do
|
1108
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1109
|
+
m = mock!.close { 'closed' }.subject
|
1110
|
+
c.fetch_or { m }
|
1111
|
+
c.revoke
|
1112
|
+
assert_true(!c.instance_variable_get(:@inactive_socks).empty?)
|
1113
|
+
|
1114
|
+
c.purge_obsolete_socks
|
1115
|
+
assert_true(c.instance_variable_get(:@active_socks).empty?)
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
test 'move key from active_socks to inactive_socks' do
|
1119
|
+
c = Fluent::Plugin::ForwardOutput::Node::SocketCache.new(10, Logger.new(nil))
|
1120
|
+
m = dont_allow(mock!).close
|
1121
|
+
stub(m).inspect # for log
|
1122
|
+
c.fetch_or { m }
|
1123
|
+
assert_true(!c.instance_variable_get(:@active_socks).empty?)
|
1124
|
+
assert_true(c.instance_variable_get(:@inactive_socks).empty?)
|
1125
|
+
|
1126
|
+
c.purge_obsolete_socks
|
1127
|
+
assert_true(!c.instance_variable_get(:@active_socks).empty?)
|
1128
|
+
assert_true(c.instance_variable_get(:@inactive_socks).empty?)
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
end
|
923
1132
|
end
|
@@ -111,4 +111,28 @@ class JsonParserTest < ::Test::Unit::TestCase
|
|
111
111
|
assert_equal text, record['time']
|
112
112
|
end
|
113
113
|
end
|
114
|
+
|
115
|
+
def test_yajl_parse_io_with_buffer_smaller_than_input
|
116
|
+
parser = Fluent::Test::Driver::Parser.new(Fluent::Plugin::JSONParser)
|
117
|
+
parser.configure(
|
118
|
+
'keep_time_key' => 'true',
|
119
|
+
'json_parser' => 'yajl',
|
120
|
+
'stream_buffer_size' => 1,
|
121
|
+
)
|
122
|
+
text = "100"
|
123
|
+
|
124
|
+
waiting(5) do
|
125
|
+
rd, wr = IO.pipe
|
126
|
+
wr.write "{\"time\":\"#{text}\"}"
|
127
|
+
|
128
|
+
parser.instance.parse_io(rd) do |time, record|
|
129
|
+
assert_equal text.to_i, time.sec
|
130
|
+
assert_equal text, record['time']
|
131
|
+
|
132
|
+
# Once a record has been received the 'write' end of the pipe must be
|
133
|
+
# closed, otherwise the test will block waiting for more input.
|
134
|
+
wr.close
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
114
138
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluentd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -772,9 +772,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
772
772
|
version: '2.1'
|
773
773
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
774
774
|
requirements:
|
775
|
-
- - "
|
775
|
+
- - ">"
|
776
776
|
- !ruby/object:Gem::Version
|
777
|
-
version:
|
777
|
+
version: 1.3.1
|
778
778
|
requirements: []
|
779
779
|
rubyforge_project:
|
780
780
|
rubygems_version: 2.6.14.1
|