franz 1.6.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02a31ad79327096a6556900d4d5e9c98cb10cbf5
4
- data.tar.gz: 6f297b8ba0b6167067cc99172712abd140f008c6
3
+ metadata.gz: 3daab71d5207b3f6b7e32c4799ada5d1c1597f64
4
+ data.tar.gz: f5440a6bff60093e02925e6e0e81804a97ff730c
5
5
  SHA512:
6
- metadata.gz: 528eba213321f9c767241e0ffee5473393d6b0ee7c2ac47ef3497b33e9e55037e830c15bc480f9433849a66fac832598a4a298ae5e7a23e607c4d802251a596b
7
- data.tar.gz: b36855cd3a21be372e7d65cccec9b6a2cd4cfcf8ff3892baa776ee556f2abd0f647e3d1b05251c3a6f091572270e252cfba6e02b7467e418c971a307af62262d
6
+ metadata.gz: a8e79de017d808a8ea074a313d7fa80de03946174dcb9533afee6d2480a595f882abd829f5c75b100a5ee0f725c36d9dfd438407f7377ad051035fc18e045fd2
7
+ data.tar.gz: 5911d098549d5afa4061bb6e7327355bdabc094d096b3c9b1bc9fa7d839b95e20b07bf857ec04f8b645e05a08b384cc89dd705917e0cb51d1298396391a97ca8
data/Readme.md CHANGED
@@ -9,9 +9,9 @@ doing the bulk of the log processing. Using this setup, RabbitMQ and logstash
9
9
  may be scaled and restarted independently, so new configurations may be applied
10
10
  without interrupting those precious log hosts.
11
11
 
12
- Even so, Franz was designed to be interruped. Before exiting, Franz drains his
13
- event queues and write any "leftover" state disk. When he's called next, he picks
14
- up those leftovers and continues as if he were paused.
12
+ Even so, Franz was designed to be interruped. Before exiting, Franz keeps a log
13
+ of checkpoints, which are used to restore application state in the event of a
14
+ crash.
15
15
 
16
16
  He's also got a couple of improvements over logstash. Let's discuss!
17
17
 
@@ -19,7 +19,7 @@ He's also got a couple of improvements over logstash. Let's discuss!
19
19
  ## Improvements
20
20
 
21
21
  First let me say logstash is an awesome hunk of software thanks to the hard
22
- work of Jordan Sissel and the entire logstash community. Keep it up!
22
+ work of Jordan Sissel and the entire logstash community.
23
23
 
24
24
  ### Multiline Flush
25
25
 
@@ -141,8 +141,12 @@ It's kinda like a JSON version of the Logstash config language:
141
141
  "play_catchup?": true // Pick up where we left off
142
142
  },
143
143
 
144
- // Only RabbitMQ is supported at the moment
144
+
145
+ // If you provide both RabbitMQ and Kafka configurations, Franz will
146
+ // prefer RabbitMQ. If you provide neither, events are printed to STDOUT
145
147
  "output": {
148
+
149
+ // RabbitMQ
146
150
  "rabbitmq": {
147
151
 
148
152
  // Must be a consistently-hashed exchange!
@@ -170,6 +174,20 @@ It's kinda like a JSON version of the Logstash config language:
170
174
  }
171
175
  },
172
176
 
177
+ // Kafka (experimental)
178
+ "kafka": {
179
+ "client_id": "hostname",
180
+ "cluster": [ "localhost:9092" ],
181
+ "type": "sync",
182
+ "compression_codec": "snappy"
183
+ "metadata_refresh_interval_ms": 600000,
184
+ "max_send_retries": 3,
185
+ "retry_backoff_ms": 100,
186
+ "required_acks": 0,
187
+ "ack_timeout_ms": 1500,
188
+ "socket_timeout_ms": 10000
189
+ }
190
+
173
191
  // Advanced configuration (optional)
174
192
  "stats_interval": 60, // Emit statistics periodically
175
193
  "bound": 25000, // Limit output queue size
@@ -193,4 +211,14 @@ At Blue Jeans, we deploy Franz with Upstart. Here's a minimal config:
193
211
  exec franz
194
212
 
