logstash-input-beats 3.1.0.beta1-java

Sign up to get free protection for your applications and to get access to all the features.
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