franz 1.6.9 → 2.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.
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