logstash-codec-multiline 2.0.2 → 2.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f2a90b45c2f55e7db24a9f2af3905b82d18bba6c
4
- data.tar.gz: 27cdceb50b94688c6902d10593db9f2ff4db33d3
3
+ metadata.gz: b89e4a5a454c8bc979aca1a959c06ad2d65e29bc
4
+ data.tar.gz: 3eff2f29480107e6212eec95c096c18b3f82b55e
5
5
  SHA512:
6
- metadata.gz: d040c49a3fba07cd543e1eedc4a608c90c03bd5106e7c431fa85425638d3ce05d5c43a4c00289af0a531f819ca2034a5e57d6bd127df75af556562b22985fefa
7
- data.tar.gz: 5ff667aa293cc3bdbfeba833ede8127041f7eb81b123a9a6a4afd641f9092d8452e570d6bd8b517fcbb88873a0d768d6e8852b0a9e2330ca2bcf867ccbf45cfc
6
+ metadata.gz: d7eb64acd24053b8ce18bfe111a5ab2917f5a41a3e979879ee1313bc64ffcf744ed85d11ade9843167bfe86579a34b8015d62b5fa0ee85d1356a1ac3360d246e
7
+ data.tar.gz: 36580542f95f5b66d042f8a2e6ed69064b49a1e2c1e2400f0aa49bfeb8f54ec510b99646fc1758d3a9887fb41862d1fd7ad44c30cbf3be721bf73323221953af
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
+ ## 2.0.3
2
+ - Add pseudo codec IdentityMapCodec. Support class for identity based multiline processing.
3
+
1
4
  ## 2.0.0
2
- - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
5
+ - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
3
6
  instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
4
7
  - Dependency on logstash-core update to 2.0
5
8
 
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Logstash Plugin
2
2
 