195
213
  We actually use the [`bjn_franz` cookbook](https://github.com/sczizzo/bjn-franz-cookbook)
196
- for Chef.
214
+ for Chef.
215
+
216
+ ### Changelog
217
+
218
+ #### v2.0.0
219
+
220
+ - Added new outputs: `StdOut`, `Kafka` (experimental)
221
+
222
+ #### v1
223
+
224
+ Intial implementation of the file-to-RabbitMQ pipeline
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.9
1
+ 2.0.0
data/bin/franz CHANGED
@@ -69,14 +69,33 @@ statz = Franz::Stats.new \
69
69
  interval: (config[:output][:stats_interval] || 300),
70
70
  logger: logger
71
71
 
72
- # Now we'll connect to our output, RabbitMQ. This creates a new thread in the
73
- # background, which will consume the events generated by our input on io
74
- Franz::Output.new \
75
- input: io,
76
- output: config[:output][:rabbitmq],
77
- logger: logger,
78
- tags: config[:output][:tags],
79
- statz: statz
72
+
73
+ # Now we'll connect to our output. This creates a new thread in the background,
74
+ # which will consume the events generated by our input on io
75
+ if config[:output][:rabbitmq]
76
+ Franz::Output::RabbitMQ.new \
77
+ input: io,
78
+ output: config[:output][:rabbitmq],
79
+ logger: logger,
80
+ tags: config[:output][:tags],
81
+ statz: statz
82
+
83
+ elsif config[:output][:kafka]
84
+ Franz::Output::Kafka.new \
85
+ input: io,
86
+ output: config[:output][:kafka],
87
+ logger: logger,
88
+ tags: config[:output][:tags],
89
+ statz: statz
90
+
91
+ else
92
+ Franz::Output::StdOut.new \
93
+ input: io,
94
+ logger: logger,
95
+ tags: config[:output][:tags],
96
+ statz: statz
97
+ end
98
+
80
99
 
81
100
  # Franz has only one kind of input, plain text files.
82
101
  Franz::Input.new \
@@ -87,6 +106,8 @@ Franz::Input.new \
87
106
  checkpoint_interval: config[:checkpoint_interval],
88
107
  statz: statz
89
108
 
109
+
110
+
90
111
  # Ensure memory doesn't grow too large (> 1GB by default)
91
112
  def mem_kb ; `ps -o rss= -p #{$$}`.strip.to_i ; end
92
113
 
data/franz.gemspec CHANGED
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.add_runtime_dependency 'colorize', '~> 0.7.0'
20
20
  s.add_runtime_dependency 'deep_merge', '~> 1.0.0'
21
21
  s.add_runtime_dependency 'eventmachine', '= 1.0.5'
22
+ s.add_runtime_dependency 'poseidon', '~> 0.0.5'
23
+ s.add_runtime_dependency 'snappy', '~> 0.0.11'
22
24
 
23
25
  s.files = `git ls-files`.split("\n")
24
26
  s.test_files = `git ls-files -- test/*`.split("\n")
@@ -0,0 +1,135 @@
1
+ require 'thread'
2
+ require 'json'
3
+
4
+ require 'poseidon'
5
+ require 'deep_merge'
6
+
7
+
8
+ module Franz
9
+ module Output
10
+
11
+ # Kafka output for Franz.
12
+ class Kafka
13
+ @@host = Socket.gethostname # We'll apply the hostname to all events
14
+
15
+
16
+ # Start a new output in the background. We'll consume from the input queue
17
+ # and ship events to STDOUT.
18
+ #
19
+ # @param [Hash] opts options for the output
20
+ # @option opts [Queue] :input ([]) "input" queue
21
+ # @option opts [Queue] :output ([]) "output" configuration
22
+ def initialize opts={}
23
+ opts = {
24
+ logger: Logger.new(STDOUT),
25
+ tags: [],
26
+ input: [],
27
+ output: {
28
+ flush_interval: 10,
29
+ flush_size: 500,
30
+ client_id: @@host,
31
+ cluster: %w[ localhost:9092 ],
32
+ type: 'sync',
33
+ compression_codec: 'snappy',
34
+ metadata_refresh_interval_ms: 600000,
35
+ max_send_retries: 3,
36
+ retry_backoff_ms: 100,
37
+ required_acks: 0,
38
+ ack_timeout_ms: 1500,
39
+ socket_timeout_ms: 10000
40
+ }
41
+ }.deep_merge!(opts)
42
+
43
+ @statz = opts[:statz] || Franz::Stats.new
44
+ @statz.create :num_output, 0
45
+
46
+ @logger = opts[:logger]
47
+
48
+ @stop = false
49
+ @foreground = opts[:foreground]
50
+
51
+ @flush_size = opts[:output].delete :flush_size
52
+ @flush_interval = opts[:output].delete :flush_interval
53
+
54
+ kafka_cluster = opts[:output].delete :cluster
55
+ kafka_client_id = opts[:output].delete :client_id
56
+ kafka_config = opts[:output].map { |k,v| v.is_a?(String) ? v.to_sym : v }
57
+
58
+ @kafka = Poseidon::Producer.new \
59
+ kafka_cluster,
60
+ kafka_client_id,
61
+ Hash[kafka_config]
62
+
63
+ @lock = Mutex.new
64
+ @messages = []
65
+
66
+
67
+ @thread = Thread.new do
68
+ loop do
69
+ ready_messages = []
70
+ @lock.synchronize do
71
+ ready_messages = @messages
72
+ @messages = []
73
+ end
74
+ @kafka.send_messages ready_messages unless ready_messages.empty?
75
+ log.debug \
76
+ event: 'periodic flush',
77
+ num_messages: ready_messages.size
78
+ sleep @flush_interval
79
+ end
80
+ end
81
+
82
+
83
+ @thread = Thread.new do
84
+ until @stop
85
+ event = opts[:input].shift
86
+
87
+ log.trace \
88
+ event: 'publish',
89
+ raw: event
90
+
91
+ payload = JSON::generate(event)
92
+ @messages << Poseidon::MessageToSend.new(event[:type].to_s, payload)
93
+
94
+ @statz.inc :num_output
95
+
96
+ if @statz.get(:num_output) % @flush_size == 0
97
+ @kafka.send_messages @messages unless @messages.empty?
98
+ log.debug \
99
+ event: 'flush',
100
+ num_messages: @messages.size
101
+ @messages = []
102
+ end
103
+
104
+ end
105
+ end
106
+
107
+ log.info event: 'output started'
108
+
109
+ @thread.join if @foreground
110
+ end
111
+
112
+
113
+ # Join the Output thread. Effectively only once.
114
+ def join
115
+ return if @foreground
116
+ @foreground = true
117
+ @thread.join
118
+ end
119
+
120
+
121
+ # Stop the Output thread. Effectively only once.
122
+ def stop
123
+ return if @foreground
124
+ @foreground = true
125
+ @thread.kill
126
+ log.info event: 'output stopped'
127
+ end
128
+
129
+
130
+ private
131
+ def log ; @logger end
132
+
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,101 @@
1
+ require 'json'
2
+
3
+ require 'bunny'
4
+ require 'deep_merge'
5
+
6
+
7
+ module Franz
8
+ module Output
9
+
10
+ # RabbitMQ output for Franz. You must declare an x-consistent-hash type
11
+ # exchange, as we generate random Integers for routing keys.
12
+ class RabbitMQ
13
+
14
+ # Start a new output in the background. We'll consume from the input queue
15
+ # and ship events to the configured RabbitMQ cluster.
16
+ #
17
+ # @param [Hash] opts options for the output
18
+ # @option opts [Queue] :input ([]) "input" queue
19
+ # @option opts [Hash] :output ({}) "output" configuration
20
+ def initialize opts={}
21
+ opts = {
22
+ logger: Logger.new(STDOUT),
23
+ tags: [],
24
+ input: [],
25
+ output: {
26
+ exchange: {
27
+ name: 'test',
28
+ durable: true
29
+ },
30
+ connection: {
31
+ host: 'localhost',
32
+ port: 5672
33
+ }
34
+ }
35
+ }.deep_merge!(opts)
36
+
37
+ @statz = opts[:statz] || Franz::Stats.new
38
+ @statz.create :num_output, 0
39
+
40
+ @logger = opts[:logger]
41
+
42
+ rabbit = Bunny.new opts[:output][:connection].merge({
43
+ network_recovery_interval: 10.0,
44
+ continuation_timeout: 10_000,
45
+ threaded: false,
46
+ logger: @logger
47
+ })
48
+
49
+ rabbit.start
50
+
51
+ channel = rabbit.create_channel
52
+ exchange = opts[:output][:exchange].delete(:name)
53
+ exchange = channel.exchange exchange, \
54
+ { type: 'x-consistent-hash' }.merge(opts[:output][:exchange])
55
+
56
+ @stop = false
57
+ @foreground = opts[:foreground]
58
+
59
+ @thread = Thread.new do
60
+ rand = Random.new
61
+ until @stop
62
+ event = opts[:input].shift
63
+
64
+ log.trace \
65
+ event: 'publish',
66
+ raw: event
67
+
68
+ exchange.publish \
69
+ JSON::generate(event),
70
+ routing_key: rand.rand(10_000),
71
+ persistent: false
72
+
73
+ @statz.inc :num_output
74
+ end
75
+ end
76
+
77
+ log.info event: 'output started'
78
+
79
+ @thread.join if @foreground
80
+ end
81
+
82
+ # Join the Output thread. Effectively only once.
83
+ def join
84
+ return if @foreground
85
+ @foreground = true
86
+ @thread.join
87
+ end
88
+
89
+ # Stop the Output thread. Effectively only once.
90
+ def stop
91
+ return if @foreground
92
+ @foreground = true
93
+ @thread.kill
94
+ log.info event: 'output stopped'
95
+ end
96
+
97
+ private
98
+ def log ; @logger end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,70 @@
1
+ require 'json'
2
+
3
+ require 'deep_merge'
4
+
5
+
6
+ module Franz
7
+ module Output
8
+
9
+ # STDOUT output for Franz.
10
+ class StdOut
11
+
12
+ # Start a new output in the background. We'll consume from the input queue
13
+ # and ship events to STDOUT.
14
+ #
15
+ # @param [Hash] opts options for the output
16
+ # @option opts [Queue] :input ([]) "input" queue
17
+ def initialize opts={}
18
+ opts = {
19
+ logger: Logger.new(STDOUT),
20
+ tags: [],
21
+ input: []
22
+ }.deep_merge!(opts)
23
+
24
+ @statz = opts[:statz] || Franz::Stats.new
25
+ @statz.create :num_output, 0
26
+
27
+ @logger = opts[:logger]
28
+
29
+ @stop = false
30
+ @foreground = opts[:foreground]
31
+
32
+ @thread = Thread.new do
33
+ until @stop
34
+ event = opts[:input].shift
35
+
36
+ log.trace \
37
+ event: 'publish',
38
+ raw: event
39
+
40
+ puts JSON::generate(event)
41
+
42
+ @statz.inc :num_output
43
+ end
44
+ end
45
+
46
+ log.info event: 'output started'
47
+
48
+ @thread.join if @foreground
49
+ end
50
+
51
+ # Join the Output thread. Effectively only once.
52
+ def join
53
+ return if @foreground
54
+ @foreground = true
55
+ @thread.join
56
+ end
57
+
58
+ # Stop the Output thread. Effectively only once.
59
+ def stop
60
+ return if @foreground
61
+ @foreground = true
62
+ @thread.kill
63
+ log.info event: 'output stopped'
64
+ end
65
+
66
+ private
67
+ def log ; @logger end
68
+ end
69
+ end
70
+ end
data/lib/franz/output.rb CHANGED
@@ -1,121 +1,4 @@
1
- require 'json'
2
-
3
- require 'bunny'
4
- require 'deep_merge'
5
-
6
1
  require_relative 'stats'
7
-
8
-
9
- module Franz
10
-
11
- # RabbitMQ output for Franz. You must declare an x-consistent-hash type
12
- # exchange, as we generate random Integers for routing keys.
13
- class Output
14
-
15
- # Start a new output in the background. We'll consume from the input queue
16
- # and ship events to the configured RabbitMQ cluster.
17
- #
18
- # @param [Hash] opts options for the output
19
- # @option opts [Queue] :input ([]) "input" queue
20
- # @option opts [Hash] :output ({}) "output" configuration
21
- def initialize opts={}
22
- opts = {
23
- logger: Logger.new(STDOUT),
24
- tags: [],
25
- input: [],
26
- output: {
27
- exchange: {
28
- name: 'test',
29
- durable: true
30
- },
31
- connection: {
32
- host: 'localhost',
33
- port: 5672
34
- }
35
- }
36
- }.deep_merge!(opts)
37
-
38
- @statz = opts[:statz] || Franz::Stats.new
39
- @statz.create :num_output, 0
40
-
41
- @logger = opts[:logger]
42
-
43
- rabbit = Bunny.new opts[:output][:connection].merge({
44
- network_recovery_interval: 10.0,
45
- continuation_timeout: 10_000,
46
- threaded: false,
47
- logger: @logger
48
- })
49
-
50
- rabbit.start
51
-
52
- channel = rabbit.create_channel
53
- exchange = opts[:output][:exchange].delete(:name)
54
- exchange = channel.exchange exchange, \
55
- { type: 'x-consistent-hash' }.merge(opts[:output][:exchange])
56
-
57
- @stop = false
58
- @foreground = opts[:foreground]
59
-
60
- @thread = Thread.new do
61
- rand = Random.new
62
- until @stop
63
- event = opts[:input].shift
64
-
65
- if event[:path]
66
- # Can't use sub!, because :path is frozen
67
- event[:path] = event[:path].sub '/home/denimuser/seam-builds/rel', ''
68
- event[:path] = event[:path].sub '/home/denimuser/seam-builds/live', ''
69
- event[:path] = event[:path].sub '/home/denimuser/seam-builds/beta', ''
70
- event[:path] = event[:path].sub '/home/denimuser/builds/rel', ''
71
- event[:path] = event[:path].sub '/home/denimuser/builds/live', ''
72
- event[:path] = event[:path].sub '/home/denimuser/builds/beta', ''
73
- event[:path] = event[:path].sub '/home/denimuser/cobalt-builds/rel', ''
74
- event[:path] = event[:path].sub '/home/denimuser/cobalt-builds/live', ''
75
- event[:path] = event[:path].sub '/home/denimuser/cobalt-builds/beta', ''
76
- event[:path] = event[:path].sub '/home/denimuser/rivet-builds', ''
77
- event[:path] = event[:path].sub '/home/denimuser/denim/logs', ''
78
- event[:path] = event[:path].sub '/home/denimuser/seam/logs', ''
79
- event[:path] = event[:path].sub '/home/denimuser/rivet/bjn/logs', ''
80
- event[:path] = event[:path].sub '/home/denimuser', ''
81
- event[:path] = event[:path].sub '/var/log/franz', ''
82
- event[:path] = event[:path].sub '/var/log', ''
83
- end
84
-
85
- log.trace \
86
- event: 'publish',
87
- raw: event
88
-
89
- exchange.publish \
90
- JSON::generate(event),
91
- routing_key: rand.rand(10_000),
92
- persistent: false
93
-
94
- @statz.inc :num_output
95
- end
96
- end
97
-
98
- log.info event: 'output started'
99
-
100
- @thread.join if @foreground
101
- end
102
-
103
- # Join the Output thread. Effectively only once.
104
- def join
105
- return if @foreground
106
- @foreground = true
107
- @thread.join
108
- end
109
-
110
- # Stop the Output thread. Effectively only once.
111
- def stop
112
- return if @foreground
113
- @foreground = true
114
- @thread.kill
115
- log.info event: 'output stopped'
116
- end
117
-
118
- private
119
- def log ; @logger end
120
- end
121
- end
2
+ require_relative 'output/kafka'
3
+ require_relative 'output/stdout'
4
+ require_relative 'output/rabbitmq'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: franz
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.9
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Clemmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-09 00:00:00.000000000 Z
11
+ date: 2015-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slog
@@ -94,6 +94,34 @@ dependencies:
94
94
  - - '='
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.0.5
97
+ - !ruby/object:Gem::Dependency
98
+ name: poseidon
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.0.5
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.0.5
111
+ - !ruby/object:Gem::Dependency
112
+ name: snappy
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.11
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.0.11
97
125
  description: Aggregate log file events and send them elsewhere.
98
126
  email: sclemmer@bluejeans.com
99
127
  executables:
@@ -118,6 +146,9 @@ files:
118
146
  - lib/franz/input_config.rb
119
147
  - lib/franz/metadata.rb
120
148
  - lib/franz/output.rb
149
+ - lib/franz/output/kafka.rb
150
+ - lib/franz/output/rabbitmq.rb
151
+ - lib/franz/output/stdout.rb
121
152
  - lib/franz/sash.rb
122
153
  - lib/franz/stats.rb
123
154
  - lib/franz/tail.rb