syslogstash 2.2.0 → 3.0.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.
data/syslogstash.gemspec CHANGED
@@ -1,40 +1,41 @@
1
1
  begin
2
- require 'git-version-bump'
2
+ require 'git-version-bump'
3
3
  rescue LoadError
4
- nil
4
+ nil
5
5
  end
6
6
 
7
7
  Gem::Specification.new do |s|
8
- s.name = "syslogstash"
8
+ s.name = "syslogstash"
9
9
 
10
- s.version = GVB.version rescue "0.0.0.1.NOGVB"
11
- s.date = GVB.date rescue Time.now.strftime("%Y-%m-%d")
10
+ s.version = GVB.version rescue "0.0.0.1.NOGVB"
11
+ s.date = GVB.date rescue Time.now.strftime("%Y-%m-%d")
12
12
 
13
- s.platform = Gem::Platform::RUBY
13
+ s.platform = Gem::Platform::RUBY
14
14
 
15
- s.summary = "Send messages from syslog UNIX sockets to logstash"
15
+ s.summary = "Send messages from syslog UNIX sockets to logstash"
16
16
 
17
- s.authors = ["Matt Palmer"]
18
- s.email = ["matt.palmer@discourse.org"]
19
- s.homepage = "https://github.com/discourse/syslogstash"
17
+ s.authors = ["Matt Palmer"]
18
+ s.email = ["matt.palmer@discourse.org"]
19
+ s.homepage = "https://github.com/discourse/syslogstash"
20
20
 
21
- s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
22
- s.executables = ["syslogstash"]
21
+ s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
22
+ s.executables = ["syslogstash"]
23
23
 
24
- s.required_ruby_version = ">= 2.1.0"
24
+ s.required_ruby_version = ">= 2.4.0"
25
25
 
26
- s.add_runtime_dependency 'frankenstein'
27
- s.add_runtime_dependency 'rack'
26
+ s.add_runtime_dependency 'config_skeleton'
27
+ s.add_runtime_dependency 'frankenstein'
28
+ s.add_runtime_dependency 'logstash_writer'
29
+ s.add_runtime_dependency 'rack'
28
30
 
29
- s.add_development_dependency 'bundler'
30
- s.add_development_dependency 'github-release'
31
- s.add_development_dependency 'guard-spork'
32
- s.add_development_dependency 'guard-rspec'
33
- s.add_development_dependency 'rake', '~> 10.4', '>= 10.4.2'
34
- # Needed for guard
35
- s.add_development_dependency 'rb-inotify', '~> 0.9'
36
- s.add_development_dependency 'redcarpet'
37
- s.add_development_dependency 'rspec'
38
- s.add_development_dependency 'webmock'
39
- s.add_development_dependency 'yard'
31
+ s.add_development_dependency 'bundler'
32
+ s.add_development_dependency 'github-release'
33
+ s.add_development_dependency 'guard-rspec'
34
+ s.add_development_dependency 'rake', '~> 10.4', '>= 10.4.2'
35
+ # Needed for guard
36
+ s.add_development_dependency 'rb-inotify', '~> 0.9'
37
+ s.add_development_dependency 'redcarpet'
38
+ s.add_development_dependency 'rspec'
39
+ s.add_development_dependency 'webmock'
40
+ s.add_development_dependency 'yard'
40
41
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syslogstash
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-10 00:00:00.000000000 Z
11
+ date: 2019-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: config_skeleton
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: frankenstein
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -25,7 +39,7 @@ dependencies:
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
- name: rack
42
+ name: logstash_writer
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -39,13 +53,13 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
- name: bundler
56
+ name: rack
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
46
60
  - !ruby/object:Gem::Version
47
61
  version: '0'
48
- type: :development
62
+ type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
@@ -53,7 +67,7 @@ dependencies:
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
- name: github-release
70
+ name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
@@ -67,7 +81,7 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: guard-spork
84
+ name: github-release
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
@@ -192,16 +206,14 @@ executables:
192
206
  extensions: []
193
207
  extra_rdoc_files: []
194
208
  files:
209
+ - ".editorconfig"
195
210
  - ".gitignore"
211
+ - ".travis.yml"
196
212
  - Dockerfile
197
213
  - LICENCE
198
- - Makefile
199
214
  - README.md
200
215
  - bin/syslogstash
201
216
  - lib/syslogstash.rb