3
+ [![Build
4
+ Status](http://build-eu-00.elastic.co/view/LS%20Plugins/view/LS%20Codecs/job/logstash-plugin-codec-multiline-unit/badge/icon)](http://build-eu-00.elastic.co/view/LS%20Plugins/view/LS%20Codecs/job/logstash-plugin-codec-multiline-unit/)
5
+
3
6
  This is a plugin for [Logstash](https://github.com/elastic/logstash).
4
7
 
5
8
  It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
@@ -0,0 +1,251 @@
1
+ # encoding: utf-8
2
+ require "logstash/namespace"
3
+ require "thread_safe"
4
+
5
+ # This class is a Codec duck type
6
+ # Using Composition, it maps from a stream identity to
7
+ # a cloned codec instance via the same API as a Codec
8
+ # it implements the codec public API
9
+
10
+ module LogStash module Codecs class IdentityMapCodec
11
+ # subclass of Exception, LS has more than limit (20000) active streams
12
+ class IdentityMapUpperLimitException < Exception; end
13
+
14
+ module EightyPercentWarning
15
+ extend self
16
+ def visit(imc)
17
+ current_size, limit = imc.current_size_and_limit
18
+ return if current_size < (limit * 0.8)
19
+ imc.logger.warn("IdentityMapCodec has reached 80% capacity",
20
+ :current_size => current_size, :upper_limit => limit)
21
+ end
22
+ end
23
+
24
+ module UpperLimitReached
25
+ extend self
26
+ def visit(imc)
27
+ current_size, limit = imc.current_size_and_limit
28
+ return if current_size < limit
29
+ # we hit the limit
30
+ # try to clean out stale streams
31
+ current_size, limit = imc.map_cleanup
32
+ return if current_size < limit
33
+ # we are still at the limit and all streams are in use
34
+ imc.logger.error("IdentityMapCodec has reached 100% capacity",
35
+ :current_size => current_size, :upper_limit => limit)
36
+ raise IdentityMapUpperLimitException.new
37
+ end
38
+ end
39
+
40
+ class MapCleaner
41
+ def initialize(imc, interval)
42
+ @imc, @interval = imc, interval
43
+ @running = false
44
+ end
45
+
46
+ def start
47
+ return self if running?
48
+ @running = true
49
+ @thread = Thread.new(@imc) do |imc|
50
+ loop do
51
+ sleep @interval
52
+ break if !@running
53
+ imc.map_cleanup
54
+ end
55
+ end
56
+ self
57
+ end
58
+
59
+ def running?
60
+ @running
61
+ end
62
+
63
+ def stop
64
+ return if !running?
65
+ @running = false
66
+ @thread.wakeup
67
+ end
68
+ end
69
+
70
+ # A composite class to hold both the codec and the eviction_timeout
71
+ # instances of this Value Object are stored in the mapping hash
72
+ class CodecValue
73
+ attr_reader :codec
74
+ attr_accessor :timeout
75
+
76
+ def initialize(codec)
77
+ @codec = codec
78
+ end
79
+ end
80
+
81
+ #maximum size of the mapping hash
82
+ MAX_IDENTITIES = 20_000
83
+
84
+ # time after which a stream is
85
+ # considered stale
86
+ # each time a stream is accessed
87
+ # it is given a new timeout
88
+ EVICT_TIMEOUT = 60 * 60 * 1 # 1 hour
89
+
90
+ # time that the cleaner thread sleeps for
91
+ # before it tries to clean out stale mappings
92
+ CLEANER_INTERVAL = 60 * 5 # 5 minutes
93
+
94
+ attr_reader :identity_map
95
+ attr_accessor :base_codec, :cleaner
96
+
97
+ def initialize(codec)
98
+ @base_codec = codec
99
+ @base_codecs = [codec]
100
+ @identity_map = ThreadSafe::Hash.new &method(:codec_builder)
101
+ @max_identities = MAX_IDENTITIES
102
+ @evict_timeout = EVICT_TIMEOUT
103
+ @cleaner = MapCleaner.new(self, CLEANER_INTERVAL)
104
+ @decode_block = lambda {|*| }
105
+ end
106
+
107
+ # ==============================================
108
+ # Constructional/builder methods
109
+ # chain this method off of new
110
+ #
111
+ # used to add a non-default maximum identities
112
+ def max_identities(max)
113
+ @max_identities = max.to_i
114
+ self
115
+ end
116
+
117
+ # used to add a non-default evict timeout
118
+ def evict_timeout(timeout)
119
+ @evict_timeout = timeout.to_i
120
+ self
121
+ end
122
+
123
+ # used to add a non-default cleaner interval
124
+ def cleaner_interval(interval)
125
+ @cleaner.stop
126
+ @cleaner = MapCleaner.new(self, interval.to_i)
127
+ self
128
+ end
129
+ # end Constructional/builder methods
130
+ # ==============================================
131
+
132
+ # ==============================================
133
+ # Codec API
134
+ def decode(data, identity = nil, &block)
135
+ @decode_block = block if @decode_block != block
136
+ stream_codec(identity).decode(data, &block)
137
+ end
138
+
139
+ alias_method :<<, :decode
140
+
141
+ def encode(event, identity = nil)
142
+ stream_codec(identity).encode(event)
143
+ end
144
+
145
+ # this method will not be called from
146
+ # the input or the pipeline unless
147
+ # we implement codec flush on shutdown
148
+ # problematic, because we may not have
149
+ # received all the multiline parts yet.
150
+ # but if we don't flush we will lose data
151
+ def flush(&block)
152
+ all_codecs.each do |codec|
153
+ #let ruby do its default args thing
154
+ block.nil? ? codec.flush : codec.flush(&block)
155
+ end
156
+ end
157
+
158
+ def close()
159
+ cleaner.stop
160
+ all_codecs.each(&:close)
161
+ end
162
+ # end Codec API
163
+ # ==============================================
164
+
165
+ def all_codecs
166
+ no_streams? ? @base_codecs : identity_map.values.map(&:codec)
167
+ end
168
+
169
+ def max_limit
170
+ @max_identities
171
+ end
172
+
173
+ def identity_count
174
+ identity_map.size
175
+ end
176
+
177
+ # support cleaning of stale stream/codecs
178
+ # a stream is considered stale if it has not
179
+ # been accessed in the last @evict_timeout
180
+ # period (default 1 hour)
181
+ def map_cleanup
182
+ cut_off = Time.now.to_i
183
+ # delete_if is atomic
184
+ # contents should not mutate during this call
185
+ identity_map.delete_if do |identity, compo|
186
+ if (flag = compo.timeout <= cut_off)
187
+ compo.codec.flush(&@decode_block)
188
+ end
189
+ flag
190
+ end
191
+ current_size_and_limit
192
+ end
193
+
194
+ def current_size_and_limit
195
+ [identity_count, max_limit]
196
+ end
197
+
198
+ def logger
199
+ # we 'borrow' the codec's logger as we don't have our own
200
+ @base_codec.logger
201
+ end
202
+
203
+ def codec_without_usage_update(identity)
204
+ find_codec_value(identity).codec
205
+ end
206
+
207
+ def eviction_timestamp_for(identity)
208
+ find_codec_value(identity).timeout
209
+ end
210
+
211
+ private
212
+
213
+ def stream_codec(identity)
214
+ return base_codec if identity.nil?
215
+ record_codec_usage(identity) # returns codec
216
+ end
217
+
218
+ def find_codec_value(identity)
219
+ identity_map[identity]
220
+ end
221
+
222
+ # for nil stream this method is not called
223
+ def record_codec_usage(identity)
224
+ check_map_limits
225
+ # only start the cleaner if streams are in use
226
+ # continuous calls to start are OK
227
+ cleaner.start
228
+ compo = find_codec_value(identity)
229
+ compo.timeout = eviction_timestamp
230
+ compo.codec
231
+ end
232
+
233
+ def eviction_timestamp
234
+ Time.now.to_i + @evict_timeout
235
+ end
236
+
237
+ def check_map_limits
238
+ UpperLimitReached.visit(self)
239
+ EightyPercentWarning.visit(self)
240
+ end
241
+
242
+ def codec_builder(hash, k)
243
+ codec = hash.empty? ? @base_codec : @base_codec.clone
244
+ compo = CodecValue.new(codec)
245
+ hash.store(k, compo)
246
+ end
247
+
248
+ def no_streams?
249
+ identity_map.empty?
250
+ end
251
+ end end end
@@ -181,7 +181,7 @@ class LogStash::Codecs::Multiline < LogStash::Codecs::Base
181
181
  end
182
182
 
183
183
  def flush(&block)
184
- if @buffer.any?
184
+ if @buffer.any?
185
185
  yield merge_events
186
186
  reset_buffer
187
187
  end
@@ -211,7 +211,7 @@ class LogStash::Codecs::Multiline < LogStash::Codecs::Base
211
211
  end
212
212
 
213
213
  def over_maximun_lines?
214
- @buffer.size > @max_lines
214
+ @buffer.size > @max_lines
215
215
  end
216
216
 
217
217
  def over_maximun_bytes?
@@ -227,4 +227,4 @@ class LogStash::Codecs::Multiline < LogStash::Codecs::Base
227
227
  @on_event.call(event, event)
228
228
  end # def encode
229
229
 
230
- end # class LogStash::Codecs::Plain
230
+ end # class LogStash::Codecs::Multiline
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-codec-multiline'
4
- s.version = '2.0.2'
4
+ s.version = '2.0.3'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "The multiline codec will collapse multiline messages and merge them into a single event."
7
7
  s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
@@ -0,0 +1,208 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/codecs/identity_map_codec"
4
+
5
+ class LogTracer
6
+ def initialize() @tracer = []; end
7
+ def warn(*args) @tracer.push [:warn, args]; end
8
+ def error(*args) @tracer.push [:error, args]; end
9
+
10
+ def trace_for(symbol)
11
+ params = @tracer.assoc(symbol)
12
+ params.nil? ? false : params.last
13
+ end
14
+ end
15
+
16
+ class IdentityMapCodecTracer
17
+ def initialize() @tracer = []; end
18
+ def clone() self.class.new; end
19
+ def decode(data) @tracer.push [:decode, data]; end
20
+ def encode(event) @tracer.push [:encode, event]; end
21
+ def flush(&block) @tracer.push [:flush, true]; end
22
+ def close() @tracer.push [:close, true]; end
23
+ def logger() @logger ||= LogTracer.new; end
24
+
25
+ def trace_for(symbol)
26
+ params = @tracer.assoc(symbol)
27
+ params.nil? ? false : params.last
28
+ end
29
+ end
30
+
31
+ describe LogStash::Codecs::IdentityMapCodec do
32
+ let(:codec) { IdentityMapCodecTracer.new }
33
+ let(:logger) { codec.logger }
34
+ let(:demuxer) { described_class.new(codec) }
35
+ let(:stream1) { "stream-a" }
36
+ let(:codec1) { demuxer.codec_without_usage_update(stream1) }
37
+ let(:arg1) { "data-a" }
38
+
39
+ after do
40
+ codec.close
41
+ end
42
+
43
+ describe "operating without stream identity" do
44
+ let(:stream1) { nil }
45
+
46
+ it "transparently refers to the original codec" do
47
+ expect(codec).to eql(codec1)
48
+ end
49
+ end
50
+
51
+ describe "operating with stream identity" do
52
+
53
+ before { demuxer.decode(arg1, stream1) }
54
+
55
+ it "the first identity refers to the original codec" do
56
+ expect(codec).to eql(codec1)
57
+ end
58
+ end
59
+
60
+ describe "#decode" do
61
+ context "when no identity is used" do
62
+ let(:stream1) { nil }
63
+
64
+ it "calls the method on the original codec" do
65
+ demuxer.decode(arg1, stream1)
66
+
67
+ expect(codec.trace_for(:decode)).to eq(arg1)
68
+ end
69
+ end
70
+
71
+ context "when multiple identities are used" do
72
+ let(:stream2) { "stream-b" }
73
+ let(:codec2) { demuxer.codec_without_usage_update(stream2) }
74
+ let(:arg2) { "data-b" }
75
+
76
+ it "calls the method on the appropriate codec" do
77
+ demuxer.decode(arg1, stream1)
78
+ demuxer.decode(arg2, stream2)
79
+
80
+ expect(codec1.trace_for(:decode)).to eq(arg1)
81
+ expect(codec2.trace_for(:decode)).to eq(arg2)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#encode" do
87
+ context "when no identity is used" do
88
+ let(:stream1) { nil }
89
+ let(:arg1) { LogStash::Event.new({"type" => "file"}) }
90
+
91
+ it "calls the method on the original codec" do
92
+ demuxer.encode(arg1, stream1)
93
+
94
+ expect(codec.trace_for(:encode)).to eq(arg1)
95
+ end
96
+ end
97
+
98
+ context "when multiple identities are used" do
99
+ let(:stream2) { "stream-b" }
100
+ let(:codec2) { demuxer.codec_without_usage_update(stream2) }
101
+ let(:arg2) { LogStash::Event.new({"type" => "file"}) }
102
+
103
+ it "calls the method on the appropriate codec" do
104
+ demuxer.encode(arg1, stream1)
105
+ demuxer.encode(arg2, stream2)
106
+
107
+ expect(codec1.trace_for(:encode)).to eq(arg1)
108
+ expect(codec2.trace_for(:encode)).to eq(arg2)
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "#close" do
114
+ context "when no identity is used" do
115
+ before do
116
+ demuxer.decode(arg1)
117
+ end
118
+
119
+ it "calls the method on the original codec" do
120
+ demuxer.close
121
+ expect(codec.trace_for(:close)).to be_truthy
122
+ end
123
+ end
124
+
125
+ context "when multiple identities are used" do
126
+ let(:stream2) { "stream-b" }
127
+ let(:codec2) { demuxer.codec_without_usage_update(stream2) }
128
+ let(:arg2) { LogStash::Event.new({"type" => "file"}) }
129
+
130
+ before do
131
+ demuxer.decode(arg1, stream1)
132
+ demuxer.decode(arg2, stream2)
133
+ end
134
+
135
+ it "calls the method on all codecs" do
136
+ demuxer.close
137
+
138
+ expect(codec1.trace_for(:close)).to be_truthy
139
+ expect(codec2.trace_for(:close)).to be_truthy
140
+ end
141
+ end
142
+ end
143
+
144
+ describe "over capacity protection" do
145
+ let(:demuxer) { described_class.new(codec).max_identities(limit) }
146
+
147
+ context "when capacity at 80% or higher" do
148
+ let(:limit) { 10 }
149
+
150
+ it "a warning is logged" do
151
+ limit.pred.times do |i|
152
+ demuxer.decode(Object.new, "stream#{i}")
153
+ end
154
+
155
+ expect(logger.trace_for(:warn).first).to match %r|has reached 80% capacity|
156
+ end
157
+ end
158
+
159
+ context "when capacity is exceeded" do
160
+ let(:limit) { 2 }
161
+ let(:error_class) { LogStash::Codecs::IdentityMapCodec::IdentityMapUpperLimitException }
162
+
163
+ it "an exception is raised" do
164
+ limit.times do |i|
165
+ demuxer.decode(Object.new, "stream#{i}")
166
+ end
167
+ expect { demuxer.decode(Object.new, "stream4") }.to raise_error(error_class)
168
+ end
169
+
170
+ context "initially but some streams are idle and can be evicted" do
171
+ let(:demuxer) { described_class.new(codec).max_identities(limit).evict_timeout(1) }
172
+
173
+ it "an exception is NOT raised" do
174
+ demuxer.decode(Object.new, "stream1")
175
+ sleep(1.2)
176
+ demuxer.decode(Object.new, "stream2")
177
+ expect(demuxer.identity_count).to eq(limit)
178
+ expect { demuxer.decode(Object.new, "stream4") }.not_to raise_error
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ describe "usage tracking" do
185
+ let(:demuxer) { described_class.new(codec).evict_timeout(10) }
186
+ context "when an operation is performed by identity" do
187
+ it "the new eviction time for that identity is recorded" do
188
+ demuxer.decode(Object.new, "stream1")
189
+ current_eviction = demuxer.eviction_timestamp_for("stream1")
190
+ sleep(2)
191
+ demuxer.decode(Object.new, "stream1")
192
+ expect(demuxer.eviction_timestamp_for("stream1")).to be >= current_eviction + 2
193
+ end
194
+ end
195
+ end
196
+
197
+ describe "codec eviction" do
198
+ let(:demuxer) { described_class.new(codec).evict_timeout(1).cleaner_interval(1) }
199
+ context "when an identity has become stale" do
200
+ it "the cleaner evicts the codec and flushes it first" do
201
+ demuxer.decode(Object.new, "stream1")
202
+ sleep(2.1)
203
+ expect(codec.trace_for(:flush)).to be_truthy
204
+ expect(demuxer.identity_map.keys).not_to include("stream1")
205
+ end
206
+ end
207
+ end
208
+ end
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-codec-multiline
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-14 00:00:00.000000000 Z
11
+ date: 2015-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- requirement: !ruby/object:Gem::Requirement
14
+ name: logstash-core
15
+ version_requirements: !ruby/object:Gem::Requirement
15
16
  requirements:
16
17
  - - '>='
17
18
  - !ruby/object:Gem::Version
@@ -19,10 +20,7 @@ dependencies:
19
20
  - - <
20
21
  - !ruby/object:Gem::Version
21
22
  version: 3.0.0
22
- name: logstash-core
23
- prerelease: false
24
- type: :runtime
25
- version_requirements: !ruby/object:Gem::Requirement
23
+ requirement: !ruby/object:Gem::Requirement
26
24
  requirements:
27
25
  - - '>='
28
26
  - !ruby/object:Gem::Version
@@ -30,64 +28,68 @@ dependencies:
30
28
  - - <
31
29
  - !ruby/object:Gem::Version
32
30
  version: 3.0.0
31
+ prerelease: false
32
+ type: :runtime
33
33
  - !ruby/object:Gem::Dependency
34
+ name: logstash-patterns-core
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
34
40
  requirement: !ruby/object:Gem::Requirement
35
41
  requirements:
36
42
  - - '>='
37
43
  - !ruby/object:Gem::Version
38
44
  version: '0'
39
- name: logstash-patterns-core
40
45
  prerelease: false
41
46
  type: :runtime
47
+ - !ruby/object:Gem::Dependency
48
+ name: jls-grok
42
49
  version_requirements: !ruby/object:Gem::Requirement
43
50
  requirements:
44
- - - '>='
51
+ - - ~>
45
52
  - !ruby/object:Gem::Version
46
- version: '0'
47
- - !ruby/object:Gem::Dependency
53
+ version: 0.11.1
48
54
  requirement: !ruby/object:Gem::Requirement
49
55
  requirements:
50
56
  - - ~>
51
57
  - !ruby/object:Gem::Version
52
58
  version: 0.11.1
53
- name: jls-grok
54
59
  prerelease: false
55
60
  type: :runtime
61
+ - !ruby/object:Gem::Dependency
62
+ name: logstash-devutils
56
63
  version_requirements: !ruby/object:Gem::Requirement
57
64
  requirements:
58
- - - ~>
65
+ - - '>='
59
66
  - !ruby/object:Gem::Version
60
- version: 0.11.1
61
- - !ruby/object:Gem::Dependency
67
+ version: '0'
62
68
  requirement: !ruby/object:Gem::Requirement
63
69
  requirements:
64
70
  - - '>='
65
71
  - !ruby/object:Gem::Version
66
72
  version: '0'
67
- name: logstash-devutils
68
73
  prerelease: false
69
74
  type: :development
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - '>='
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
75
  description: This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program
76
76
  email: info@elastic.co
77
77
  executables: []
78
78
  extensions: []
79
79
  extra_rdoc_files: []
80
80
  files:
81
+ - lib/logstash/codecs/identity_map_codec.rb
82
+ - lib/logstash/codecs/multiline.rb
83
+ - spec/codecs/multiline_spec.rb
84
+ - spec/codecs/identity_map_codec_spec.rb
85
+ - spec/supports/helpers.rb
86
+ - logstash-codec-multiline.gemspec
87
+ - README.md
81
88
  - CHANGELOG.md
82
89
  - CONTRIBUTORS
83
90
  - Gemfile
84
91
  - LICENSE
85
92
  - NOTICE.TXT
86
- - README.md
87
- - lib/logstash/codecs/multiline.rb
88
- - logstash-codec-multiline.gemspec
89
- - spec/codecs/multiline_spec.rb
90
- - spec/supports/helpers.rb
91
93
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
92
94
  licenses:
93
95
  - Apache License (2.0)
@@ -110,10 +112,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
112
  version: '0'
111
113
  requirements: []
112
114
  rubyforge_project:
113
- rubygems_version: 2.4.8
115
+ rubygems_version: 2.1.9
114
116
  signing_key:
115
117
  specification_version: 4
116
118
  summary: The multiline codec will collapse multiline messages and merge them into a single event.
117
119
  test_files:
118
120
  - spec/codecs/multiline_spec.rb
121
+ - spec/codecs/identity_map_codec_spec.rb
119
122
  - spec/supports/helpers.rb