logstash-input-beats 3.1.0.beta1-java

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +131 -0
  3. data/CONTRIBUTORS +17 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +14 -0
  6. data/NOTICE.TXT +5 -0
  7. data/PROTOCOL.md +127 -0
  8. data/README.md +98 -0
  9. data/VERSION +1 -0
  10. data/lib/logstash-input-beats_jars.rb +17 -0
  11. data/lib/logstash/inputs/beats.rb +184 -0
  12. data/lib/logstash/inputs/beats/codec_callback_listener.rb +26 -0
  13. data/lib/logstash/inputs/beats/decoded_event_transform.rb +34 -0
  14. data/lib/logstash/inputs/beats/event_transform_common.rb +48 -0
  15. data/lib/logstash/inputs/beats/message_listener.rb +96 -0
  16. data/lib/logstash/inputs/beats/raw_event_transform.rb +18 -0
  17. data/lib/logstash/inputs/beats/tls.rb +40 -0
  18. data/lib/tasks/build.rake +15 -0
  19. data/lib/tasks/test.rake +65 -0
  20. data/logstash-input-beats.gemspec +41 -0
  21. data/spec/inputs/beats/codec_callback_listener_spec.rb +33 -0
  22. data/spec/inputs/beats/decoded_event_transform_spec.rb +74 -0
  23. data/spec/inputs/beats/event_transform_common_spec.rb +11 -0
  24. data/spec/inputs/beats/message_listener_spec.rb +108 -0
  25. data/spec/inputs/beats/raw_event_transform_spec.rb +26 -0
  26. data/spec/inputs/beats/tls_spec.rb +39 -0
  27. data/spec/inputs/beats_spec.rb +99 -0
  28. data/spec/integration/filebeat_spec.rb +234 -0
  29. data/spec/integration/logstash_forwarder_spec.rb +104 -0
  30. data/spec/spec_helper.rb +14 -0
  31. data/spec/support/client_process_helpers.rb +28 -0
  32. data/spec/support/file_helpers.rb +61 -0
  33. data/spec/support/flores_extensions.rb +82 -0
  34. data/spec/support/integration_shared_context.rb +73 -0
  35. data/spec/support/logstash_test.rb +66 -0
  36. data/spec/support/shared_examples.rb +56 -0
  37. data/vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-annotations/2.7.5/jackson-annotations-2.7.5.jar +0 -0
  38. data/vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-core/2.7.5/jackson-core-2.7.5.jar +0 -0
  39. data/vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-databind/2.7.5/jackson-databind-2.7.5.jar +0 -0
  40. data/vendor/jar-dependencies/com/fasterxml/jackson/module/jackson-module-afterburner/2.7.5/jackson-module-afterburner-2.7.5.jar +0 -0
  41. data/vendor/jar-dependencies/io/netty/netty-all/4.1.1.Final/netty-all-4.1.1.Final.jar +0 -0
  42. data/vendor/jar-dependencies/io/netty/netty-tcnative-boringssl-static/1.1.33.Fork17/netty-tcnative-boringssl-static-1.1.33.Fork17.jar +0 -0
  43. data/vendor/jar-dependencies/org/apache/logging/log4j/log4j-1.2-api/2.6.1/log4j-1.2-api-2.6.1.jar +0 -0
  44. data/vendor/jar-dependencies/org/apache/logging/log4j/log4j-api/2.6.1/log4j-api-2.6.1.jar +0 -0
  45. data/vendor/jar-dependencies/org/apache/logging/log4j/log4j-core/2.6.1/log4j-core-2.6.1.jar +0 -0
  46. data/vendor/jar-dependencies/org/apache/logging/log4j/log4j-slf4j-impl/2.6.1/log4j-slf4j-impl-2.6.1.jar +0 -0
  47. data/vendor/jar-dependencies/org/bouncycastle/bcpkix-jdk15on/1.54/bcpkix-jdk15on-1.54.jar +0 -0
  48. data/vendor/jar-dependencies/org/bouncycastle/bcprov-jdk15on/1.54/bcprov-jdk15on-1.54.jar +0 -0
  49. data/vendor/jar-dependencies/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar +0 -0
  50. data/vendor/jar-dependencies/org/logstash/beats/logstash-input-beats/3.1.0.beta1/logstash-input-beats-3.1.0.beta1.jar +0 -0
  51. metadata +313 -0
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/beats"
3
+
4
+ module LogStash module Inputs class Beats
5
+ # Use the new callback based approch instead of using blocks
6
+ # so we can retain some context of the execution, and make it easier to test
7
+ class CodecCallbackListener
8
+ attr_accessor :data
9
+ # The path acts as the `stream_identity`,
10
+ # usefull when the clients is reading multiples files
11
+ attr_accessor :path
12
+
13
+ def initialize(data, hash, path, transformer, queue)
14
+ @data = data
15
+ @hash = hash
16
+ @path = path
17
+ @queue = queue
18
+ @transformer = transformer
19
+ end
20
+
21
+ def process_event(event)
22
+ @transformer.transform(event, @hash)
23
+ @queue << event
24
+ end
25
+ end
26
+ end; end; end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/beats/event_transform_common"
3
+ module LogStash module Inputs class Beats
4
+ # Take the extracted content from the codec, merged with the other data coming
5
+ # from beats, apply the configured tags, normalize the host and try to coerce
6
+ # the timestamp if it was provided in the hash.
7
+ class DecodedEventTransform < EventTransformCommon
8
+ def transform(event, hash)
9
+ ts = coerce_ts(hash.delete("@timestamp"))
10
+
11
+ event.set("@timestamp", ts) unless ts.nil?
12
+ hash.each { |k, v| event.set(k, v) }
13
+ super(event)
14
+ event.tag("beats_input_codec_#{codec_name}_applied")
15
+ event
16
+ end
17
+
18
+ private
19
+ def coerce_ts(ts)
20
+ return nil if ts.nil?
21
+ timestamp = LogStash::Timestamp.coerce(ts)
22
+
23
+ return timestamp if timestamp
24
+
25
+ @logger.warn("Unrecognized @timestamp value, setting current time to @timestamp",
26
+ :value => ts.inspect)
27
+ return nil
28
+ rescue LogStash::TimestampParserError => e
29
+ @logger.warn("Error parsing @timestamp string, setting current time to @timestamp",
30
+ :value => ts.inspect, :exception => e.message)
31
+ return nil
32
+ end
33
+ end
34
+ end; end; end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ module LogStash module Inputs class Beats
3
+ # Base Transform class, expose the plugin decorate method,
4
+ # apply the tags and make sure we copy the beat hostname into `host`
5
+ # for backward compatibility.
6
+ class EventTransformCommon
7
+ def initialize(input)
8
+ @input = input
9
+ @logger = input.logger
10
+ end
11
+
12
+ # Copies the beat.hostname field into the host field unless
13
+ # the host field is already defined
14
+ def copy_beat_hostname(event)
15
+ host = event.get("[beat][hostname]")
16
+
17
+ if host && event.get("host").nil?
18
+ event.set("host", host)
19
+ end
20
+ end
21
+
22
+ # This break the `#decorate` method visibility of the plugin base
23
+ # class, the method is protected and we cannot access it, but well ruby
24
+ # can let you do all the wrong thing.
25
+ #
26
+ # I think the correct behavior would be to allow the plugin to return a
27
+ # `Decorator` object that we can pass to other objects, since only the
28
+ # plugin know the data used to decorate. This would allow a more component
29
+ # based workflow.
30
+ def decorate(event)
31
+ @input.send(:decorate, event)
32
+ end
33
+
34
+ def codec_name
35
+ @codec_name ||= if @input.codec.respond_to?(:base_codec)
36
+ @input.codec.base_codec.class.config_name
37
+ else
38
+ @input.codec.class.config_name
39
+ end
40
+ end
41
+
42
+ def transform(event)
43
+ copy_beat_hostname(event)
44
+ decorate(event)
45
+ event
46
+ end
47
+ end
48
+ end; end; end
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+ require "thread_safe"
3
+ require "logstash-input-beats_jars"
4
+ import "org.logstash.beats.MessageListener"
5
+
6
+ module LogStash module Inputs class Beats
7
+ class MessageListener
8
+ include org.logstash.beats.IMessageListener
9
+
10
+ FILEBEAT_LOG_LINE_FIELD = "message".freeze
11
+ LSF_LOG_LINE_FIELD = "line".freeze
12
+
13
+ ConnectionState = Struct.new(:ctx, :codec)
14
+
15
+ attr_reader :logger, :input, :connections_list
16
+
17
+ def initialize(queue, input)
18
+ @connections_list = ThreadSafe::Hash.new
19
+ @queue = queue
20
+ @logger = input.logger
21
+ @input = input
22
+
23
+ @nocodec_transformer = RawEventTransform.new(@input)
24
+ @codec_transformer = DecodedEventTransform.new(@input)
25
+ end
26
+
27
+ def onNewMessage(ctx, message)
28
+ hash = message.getData()
29
+
30
+ target_field = extract_target_field(hash)
31
+
32
+ if target_field.nil?
33
+ event = LogStash::Event.new(hash)
34
+ @nocodec_transformer.transform(event)
35
+ @queue << event
36
+ else
37
+ codec(ctx).accept(CodecCallbackListener.new(target_field,
38
+ hash,
39
+ message.getIdentityStream(),
40
+ @codec_transformer,
41
+ @queue))
42
+ end
43
+ end
44
+
45
+ def onNewConnection(ctx)
46
+ register_connection(ctx)
47
+ end
48
+
49
+ def onConnectionClose(ctx)
50
+ unregister_connection(ctx)
51
+ end
52
+
53
+ def onException(ctx)
54
+ unregister_connection(ctx)
55
+ end
56
+
57
+ private
58
+ def codec(ctx)
59
+ connections_list[ctx].codec
60
+ end
61
+
62
+ def register_connection(ctx)
63
+ connections_list[ctx] = ConnectionState.new(ctx, input.codec.dup)
64
+ end
65
+
66
+ def unregister_connection(ctx)
67
+ flush_buffer(ctx)
68
+ connections_list.delete(ctx)
69
+ end
70
+
71
+ def flush_buffer(ctx)
72
+ transformer = EventTransformCommon.new(@input)
73
+
74
+ codec(ctx).flush do |event|
75
+ transformer.transform(event)
76
+ @queue << event
77
+ end
78
+ end
79
+
80
+ def extract_target_field(hash)
81
+ if from_filebeat?(hash)
82
+ hash.delete(FILEBEAT_LOG_LINE_FIELD).to_s
83
+ elsif from_logstash_forwarder?(hash)
84
+ hash.delete(LSF_LOG_LINE_FIELD).to_s
85
+ end
86
+ end
87
+
88
+ def from_filebeat?(hash)
89
+ !hash[FILEBEAT_LOG_LINE_FIELD].nil?
90
+ end
91
+
92
+ def from_logstash_forwarder?(hash)
93
+ !hash[LSF_LOG_LINE_FIELD].nil?
94
+ end
95
+ end
96
+ end; end; end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/beats/event_transform_common"
3
+ module LogStash module Inputs class Beats
4
+ # Take the the raw output from the library, decorate it with
5
+ # the configured tags in the plugins and normalize the hostname
6
+ # for backward compatibility
7
+ #
8
+ #
9
+ # @see [Lumberjack::Beats::Parser]
10
+ #
11
+ class RawEventTransform < EventTransformCommon
12
+ def transform(event)
13
+ super(event)
14
+ event.tag("beats_input_raw_event")
15
+ event
16
+ end
17
+ end
18
+ end; end;end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ module LogStash module Inputs class Beats
3
+ class TLS
4
+ class TLSOption
5
+ include Comparable
6
+
7
+ attr_reader :name, :version
8
+ def initialize(name, version)
9
+ @name = name
10
+ @version = version
11
+ end
12
+
13
+ def <=>(other)
14
+ version <=> other.version
15
+ end
16
+ end
17
+
18
+ TLS_PROTOCOL_OPTIONS = [
19
+ TLSOption.new("TLSv1", 1),
20
+ TLSOption.new("TLSv1.1", 1.1),
21
+ TLSOption.new("TLSv1.2", 1.2)
22
+ ]
23
+
24
+ def self.min
25
+ TLS_PROTOCOL_OPTIONS.min
26
+ end
27
+
28
+ def self.max
29
+ TLS_PROTOCOL_OPTIONS.max
30
+ end
31
+
32
+ def self.get_supported(versions)
33
+ if versions.is_a?(Range)
34
+ TLS_PROTOCOL_OPTIONS.select { |tls| versions.cover?(tls.version) }
35
+ else
36
+ TLS_PROTOCOL_OPTIONS.select { |tls| versions == tls.version }
37
+ end
38
+ end
39
+ end
40
+ end; end; end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ require "jars/installer"
3
+ require "fileutils"
4
+
5
+ task :vendor do
6
+ system("./gradlew vendor")
7
+ version = File.read("VERSION").strip
8
+ end
9
+
10
+ desc "clean"
11
+ task :clean do
12
+ ["build", "vendor/jar-dependencies", "Gemfile.lock"].each do |p|
13
+ FileUtils.rm_rf(p)
14
+ end
15
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ OS_PLATFORM = RbConfig::CONFIG["host_os"]
3
+ VENDOR_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "vendor"))
4
+
5
+ if OS_PLATFORM == "linux"
6
+ FILEBEAT_URL = "https://beats-nightlies.s3.amazonaws.com/filebeat/filebeat-5.0.0-alpha4-SNAPSHOT-linux-x86_64.tar.gz"
7
+ elsif OS_PLATFORM == "darwin"
8
+ FILEBEAT_URL = "https://beats-nightlies.s3.amazonaws.com/filebeat/filebeat-5.0.0-alpha4-SNAPSHOT-darwin-x86_64.tar.gz"
9
+ end
10
+
11
+ LSF_URL = "https://download.elastic.co/logstash-forwarder/binaries/logstash-forwarder_#{OS_PLATFORM}_amd64"
12
+
13
+ require "fileutils"
14
+ @files=[]
15
+
16
+ task :default do
17
+ system("rake -T")
18
+ end
19
+
20
+ require "logstash/devutils/rake"
21
+
22
+ namespace :test do
23
+ namespace :integration do
24
+ task :setup do
25
+ Rake::Task["test:integration:setup:filebeat"].invoke
26
+ Rake::Task["test:integration:setup:lsf"].invoke
27
+ end
28
+
29
+ namespace :setup do
30
+ desc "Download lastest stable version of Logstash-forwarder"
31
+ task :lsf do
32
+ destination = File.join(VENDOR_PATH, "logstash-forwarder")
33
+ FileUtils.rm_rf(destination)
34
+ FileUtils.mkdir_p(destination)
35
+ download_destination = File.join(destination, "logstash-forwarder")
36
+ puts "Logstash-forwarder: downloading from #{LSF_URL} to #{download_destination}"
37
+ download(LSF_URL, download_destination)
38
+ File.chmod(0755, download_destination)
39
+ end
40
+
41
+ desc "Download nigthly filebeat for integration testing"
42
+ task :filebeat do
43
+ FileUtils.mkdir_p(VENDOR_PATH)
44
+ download_destination = File.join(VENDOR_PATH, "filebeat.tar.gz")
45
+ destination = File.join(VENDOR_PATH, "filebeat")
46
+ FileUtils.rm_rf(download_destination)
47
+ FileUtils.rm_rf(destination)
48
+ FileUtils.rm_rf(File.join(VENDOR_PATH, "filebeat.tar"))
49
+ puts "Filebeat: downloading from #{FILEBEAT_URL} to #{download_destination}"
50
+ download(FILEBEAT_URL, download_destination)
51
+
52
+ untar_all(download_destination, File.join(VENDOR_PATH, "filebeat")) { |e| e }
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Uncompress all the file from the archive this only work with
59
+ # one level directory structure and its fine for LSF and filebeat packaging.
60
+ def untar_all(file, destination)
61
+ untar(file) do |entry|
62
+ out = entry.full_name.split("/").last
63
+ File.join(destination, out)
64
+ end
65
+ end
@@ -0,0 +1,41 @@
1
+ BEATS_VERSION = File.read("VERSION").strip unless defined?(BEATS_VERSION)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "logstash-input-beats"
5
+ s.version = BEATS_VERSION
6
+ s.licenses = ["Apache License (2.0)"]
7
+ s.summary = "Receive events using the lumberjack protocol."
8
+ s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
9
+ s.authors = ["Elastic"]
10
+ s.email = "info@elastic.co"
11
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
12
+ s.require_paths = ["lib", "vendor/jar-dependencies"]
13
+
14
+ # Files
15
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION"]
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency "logstash-core-plugin-api", "~> 2.0"
24
+
25
+ s.add_runtime_dependency "logstash-codec-plain"
26
+ s.add_runtime_dependency "concurrent-ruby", [ ">= 0.9.2", "<= 1.0.0" ]
27
+ s.add_runtime_dependency "thread_safe", "~> 0.3.5"
28
+ s.add_runtime_dependency "logstash-codec-multiline", "~> 3.0"
29
+ s.add_runtime_dependency 'jar-dependencies', '~> 0.3.4'
30
+
31
+ s.add_development_dependency "flores", "~>0.0.6"
32
+ s.add_development_dependency "rspec"
33
+ s.add_development_dependency "stud"
34
+ s.add_development_dependency "pry"
35
+ s.add_development_dependency "rspec-wait"
36
+ s.add_development_dependency "logstash-devutils"
37
+ s.add_development_dependency "logstash-codec-json"
38
+ s.add_development_dependency "childprocess" # To make filebeat/LSF integration test easier to write.
39
+
40
+ s.platform = 'java'
41
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/beats"
3
+ require "logstash/event"
4
+ require "spec_helper"
5
+ require "thread"
6
+
7
+ describe LogStash::Inputs::Beats::CodecCallbackListener do
8
+ let(:data) { "Hello world" }
9
+ let(:map) do
10
+ {
11
+ "beat" => { "hostname" => "newhost" }
12
+ }
13
+ end
14
+ let(:path) { "/var/log/message" }
15
+ let(:transformer) { double("codec_transformer") }
16
+ let(:queue_timeout) { 1 }
17
+ let(:event) { LogStash::Event.new }
18
+ let(:queue) { Queue.new }
19
+
20
+ before do
21
+ allow(transformer).to receive(:transform).with(event, map).and_return(event)
22
+ end
23
+
24
+ subject { described_class.new(data, map, path, transformer, queue) }
25
+
26
+ it "expose the data" do
27
+ expect(subject.data).to eq(data)
28
+ end
29
+
30
+ it "expose the path" do
31
+ expect(subject.path).to eq(path)
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+ require "logstash/event"
3
+ require "logstash/timestamp"
4
+ require "logstash/inputs/beats"
5
+ require_relative "../../support/shared_examples"
6
+ require "spec_helper"
7
+
8
+ describe LogStash::Inputs::Beats::DecodedEventTransform do
9
+ let(:config) do
10
+ {
11
+ "port" => 0,
12
+ "type" => "example",
13
+ "tags" => "beats"
14
+ }
15
+ end
16
+
17
+ let(:input) do
18
+ LogStash::Inputs::Beats.new(config).tap do |i|
19
+ i.register
20
+ end
21
+ end
22
+ let(:event) { LogStash::Event.new }
23
+ let(:map) do
24
+ {
25
+ "@metadata" => { "hello" => "world"},
26
+ "super" => "mario"
27
+ }
28
+ end
29
+
30
+ subject { described_class.new(input).transform(event, map) }
31
+
32
+ include_examples "Common Event Transformation"
33
+
34
+ it "tags the event" do
35
+ expect(subject.get("tags")).to include("beats_input_codec_plain_applied")
36
+ end
37
+
38
+ it "merges the other data from the map to the event" do
39
+ expect(subject.get("super")).to eq(map["super"])
40
+ expect(subject.get("@metadata")).to include(map["@metadata"])
41
+ end
42
+
43
+ context "map contains a timestamp" do
44
+ context "when its valid" do
45
+ let(:timestamp) { Time.now }
46
+ let(:map) { super.merge({"@timestamp" => timestamp }) }
47
+
48
+ it "uses as the event timestamp" do
49
+ expect(subject.get("@timestamp")).to eq(LogStash::Timestamp.coerce(timestamp))
50
+ end
51
+ end
52
+
53
+ context "when its not valid" do
54
+ let(:map) { super.merge({"@timestamp" => "invalid" }) }
55
+
56
+ it "fallback the current time" do
57
+ expect(subject.get("@timestamp")).to be_kind_of(LogStash::Timestamp)
58
+ end
59
+ end
60
+ end
61
+
62
+ context "when the map doesn't provide a timestamp" do
63
+ it "fallback the current time" do
64
+ expect(subject.get("@timestamp")).to be_kind_of(LogStash::Timestamp)
65
+ end
66
+ end
67
+
68
+ context "when the codec is a base_codec wrapper" do
69
+ before { config.update("codec" => BeatsInputTest::DummyCodec.new) }
70
+ it "gets the codec config name from the base codec" do
71
+ expect(subject.get("tags")).to include("beats_input_codec_dummy_applied")
72
+ end
73
+ end
74
+ end