fluent-plugin-mongo 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 709f3068ce7f97bcad4737b3c7a9db60bdea998b184949afa7f3fa1794831474
4
- data.tar.gz: 53d8b989b2d30d8b0df0e37701af671713460dd46fe920435016ececcaef6c36
3
+ metadata.gz: fc300a11be780d8b3ea62e71db08393171fad3625d1d9259cb65a7abe11f9e5e
4
+ data.tar.gz: db12f19c83ae360e9720c41a2d64c3ce8bd65487d554ed2e416277d0f158b311
5
5
  SHA512:
6
- metadata.gz: 6ce121ba68754c2d6687a7d1ed8cedf8c9fe94652f85476b03aacddf92bede132aa7f45444c8cb4153ff5509143bf00688cffc5c872c15e76c754e0cd6f5ff8f
7
- data.tar.gz: 4391ce8e22ca8173f38630a6f22024acdccae01d48a3bf5c86f255bbdbe057cdbbb9039b9f55b209bfaf377188124a5b1dd83fcaf8b58db80785e16348e8caa0
6
+ metadata.gz: c105cf4d712af0524a554b072f86a2af2cfc5485825db24f47dcd47cc850628cfeb08af8f7db4337aefc7342b2325e35698707db22aa0e595d786a6d72317320
7
+ data.tar.gz: '08454ce8638ee2bf64d1f6ae2d6adfd0632ed43ad47667558b9ee00dccb54a12d8fbac31c0b8032bd69598752a442bac02e7425e2ccd649754d7c48df1723b8b'
@@ -0,0 +1,49 @@
1
+ name: Test
2
+ on:
3
+ push:
4
+ branches: [master]
5
+ pull_request:
6
+ branches: [master]
7
+ jobs:
8
+ build:
9
+ runs-on: ${{ matrix.os }}
10
+ continue-on-error: ${{ matrix.experimental }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ ruby: [ '3.1', '3.0', '2.7', '2.6' ]
15
+ mongodb-version: ['5.0', '4.4', '4.2', '4.0']
16
+ os:
17
+ - ubuntu-latest
18
+ experimental: [false]
19
+ include:
20
+ - ruby: head
21
+ os: ubuntu-latest
22
+ experimental: true
23
+ mongodb-version: '4.0'
24
+ - ruby: head
25
+ os: ubuntu-latest
26
+ experimental: true
27
+ mongodb-version: '4.2'
28
+ - ruby: head
29
+ os: ubuntu-latest
30
+ experimental: true
31
+ mongodb-version: '4.4'
32
+
33
+ name: Ruby ${{ matrix.ruby }} and MongoDB ${{ matrix.mongodb-version }} on ${{ matrix.os }}
34
+ steps:
35
+ - uses: actions/checkout@v2
36
+ - uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: ${{ matrix.ruby }}
39
+ - name: Start MongoDB
40
+ uses: supercharge/mongodb-github-action@1.3.0
41
+ with:
42
+ mongodb-version: ${{ matrix.mongodb-version }}
43
+ - name: unit testing
44
+ env:
45
+ CI: true
46
+ run: |
47
+ gem install bundler rake
48
+ bundle install --jobs 4 --retry 3
49
+ bundle exec rake test
data/ChangeLog CHANGED
@@ -1,3 +1,16 @@
1
+ Release 1.6.0 - 2022/07/15
2
+
3
+ * Update mongo gem dependency
4
+ * out_mongo: Add object_id_keys parameter to convert string to Mongo ObjectId
5
+
6
+ Release 1.5.0 - 2020/11/26
7
+
8
+ * out_mongo: Support nested fields in date_keys
9
+
10
+ Release 1.4.1 - 2020/08/21
11
+
12
+ * out_mongo: Add expire_after parameter
13
+
1
14
  Release 1.4.0 - 2020/04/28
2
15
 
3
16
  * out_mongo: Add date_keys parameter to support MongoDB Date object in record fields.
data/README.rdoc CHANGED
@@ -59,6 +59,10 @@ Use _mongo_ type in match.
59
59
  # eg: updated_at: "2020-02-01T08:22:23.780Z" or updated_at: 1580546457010
60
60
  date_keys updated_at
61
61
 
62
+ # Specify id fields in record to use MongoDB's BSON ObjectID (Optional) default: nil
63
+ # eg: my_id: "507f1f77bcf86cd799439011"
64
+ object_id_keys my_id
65
+
62
66
  # Other buffer configurations here
63
67
  </match>
64
68
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0
1
+ 1.6.0
@@ -17,10 +17,11 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ['lib']
18
18
 
19
19
  gem.add_dependency "fluentd", [">= 0.14.22", "< 2"]
20
- gem.add_runtime_dependency "mongo", "~> 2.6.0"
20
+ gem.add_runtime_dependency "mongo", ">= 2.15.0", "< 2.19.0"
21
21
  gem.add_development_dependency "rake", ">= 0.9.2"
22
22
  gem.add_development_dependency "simplecov", ">= 0.5.4"
23
23
  gem.add_development_dependency "rr", ">= 1.0.0"
24
24
  gem.add_development_dependency "test-unit", ">= 3.0.0"
25
- gem.add_development_dependency "timecop", "~> 0.8.0"
25
+ gem.add_development_dependency "timecop", "~> 0.9.4"
26
+ gem.add_development_dependency "webrick", ">= 1.7.0"
26
27
  end
@@ -8,7 +8,7 @@ module Fluent::Plugin
8
8
  class MongoOutput < Output
9
9
  Fluent::Plugin.register_output('mongo', self)
10
10
 
11
- helpers :event_emitter, :inject, :compat_parameters
11
+ helpers :event_emitter, :inject, :compat_parameters, :record_accessor
12
12
 
13
13
  include Fluent::MongoAuthParams
14
14
  include Fluent::MongoAuth
@@ -41,6 +41,10 @@ module Fluent::Plugin
41
41
  # Additional date field to be used to Date object
42
42
  desc "Specify keys to use MongoDB's Date. Supported value types are Integer/Float/EventTime/String"
43
43
  config_param :date_keys, :array, default: nil
44
+ desc "Specify if the fields in date_keys are of type Integer or Float"
45
+ config_param :parse_string_number_date, :bool, default: false
46
+ desc "Specify keys to use MongoDB's ObjectId"
47
+ config_param :object_id_keys, :array, default: nil
44
48
 
45
49
  # tag mapping mode
46
50
  desc "Use tag_mapped mode"
@@ -49,6 +53,9 @@ module Fluent::Plugin
49
53
  desc "Remove tag prefix"
50
54
  config_param :remove_tag_prefix, :string, default: nil,
51
55
  deprecated: "use @label instead for event routing."
56
+ # expire indexes
57
+ desc "Specify expire after seconds"
58
+ config_param :expire_after, :time, default: 0
52
59
 
53
60
  # SSL connection
54
61
  config_param :ssl, :bool, default: false
@@ -72,6 +79,8 @@ module Fluent::Plugin
72
79
  @nodes = nil
73
80
  @client_options = {}
74
81
  @collection_options = {capped: false}
82
+ @date_accessors = {}
83
+ @object_id_accessors = {}
75
84
  end
76
85
 
77
86
  # Following limits are heuristic. BSON is sometimes bigger than MessagePack and JSON.
@@ -121,10 +130,6 @@ module Fluent::Plugin
121
130
  raise Fluent::ConfigError, "connection_string or database parameter is required"
122
131
  end
123
132
 
124
- unless @ignore_invalid_record
125
- log.warn "Since v0.8, invalid record detection will be removed because mongo driver v2.x and API spec don't provide it. You may lose invalid records, so you should not send such records to mongo plugin"
126
- end
127
-
128
133
  if conf.has_key?('tag_mapped')
129
134
  log.warn "'tag_mapped' feature is replaced with built-in config placeholder. Please consider to use 'collection ${tag}'."
130
135
  @collection = '${tag}'
@@ -158,6 +163,19 @@ module Fluent::Plugin
158
163
  configure_logger(@mongo_log_level)
159
164
 
160
165
  log.debug "Setup mongo configuration: mode = #{@tag_mapped ? 'tag mapped' : 'normal'}"
166
+
167
+ if @date_keys
168
+ @date_keys.each { |field_name|
169
+ @date_accessors[field_name.to_s] = record_accessor_create(field_name)
170
+ }
171
+ log.debug "Setup record accessor for every date key"
172
+ end
173
+ if @object_id_keys
174
+ @object_id_keys.each { |field_name|
175
+ @object_id_accessors[field_name.to_s] = record_accessor_create(field_name)
176
+ }
177
+ log.debug "Setup record accessor for every object_id key"
178
+ end
161
179
  end
162
180
 
163
181
  def start
@@ -204,6 +222,7 @@ module Fluent::Plugin
204
222
  records = []
205
223
  time_key = @inject_config.time_key if @inject_config
206
224
  date_keys = @date_keys
225
+ object_id_keys = @object_id_keys
207
226
 
208
227
  tag = chunk.metadata.tag
209
228
  chunk.msgpack_each {|time, record|
@@ -212,28 +231,58 @@ module Fluent::Plugin
212
231
  record[time_key] = Time.at(time || record[time_key]) if time_key
213
232
 
214
233
  if date_keys
215
- date_keys.each { |date_key|
234
+ @date_accessors.each_pair { |date_key, date_key_accessor|
216
235
  begin
217
- date_value = record[date_key]
236
+ date_value = date_key_accessor.call(record)
218
237
  case date_value
219
238
  when Fluent::EventTime
220
- record[date_key] = date_value.to_time
239
+ value_to_set = date_value.to_time
221
240
  when Integer
222
- record[date_key] = if date_value > 9999999999
241
+ value_to_set = if date_value > 9999999999
242
+ # epoch with milliseconds: e.g. javascript
243
+ Time.at(date_value / 1000.0)
244
+ else
245
+ # epoch with seconds: e.g. ruby
246
+ Time.at(date_value)
247
+ end
248
+ when Float
249
+ value_to_set = Time.at(date_value)
250
+ else
251
+ if @parse_string_number_date
252
+ if date_value.to_i.to_s == date_value
253
+ date_value = date_value.to_i
254
+ value_to_set = if date_value > 9999999999
223
255
  # epoch with milliseconds: e.g. javascript
224
- Time.at(date_value / 1000.0)
256
+ date_value / 1000.0
225
257
  else
226
258
  # epoch with seconds: e.g. ruby
227
- Time.at(date_value)
259
+ date_value
228
260
  end
229
- when Float
230
- record[date_key] = Time.at(date_value)
231
- else
232
- record[date_key] = Time.parse(date_value)
261
+ elsif date_value.to_f.to_s == date_value
262
+ date_value = date_value.to_f
263
+ end
264
+ value_to_set = date_value.is_a?(String) ? Time.parse(date_value) : Time.at(date_value)
265
+ else
266
+ value_to_set = Time.parse(date_value)
267
+ end
233
268
  end
269
+
270
+ date_key_accessor.set(record, value_to_set)
234
271
  rescue ArgumentError
235
- log.warn "Failed to parse '#{date_key}' field. Expected date types are Integer/Float/String/EventTime: #{record[date_key]}"
236
- record[date_key] = nil
272
+ log.warn "Failed to parse '#{date_key}' field. Expected date types are Integer/Float/String/EventTime: #{date_value}"
273
+ date_key_accessor.set(record, nil)
274
+ end
275
+ }
276
+ end
277
+ if object_id_keys
278
+ @object_id_accessors.each_pair { |object_id_key, object_id_key_accessor|
279
+ begin
280
+ object_id_value = object_id_key_accessor.call(record)
281
+ value_to_set = BSON::ObjectId(object_id_value)
282
+ object_id_key_accessor.set(record, value_to_set)
283
+ rescue BSON::ObjectId::Invalid
284
+ log.warn "Failed to parse '#{object_id_key}' field. Expected object_id types are String: #{object_id_value}"
285
+ object_id_key_accessor.set(record, nil)
237
286
  end
238
287
  }
239
288
  end
@@ -274,6 +323,13 @@ module Fluent::Plugin
274
323
  unless collection_exists?(name)
275
324
  log.trace "Create collection #{name} with options #{options}"
276
325
  @client[name, options].create
326
+ if @expire_after > 0 && @inject_config
327
+ log.trace "Create expiring index with key: \"#{@inject_config.time_key}\" and seconds: \"#{@expire_after}\""
328
+ @client[name].indexes.create_one(
329
+ {"#{@inject_config.time_key}": 1},
330
+ expire_after: @expire_after
331
+ )
332
+ end
277
333
  end
278
334
  @collections[name] = true
279
335
  @client[name]
@@ -114,7 +114,7 @@ class MongoTailInputTest < Test::Unit::TestCase
114
114
  time_key time
115
115
  ])
116
116
  d.run(expect_records: 1, timeout: 5) do
117
- @client[collection_name].insert_one({message: "test", tag: "user.defined", time: Fluent::Engine.now})
117
+ @client[collection_name].insert_one({message: "test", tag: "user.defined", time: Time.at(Fluent::Engine.now)})
118
118
  end
119
119
  events = d.events
120
120
  assert_equal "user.defined", events[0][0]
@@ -180,6 +180,10 @@ class MongoOutputTest < ::Test::Unit::TestCase
180
180
  @client[collection].find.to_a.map {|e| e.delete('_id'); e}
181
181
  end
182
182
 
183
+ def get_indexes(collection = collection_name)
184
+ @client[collection].indexes
185
+ end
186
+
183
187
  def emit_documents(d)
184
188
  time = event_time("2011-01-02 13:14:15 UTC")
185
189
  d.feed(time, {'a' => 1})
@@ -220,6 +224,31 @@ class MongoOutputTest < ::Test::Unit::TestCase
220
224
  assert_equal(expected, actual_documents)
221
225
  end
222
226
 
227
+ def test_write_with_expire_index
228
+ d = create_driver(%[
229
+ @type mongo
230
+ connection_string mongodb://localhost:#{port}/#{database_name}
231
+ collection #{collection_name}
232
+ capped
233
+ capped_size 100
234
+ expire_after 120
235
+ ])
236
+ assert_equal("mongodb://localhost:#{port}/#{database_name}", d.instance.connection_string)
237
+ assert_nil d.instance.database
238
+ d.run(default_tag: 'test') do
239
+ emit_documents(d)
240
+ end
241
+ actual_documents = get_documents
242
+ time = event_time("2011-01-02 13:14:15 UTC")
243
+ expected = [{'a' => 1, d.instance.inject_config.time_key => Time.at(time).localtime},
244
+ {'a' => 2, d.instance.inject_config.time_key => Time.at(time).localtime}]
245
+ assert_equal(expected, actual_documents)
246
+
247
+ indexes = get_indexes()
248
+ expire_after_hash = indexes.map {|e| e.select{|k, v| k == "expireAfterSeconds"} }.reject{|e| e.empty?}.first
249
+ assert_equal({"expireAfterSeconds"=>120.0}, expire_after_hash)
250
+ end
251
+
223
252
  class WriteWithCollectionPlaceholder < self
224
253
  def setup
225
254
  @tag = 'custom'
@@ -409,6 +438,20 @@ class MongoOutputTest < ::Test::Unit::TestCase
409
438
  time
410
439
  end
411
440
 
441
+ def emit_nested_date_documents(d)
442
+ time = event_time("2011-01-02 13:14:15 UTC")
443
+ d.feed(time, {'a' => 1, updated_at: { 'time': @updated_at_str}})
444
+ d.feed(time, {'a' => 2, updated_at: { 'time': @updated_at_t.to_f}})
445
+ d.feed(time, {'a' => 3, updated_at: { 'time': @updated_at_t.to_i}})
446
+ time
447
+ end
448
+
449
+ def emit_nested_invalid_date_documents(d)
450
+ time = event_time("2011-01-02 13:14:15 UTC")
451
+ d.feed(time, {'a' => 1, 'updated_at': { 'time': "Invalid Date String"}})
452
+ time
453
+ end
454
+
412
455
  def test_write_with_date_keys
413
456
  d = create_driver(default_config + %[
414
457
  date_keys updated_at
@@ -439,5 +482,130 @@ class MongoOutputTest < ::Test::Unit::TestCase
439
482
  actual_documents = get_documents
440
483
  assert_nil actual_documents.first['updated_at']
441
484
  end
485
+
486
+ def test_write_with_date_nested_keys
487
+ d = create_driver(default_config + %[
488
+ replace_dot_in_key_with _
489
+ replace_dollar_in_key_with _
490
+ date_keys $.updated_at.time
491
+ time_key created_at
492
+ ])
493
+
494
+ d.run(default_tag: 'test') do
495
+ emit_nested_date_documents(d)
496
+ end
497
+
498
+ actual_documents = get_documents
499
+ actual_documents.each_with_index { |doc, i|
500
+ assert_equal(i + 1, doc['a'])
501
+ assert doc['updated_at']['time'].is_a?(Time)
502
+ }
503
+ end
504
+
505
+ def test_write_with_parsed_date_nested_key_invalid_string
506
+ d = create_driver(default_config + %[
507
+ replace_dot_in_key_with _
508
+ replace_dollar_in_key_with _
509
+ date_keys $.updated_at.time
510
+ time_key created_at
511
+ ])
512
+
513
+ d.run(default_tag: 'test') do
514
+ emit_nested_invalid_date_documents(d)
515
+ end
516
+ actual_documents = get_documents
517
+ assert_nil actual_documents.first['updated_at']['time']
518
+ end
519
+ end
520
+
521
+ sub_test_case 'object_id_keys' do
522
+ setup do
523
+ @my_id_str = "507f1f77bcf86cd799439011"
524
+ end
525
+
526
+ def emit_date_documents(d)
527
+ time = event_time("2011-01-02 13:14:15 UTC")
528
+ d.feed(time, {'a' => 1, my_id: @my_id_str})
529
+ time
530
+ end
531
+
532
+ def emit_invalid_date_documents(d)
533
+ time = event_time("2011-01-02 13:14:15 UTC")
534
+ d.feed(time, {'a' => 1, my_id: "Invalid ObjectId String"})
535
+ time
536
+ end
537
+
538
+ def emit_nested_date_documents(d)
539
+ time = event_time("2011-01-02 13:14:15 UTC")
540
+ d.feed(time, {'a' => 1, my_id: { 'id': @my_id_str}})
541
+ time
542
+ end
543
+
544
+ def emit_nested_invalid_date_documents(d)
545
+ time = event_time("2011-01-02 13:14:15 UTC")
546
+ d.feed(time, {'a' => 1, 'my_id': { 'id': "Invalid ObjectId String"}})
547
+ time
548
+ end
549
+
550
+ def test_write_with_object_id_keys
551
+ d = create_driver(default_config + %[
552
+ object_id_keys my_id
553
+ ])
554
+
555
+ d.run(default_tag: 'test') do
556
+ emit_date_documents(d)
557
+ end
558
+
559
+ actual_documents = get_documents
560
+ object_id_key = d.instance.object_id_keys.first
561
+ actual_documents.each_with_index { |doc, i|
562
+ assert_equal(i + 1, doc['a'])
563
+ assert doc[object_id_key].is_a?(BSON::ObjectId)
564
+ }
565
+ end
566
+
567
+ def test_write_with_parsed_object_id_key_invalid_string
568
+ d = create_driver(default_config + %[
569
+ object_id_keys my_id
570
+ ])
571
+
572
+ d.run(default_tag: 'test') do
573
+ emit_invalid_date_documents(d)
574
+ end
575
+ actual_documents = get_documents
576
+ assert_nil actual_documents.first['my_id']
577
+ end
578
+
579
+ def test_write_with_date_nested_keys
580
+ d = create_driver(default_config + %[
581
+ replace_dot_in_key_with _
582
+ replace_dollar_in_key_with _
583
+ object_id_keys $.my_id.id
584
+ ])
585
+
586
+ d.run(default_tag: 'test') do
587
+ emit_nested_date_documents(d)
588
+ end
589
+
590
+ actual_documents = get_documents
591
+ actual_documents.each_with_index { |doc, i|
592
+ assert_equal(i + 1, doc['a'])
593
+ assert doc['my_id']['id'].is_a?(BSON::ObjectId)
594
+ }
595
+ end
596
+
597
+ def test_write_with_parsed_date_nested_key_invalid_string
598
+ d = create_driver(default_config + %[
599
+ replace_dot_in_key_with _
600
+ replace_dollar_in_key_with _
601
+ object_id_keys $.my_id.id
602
+ ])
603
+
604
+ d.run(default_tag: 'test') do
605
+ emit_nested_invalid_date_documents(d)
606
+ end
607
+ actual_documents = get_documents
608
+ assert_nil actual_documents.first['my_id']['id']
609
+ end
442
610
  end
443
611
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahiro Nakagawa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-28 00:00:00.000000000 Z
11
+ date: 2022-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -34,16 +34,22 @@ dependencies:
34
34
  name: mongo
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 2.15.0
40
+ - - "<"
38
41
  - !ruby/object:Gem::Version
39
- version: 2.6.0
42
+ version: 2.19.0
40
43
  type: :runtime
41
44
  prerelease: false
42
45
  version_requirements: !ruby/object:Gem::Requirement
43
46
  requirements:
44
- - - "~>"
47
+ - - ">="
45
48
  - !ruby/object:Gem::Version
46
- version: 2.6.0
49
+ version: 2.15.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 2.19.0
47
53
  - !ruby/object:Gem::Dependency
48
54
  name: rake
49
55
  requirement: !ruby/object:Gem::Requirement
@@ -106,14 +112,28 @@ dependencies:
106
112
  requirements:
107
113
  - - "~>"
108
114
  - !ruby/object:Gem::Version
109
- version: 0.8.0
115
+ version: 0.9.4
110
116
  type: :development
111
117
  prerelease: false
112
118
  version_requirements: !ruby/object:Gem::Requirement
113
119
  requirements:
114
120
  - - "~>"
115
121
  - !ruby/object:Gem::Version
116
- version: 0.8.0
122
+ version: 0.9.4
123
+ - !ruby/object:Gem::Dependency
124
+ name: webrick
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 1.7.0
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 1.7.0
117
137
  description: MongoDB plugin for Fluentd
118
138
  email: repeatedly@gmail.com
119
139
  executables:
@@ -121,6 +141,7 @@ executables:
121
141
  extensions: []
122
142
  extra_rdoc_files: []
123
143
  files:
144
+ - ".github/workflows/linux.yml"
124
145
  - ".gitignore"
125
146
  - ".travis.yml"
126
147
  - AUTHORS
@@ -159,12 +180,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
180
  - !ruby/object:Gem::Version
160
181
  version: '0'
161
182
  requirements: []
162
- rubygems_version: 3.0.3
183
+ rubygems_version: 3.3.5
163
184
  signing_key:
164
185
  specification_version: 4
165
186
  summary: MongoDB plugin for Fluentd
166
- test_files:
167
- - test/helper.rb
168
- - test/plugin/test_in_mongo_tail.rb
169
- - test/plugin/test_out_mongo.rb
170
- - test/plugin/test_out_mongo_replset.rb
187
+ test_files: []