gelf 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +15 -11
- data/CHANGELOG +9 -1
- data/Gemfile +6 -3
- data/Gemfile.lock +49 -40
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/gelf.gemspec +21 -17
- data/lib/gelf.rb +0 -1
- data/lib/gelf/logger.rb +14 -12
- data/lib/gelf/notifier.rb +45 -36
- data/lib/gelf/transport/tcp.rb +63 -0
- data/lib/gelf/transport/tcp_tls.rb +112 -0
- data/lib/gelf/transport/udp.rb +41 -0
- data/test/helper.rb +1 -1
- data/test/test_notifier.rb +16 -5
- data/test/test_ruby_sender.rb +1 -1
- metadata +28 -12
- data/lib/gelf/ruby_sender.rb +0 -147
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e1c02912c99651ae9b78b3d80a254b39e44b880
|
4
|
+
data.tar.gz: 0723b767126b56dbe6d86bcb2ae77da7024eb128
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa809d0a268591b148822d58bf1f32d703b18ba8c1149e0c70d160cd30e49b834ddb2ef4d9bc206bd0650a6432e9021823d299bb760bfaa126dfd34d6960dc42
|
7
|
+
data.tar.gz: 99f27da7b54e6a0e19713ae4c926a2005a7c94236c0b4d28a322cf78db81361e3cb4504a93c138cce9bcbc0025e71956adc86b3eb5b1e341f028c79f07703509
|
data/.travis.yml
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
rvm:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
2
|
+
- 1.9.3
|
3
|
+
- 2.0.0
|
4
|
+
- 2.1.0
|
5
|
+
- 2.2.0
|
6
|
+
- 2.3.0
|
7
|
+
- jruby-19mode
|
8
|
+
deploy:
|
9
|
+
provider: rubygems
|
10
|
+
api_key:
|
11
|
+
secure: Pm1yAqo2ldet/Fd2jK5dKZkDNmyx5KkYtViL+eOR4Rxs82dwqOcCSyrBL6BOzuRfJtpjgXUX6FDapKgLDYW/2EDW8mmiciOL0WLzxYQsmJXcerJeUl2tTeFKpyEf+8aaDQpAlsK0m1dPmQjCJF9MWsa7m3oshm3mJ1VITY90chM=
|
12
|
+
gem: gelf
|
13
|
+
gemspec: gelf.gemspec
|
14
|
+
on:
|
15
|
+
tags: true
|
16
|
+
repo: Graylog2/gelf-rb
|
data/CHANGELOG
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
+
3.0.0, 2016-08-21
|
2
|
+
+ Overhaul TCP support
|
3
|
+
+ Include automatic support for Celluloid::IO if available
|
4
|
+
+ Add TLS support to TCP transport
|
5
|
+
- Remove support for ancient Rubygems versions
|
6
|
+
- Remove support for Ruby 1.9.2 (1.9.3 works!)
|
7
|
+
- Remove already-deprecated `host` and `port` methods on GELF::Notifier
|
8
|
+
|
1
9
|
2.0.0, 2016-02-02
|
2
|
-
|
10
|
+
+ Added GELF TCP support.
|
3
11
|
|
4
12
|
1.3.2, 2011-12-02:
|
5
13
|
* support for rubygems-test.
|
data/Gemfile
CHANGED
@@ -2,7 +2,10 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
group :development do
|
4
4
|
gem "shoulda", "~> 2.11.3"
|
5
|
-
gem "jeweler", "~> 1.
|
6
|
-
|
7
|
-
|
5
|
+
gem "jeweler", "~> 2.1.1"
|
6
|
+
# Because of a dependency chain jeweler->github_api->oauth2->rack,
|
7
|
+
# pin the version: Rack 2.0.x doesn't work on < Ruby 2.2
|
8
|
+
gem 'rack', '< 2.0'
|
9
|
+
gem "mocha", "~> 1.1.0"
|
10
|
+
gem "test-unit", "~> 3.2.0"
|
8
11
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,61 +1,70 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
addressable (2.
|
4
|
+
addressable (2.4.0)
|
5
5
|
builder (3.2.2)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
jeweler (1.
|
6
|
+
descendants_tracker (0.0.4)
|
7
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
8
|
+
faraday (0.9.2)
|
9
|
+
multipart-post (>= 1.2, < 3)
|
10
|
+
git (1.3.0)
|
11
|
+
github_api (0.14.5)
|
12
|
+
addressable (~> 2.4.0)
|
13
|
+
descendants_tracker (~> 0.0.4)
|
14
|
+
faraday (~> 0.8, < 0.10)
|
15
|
+
hashie (>= 3.4)
|
16
|
+
oauth2 (~> 1.0)
|
17
|
+
hashie (3.4.4)
|
18
|
+
highline (1.7.8)
|
19
|
+
jeweler (2.1.1)
|
20
20
|
builder
|
21
|
-
bundler (
|
21
|
+
bundler (>= 1.0)
|
22
22
|
git (>= 1.2.5)
|
23
|
-
github_api
|
23
|
+
github_api
|
24
24
|
highline (>= 1.6.15)
|
25
|
-
nokogiri (
|
25
|
+
nokogiri (>= 1.5.10)
|
26
26
|
rake
|
27
27
|
rdoc
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
metaclass (0.0.
|
32
|
-
|
28
|
+
semver
|
29
|
+
json (1.8.3)
|
30
|
+
jwt (1.5.4)
|
31
|
+
metaclass (0.0.4)
|
32
|
+
mini_portile2 (2.1.0)
|
33
|
+
mocha (1.1.0)
|
33
34
|
metaclass (~> 0.0.1)
|
34
|
-
multi_json (1.
|
35
|
+
multi_json (1.12.1)
|
35
36
|
multi_xml (0.5.5)
|
36
|
-
multipart-post (
|
37
|
-
nokogiri (1.
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
multipart-post (2.0.0)
|
38
|
+
nokogiri (1.6.8)
|
39
|
+
mini_portile2 (~> 2.1.0)
|
40
|
+
pkg-config (~> 1.1.7)
|
41
|
+
oauth2 (1.2.0)
|
42
|
+
faraday (>= 0.8, < 0.10)
|
43
|
+
jwt (~> 1.0)
|
44
|
+
multi_json (~> 1.3)
|
43
45
|
multi_xml (~> 0.5)
|
44
|
-
rack (
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
rack (>= 1.2, < 3)
|
47
|
+
pkg-config (1.1.7)
|
48
|
+
power_assert (0.3.0)
|
49
|
+
rack (1.6.4)
|
50
|
+
rake (11.2.2)
|
51
|
+
rdoc (4.2.2)
|
49
52
|
json (~> 1.4)
|
53
|
+
semver (1.0.1)
|
50
54
|
shoulda (2.11.3)
|
51
|
-
test-unit (3.
|
55
|
+
test-unit (3.2.1)
|
52
56
|
power_assert
|
57
|
+
thread_safe (0.3.5)
|
53
58
|
|
54
59
|
PLATFORMS
|
55
60
|
ruby
|
56
61
|
|
57
62
|
DEPENDENCIES
|
58
|
-
jeweler (~> 1.
|
59
|
-
mocha (~>
|
63
|
+
jeweler (~> 2.1.1)
|
64
|
+
mocha (~> 1.1.0)
|
65
|
+
rack (< 2.0)
|
60
66
|
shoulda (~> 2.11.3)
|
61
|
-
test-unit (~> 3.0
|
67
|
+
test-unit (~> 3.2.0)
|
68
|
+
|
69
|
+
BUNDLED WITH
|
70
|
+
1.12.5
|
data/README.md
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
This is the new GELF gem written by Alexey Palazhchenko. It is based on the old gem by Lennart Koopmann and allows you to send GELF messages to Graylog2 server instances. See [http://www.graylog2.org/about/gelf](http://www.graylog2.org/about/gelf) for more information about GELF and [http://rdoc.info/github/Graylog2/gelf-rb/master/frames](http://rdoc.info/github/Graylog2/gelf-rb/master/frames) for API documentation.
|
4
4
|
|
5
|
-
Tested with Ruby 1.
|
5
|
+
Tested with Ruby 1.9, 2.0, 2.1, 2.2 and 2.3.
|
6
6
|
|
7
|
-
![](https://travis-ci.org/Graylog2/gelf-rb.png?branch=master)
|
7
|
+
![](https://travis-ci.org/Graylog2/gelf-rb.png?branch=master) [![Code Climate](https://codeclimate.com/github/Graylog2/gelf-rb/badges/gpa.svg)](https://codeclimate.com/github/Graylog2/gelf-rb)
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
### Gelf::Notifier
|
data/Rakefile
CHANGED
@@ -13,9 +13,9 @@ begin
|
|
13
13
|
gem.name = "gelf"
|
14
14
|
gem.summary = 'Library to send GELF messages to Graylog logging server.'
|
15
15
|
gem.description = 'Library to send GELF messages to Graylog logging server. Supports plain-text, GELF messages and exceptions via UDP and TCP.'
|
16
|
-
gem.email = "
|
16
|
+
gem.email = "mail@marcusilgner.com"
|
17
17
|
gem.homepage = "http://github.com/Graylog2/gelf-rb"
|
18
|
-
gem.authors = ["Alexey Palazhchenko", "Lennart Koopmann", "Zac Sprackett"]
|
18
|
+
gem.authors = ["Alexey Palazhchenko", "Lennart Koopmann", "Zac Sprackett", "Marcus Ilgner"]
|
19
19
|
gem.add_dependency "json"
|
20
20
|
gem.licenses = ["MIT"]
|
21
21
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.0
|
data/gelf.gemspec
CHANGED
@@ -2,20 +2,19 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: gelf
|
5
|
+
# stub: gelf 3.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "gelf"
|
9
|
-
s.version = "
|
9
|
+
s.version = "3.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
|
-
s.authors = ["Alexey Palazhchenko", "Lennart Koopmann", "Zac Sprackett"]
|
14
|
-
s.date = "2016-
|
13
|
+
s.authors = ["Alexey Palazhchenko", "Lennart Koopmann", "Zac Sprackett", "Marcus Ilgner"]
|
14
|
+
s.date = "2016-08-21"
|
15
15
|
s.description = "Library to send GELF messages to Graylog logging server. Supports plain-text, GELF messages and exceptions via UDP and TCP."
|
16
|
-
s.email = "
|
16
|
+
s.email = "mail@marcusilgner.com"
|
17
17
|
s.extra_rdoc_files = [
|
18
|
-
"CHANGELOG",
|
19
18
|
"LICENSE",
|
20
19
|
"README.md"
|
21
20
|
]
|
@@ -35,8 +34,10 @@ Gem::Specification.new do |s|
|
|
35
34
|
"lib/gelf.rb",
|
36
35
|
"lib/gelf/logger.rb",
|
37
36
|
"lib/gelf/notifier.rb",
|
38
|
-
"lib/gelf/ruby_sender.rb",
|
39
37
|
"lib/gelf/severity.rb",
|
38
|
+
"lib/gelf/transport/tcp.rb",
|
39
|
+
"lib/gelf/transport/tcp_tls.rb",
|
40
|
+
"lib/gelf/transport/udp.rb",
|
40
41
|
"test/helper.rb",
|
41
42
|
"test/test_logger.rb",
|
42
43
|
"test/test_notifier.rb",
|
@@ -45,7 +46,7 @@ Gem::Specification.new do |s|
|
|
45
46
|
]
|
46
47
|
s.homepage = "http://github.com/Graylog2/gelf-rb"
|
47
48
|
s.licenses = ["MIT"]
|
48
|
-
s.rubygems_version = "2.
|
49
|
+
s.rubygems_version = "2.5.1"
|
49
50
|
s.summary = "Library to send GELF messages to Graylog logging server."
|
50
51
|
|
51
52
|
if s.respond_to? :specification_version then
|
@@ -53,22 +54,25 @@ Gem::Specification.new do |s|
|
|
53
54
|
|
54
55
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
55
56
|
s.add_development_dependency(%q<shoulda>, ["~> 2.11.3"])
|
56
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.
|
57
|
-
s.add_development_dependency(%q<
|
58
|
-
s.add_development_dependency(%q<
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.1.1"])
|
58
|
+
s.add_development_dependency(%q<rack>, ["< 2.0"])
|
59
|
+
s.add_development_dependency(%q<mocha>, ["~> 1.1.0"])
|
60
|
+
s.add_development_dependency(%q<test-unit>, ["~> 3.2.0"])
|
59
61
|
s.add_runtime_dependency(%q<json>, [">= 0"])
|
60
62
|
else
|
61
63
|
s.add_dependency(%q<shoulda>, ["~> 2.11.3"])
|
62
|
-
s.add_dependency(%q<jeweler>, ["~> 1.
|
63
|
-
s.add_dependency(%q<
|
64
|
-
s.add_dependency(%q<
|
64
|
+
s.add_dependency(%q<jeweler>, ["~> 2.1.1"])
|
65
|
+
s.add_dependency(%q<rack>, ["< 2.0"])
|
66
|
+
s.add_dependency(%q<mocha>, ["~> 1.1.0"])
|
67
|
+
s.add_dependency(%q<test-unit>, ["~> 3.2.0"])
|
65
68
|
s.add_dependency(%q<json>, [">= 0"])
|
66
69
|
end
|
67
70
|
else
|
68
71
|
s.add_dependency(%q<shoulda>, ["~> 2.11.3"])
|
69
|
-
s.add_dependency(%q<jeweler>, ["~> 1.
|
70
|
-
s.add_dependency(%q<
|
71
|
-
s.add_dependency(%q<
|
72
|
+
s.add_dependency(%q<jeweler>, ["~> 2.1.1"])
|
73
|
+
s.add_dependency(%q<rack>, ["< 2.0"])
|
74
|
+
s.add_dependency(%q<mocha>, ["~> 1.1.0"])
|
75
|
+
s.add_dependency(%q<test-unit>, ["~> 3.2.0"])
|
72
76
|
s.add_dependency(%q<json>, [">= 0"])
|
73
77
|
end
|
74
78
|
end
|
data/lib/gelf.rb
CHANGED
data/lib/gelf/logger.rb
CHANGED
@@ -5,7 +5,7 @@ module GELF
|
|
5
5
|
attr_accessor :formatter
|
6
6
|
|
7
7
|
# Use it like Logger#add... or better not to use at all.
|
8
|
-
def add(level, message = nil, progname = nil
|
8
|
+
def add(level, message = nil, progname = nil)
|
9
9
|
progname ||= default_options['facility']
|
10
10
|
|
11
11
|
if message.nil?
|
@@ -21,11 +21,11 @@ module GELF
|
|
21
21
|
|
22
22
|
if message.is_a?(Hash)
|
23
23
|
# Stringify keys.
|
24
|
-
message.each do |
|
25
|
-
message_hash[
|
24
|
+
message.each do |key, value|
|
25
|
+
message_hash[key.to_s] = value
|
26
26
|
end
|
27
27
|
else
|
28
|
-
message_hash['short_message'] = message
|
28
|
+
message_hash['short_message'] = message.to_s
|
29
29
|
end
|
30
30
|
|
31
31
|
if message.is_a?(Exception)
|
@@ -37,15 +37,17 @@ module GELF
|
|
37
37
|
|
38
38
|
# Redefines methods in +Notifier+.
|
39
39
|
GELF::Levels.constants.each do |const|
|
40
|
-
|
41
|
-
def #{const.downcase}(progname = nil, &block) # def debug(progname = nil, &block)
|
42
|
-
add(GELF::#{const}, nil, progname, &block) # add(GELF::DEBUG, nil, progname, &block)
|
43
|
-
end # end
|
40
|
+
method_name = const.downcase
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
define_method(method_name) do |progname=nil, &block|
|
43
|
+
const_level = GELF.const_get(const)
|
44
|
+
add(const_level, nil, progname, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
define_method("#{method_name}?") do
|
48
|
+
const_level = GELF.const_get(const)
|
49
|
+
const_level >= level
|
50
|
+
end
|
49
51
|
end
|
50
52
|
|
51
53
|
def <<(message)
|
data/lib/gelf/notifier.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
require 'gelf/transport/udp'
|
2
|
+
require 'gelf/transport/tcp'
|
3
|
+
require 'gelf/transport/tcp_tls'
|
4
|
+
|
1
5
|
module GELF
|
2
6
|
# Graylog2 notifier.
|
3
7
|
class Notifier
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
+
# Maximum number of GELF chunks as per GELF spec
|
9
|
+
MAX_CHUNKS = 128
|
10
|
+
MAX_CHUNK_SIZE_WAN = 1420
|
11
|
+
MAX_CHUNK_SIZE_LAN = 8154
|
8
12
|
|
9
13
|
attr_accessor :enabled, :collect_file_and_line, :rescue_network_errors
|
10
14
|
attr_reader :max_chunk_size, :level, :default_options, :level_mapping
|
@@ -15,24 +19,21 @@ module GELF
|
|
15
19
|
def initialize(host = 'localhost', port = 12201, max_size = 'WAN', default_options = {})
|
16
20
|
@enabled = true
|
17
21
|
@collect_file_and_line = true
|
22
|
+
@random = Random.new
|
18
23
|
|
19
24
|
self.level = GELF::DEBUG
|
20
25
|
self.max_chunk_size = max_size
|
21
26
|
self.rescue_network_errors = false
|
22
27
|
|
23
|
-
self.default_options = default_options
|
28
|
+
self.default_options = default_options.dup
|
24
29
|
self.default_options['version'] = SPEC_VERSION
|
25
30
|
self.default_options['host'] ||= Socket.gethostname
|
26
31
|
self.default_options['level'] ||= GELF::UNKNOWN
|
27
32
|
self.default_options['facility'] ||= 'gelf-rb'
|
28
33
|
self.default_options['protocol'] ||= GELF::Protocol::UDP
|
29
34
|
|
30
|
-
if self.default_options['protocol'] == GELF::Protocol::TCP
|
31
|
-
@sender = RubyTcpSender.new([[host, port]])
|
32
|
-
else
|
33
|
-
@sender = RubyUdpSender.new([[host, port]])
|
34
|
-
end
|
35
35
|
self.level_mapping = :logger
|
36
|
+
@sender = create_sender(host, port)
|
36
37
|
end
|
37
38
|
|
38
39
|
# Get a list of receivers.
|
@@ -47,24 +48,14 @@ module GELF
|
|
47
48
|
@sender.addresses = addrs
|
48
49
|
end
|
49
50
|
|
50
|
-
def host
|
51
|
-
warn "GELF::Notifier#host is deprecated. Use #addresses instead."
|
52
|
-
self.addresses.first[0]
|
53
|
-
end
|
54
|
-
|
55
|
-
def port
|
56
|
-
warn "GELF::Notifier#port is deprecated. Use #addresses instead."
|
57
|
-
self.addresses.first[1]
|
58
|
-
end
|
59
|
-
|
60
51
|
# +size+ may be a number of bytes, 'WAN' (1420 bytes) or 'LAN' (8154).
|
61
52
|
# Default (safe) value is 'WAN'.
|
62
53
|
def max_chunk_size=(size)
|
63
54
|
case size.to_s.downcase
|
64
55
|
when 'wan'
|
65
|
-
@max_chunk_size =
|
56
|
+
@max_chunk_size = MAX_CHUNK_SIZE_WAN
|
66
57
|
when 'lan'
|
67
|
-
@max_chunk_size =
|
58
|
+
@max_chunk_size = MAX_CHUNK_SIZE_LAN
|
68
59
|
else
|
69
60
|
@max_chunk_size = size.to_int
|
70
61
|
end
|
@@ -129,18 +120,32 @@ module GELF
|
|
129
120
|
end
|
130
121
|
|
131
122
|
GELF::Levels.constants.each do |const|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
EOT
|
123
|
+
define_method(const.downcase) do |*args|
|
124
|
+
level = GELF.const_get(const)
|
125
|
+
notify_with_level(level, *args)
|
126
|
+
end
|
137
127
|
end
|
138
128
|
|
139
129
|
private
|
130
|
+
|
131
|
+
def create_sender(host, port)
|
132
|
+
addresses = [[host, port]]
|
133
|
+
if default_options['protocol'] == GELF::Protocol::TCP
|
134
|
+
if default_options.key?('tls')
|
135
|
+
tls_options = default_options.delete('tls')
|
136
|
+
GELF::Transport::TCPTLS.new(addresses, tls_options)
|
137
|
+
else
|
138
|
+
GELF::Transport::TCP.new(addresses)
|
139
|
+
end
|
140
|
+
else
|
141
|
+
GELF::Transport::UDP.new(addresses)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
140
145
|
def notify_with_level(message_level, *args)
|
141
146
|
notify_with_level!(message_level, *args)
|
142
147
|
rescue SocketError, SystemCallError
|
143
|
-
raise unless
|
148
|
+
raise unless rescue_network_errors
|
144
149
|
rescue Exception => exception
|
145
150
|
notify_with_level!(GELF::UNKNOWN, exception)
|
146
151
|
end
|
@@ -150,7 +155,7 @@ module GELF
|
|
150
155
|
extract_hash(*args)
|
151
156
|
@hash['level'] = message_level unless message_level.nil?
|
152
157
|
if @hash['level'] >= level
|
153
|
-
if
|
158
|
+
if default_options['protocol'] == GELF::Protocol::TCP
|
154
159
|
validate_hash
|
155
160
|
@sender.send(@hash.to_json + "\0")
|
156
161
|
else
|
@@ -180,7 +185,10 @@ module GELF
|
|
180
185
|
|
181
186
|
def self.extract_hash_from_exception(exception)
|
182
187
|
bt = exception.backtrace || ["Backtrace is not available."]
|
183
|
-
{
|
188
|
+
{
|
189
|
+
'short_message' => "#{exception.class}: #{exception.message}",
|
190
|
+
'full_message' => "Backtrace:\n" + bt.join("\n")
|
191
|
+
}
|
184
192
|
end
|
185
193
|
|
186
194
|
# Converts Hoptoad-specific keys in +@hash+ to Graylog2-specific.
|
@@ -197,9 +205,7 @@ module GELF
|
|
197
205
|
|
198
206
|
def set_file_and_line
|
199
207
|
stack = caller
|
200
|
-
|
201
|
-
frame = stack.shift
|
202
|
-
end while frame.include?(LIB_GELF_PATTERN)
|
208
|
+
frame = stack.find { |f| !f.include?(LIB_GELF_PATTERN) }
|
203
209
|
match = CALLER_REGEXP.match(frame)
|
204
210
|
@hash['file'] = match[1]
|
205
211
|
@hash['line'] = match[2].to_i
|
@@ -223,9 +229,12 @@ module GELF
|
|
223
229
|
|
224
230
|
# Maximum total size is 8192 byte for UDP datagram. Split to chunks if bigger. (GELF v1.0 supports chunking)
|
225
231
|
if data.count > @max_chunk_size
|
226
|
-
id =
|
232
|
+
id = @random.bytes(8)
|
227
233
|
msg_id = Digest::MD5.digest("#{Time.now.to_f}-#{id}")[0, 8]
|
228
234
|
num, count = 0, (data.count.to_f / @max_chunk_size).ceil
|
235
|
+
if count > MAX_CHUNKS
|
236
|
+
raise ArgumentError, "Data too big (#{data.count} bytes), would create more than #{MAX_CHUNKS} chunks!"
|
237
|
+
end
|
229
238
|
data.each_slice(@max_chunk_size) do |slice|
|
230
239
|
datagrams << "\x1e\x0f" + msg_id + [num, count, *slice].pack('C*')
|
231
240
|
num += 1
|
@@ -239,7 +248,6 @@ module GELF
|
|
239
248
|
|
240
249
|
def validate_hash
|
241
250
|
raise ArgumentError.new("Hash is empty.") if @hash.nil? || @hash.empty?
|
242
|
-
|
243
251
|
@hash['level'] = @level_mapping[@hash['level']]
|
244
252
|
end
|
245
253
|
|
@@ -252,7 +260,8 @@ module GELF
|
|
252
260
|
def self.stringify_keys(hash)
|
253
261
|
hash.keys.each do |key|
|
254
262
|
value, key_s = hash.delete(key), key.to_s
|
255
|
-
raise ArgumentError.new("Both #{key.inspect} and #{key_s} are present.") if hash.
|
263
|
+
raise ArgumentError.new("Both #{key.inspect} and #{key_s} are present.") if hash.key?(key_s)
|
264
|
+
value = stringify_keys(value) if value.is_a?(Hash)
|
256
265
|
hash[key_s] = value
|
257
266
|
end
|
258
267
|
hash
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module GELF
|
2
|
+
module Transport
|
3
|
+
class TCP
|
4
|
+
attr_reader :addresses
|
5
|
+
|
6
|
+
# `addresses` Array of [host, port] pairs
|
7
|
+
def initialize(addresses)
|
8
|
+
@sockets = []
|
9
|
+
self.addresses = addresses
|
10
|
+
end
|
11
|
+
|
12
|
+
def addresses=(addresses)
|
13
|
+
@addresses = addresses.dup.freeze.tap do |addrs|
|
14
|
+
@sockets.each(&:close)
|
15
|
+
@sockets = addrs.map { |peer| connect(*peer) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def send(message)
|
20
|
+
return if @addresses.empty?
|
21
|
+
loop do
|
22
|
+
connected = @sockets.reject(&:closed?)
|
23
|
+
reconnect_all if connected.empty?
|
24
|
+
break if write_any(connected, message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def connect(host, port)
|
31
|
+
socket_class.new(host, port)
|
32
|
+
end
|
33
|
+
|
34
|
+
def reconnect_all
|
35
|
+
@sockets = @sockets.each_with_index.map do |old_socket, index|
|
36
|
+
old_socket.closed? ? connect(*@addresses[index]) : old_socket
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def socket_class
|
41
|
+
if defined?(Celluloid::IO::TCPSocket)
|
42
|
+
Celluloid::IO::TCPSocket
|
43
|
+
else
|
44
|
+
::TCPSocket
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_any(sockets, message)
|
49
|
+
sockets.shuffle.each do |socket|
|
50
|
+
return true if write_socket(socket, message)
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_socket(socket, message)
|
56
|
+
socket.write(message) > 0
|
57
|
+
rescue IOError, SystemCallError
|
58
|
+
socket.close unless socket.closed?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module GELF
|
4
|
+
module Transport
|
5
|
+
# Provides encryption capabilities for TCP connections
|
6
|
+
class TCPTLS < TCP
|
7
|
+
# Supported tls_options:
|
8
|
+
# 'no_default_ca' [Boolean] prevents OpenSSL from using the systems CA store.
|
9
|
+
# 'tls_version' [Symbol] any of :TLSv1, :TLSv1_1, :TLSv1_2 (default)
|
10
|
+
# 'cert' [String, IO] the client certificate file
|
11
|
+
# 'key' [String, IO] the key for the client certificate
|
12
|
+
# 'all_ciphers' [Boolean] allows any ciphers to be used, may be insecure
|
13
|
+
def initialize(addresses, tls_options={})
|
14
|
+
@tls_options = tls_options
|
15
|
+
super(addresses)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def write_socket(socket, message)
|
21
|
+
super(socket, message)
|
22
|
+
rescue OpenSSL::SSL::SSLError
|
23
|
+
socket.close unless socket.closed?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect(host, port)
|
28
|
+
plain_socket = super(host, port)
|
29
|
+
start_tls(plain_socket)
|
30
|
+
rescue OpenSSL::SSL::SSLError
|
31
|
+
plain_socket.close unless plain_socket.closed?
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Initiates TLS communication on the socket
|
36
|
+
def start_tls(plain_socket)
|
37
|
+
ssl_socket_class.new(plain_socket, ssl_context).tap do |ssl_socket|
|
38
|
+
ssl_socket.sync_close = true
|
39
|
+
ssl_socket.connect
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def ssl_socket_class
|
44
|
+
if defined?(Celluloid::IO::SSLSocket)
|
45
|
+
Celluloid::IO::SSLSocket
|
46
|
+
else
|
47
|
+
OpenSSL::SSL::SSLSocket
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ssl_context
|
52
|
+
@ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
53
|
+
ctx.cert_store = ssl_cert_store
|
54
|
+
ctx.ssl_version = tls_version
|
55
|
+
ctx.verify_mode = verify_mode
|
56
|
+
set_certificate_and_key(ctx)
|
57
|
+
restrict_ciphers(ctx) unless @tls_options['all_ciphers']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_certificate_and_key(context)
|
62
|
+
return unless @tls_options['cert'] && @tls_options['key']
|
63
|
+
context.cert = OpenSSL::X509::Certificate.new(resource(@tls_options['cert']))
|
64
|
+
context.key = OpenSSL::PKey::RSA.new(resource(@tls_options['key']))
|
65
|
+
end
|
66
|
+
|
67
|
+
# checks whether {resource} is a filename and tries to read it
|
68
|
+
# otherwise treats it as if it already contains certificate/key data
|
69
|
+
def resource(data)
|
70
|
+
if data.is_a?(String) && File.exist?(data)
|
71
|
+
File.read(data)
|
72
|
+
else
|
73
|
+
data
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# These are A-level ciphers as reported from Graylog 2.0.1
|
78
|
+
# which were also available on Ruby using OpenSSL 1.0.2h
|
79
|
+
# A lot of AES-128-CBC based ciphers were not available
|
80
|
+
SECURE_CIPHERS = %w(
|
81
|
+
AES128-GCM-SHA256
|
82
|
+
ECDHE-RSA-AES128-GCM-SHA256
|
83
|
+
DHE-RSA-AES128-GCM-SHA256
|
84
|
+
).freeze
|
85
|
+
def restrict_ciphers(ctx)
|
86
|
+
ctx.ciphers = SECURE_CIPHERS
|
87
|
+
end
|
88
|
+
|
89
|
+
def verify_mode
|
90
|
+
@tls_options['no_verify'] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
91
|
+
end
|
92
|
+
|
93
|
+
# SSL v2&3 are insecure, forces at least TLS v1.0 and defaults to v1.2
|
94
|
+
def tls_version
|
95
|
+
if @tls_options.key?('version') &&
|
96
|
+
OpenSSL::SSL::SSLContext::METHODS.include?(@tls_options['version']) &&
|
97
|
+
@tls_options['version'] =~ /\ATLSv/
|
98
|
+
@tls_options['version']
|
99
|
+
else
|
100
|
+
:TLSv1_2
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def ssl_cert_store
|
105
|
+
OpenSSL::X509::Store.new.tap do |store|
|
106
|
+
# TODO: allow passing in expected server certificate and disabling system CAs
|
107
|
+
store.set_default_paths
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module GELF
|
2
|
+
module Transport
|
3
|
+
class UDP
|
4
|
+
attr_accessor :addresses
|
5
|
+
|
6
|
+
def initialize(addresses)
|
7
|
+
@addresses = addresses
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_datagrams(datagrams)
|
11
|
+
socket = get_socket
|
12
|
+
idx = get_address_index
|
13
|
+
|
14
|
+
host, port = @addresses[idx]
|
15
|
+
set_address_index((idx + 1) % @addresses.length)
|
16
|
+
datagrams.each do |datagram|
|
17
|
+
socket.send(datagram, 0, host, port)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
socket = get_socket
|
23
|
+
socket.close if socket
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def get_socket
|
29
|
+
Thread.current[:gelf_udp_socket] ||= UDPSocket.open
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_address_index
|
33
|
+
Thread.current[:gelf_udp_address_idx] ||= 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_address_index(value)
|
37
|
+
Thread.current[:gelf_udp_address_idx] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/helper.rb
CHANGED
data/test/test_notifier.rb
CHANGED
@@ -7,7 +7,7 @@ class TestNotifier < Test::Unit::TestCase
|
|
7
7
|
Socket.expects(:gethostname).returns('default_hostname')
|
8
8
|
n = GELF::Notifier.new
|
9
9
|
assert_equal [[['localhost', 12201]], 1420], [n.addresses, n.max_chunk_size]
|
10
|
-
assert_equal( { 'version' => '1.0', 'level' => GELF::UNKNOWN,
|
10
|
+
assert_equal( { 'version' => '1.0', 'level' => GELF::UNKNOWN, 'protocol' => 0,
|
11
11
|
'host' => 'default_hostname', 'facility' => 'gelf-rb' },
|
12
12
|
n.default_options )
|
13
13
|
n.addresses, n.max_chunk_size, n.default_options = [['graylog2.org', 7777]], :lan, {:host => 'grayhost'}
|
@@ -52,14 +52,14 @@ class TestNotifier < Test::Unit::TestCase
|
|
52
52
|
e.set_backtrace(caller)
|
53
53
|
hash = @notifier.__send__(:extract_hash, e)
|
54
54
|
assert_equal 'RuntimeError: message', hash['short_message']
|
55
|
-
assert_match
|
55
|
+
assert_match(/Backtrace/, hash['full_message'])
|
56
56
|
assert_equal GELF::ERROR, hash['level']
|
57
57
|
end
|
58
58
|
|
59
59
|
should "work with exception without backtrace" do
|
60
60
|
e = RuntimeError.new('message')
|
61
61
|
hash = @notifier.__send__(:extract_hash, e)
|
62
|
-
assert_match
|
62
|
+
assert_match(/Backtrace is not available/, hash['full_message'])
|
63
63
|
end
|
64
64
|
|
65
65
|
should "work with exception and hash" do
|
@@ -108,7 +108,7 @@ class TestNotifier < Test::Unit::TestCase
|
|
108
108
|
should "set file and line" do
|
109
109
|
line = __LINE__
|
110
110
|
hash = @notifier.__send__(:extract_hash, { 'version' => '1.0', 'short_message' => 'message' })
|
111
|
-
assert_match
|
111
|
+
assert_match(/test_notifier.rb/, hash['file'])
|
112
112
|
assert_equal line + 1, hash['line']
|
113
113
|
end
|
114
114
|
|
@@ -153,7 +153,7 @@ class TestNotifier < Test::Unit::TestCase
|
|
153
153
|
asserted = "\x78\x9c"
|
154
154
|
if RUBY_VERSION[0,1].to_i >= 2
|
155
155
|
puts "I'm a Ruby > 2.0.0. Enforcing ASCII-8BIT. (#{RUBY_VERSION}/#{RUBY_VERSION[0,1].to_i})"
|
156
|
-
# lol well yeah, Rubby.
|
156
|
+
# lol well yeah, Rubby.
|
157
157
|
# http://stackoverflow.com/questions/15843684/binary-string-literals-in-ruby-2-0
|
158
158
|
asserted = asserted.b
|
159
159
|
end
|
@@ -200,6 +200,17 @@ class TestNotifier < Test::Unit::TestCase
|
|
200
200
|
assert_equal datagrams.count, datagram[11].ord
|
201
201
|
end
|
202
202
|
end
|
203
|
+
|
204
|
+
should "throw an error if more than MAX_CHUNKS will be created" do
|
205
|
+
srand(1) # for stable tests
|
206
|
+
hash = { 'version' => '1.0', 'short_message' => 'message' }
|
207
|
+
hash.merge!('something' => (0..3000).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join) # or it will be compressed too good
|
208
|
+
@notifier.max_chunk_size = 10
|
209
|
+
@notifier.instance_variable_set('@hash', hash)
|
210
|
+
assert_raise(ArgumentError) do
|
211
|
+
@notifier.__send__(:datagrams_from_hash)
|
212
|
+
end
|
213
|
+
end
|
203
214
|
end
|
204
215
|
|
205
216
|
context "level threshold" do
|
data/test/test_ruby_sender.rb
CHANGED
@@ -4,7 +4,7 @@ class TestRubyUdpSender < Test::Unit::TestCase
|
|
4
4
|
context "with ruby sender" do
|
5
5
|
setup do
|
6
6
|
@addresses = [['localhost', 12201], ['localhost', 12202]]
|
7
|
-
@sender = GELF::
|
7
|
+
@sender = GELF::Transport::UDP.new(@addresses)
|
8
8
|
@datagrams1 = %w(d1 d2 d3)
|
9
9
|
@datagrams2 = %w(e1 e2 e3)
|
10
10
|
end
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gelf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Palazhchenko
|
8
8
|
- Lennart Koopmann
|
9
9
|
- Zac Sprackett
|
10
|
+
- Marcus Ilgner
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
|
-
date: 2016-
|
14
|
+
date: 2016-08-21 00:00:00.000000000 Z
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
16
17
|
name: shoulda
|
@@ -32,42 +33,56 @@ dependencies:
|
|
32
33
|
requirements:
|
33
34
|
- - "~>"
|
34
35
|
- !ruby/object:Gem::Version
|
35
|
-
version: 1.
|
36
|
+
version: 2.1.1
|
36
37
|
type: :development
|
37
38
|
prerelease: false
|
38
39
|
version_requirements: !ruby/object:Gem::Requirement
|
39
40
|
requirements:
|
40
41
|
- - "~>"
|
41
42
|
- !ruby/object:Gem::Version
|
42
|
-
version: 1.
|
43
|
+
version: 2.1.1
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rack
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '2.0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "<"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '2.0'
|
43
58
|
- !ruby/object:Gem::Dependency
|
44
59
|
name: mocha
|
45
60
|
requirement: !ruby/object:Gem::Requirement
|
46
61
|
requirements:
|
47
62
|
- - "~>"
|
48
63
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
64
|
+
version: 1.1.0
|
50
65
|
type: :development
|
51
66
|
prerelease: false
|
52
67
|
version_requirements: !ruby/object:Gem::Requirement
|
53
68
|
requirements:
|
54
69
|
- - "~>"
|
55
70
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
71
|
+
version: 1.1.0
|
57
72
|
- !ruby/object:Gem::Dependency
|
58
73
|
name: test-unit
|
59
74
|
requirement: !ruby/object:Gem::Requirement
|
60
75
|
requirements:
|
61
76
|
- - "~>"
|
62
77
|
- !ruby/object:Gem::Version
|
63
|
-
version: 3.0
|
78
|
+
version: 3.2.0
|
64
79
|
type: :development
|
65
80
|
prerelease: false
|
66
81
|
version_requirements: !ruby/object:Gem::Requirement
|
67
82
|
requirements:
|
68
83
|
- - "~>"
|
69
84
|
- !ruby/object:Gem::Version
|
70
|
-
version: 3.0
|
85
|
+
version: 3.2.0
|
71
86
|
- !ruby/object:Gem::Dependency
|
72
87
|
name: json
|
73
88
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,11 +99,10 @@ dependencies:
|
|
84
99
|
version: '0'
|
85
100
|
description: Library to send GELF messages to Graylog logging server. Supports plain-text,
|
86
101
|
GELF messages and exceptions via UDP and TCP.
|
87
|
-
email:
|
102
|
+
email: mail@marcusilgner.com
|
88
103
|
executables: []
|
89
104
|
extensions: []
|
90
105
|
extra_rdoc_files:
|
91
|
-
- CHANGELOG
|
92
106
|
- LICENSE
|
93
107
|
- README.md
|
94
108
|
files:
|
@@ -107,8 +121,10 @@ files:
|
|
107
121
|
- lib/gelf.rb
|
108
122
|
- lib/gelf/logger.rb
|
109
123
|
- lib/gelf/notifier.rb
|
110
|
-
- lib/gelf/ruby_sender.rb
|
111
124
|
- lib/gelf/severity.rb
|
125
|
+
- lib/gelf/transport/tcp.rb
|
126
|
+
- lib/gelf/transport/tcp_tls.rb
|
127
|
+
- lib/gelf/transport/udp.rb
|
112
128
|
- test/helper.rb
|
113
129
|
- test/test_logger.rb
|
114
130
|
- test/test_notifier.rb
|
@@ -134,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
150
|
version: '0'
|
135
151
|
requirements: []
|
136
152
|
rubyforge_project:
|
137
|
-
rubygems_version: 2.4.
|
153
|
+
rubygems_version: 2.4.8
|
138
154
|
signing_key:
|
139
155
|
specification_version: 4
|
140
156
|
summary: Library to send GELF messages to Graylog logging server.
|
data/lib/gelf/ruby_sender.rb
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
module GELF
|
2
|
-
# Plain Ruby UDP sender.
|
3
|
-
class RubyUdpSender
|
4
|
-
attr_accessor :addresses
|
5
|
-
|
6
|
-
def initialize(addresses)
|
7
|
-
@addresses = addresses
|
8
|
-
@i = 0
|
9
|
-
@socket = UDPSocket.open
|
10
|
-
end
|
11
|
-
|
12
|
-
def send_datagrams(datagrams)
|
13
|
-
host, port = @addresses[@i]
|
14
|
-
@i = (@i + 1) % @addresses.length
|
15
|
-
datagrams.each do |datagram|
|
16
|
-
@socket.send(datagram, 0, host, port)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def close
|
21
|
-
@socket.close
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class RubyTcpSocket
|
26
|
-
attr_accessor :socket
|
27
|
-
|
28
|
-
def initialize(host, port)
|
29
|
-
@host = host
|
30
|
-
@port = port
|
31
|
-
connect
|
32
|
-
end
|
33
|
-
|
34
|
-
def connected?
|
35
|
-
if not @connected
|
36
|
-
begin
|
37
|
-
if @socket.nil?
|
38
|
-
@socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
39
|
-
end
|
40
|
-
sockaddr = Socket.sockaddr_in(@port, @host)
|
41
|
-
@socket.connect_nonblock(sockaddr)
|
42
|
-
rescue Errno::EISCONN
|
43
|
-
@connected = true
|
44
|
-
rescue Errno::EINPROGRESS, Errno::EALREADY
|
45
|
-
@connected = false
|
46
|
-
rescue SystemCallError
|
47
|
-
@socket = nil
|
48
|
-
@connected = false
|
49
|
-
end
|
50
|
-
end
|
51
|
-
return @connected
|
52
|
-
end
|
53
|
-
|
54
|
-
def connect
|
55
|
-
@connected = false
|
56
|
-
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
57
|
-
sockaddr = Socket.sockaddr_in(@port, @host)
|
58
|
-
begin
|
59
|
-
socket.connect_nonblock(sockaddr)
|
60
|
-
rescue Errno::EISCONN
|
61
|
-
@connected = true
|
62
|
-
rescue SystemCallError
|
63
|
-
return false
|
64
|
-
end
|
65
|
-
@socket = socket
|
66
|
-
return true
|
67
|
-
end
|
68
|
-
|
69
|
-
def matches?(host, port)
|
70
|
-
if @host == host and @port == port
|
71
|
-
true
|
72
|
-
else
|
73
|
-
false
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class RubyTcpSender
|
79
|
-
attr_reader :addresses
|
80
|
-
|
81
|
-
def initialize(addresses)
|
82
|
-
@sockets = []
|
83
|
-
addresses.each do |address|
|
84
|
-
s = RubyTcpSocket.new(address[0], address[1])
|
85
|
-
@sockets.push(s)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def addresses=(addresses)
|
90
|
-
addresses.each do |address|
|
91
|
-
found = false
|
92
|
-
# handle pre existing sockets
|
93
|
-
@sockets.each do |socket|
|
94
|
-
if socket.matches?(address[0], address[1])
|
95
|
-
found = true
|
96
|
-
break
|
97
|
-
end
|
98
|
-
end
|
99
|
-
if not found
|
100
|
-
s = RubyTcpSocket.new(address[0], address[1])
|
101
|
-
@sockets.push(s)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def send(message)
|
107
|
-
while true do
|
108
|
-
sent = false
|
109
|
-
sockets = @sockets.map { |s|
|
110
|
-
if s.connected?
|
111
|
-
s.socket
|
112
|
-
end
|
113
|
-
}
|
114
|
-
sockets.compact!
|
115
|
-
next unless not sockets.empty?
|
116
|
-
begin
|
117
|
-
result = select( nil, sockets, nil, 1)
|
118
|
-
if result
|
119
|
-
writers = result[1]
|
120
|
-
sent = write_any(writers, message)
|
121
|
-
end
|
122
|
-
break if sent
|
123
|
-
rescue SystemCallError, IOError
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
private
|
129
|
-
def write_any(writers, message)
|
130
|
-
writers.shuffle.each do |w|
|
131
|
-
begin
|
132
|
-
w.write(message)
|
133
|
-
return true
|
134
|
-
rescue Errno::EPIPE
|
135
|
-
@sockets.each do |s|
|
136
|
-
if s.socket == w
|
137
|
-
s.socket.close
|
138
|
-
s.socket = nil
|
139
|
-
s.connect
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
return false
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|