logstash-input-gelf 0.1.0

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTA4MGNkMjhkYzVhYjkxZWM4MjlmYzRhNTc3MGI1OWIxODZkNmE3ZQ==
5
+ data.tar.gz: !binary |-
6
+ OWMwYTk4NzNjOTdkODJkMWU2MDc4MTRhNGU2YThlY2M1ZjllZDIwOQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NGM0NzJkZTY2ZDE1MWY3OGI1NmIzYmQ2Y2JmM2M1NTY0OTgyMDk3Yjc4MTA1
10
+ NWRkNmU0NjZlMjk0Mzc0ZDNiYjg5ZDhlYjZmZWQ0OWY0Y2E4ZTRjMTE5OTQ0
11
+ OTFlZmE4MDZjZmI2NWYyNmZlZjI2NmI1ZDJlMzk2MzRiZTc2M2I=
12
+ data.tar.gz: !binary |-
13
+ NTBiNDI0NTRhMDAwNjIxZjhhMTZiM2ZlNTZlNDZhMTA4MmYxOGI0YmE2ZjIz
14
+ YTU2ZjY3Y2RjMDAwNGQ2MjVjY2ZhNWY3NGFiZmVjY2IzZWExYzY2YmQxNmM5
15
+ NjFkZTVjZWRlNWYzZmYwZmRjMjI4YzM1NzE4NDY2OWNiNjM2M2Q=
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ Gemfile.bak
4
+ .bundle
5
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+ gem 'rake'
3
+ gem 'gem_publisher'
4
+ gem 'archive-tar-minitar'
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012-2014 Elasticsearch <http://www.elasticsearch.org>
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ @files=[]
2
+
3
+ task :default do
4
+ system("rake -T")
5
+ end
6
+
@@ -0,0 +1,137 @@
1
+ # encoding: utf-8
2
+ require "date"
3
+ require "logstash/inputs/base"
4
+ require "logstash/namespace"
5
+ require "logstash/json"
6
+ require "logstash/timestamp"
7
+ require "socket"
8
+
9
+ # This input will read GELF messages as events over the network,
10
+ # making it a good choice if you already use Graylog2 today.
11
+ #
12
+ # The main use case for this input is to leverage existing GELF
13
+ # logging libraries such as the GELF log4j appender.
14
+ #
15
+ class LogStash::Inputs::Gelf < LogStash::Inputs::Base
16
+ config_name "gelf"
17
+ milestone 2
18
+
19
+ default :codec, "plain"
20
+
21
+ # The IP address or hostname to listen on.
22
+ config :host, :validate => :string, :default => "0.0.0.0"
23
+
24
+ # The port to listen on. Remember that ports less than 1024 (privileged
25
+ # ports) may require root to use.
26
+ config :port, :validate => :number, :default => 12201
27
+
28
+ # Whether or not to remap the GELF message fields to Logstash event fields or
29
+ # leave them intact.
30
+ #
31
+ # Remapping converts the following GELF fields to Logstash equivalents:
32
+ #
33
+ # * `full\_message` becomes event["message"].
34
+ # * if there is no `full\_message`, `short\_message` becomes event["message"].
35
+ config :remap, :validate => :boolean, :default => true
36
+
37
+ # Whether or not to remove the leading '\_' in GELF fields or leave them
38
+ # in place. (Logstash < 1.2 did not remove them by default.). Note that
39
+ # GELF version 1.1 format now requires all non-standard fields to be added
40
+ # as an "additional" field, beginning with an underscore.
41
+ #
42
+ # e.g. `\_foo` becomes `foo`
43
+ #
44
+ config :strip_leading_underscore, :validate => :boolean, :default => true
45
+
46
+ public
47
+ def initialize(params)
48
+ super
49
+ BasicSocket.do_not_reverse_lookup = true
50
+ end # def initialize
51
+
52
+ public
53
+ def register
54
+ require 'gelfd'
55
+ @udp = nil
56
+ end # def register
57
+
58
+ public
59
+ def run(output_queue)
60
+ begin
61
+ # udp server
62
+ udp_listener(output_queue)
63
+ rescue => e
64
+ @logger.warn("gelf listener died", :exception => e, :backtrace => e.backtrace)
65
+ sleep(5)
66
+ retry
67
+ end # begin
68
+ end # def run
69
+
70
+ private
71
+ def udp_listener(output_queue)
72
+ @logger.info("Starting gelf listener", :address => "#{@host}:#{@port}")
73
+
74
+ if @udp
75
+ @udp.close_read rescue nil
76
+ @udp.close_write rescue nil
77
+ end
78
+
79
+ @udp = UDPSocket.new(Socket::AF_INET)
80
+ @udp.bind(@host, @port)
81
+
82
+ while true
83
+ line, client = @udp.recvfrom(8192)
84
+ begin
85
+ data = Gelfd::Parser.parse(line)
86
+ rescue => ex
87
+ @logger.warn("Gelfd failed to parse a message skipping", :exception => ex, :backtrace => ex.backtrace)
88
+ next
89
+ end
90
+
91
+ # Gelfd parser outputs null if it received and cached a non-final chunk
92
+ next if data.nil?
93
+
94
+ event = LogStash::Event.new(LogStash::Json.load(data))
95
+ event["source_host"] = client[3]
96
+ if event["timestamp"].is_a?(Numeric)
97
+ event.timestamp = LogStash::Timestamp.at(event["timestamp"])
98
+ event.remove("timestamp")
99
+ end
100
+ remap_gelf(event) if @remap
101
+ strip_leading_underscore(event) if @strip_leading_underscore
102
+ decorate(event)
103
+ output_queue << event
104
+ end
105
+ rescue LogStash::ShutdownSignal
106
+ # Do nothing, shutdown.
107
+ ensure
108
+ if @udp
109
+ @udp.close_read rescue nil
110
+ @udp.close_write rescue nil
111
+ end
112
+ end # def udp_listener
113
+
114
+ private
115
+ def remap_gelf(event)
116
+ if event["full_message"]
117
+ event["message"] = event["full_message"].dup
118
+ event.remove("full_message")
119
+ if event["short_message"] == event["message"]
120
+ event.remove("short_message")
121
+ end
122
+ elsif event["short_message"]
123
+ event["message"] = event["short_message"].dup
124
+ event.remove("short_message")
125
+ end
126
+ end # def remap_gelf
127
+
128
+ private
129
+ def strip_leading_underscore(event)
130
+ # Map all '_foo' fields to simply 'foo'
131
+ event.to_hash.keys.each do |key|
132
+ next unless key[0,1] == "_"
133
+ event[key[1..-1]] = event[key]
134
+ event.remove(key)
135
+ end
136
+ end # deef removing_leading_underscores
137
+ end # class LogStash::Inputs::Gelf
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-input-gelf'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "This input will read GELF messages as events over the network, making it a good choice if you already use Graylog2 today."
7
+ s.description = "This input will read GELF messages as events over the network, making it a good choice if you already use Graylog2 today."
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'richard.pijnenburg@elasticsearch.com'
10
+ s.homepage = "http://logstash.net/"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
15
+
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", "group" => "input" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ s.add_runtime_dependency "gelfd", ["0.2.0"] #(Apache 2.0 license)
26
+ s.add_runtime_dependency "gelf", ["1.3.2"] #(MIT license)
27
+ s.add_runtime_dependency 'logstash-codec-plain'
28
+
29
+ end
30
+
@@ -0,0 +1,9 @@
1
+ require "gem_publisher"
2
+
3
+ desc "Publish gem to RubyGems.org"
4
+ task :publish_gem do |t|
5
+ gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
6
+ gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
7
+ puts "Published #{gem}" if gem
8
+ end
9
+
@@ -0,0 +1,169 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "digest/sha1"
4
+
5
+ def vendor(*args)
6
+ return File.join("vendor", *args)
7
+ end
8
+
9
+ directory "vendor/" => ["vendor"] do |task, args|
10
+ mkdir task.name
11
+ end
12
+
13
+ def fetch(url, sha1, output)
14
+
15
+ puts "Downloading #{url}"
16
+ actual_sha1 = download(url, output)
17
+
18
+ if actual_sha1 != sha1
19
+ fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
20
+ end
21
+ end # def fetch
22
+
23
+ def file_fetch(url, sha1)
24
+ filename = File.basename( URI(url).path )
25
+ output = "vendor/#{filename}"
26
+ task output => [ "vendor/" ] do
27
+ begin
28
+ actual_sha1 = file_sha1(output)
29
+ if actual_sha1 != sha1
30
+ fetch(url, sha1, output)
31
+ end
32
+ rescue Errno::ENOENT
33
+ fetch(url, sha1, output)
34
+ end
35
+ end.invoke
36
+
37
+ return output
38
+ end
39
+
40
+ def file_sha1(path)
41
+ digest = Digest::SHA1.new
42
+ fd = File.new(path, "r")
43
+ while true
44
+ begin
45
+ digest << fd.sysread(16384)
46
+ rescue EOFError
47
+ break
48
+ end
49
+ end
50
+ return digest.hexdigest
51
+ ensure
52
+ fd.close if fd
53
+ end
54
+
55
+ def download(url, output)
56
+ uri = URI(url)
57
+ digest = Digest::SHA1.new
58
+ tmp = "#{output}.tmp"
59
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
60
+ request = Net::HTTP::Get.new(uri.path)
61
+ http.request(request) do |response|
62
+ fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
63
+ size = (response["content-length"].to_i || -1).to_f
64
+ count = 0
65
+ File.open(tmp, "w") do |fd|
66
+ response.read_body do |chunk|
67
+ fd.write(chunk)
68
+ digest << chunk
69
+ if size > 0 && $stdout.tty?
70
+ count += chunk.bytesize
71
+ $stdout.write(sprintf("\r%0.2f%%", count/size * 100))
72
+ end
73
+ end
74
+ end
75
+ $stdout.write("\r \r") if $stdout.tty?
76
+ end
77
+ end
78
+
79
+ File.rename(tmp, output)
80
+
81
+ return digest.hexdigest
82
+ rescue SocketError => e
83
+ puts "Failure while downloading #{url}: #{e}"
84
+ raise
85
+ ensure
86
+ File.unlink(tmp) if File.exist?(tmp)
87
+ end # def download
88
+
89
+ def untar(tarball, &block)
90
+ require "archive/tar/minitar"
91
+ tgz = Zlib::GzipReader.new(File.open(tarball))
92
+ # Pull out typesdb
93
+ tar = Archive::Tar::Minitar::Input.open(tgz)
94
+ tar.each do |entry|
95
+ path = block.call(entry)
96
+ next if path.nil?
97
+ parent = File.dirname(path)
98
+
99
+ mkdir_p parent unless File.directory?(parent)
100
+
101
+ # Skip this file if the output file is the same size
102
+ if entry.directory?
103
+ mkdir path unless File.directory?(path)
104
+ else
105
+ entry_mode = entry.instance_eval { @mode } & 0777
106
+ if File.exists?(path)
107
+ stat = File.stat(path)
108
+ # TODO(sissel): Submit a patch to archive-tar-minitar upstream to
109
+ # expose headers in the entry.
110
+ entry_size = entry.instance_eval { @size }
111
+ # If file sizes are same, skip writing.
112
+ next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
113
+ end
114
+ puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
115
+ File.open(path, "w") do |fd|
116
+ # eof? check lets us skip empty files. Necessary because the API provided by
117
+ # Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
118
+ # IO object. Something about empty files in this EntryStream causes
119
+ # IO.copy_stream to throw "can't convert nil into String" on JRuby
120
+ # TODO(sissel): File a bug about this.
121
+ while !entry.eof?
122
+ chunk = entry.read(16384)
123
+ fd.write(chunk)
124
+ end
125
+ #IO.copy_stream(entry, fd)
126
+ end
127
+ File.chmod(entry_mode, path)
128
+ end
129
+ end
130
+ tar.close
131
+ File.unlink(tarball) if File.file?(tarball)
132
+ end # def untar
133
+
134
+ def ungz(file)
135
+
136
+ outpath = file.gsub('.gz', '')
137
+ tgz = Zlib::GzipReader.new(File.open(file))
138
+ begin
139
+ File.open(outpath, "w") do |out|
140
+ IO::copy_stream(tgz, out)
141
+ end
142
+ File.unlink(file)
143
+ rescue
144
+ File.unlink(outpath) if File.file?(outpath)
145
+ raise
146
+ end
147
+ tgz.close
148
+ end
149
+
150
+ desc "Process any vendor files required for this plugin"
151
+ task "vendor" do |task, args|
152
+
153
+ @files.each do |file|
154
+ download = file_fetch(file['url'], file['sha1'])
155
+ if download =~ /.tar.gz/
156
+ prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
157
+ untar(download) do |entry|
158
+ if !file['files'].nil?
159
+ next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
160
+ out = entry.full_name.split("/").last
161
+ end
162
+ File.join('vendor', out)
163
+ end
164
+ elsif download =~ /.gz/
165
+ ungz(download)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,52 @@
1
+
2
+ require "spec_helper"
3
+ require "gelf"
4
+ describe "inputs/gelf" do
5
+
6
+
7
+ describe "reads chunked gelf messages " do
8
+ port = 12209
9
+ host = "127.0.0.1"
10
+ chunksize = 1420
11
+ gelfclient = GELF::Notifier.new(host,port,chunksize)
12
+
13
+ config <<-CONFIG
14
+ input {
15
+ gelf {
16
+ port => "#{port}"
17
+ host => "#{host}"
18
+ }
19
+ }
20
+ CONFIG
21
+
22
+ input do |pipeline, queue|
23
+ Thread.new { pipeline.run }
24
+ sleep 0.1 while !pipeline.ready?
25
+
26
+ # generate random characters (message is zipped!) from printable ascii ( SPACE till ~ )
27
+ # to trigger gelf chunking
28
+ s = StringIO.new
29
+ for i in 1..2000
30
+ s << 32 + rand(126-32)
31
+ end
32
+ large_random = s.string
33
+
34
+ [ "hello",
35
+ "world",
36
+ large_random,
37
+ "we survived gelf!"
38
+ ].each do |m|
39
+ gelfclient.notify!( "short_message" => m )
40
+ # poll at most 10 times
41
+ waits = 0
42
+ while waits < 10 and queue.size == 0
43
+ sleep 0.1
44
+ waits += 1
45
+ end
46
+ insist { queue.size } > 0
47
+ insist { queue.pop["message"] } == m
48
+ end
49
+
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-input-gelf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Elasticsearch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.4.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: gelfd
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.0
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 0.2.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: gelf
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.2
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '='
59
+ - !ruby/object:Gem::Version
60
+ version: 1.3.2
61
+ - !ruby/object:Gem::Dependency
62
+ name: logstash-codec-plain
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ description: This input will read GELF messages as events over the network, making
76
+ it a good choice if you already use Graylog2 today.
77
+ email: richard.pijnenburg@elasticsearch.com
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - .gitignore
83
+ - Gemfile
84
+ - LICENSE
85
+ - Rakefile
86
+ - lib/logstash/inputs/gelf.rb
87
+ - logstash-input-gelf.gemspec
88
+ - rakelib/publish.rake
89
+ - rakelib/vendor.rake
90
+ - spec/inputs/gelf_spec.rb
91
+ homepage: http://logstash.net/
92
+ licenses:
93
+ - Apache License (2.0)
94
+ metadata:
95
+ logstash_plugin: 'true'
96
+ group: input
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.4.1
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: This input will read GELF messages as events over the network, making it
117
+ a good choice if you already use Graylog2 today.
118
+ test_files:
119
+ - spec/inputs/gelf_spec.rb