logstash-codec-joinlines 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTORS +19 -0
- data/DEVELOPER.md +3 -0
- data/Gemfile +2 -0
- data/LICENSE +11 -0
- data/README.md +86 -0
- data/lib/logstash/codecs/auto_flush.rb +48 -0
- data/lib/logstash/codecs/identity_map_codec.rb +347 -0
- data/lib/logstash/codecs/joinlines.rb +301 -0
- data/lib/logstash/codecs/retriggerable_task.rb +81 -0
- data/logstash-codec-joinlines.gemspec +28 -0
- data/spec/codecs/joinlines_spec.rb +435 -0
- data/spec/spec_helper.rb +140 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cff8b92826a48b224f39c357774bf38c576d084fc4c367dfeab8235fb2b8df69
|
4
|
+
data.tar.gz: c3788765ed29c61f7bc3916ffdea4289e8401f9f54454b8478be467f4126482a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8e95490d1e38db256450e56ce14b16e3d7dfa380b8178cb2fd71d2c0af52d9039a459864d89a6980096caa3007cdb73a669f3991d87143cad6c2d6b8f527bc24
|
7
|
+
data.tar.gz: 32b1604e0071833000c9414b1654b6852a333189894c4b0a22fad9a5a5c90cdf40733268af14d4a2a22538bd96c11d23a1d903133843f4d6c38dfcfbc1217611
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTORS
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
The following is a list of people who have contributed ideas, code, bug
|
2
|
+
reports, or in general have helped logstash along its way.
|
3
|
+
|
4
|
+
Contributors:
|
5
|
+
* Developers of logstash-codec-multiline
|
6
|
+
* Colin Surprenant (colinsurprenant)
|
7
|
+
* Jordan Sissel (jordansissel)
|
8
|
+
* João Duarte (jsvd)
|
9
|
+
* Kurt Hurtado (kurtado)
|
10
|
+
* Pier-Hugues Pellerin (ph)
|
11
|
+
* Richard Pijnenburg (electrical)
|
12
|
+
* Suyog Rao (suyograo)
|
13
|
+
* Guy Boertje (guyboertje)
|
14
|
+
* Svein L. Ellingsen (lovmoen)
|
15
|
+
|
16
|
+
Note: If you've sent us patches, bug reports, or otherwise contributed to
|
17
|
+
Logstash, and you aren't on the list above and want to be, please let us know
|
18
|
+
and we'll make sure you're here. Contributions from folks like you are what make
|
19
|
+
open source awesome.
|
data/DEVELOPER.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
you may not use this file except in compliance with the License.
|
3
|
+
You may obtain a copy of the License at
|
4
|
+
|
5
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
See the License for the specific language governing permissions and
|
11
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Logstash Plugin
|
2
|
+
|
3
|
+
This is a plugin for [Logstash](https://github.com/elastic/logstash).
|
4
|
+
|
5
|
+
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.
|
6
|
+
|
7
|
+
## Documentation
|
8
|
+
|
9
|
+
Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
|
10
|
+
|
11
|
+
- For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
|
12
|
+
- For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
|
13
|
+
|
14
|
+
## Need Help?
|
15
|
+
|
16
|
+
Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
|
17
|
+
|
18
|
+
## Developing
|
19
|
+
|
20
|
+
### 1. Plugin Developement and Testing
|
21
|
+
|
22
|
+
#### Code
|
23
|
+
- To get started, you'll need JRuby with the Bundler gem installed.
|
24
|
+
|
25
|
+
- Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
|
26
|
+
|
27
|
+
- Install dependencies
|
28
|
+
```sh
|
29
|
+
bundle install
|
30
|
+
```
|
31
|
+
|
32
|
+
#### Test
|
33
|
+
|
34
|
+
- Update your dependencies
|
35
|
+
|
36
|
+
```sh
|
37
|
+
bundle install
|
38
|
+
```
|
39
|
+
|
40
|
+
- Run tests
|
41
|
+
|
42
|
+
```sh
|
43
|
+
bundle exec rspec
|
44
|
+
```
|
45
|
+
|
46
|
+
### 2. Running your unpublished Plugin in Logstash
|
47
|
+
|
48
|
+
#### 2.1 Run in a local Logstash clone
|
49
|
+
|
50
|
+
- Edit Logstash `Gemfile` and add the local plugin path, for example:
|
51
|
+
```ruby
|
52
|
+
gem "logstash-codec-awesome", :path => "/your/local/logstash-codec-awesome"
|
53
|
+
```
|
54
|
+
- Install plugin
|
55
|
+
```sh
|
56
|
+
bin/logstash-plugin install --no-verify
|
57
|
+
```
|
58
|
+
- Run Logstash with your plugin
|
59
|
+
```sh
|
60
|
+
bin/logstash -e 'codec {awesome {}}'
|
61
|
+
```
|
62
|
+
At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
|
63
|
+
|
64
|
+
#### 2.2 Run in an installed Logstash
|
65
|
+
|
66
|
+
You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
|
67
|
+
|
68
|
+
- Build your plugin gem
|
69
|
+
```sh
|
70
|
+
gem build logstash-codec-awesome.gemspec
|
71
|
+
```
|
72
|
+
- Install the plugin from the Logstash home
|
73
|
+
```sh
|
74
|
+
bin/logstash-plugin install /your/local/plugin/logstash-codec-awesome.gem
|
75
|
+
```
|
76
|
+
- Start Logstash and proceed to test the plugin
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
|
81
|
+
|
82
|
+
Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
|
83
|
+
|
84
|
+
It is more important to the community that you are able to contribute.
|
85
|
+
|
86
|
+
For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "concurrent"
|
3
|
+
require "logstash/codecs/retriggerable_task"
|
4
|
+
|
5
|
+
module LogStash module Codecs class AutoFlush
|
6
|
+
def initialize(mc, interval)
|
7
|
+
@mc, @interval = mc, interval
|
8
|
+
@stopped = Concurrent::AtomicBoolean.new # false by default
|
9
|
+
@task = RetriggerableTask.new(@interval, self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def timeout
|
13
|
+
@mc.auto_flush
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
# can't start if pipeline is stopping
|
18
|
+
return self if stopped?
|
19
|
+
@task.retrigger
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def stopped?
|
24
|
+
@stopped.value
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
@stopped.make_true
|
29
|
+
@task.close
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class AutoFlushUnset
|
34
|
+
def initialize(mc, interval)
|
35
|
+
end
|
36
|
+
|
37
|
+
def stopped?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def start
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end end end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/namespace"
|
3
|
+
require "thread_safe"
|
4
|
+
require "concurrent"
|
5
|
+
|
6
|
+
# This class is a Codec duck type
|
7
|
+
# Using Composition, it maps from a stream identity to
|
8
|
+
# a cloned codec instance via the same API as a Codec
|
9
|
+
# it implements the codec public API
|
10
|
+
|
11
|
+
module LogStash module Codecs class IdentityMapCodec
|
12
|
+
# subclass of Exception, LS has more than limit (20000) active streams
|
13
|
+
class IdentityMapUpperLimitException < Exception; end
|
14
|
+
|
15
|
+
module EightyPercentWarning
|
16
|
+
extend self
|
17
|
+
def visit(imc)
|
18
|
+
current_size, limit = imc.current_size_and_limit
|
19
|
+
return if current_size < (limit * 0.8)
|
20
|
+
imc.logger.warn("IdentityMapCodec has reached 80% capacity",
|
21
|
+
:current_size => current_size, :upper_limit => limit)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module UpperLimitReached
|
26
|
+
extend self
|
27
|
+
def visit(imc)
|
28
|
+
current_size, limit = imc.current_size_and_limit
|
29
|
+
return if current_size < limit
|
30
|
+
# we hit the limit
|
31
|
+
# try to clean out stale streams
|
32
|
+
current_size, limit = imc.map_cleanup
|
33
|
+
return if current_size < limit
|
34
|
+
# we are still at the limit and all streams are in use
|
35
|
+
imc.logger.error("IdentityMapCodec has reached 100% capacity",
|
36
|
+
:current_size => current_size, :upper_limit => limit)
|
37
|
+
raise IdentityMapUpperLimitException.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class PeriodicRunner
|
42
|
+
def initialize(listener, interval, method_symbol)
|
43
|
+
@listener, @interval = listener, interval
|
44
|
+
@method_symbol = method_symbol
|
45
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
def start
|
49
|
+
return self if running?
|
50
|
+
@running.make_true
|
51
|
+
@thread = Thread.new() do
|
52
|
+
while running? do
|
53
|
+
sleep @interval
|
54
|
+
break if !running?
|
55
|
+
@listener.send(@method_symbol)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def running?
|
62
|
+
@running.value
|
63
|
+
end
|
64
|
+
|
65
|
+
def stop
|
66
|
+
return if !running?
|
67
|
+
@running.make_false
|
68
|
+
if @thread.alive?
|
69
|
+
@thread.wakeup
|
70
|
+
@thread.join
|
71
|
+
end
|
72
|
+
@listener = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class NoopRunner
|
77
|
+
attr_reader :start, :stop
|
78
|
+
def running?() false; end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A composite class to hold both the codec, the eviction_timeout and a last_used timestamp
|
82
|
+
# instances of this Value Object are stored in the mapping hash
|
83
|
+
class CodecValue
|
84
|
+
attr_reader :codec
|
85
|
+
attr_accessor :eviction_timeout, :auto_flush_timeout
|
86
|
+
|
87
|
+
def initialize(codec)
|
88
|
+
@codec = codec
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
#maximum size of the mapping hash
|
93
|
+
MAX_IDENTITIES = 20_000
|
94
|
+
|
95
|
+
# time after which a stream is
|
96
|
+
# considered stale
|
97
|
+
# each time a stream is accessed
|
98
|
+
# it is given a new timeout
|
99
|
+
EVICT_TIMEOUT = 60 * 60 * 1 # 1 hour
|
100
|
+
|
101
|
+
# time that the cleaner thread sleeps for
|
102
|
+
# before it tries to clean out stale mappings
|
103
|
+
CLEANER_INTERVAL = 60 * 5 # 5 minutes
|
104
|
+
|
105
|
+
attr_reader :identity_map
|
106
|
+
attr_accessor :base_codec, :cleaner, :auto_flusher
|
107
|
+
|
108
|
+
def initialize(codec)
|
109
|
+
@base_codec = codec
|
110
|
+
@base_codecs = [codec]
|
111
|
+
@identity_map = ThreadSafe::Hash.new &method(:codec_builder)
|
112
|
+
@max_identities = MAX_IDENTITIES
|
113
|
+
@evict_timeout = EVICT_TIMEOUT
|
114
|
+
cleaner_interval(CLEANER_INTERVAL)
|
115
|
+
if codec.respond_to?(:use_mapper_auto_flush) &&
|
116
|
+
(@auto_flush_interval = codec.use_mapper_auto_flush)
|
117
|
+
@auto_flusher = PeriodicRunner.new(self, 0.5, :auto_flush_mapped)
|
118
|
+
else
|
119
|
+
@auto_flusher = NoopRunner.new
|
120
|
+
end
|
121
|
+
|
122
|
+
@decode_block = lambda {|*| true }
|
123
|
+
@eviction_block = nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# ==============================================
|
127
|
+
# Constructional/builder methods
|
128
|
+
# chain this method off of new
|
129
|
+
#
|
130
|
+
# used to add a non-default maximum identities
|
131
|
+
def max_identities(max)
|
132
|
+
@max_identities = max.to_i
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
# used to add a non-default evict timeout
|
137
|
+
def evict_timeout(timeout)
|
138
|
+
@evict_timeout = timeout.to_i
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
# used to add a non-default cleaner interval
|
143
|
+
def cleaner_interval(interval)
|
144
|
+
@cleaner.stop if @cleaner
|
145
|
+
@cleaner = PeriodicRunner.new(self, interval.to_i, :map_cleanup)
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# used to add a non-default eviction block
|
150
|
+
def eviction_block(block)
|
151
|
+
@eviction_block = block
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
# end Constructional/builder methods
|
156
|
+
# ==============================================
|
157
|
+
|
158
|
+
# ==============================================
|
159
|
+
# IdentityMapCodec API
|
160
|
+
def evict(identity)
|
161
|
+
# maybe called more than once
|
162
|
+
if (compo = identity_map.delete(identity))
|
163
|
+
compo.codec.auto_flush if compo.codec.respond_to?(:auto_flush)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
# end IdentityMapCodec API
|
167
|
+
# ==============================================
|
168
|
+
|
169
|
+
# ==============================================
|
170
|
+
# Codec API
|
171
|
+
def decode(data, identity = nil, &block)
|
172
|
+
@decode_block = block if @decode_block != block
|
173
|
+
stream_codec(identity).decode(data, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
def accept(listener)
|
177
|
+
stream_codec(listener.path).accept(listener)
|
178
|
+
end
|
179
|
+
|
180
|
+
alias_method :<<, :decode
|
181
|
+
|
182
|
+
def encode(event, identity = nil)
|
183
|
+
stream_codec(identity).encode(event)
|
184
|
+
end
|
185
|
+
|
186
|
+
def flush(&block)
|
187
|
+
all_codecs.each do |codec|
|
188
|
+
#let ruby do its default args thing
|
189
|
+
if block_given?
|
190
|
+
codec.flush(&block)
|
191
|
+
else
|
192
|
+
if codec.respond_to?(:auto_flush)
|
193
|
+
codec.auto_flush
|
194
|
+
else
|
195
|
+
#try this, no guarantees
|
196
|
+
codec.flush
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def close()
|
203
|
+
cleaner.stop
|
204
|
+
auto_flusher.stop
|
205
|
+
all_codecs.each(&:close)
|
206
|
+
end
|
207
|
+
# end Codec API
|
208
|
+
# ==============================================
|
209
|
+
|
210
|
+
def auto_flush_mapped
|
211
|
+
if !identity_count.zero?
|
212
|
+
nowf = Time.now.to_f
|
213
|
+
identity_map.each do |identity, compo|
|
214
|
+
next if compo.auto_flush_timeout.zero?
|
215
|
+
next unless nowf > compo.auto_flush_timeout
|
216
|
+
compo.codec.auto_flush
|
217
|
+
# at eof (tail and read) no more lines for a while or ever
|
218
|
+
# so reset compo.auto_flush_timeout
|
219
|
+
compo.auto_flush_timeout = 0
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def flush_mapped(listener)
|
225
|
+
listener_has_path = listener.respond_to?(:path)
|
226
|
+
identity_map.each do |identity, compo|
|
227
|
+
listener.path = identity if listener_has_path
|
228
|
+
compo.codec.auto_flush(listener)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def all_codecs
|
233
|
+
no_streams? ? @base_codecs : identity_map.values.map(&:codec)
|
234
|
+
end
|
235
|
+
|
236
|
+
def max_limit
|
237
|
+
@max_identities
|
238
|
+
end
|
239
|
+
|
240
|
+
def identity_count
|
241
|
+
identity_map.size
|
242
|
+
end
|
243
|
+
|
244
|
+
# support cleaning of stale stream/codecs
|
245
|
+
# a stream is considered stale if it has not
|
246
|
+
# been accessed in the last @evict_timeout
|
247
|
+
# period (default 1 hour)
|
248
|
+
def map_cleanup
|
249
|
+
if !identity_count.zero?
|
250
|
+
nowi = Time.now.to_i
|
251
|
+
# delete_if is atomic
|
252
|
+
# contents should not mutate during this call
|
253
|
+
identity_map.delete_if do |identity, compo|
|
254
|
+
if (flag = compo.eviction_timeout <= nowi)
|
255
|
+
evict_flush(compo.codec)
|
256
|
+
end
|
257
|
+
flag
|
258
|
+
end
|
259
|
+
end
|
260
|
+
current_size_and_limit
|
261
|
+
end
|
262
|
+
|
263
|
+
def evict_flush(codec)
|
264
|
+
if codec.respond_to?(:auto_flush)
|
265
|
+
codec.auto_flush
|
266
|
+
else
|
267
|
+
if (block = @eviction_block || @decode_block)
|
268
|
+
codec.flush(&block)
|
269
|
+
end
|
270
|
+
# all else - can't do anything
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def current_size_and_limit
|
275
|
+
[identity_count, max_limit]
|
276
|
+
end
|
277
|
+
|
278
|
+
def logger
|
279
|
+
# we 'borrow' the codec's logger as we don't have our own
|
280
|
+
@base_codec.logger
|
281
|
+
end
|
282
|
+
|
283
|
+
def codec_without_usage_update(identity)
|
284
|
+
find_codec_value(identity).codec
|
285
|
+
end
|
286
|
+
|
287
|
+
def eviction_timestamp_for(identity)
|
288
|
+
find_codec_value(identity).eviction_timeout
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
|
293
|
+
def stream_codec(identity)
|
294
|
+
return base_codec if identity.nil?
|
295
|
+
record_codec_usage(identity) # returns codec
|
296
|
+
end
|
297
|
+
|
298
|
+
def find_codec_value(identity)
|
299
|
+
identity_map[identity]
|
300
|
+
end
|
301
|
+
|
302
|
+
# for nil stream this method is not called
|
303
|
+
def record_codec_usage(identity)
|
304
|
+
check_map_limits
|
305
|
+
# only start the cleaner if streams are in use
|
306
|
+
# continuous calls to start are OK
|
307
|
+
cleaner.start
|
308
|
+
auto_flusher.start
|
309
|
+
compo = find_codec_value(identity)
|
310
|
+
now = Time.now
|
311
|
+
compo.eviction_timeout = eviction_timestamp(now)
|
312
|
+
compo.auto_flush_timeout = auto_flush_timestamp(now)
|
313
|
+
compo.codec
|
314
|
+
end
|
315
|
+
|
316
|
+
def auto_flush_timestamp(now = Time.now)
|
317
|
+
now.to_f + @auto_flush_interval.to_f
|
318
|
+
end
|
319
|
+
|
320
|
+
def eviction_timestamp(now = Time.now)
|
321
|
+
now.to_i + @evict_timeout
|
322
|
+
end
|
323
|
+
|
324
|
+
def check_map_limits
|
325
|
+
UpperLimitReached.visit(self)
|
326
|
+
EightyPercentWarning.visit(self)
|
327
|
+
end
|
328
|
+
|
329
|
+
def codec_builder(hash, k)
|
330
|
+
codec = hash.empty? ? @base_codec : @base_codec.clone
|
331
|
+
codec.use_mapper_auto_flush if using_mapped_auto_flush?
|
332
|
+
compo = CodecValue.new(codec).tap do |o|
|
333
|
+
now = Time.now
|
334
|
+
o.eviction_timeout = eviction_timestamp(now)
|
335
|
+
o.auto_flush_timeout = auto_flush_timestamp(now)
|
336
|
+
end
|
337
|
+
hash.store(k, compo)
|
338
|
+
end
|
339
|
+
|
340
|
+
def no_streams?
|
341
|
+
identity_map.empty?
|
342
|
+
end
|
343
|
+
|
344
|
+
def using_mapped_auto_flush?
|
345
|
+
!@auto_flush_interval.nil?
|
346
|
+
end
|
347
|
+
end end end
|