202
- - lib/syslogstash/config.rb
203
- - lib/syslogstash/logstash_writer.rb
204
- - lib/syslogstash/prometheus_exporter.rb
205
217
  - lib/syslogstash/syslog_reader.rb
206
218
  - syslogstash.gemspec
207
219
  homepage: https://github.com/discourse/syslogstash
@@ -215,15 +227,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
215
227
  requirements:
216
228
  - - ">="
217
229
  - !ruby/object:Gem::Version
218
- version: 2.1.0
230
+ version: 2.4.0
219
231
  required_rubygems_version: !ruby/object:Gem::Requirement
220
232
  requirements:
221
233
  - - ">="
222
234
  - !ruby/object:Gem::Version
223
235
  version: '0'
224
236
  requirements: []
225
- rubyforge_project:
226
- rubygems_version: 2.7.7
237
+ rubygems_version: 3.0.1
227
238
  signing_key:
228
239
  specification_version: 4
229
240
  summary: Send messages from syslog UNIX sockets to logstash
data/Makefile DELETED
@@ -1,14 +0,0 @@
1
- IMAGE := discourse/syslogstash
2
- TAG := $(shell date -u +%Y%m%d.%H%M%S)
3
-
4
- .PHONY: default
5
- default: push
6
- @printf "${IMAGE}:${TAG} ready\n"
7
-
8
- .PHONY: push
9
- push: build
10
- docker push ${IMAGE}:${TAG}
11
-
12
- .PHONY: build
13
- build:
14
- docker build --build-arg=http_proxy=${http_proxy} -t ${IMAGE}:${TAG} .
@@ -1,118 +0,0 @@
1
- require 'logger'
2
-
3
- class Syslogstash::Config
4
- class ConfigurationError < StandardError; end
5
-
6
- # Raised if any problems were found with the config
7
- class InvalidEnvironmentError < StandardError; end
8
-
9
- attr_reader :logstash_server,
10
- :syslog_socket,
11
- :backlog_size,
12
- :stats_server,
13
- :add_fields,
14
- :relay_sockets
15
-
16
- attr_reader :logger
17
-
18
- attr_accessor :relay_to_stdout
19
-
20
- # Create a new syslogstash config based on environment variables.
21
- #
22
- # Examines the environment passed in, and then creates a new config
23
- # object if all is well.
24
- #
25
- # @param env [Hash] the set of environment variables to use.
26
- #
27
- # @param logger [Logger] the logger to which all diagnostic and error
28
- # data will be sent.
29
- #
30
- # @raise [ConfigurationError] if any problems are detected with the
31
- # environment variables found.
32
- #
33
- def initialize(env, logger:)
34
- @logger = logger
35
-
36
- parse_env(env)
37
- end
38
-
39
- private
40
-
41
- def parse_env(env)
42
- @logger.info("config") { "Parsing environment:\n" + env.map { |k, v| "#{k}=#{v.inspect}" }.join("\n") }
43
-
44
- @logstash_server = pluck_string(env, "LOGSTASH_SERVER")
45
- @syslog_socket = pluck_string(env, "SYSLOG_SOCKET")
46
- @relay_to_stdout = pluck_boolean(env, "RELAY_TO_STDOUT", default: false)
47
- @stats_server = pluck_boolean(env, "STATS_SERVER", default: false)
48
- @backlog_size = pluck_integer(env, "BACKLOG_SIZE", valid_range: 0..(2**31 - 1), default: 1_000_000)
49
- @add_fields = pluck_prefix_list(env, "ADD_FIELD_")
50
- @relay_sockets = pluck_path_list(env, "RELAY_SOCKETS", default: [])
51
- end
52
-
53
- def pluck_string(env, key, default: nil)
54
- maybe_default(env, key, default) { env[key] }
55
- end
56
-
57
- def pluck_boolean(env, key, default: nil)
58
- maybe_default(env, key, default) do
59
- case env[key]
60
- when /\A(no|off|0|false)\z/
61
- false
62
- when /\A(yes|on|1|true)\z/
63
- true
64
- else
65
- raise ConfigurationError,
66
- "Value for #{key} (#{env[key].inspect}) is not a valid boolean"
67
- end
68
- end
69
- end
70
-
71
- def pluck_integer(env, key, valid_range: nil, default: nil)
72
- maybe_default(env, key, default) do
73
- if env[key] !~ /\A\d+\z/
74
- raise InvalidEnvironmentError,
75
- "Value for #{key} (#{env[key].inspect}) is not an integer"
76
- end
77
-
78
- env[key].to_i.tap do |v|
79
- unless valid_range.nil? || !valid_range.include?(v)
80
- raise InvalidEnvironmentError,
81
- "Value for #{key} (#{env[key]}) out of range (must be between #{valid_range.first} and #{valid_range.last} inclusive)"
82
- end
83
- end
84
- end
85
- end
86
-
87
- def pluck_prefix_list(env, prefix)
88
- {}.tap do |list|
89
- env.each do |k, v|
90
- next unless k.start_with? prefix
91
- key = k.sub(prefix, '')
92
- list[key] = v
93
- end
94
-
95
- @logger.debug("config") { "Prefix list for #{prefix.inspect} is #{list.inspect}" }
96
- end
97
- end
98
-
99
- def pluck_path_list(env, key, default: nil)
100
- maybe_default(env, key, default) do
101
- env[key].split(":")
102
- end
103
- end
104
-
105
- def maybe_default(env, key, default)
106
- if env[key].nil? || env[key].empty?
107
- if default.nil?
108
- raise ConfigurationError,
109
- "Required environment variable #{key} not specified"
110
- else
111
- @logger.debug("config") { "Using default value #{default.inspect} for config parameter #{key}" }
112
- default
113
- end
114
- else
115
- yield.tap { |v| @logger.debug("config") { "Using plucked value #{v.inspect} for config parameter #{key}" } }
116
- end
117
- end
118
- end
@@ -1,202 +0,0 @@
1
- require 'resolv'
2
- require 'ipaddr'
3
-
4
- # Write messages to a logstash server.
5
- #
6
- class Syslogstash::LogstashWriter
7
- Target = Struct.new(:hostname, :port)
8
-
9
- attr_reader :thread
10
-
11
- # Create a new logstash writer.
12
- #
13
- # Once the object is created, you're ready to give it messages by
14
- # calling #send_entry. No messages will actually be *delivered* to
15
- # logstash, though, until you call #run.
16
- #
17
- def initialize(cfg, stats)
18
- @server_name, @logger, @backlog, @stats = cfg.logstash_server, cfg.logger, cfg.backlog_size, stats
19
-
20
- @entries = []
21
- @entries_mutex = Mutex.new
22
- @cs_mutex = Mutex.new
23
- end
24
-
25
- # Add an entry to the list of messages to be sent to logstash. Actual
26
- # message delivery will happen in a worker thread that is started with
27
- # #run.
28
- #
29
- def send_entry(e)
30
- @entries_mutex.synchronize do
31
- @entries << { content: e, arrival_timestamp: Time.now }
32
- while @entries.length > @backlog
33
- @entries.shift
34
- @stats.dropped
35
- end
36
- end
37
-
38
- @thread.run if @thread
39
- end
40
-
41
- # Start sending messages to logstash servers. This method will return
42
- # almost immediately, and actual message sending will occur in a
43
- # separate thread.
44
- #
45
- def run
46
- @thread = Thread.new { send_messages }
47
- end
48
-
49
- # Cause the writer to disconnect from the currently-active server.
50
- #
51
- def force_disconnect!
52
- @cs_mutex.synchronize do
53
- @logger.info("writer") { "Forced disconnect from #{server_id(@current_server) }" }
54
- @current_server.close if @current_server
55
- @current_server = nil
56
- end
57
- end
58
-
59
- private
60
-
61
- def send_messages
62
- loop do
63
- if @entries_mutex.synchronize { @entries.empty? }
64
- sleep 1
65
- else
66
- begin
67
- entry = @entries_mutex.synchronize { @entries.shift }
68
-
69
- current_server do |s|
70
- s.puts entry[:content]
71
- @stats.sent(server_id(s), entry[:arrival_timestamp])
72
- end
73
-
74
- # If we got here, we sent successfully, so we don't want
75
- # to put the entry back on the queue in the ensure block
76
- entry = nil
77
- rescue StandardError => ex
78
- @logger.error("writer") { (["Unhandled exception while writing entry: #{ex.message} (#{ex.class})"] + ex.backtrace).join("\n ") }
79
- ensure
80
- @entries_mutex.synchronize { @entries.unshift if entry }
81
- end
82
- end
83
- end
84
- end
85
-
86
- # *Yield* a TCPSocket connected to the server we currently believe to
87
- # be accepting log entries, so that something can send log entries to
88
- # it.
89
- #
90
- # The yielding is very deliberate: it allows us to centralise all
91
- # error detection and handling within this one method, and retry
92
- # sending just by calling `yield` again when we've connected to
93
- # another server.
94
- #
95
- def current_server
96
- # I could handle this more cleanly with recursion, but I don't want
97
- # to fill the stack if we have to retry a lot of times
98
- done = false
99
-
100
- until done
101
- @cs_mutex.synchronize do
102
- if @current_server
103
- begin
104
- @logger.debug("writer") { "Using current server #{server_id(@current_server)}" }
105
- yield @current_server
106
- done = true
107
- rescue SystemCallError => ex
108
- # Something went wrong during the send; disconnect from this
109
- # server and recycle
110
- @logger.debug("writer") { "Error while writing to current server: #{ex.message} (#{ex.class})" }
111
- @current_server.close
112
- @current_server = nil
113
- sleep 0.1
114
- end
115
- else
116
- candidates = resolve_server_name
117
- @logger.debug("writer") { "Server candidates: #{candidates.inspect}" }
118
-
119
- begin
120
- next_server = candidates.shift
121
-
122
- if next_server
123
- @logger.debug("writer") { "Trying to connect to #{next_server.to_s}" }
124
- @current_server = TCPSocket.new(next_server.hostname, next_server.port)
125
- else
126
- @logger.debug("writer") { "Could not connect to any server; pausing before trying again" }
127
- @current_server = nil
128
- sleep 5
129
- end
130
- rescue SystemCallError => ex
131
- # Connection failed for any number of reasons; try the next one in the list
132
- @logger.warn("writer") { "Failed to connect to #{next_server.to_s}: #{ex.message} (#{ex.class})" }
133
- sleep 0.1
134
- retry
135
- end
136
- end
137
- end
138
- end
139
- end
140
-
141
- def server_id(s)
142
- pa = s.peeraddr
143
- if pa[0] == "AF_INET6"
144
- "[#{pa[3]}]:#{pa[1]}"
145
- else
146
- "#{pa[3]}:#{pa[1]}"
147
- end
148
- end
149
-
150
- def resolve_server_name
151
- return [static_target] if static_target
152
-
153
- # The IPv6 literal case should have been taken care of by
154
- # static_target, so the only two cases we have to deal with
155
- # here are specified-port (assume A/AAAA) or no port (assume SRV).
156
- if @server_name =~ /:/
157
- host, port = @server_name.split(":", 2)
158
- addrs = Resolv::DNS.new.getaddresses(host)
159
- if addrs.empty?
160
- @logger.warn("writer") { "No addresses resolved for server_name #{host.inspect}" }
161
- end
162
- addrs.sort_by { rand }.map { |a| Target.new(a.to_s, port.to_i) }
163
- else
164
- # SRV records ftw
165
- [].tap do |list|
166
- left = Resolv::DNS.new.getresources(@server_name, Resolv::DNS::Resource::IN::SRV)
167
- if left.empty?
168
- @logger.warn("writer") { "No SRV records found for server_name #{@server_name.inspect}" }
169
- end
170
- until left.empty?
171
- prio = left.map { |rr| rr.priority }.uniq.min
172
- candidates = left.select { |rr| rr.priority == prio }
173
- left -= candidates
174
- candidates.sort_by! { |rr| [rr.weight, rr.target.to_s] }
175
- until candidates.empty?
176
- selector = rand(candidates.inject(1) { |n, rr| n + rr.weight })
177
- chosen = candidates.inject(0) do |n, rr|
178
- break rr if n + rr.weight >= selector
179
- n + rr.weight
180
- end
181
- candidates.delete(chosen)
182
- list << Target.new(chosen.target.to_s, chosen.port)
183
- end
184
- end
185
- end
186
- end
187
- end
188
-
189
- def static_target
190
- @static_target ||= begin
191
- if @server_name =~ /\A(.*):(\d+)\z/
192
- begin
193
- Target.new(IPAddr.new($1).to_s, $2.to_i)
194
- rescue ArgumentError
195
- # Whatever is on the LHS isn't a recognisable address;
196
- # assume hostname and continue
197
- nil
198
- end
199
- end
200
- end
201
- end
202
- end