franz 2.0.2 → 2.1.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: 18c5e2e6c0b3f391583e4f98d594091363865650
4
- data.tar.gz: 31764ccf1465afbbc33ca45d198f85c839c20fdd
3
+ metadata.gz: bdc8e2f8a34ec53cc121ba4c3208bcff7c52b496
4
+ data.tar.gz: 8f70dbf552fa97e18f0c435ecf2f05b96bdfdf0d
5
5
  SHA512:
6
- metadata.gz: d132f1ebf38ca2691b12cb9e5143414b07ae2301254a6b8c7f608176818311e2ffc29a97600a1b935850b6b0f738e99a8e650257da59e2d1264b1c770dead8c2
7
- data.tar.gz: 757893332467aaeeb7eb9398534b817b981dea1a518e27e83b07b57add7fab505dc86bc1636309884d1e6b135aaed0ba6e9bddd926853273b967b67711bdfb73
6
+ metadata.gz: aeff8102b0d4635749bfc9751ee01e5dcddb1674635c5130d997800d561c6f955a55575deb015590d2be6586d3bec7ce7d5414504eeb469ec0b251887bffc9d7
7
+ data.tar.gz: 9d9dba062334ea71d64a2be833a061911d3215738e7ff762695e69369f12071600f0a16619f77fffd2472bf51e2d0ba875d28a0a37b2f0bcd4a05b81f53a3206
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.2
1
+ 2.1.0
@@ -20,6 +20,11 @@ module Franz
20
20
  # Turn here to strangle your dictator
21
21
  EMAIL = 'sclemmer@bluejeans.com'
22
22
 
23
+ # Bundled extensions
24
+ TRAVELING_RUBY_VERSION = '20150210-2.1.5'
25
+ SNAPPY_VERSION = '0.0.11'
26
+ EM_VERSION = '1.0.5'
27
+
23
28
  # Every project deserves its own ASCII art
24
29
  ART = <<-'EOART' % VERSION
25
30
 
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: 2.0.2
4
+ version: 2.1.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-03-10 00:00:00.000000000 Z
11
+ date: 2015-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slog
@@ -81,47 +81,47 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: 1.0.0
83
83
  - !ruby/object:Gem::Dependency
84
- name: eventmachine
84
+ name: poseidon
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '='
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.0.5
89
+ version: 0.0.5
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '='
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 1.0.5
96
+ version: 0.0.5
97
97
  - !ruby/object:Gem::Dependency
98
- name: poseidon
98
+ name: snappy
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: 0.0.5
103
+ version: 0.0.11
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: 0.0.5
110
+ version: 0.0.11
111
111
  - !ruby/object:Gem::Dependency
112
- name: snappy
112
+ name: eventmachine
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
115
+ - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 0.0.11
117
+ version: 1.0.5
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "~>"
122
+ - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 0.0.11
124
+ version: 1.0.5
125
125
  description: Aggregate log file events and send them elsewhere.
126
126
  email: sclemmer@bluejeans.com
127
127
  executables:
@@ -129,15 +129,10 @@ executables:
129
129
  extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
- - ".gitignore"
133
- - Gemfile
134
- - History.md
135
132
  - LICENSE
136
- - Rakefile
137
133
  - Readme.md
138
134
  - VERSION
139
135
  - bin/franz
140
- - franz.gemspec
141
136
  - lib/franz.rb
142
137
  - lib/franz/agg.rb
143
138
  - lib/franz/config.rb
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- *~
2
- *.gem
3
- *.log
4
- *.out
5
- *.pid
6
- *.swp
7
- *.state
8
- *.checkpoint
9
- .DS_Store
10
- .yardoc
11
- doc
12
- pkg
13
- config.json
14
- Gemfile.lock
data/Gemfile DELETED
@@ -1,15 +0,0 @@
1
- source 'http://rubygems.org'
2
-
3
- gemspec
4
-
5
- group :development do
6
- gem 'pry'
7
- gem 'rake'
8
- gem 'yard'
9
- gem 'version'
10
- gem 'rubygems-tasks'
11
- end
12
-
13
- group :test do
14
- gem 'minitest'
15
- end
data/History.md DELETED
@@ -1,287 +0,0 @@
1
- # Franz
2
-
3
- Hi there, your old pal Sean Clemmer here. Imma talk quite a lot, so we might as
4
- well get acquainted. I work on the Operations team at Blue Jeans Network, an
5
- enterprise videoconferencing provider based in Mountain View, CA. Our team
6
- provides infrastructure, tools, and support to the Engineering team, including,
7
- most importantly for our purposes, log storage and processing.
8
-
9
-
10
- ## A lil History
11
-
12
- Before the latest rearchitecture, logs at Blue Jeans were basically `rsync`ed
13
- from every host to a central log server. Once on the box, a few processes came
14
- afterwards to compress the files and scan for meeting identifiers. Our reporting
15
- tools queried the log server with a meeting ID, and it replied with a list of
16
- files and their location.
17
-
18
- Compression saved a lot of space, and the search index was fairly small, since
19
- we only needed to store a map of meeting IDs to file paths. If you wanted to
20
- search the text, you need to log into the log server itself and `grep`.
21
-
22
- And all of that worked for everyone until a point. At a certain number of files,
23
- `grep` just wasn't fast enough, and worse, it was stealing resources necessary
24
- for processing logs. At a certain volume, we just couldn't scan the logs fast
25
- enough. Our scripts were getting harder to maintain, and we were looking for
26
- answers sooner rather than later.
27
-
28
-
29
- ### Exploring our options
30
-
31
- We did a fair amount of research and fiddling before deciding anything. We
32
- looked especially hard at the Elasticsearch-Logstash-Kibana (ELK) stack,
33
- Graylog2, and rearchitecting our scripts as a distributed system. In the end,
34
- we decided we weren't smart enough and there wasn't enough time to design our
35
- own system from the ground up. We also found Graylog2 to be a bit immature and
36
- lacking in features compared to the ELK stack.
37
-
38
- In the end, we appreciated the ELK stack had a lot of community and corporate
39
- support, it was easy to get started, and everything was fairly well-documented.
40
- Elasticsearch in particular seemed like a well-architected and professional
41
- software project. Logstash had an active development community, and the author
42
- Jordan Sissel soon joined Elasticsearch, Inc. as an employee.
43
-
44
- There was a lot of hype around ELK, and I thought we could make it work.
45
-
46
-
47
- ### Our requirements
48
-
49
- If you'll recall, the old system was really geared to look up log files based
50
- on a special meeting ID. Quick, but no real search.
51
-
52
- To emulate this with Elasticsearch we might store the whole file in a document
53
- along with the meeting ID, which would make queries straightforward. A more
54
- common approach in the ELK community is to store individual lines of a log file
55
- in Elasticsearch documents. I figured we could get the file back by asking the
56
- Elasticsearch cluster for an aggregation of documents corresponding to a given
57
- file path on a given host.
58
-
59
- To prove it to I slapped a couple scripts together, although the initial
60
- implementation actually used *facets* and not *aggregations*. If we could get
61
- the file back, that was everything we needed; we got advanced query support with
62
- Elasticsearch and visualization with Kibana for free. Logstash was making it
63
- easy for me to play around. Fun, even.
64
-
65
-
66
- ### Moving forward with ELK
67
-
68
- I got to reading about the pipelines, and I realized pretty quick we were't
69
- just gonna be able to hook Logstash right into Elasticsearch. You should put
70
- a kind of buffer inbetween, and both RabbitMQ and Redis were popular at the
71
- time. While I was developing our solution, alternative "forwarders" like
72
- Lumberjack were just being introduced. After evaluating our options, I decided
73
- on RabbitMQ based on team experience and the native Logstash support.
74
-
75
- So the initial pipeline looked like this:
76
-
77
- Logstash -> RabbitMQ -> Logstash -> Elasticsearch <- Kibana
78
-
79
- The first Logstash stage picked up logs with the `file` input and shipped them
80
- out with the `rabbitmq` output. These log *events* sat in RabbitMQ until a
81
- second, dedicated Logstash agent came along to parse it and shove it into
82
- Elasticsearch for long-term storage and search. I slapped Kibana on top to
83
- provide our users a usable window into the cluster.
84
-
85
- And it all *kinda* worked. It wasn't very fast, and outages were fairly common,
86
- but the all pieces were on the board. Over a few weeks I tuned and expanded the
87
- RabbitMQ and Elasticsearch clusters, but still we were missing chunks of files,
88
- missing whole files, and Logstash would die regularly with all kinds of strange
89
- issues. Encoding issues, buffer issues, timeout issues, heap size issues.
90
-
91
-
92
- ### Fighting with Logstash
93
-
94
- Surely we weren't the only people running into issues with this very popular
95
- piece of open source software? Logstash has a GitHub project, a JIRA account,
96
- and an active mailing list. I scoured issues, source code, pull requests, and
97
- e-mail archives. These seemed like huge bugs:
98
-
99
- 1. *Multiline:* Both the multiline codec and filter had their issues. The codec
100
- is generally preffered, but even still you might miss the last line of your
101
- file, because Logstash does not implement *multiline flush*. Logstash will
102
- buffer the last line of a file indefinitely, thinking you may come back and
103
- write to the log.
104
- 2. *File handling:* Because Logstash keeps files open indefinitely, it can soak
105
- up file handles after running a while. We had tens of thousands of log files
106
- on some hosts, and Logstash just wasn't having it.
107
- 3. *Reconstruction:* Despite my initial proofs, we were having a lot of trouble
108
- reconstructing log files from individual events. Lines were often missing,
109
- truncated, and shuffled.
110
-
111
- The multiline issue was actually fixed by the community, so I forked Logstash,
112
- applied some patches, and did a little hacking to get it working right.
113
-
114
- Fixing the second issue required delving deep into the depths of Logstash, the
115
- `file` input, and Jordan Sissel's FileWatch project. FileWatch provides most of
116
- the implementation for the `file` input, but it was riddled with bugs. I forked
117
- the project and went through a major refactor to simplify and sanitize the code.
118
- Eventually I was able to make it so Logstash would relinquish a file handle
119
- some short interval after reading the file had ceased.
120
-
121
- The third issue was rather more difficult. Subtle bugs at play. Rather than
122
- relying on the `@timestamp` field, which we found did not have enough
123
- resolution, I added a new field called `@seq`, just a simple counter, which
124
- enabled us to put the events back in order. Still we were missing chunks, and
125
- some lines appeared to be interleaved. Just weird stuff.
126
-
127
- After hacking Logstash half to death we decided the first stage of the pipeline
128
- would have to change. We'd still use Logstash to move events from RabbitMQ into
129
- Elasticsearch, but we couldn't trust it to collect files.
130
-
131
-
132
- ### And so Franz was born
133
-
134
- I researched Logstash alternatives, but there weren't many at the time. Fluentd
135
- looked promising, but early testing revealed the multiline facility wasn't quite
136
- there yet. Lumberjack was just gaining some popularity, but it was still too
137
- immature. In the end, I decided I had a pretty good handle on our requirements
138
- and I would take a stab at implementing a solution.
139
-
140
- It would be risky, but Logstash and the community just weren't moving fast
141
- enough for our needs. Engineering was justly upset with our logging "solution",
142
- and I was pretty frantic after weeks of hacking and debugging. How hard could
143
- it really be to tail a file and send the lines out to a queue?
144
-
145
- After a few prototypes and a couple false starts, we had our boy Franz.
146
-
147
-
148
-
149
- ## Design and Implementation
150
-
151
- From 10,000 feet Franz and Logstash are pretty similar; you can imagine Franz is
152
- basically a Logstash agent configured with a `file` input and `rabbitmq` output.
153
- Franz accepts a single configuration file that tells the process which files to
154
- tail, how to handle them, and where to send the output. Besides solving the
155
- three issues we discussed earlier, Franz provides a kind of `json` codec and
156
- `drop` filter (in Logstash parlance).
157
-
158
- I decided early on to implement Franz in Ruby, like Logstash. Unlike Logstash,
159
- which is typically executed by JRuby, I decided to stick with Mat'z Ruby for
160
- Franz in order to obtain a lower resource footprint at the expense of true
161
- concurrency (MRI has a GIL).
162
-
163
- Implementation-wise, Franz bears little resemblance to Logstash. Logstash has
164
- a clever system which "compiles" the inputs, filters, and outputs into a single
165
- block of code. Franz is a fairly straightward Ruby program with only a handful
166
- of classes and a simple execution path.
167
-
168
-
169
- ### The Twelve-Factor App
170
-
171
- I was heavily influenced by [the Twelve-Factor App](http://12factor.net):
172
-
173
- 1. *Codebase:* Franz is contained in a single repo on [GitHub](https://github.com/sczizzo/franz).
174
- 2. *Dependencies:* Franz provides a `Gemfile` to isolate dependencies.
175
- 3. *Config:* Franz separates code from configuration (no env vars, though).
176
- 4. *Backing Services:* Franz is agnostic to the connected RabbitMQ server.
177
- 5. *Build, release, run:* Franz is versioned and released as a [RubyGem](https://rubygems.org/gems/franz).
178
- 6. *Processes:* Franz provides mostly-stateless share-nothing executions.
179
- 7. *Port binding:* Franz isn't a Web service, so no worries here!
180
- 8. *Concurrency:* Franz is a single process and plays nice with Upstart.
181
- 9. *Disposability:* Franz uses a crash-only architecture, discussed below.
182
- 10. *Dev/prod parity:* We run the same configuration in every environment.
183
- 11. *Logs:* Franz provides structured logs which can be routed to file.
184
- 12. *Admin processes:* Franz is simple enough this isn't necessary.
185
-
186
-
187
- ### Crash-Only Architecture
188
-
189
- Logstash assumes you might want to stop the process and restart it later, having
190
- the new instance pick up where the last left off. To support this, Logstash (or
191
- really, FileWatch) keeps a small "checkpoint" file, which is written whenever
192
- Logstash is shut down.
193
-
194
- Franz takes this one step further and implements a ["crash-only" design](http://lwn.net/Articles/191059).
195
- The basic idea here the application does not distinguish between a crash and
196
- a restart. In practical terms, Franz simply writes a checkpoint at regular
197
- intervals; when asked to shut down, it aborts immediately.
198
-
199
- Franz checkpoints are simple, too. It's just a `Hash` from log file paths to
200
- the current `cursor` (byte offset) and `seq` (sequence number):
201
-
202
- {
203
- "/path/to/my.log": {
204
- "cursor": 1234,
205
- "seq": 99
206
- }
207
- }
208
-
209
- The checkpoint file contains the `Marshal` representation of this `Hash`.
210
-
211
-
212
- ### Sash
213
-
214
- The `Sash` is a data structure I discovered during the development of Franz,
215
- which came out of the implementation of multiline flush. Here's a taste:
216
-
217
- s = Sash.new # => #<Sash...>
218
- s.keys # => []
219
- s.insert :key, :value # => :value
220
- s.get :key # => [ :value ]
221
- s.insert :key, :crazy # => :crazy
222
- s.mtime :key # => 2014-02-18 21:24:30 -0800
223
- s.flush :key # => [ :value, :crazy ]
224
-
225
- For multilining, what you do is create a `Sash` keyed by each path, and insert
226
- each line in the appropriate key as they come in from upstream. Before you
227
- insert it, though, you check if the line in question matches the multiline
228
- pattern for the key: If so, you flush the `Sash` key and write the result out as
229
- an event. Now the `Sash` key will buffer the next event.
230
-
231
- In fact, a `Sash` key will only ever contain lines for at most one event, and
232
- the `mtime` method allows us to know how recently that key was modified. To
233
- implement multiline flush correctly, we periodically check the `Sash` for old
234
- keys and flush them according to configuration. `Sash` methods are threadsafe,
235
- so we can do this on the side without interrupting the main thread.
236
-
237
-
238
- ### Slog
239
-
240
- [Slog](https://github.com/sczizzo/slog) was also factored out of Franz, but
241
- it's heavily inspired by Logstash. By default, output is pretty and colored (I
242
- swear):
243
-
244
- Slog.new.info 'example'
245
- #
246
- # {
247
- # "level": "info",
248
- # "@timestamp": "2014-12-25T06:22:43.459-08:00",
249
- # "message": "example"
250
- # }
251
- #
252
-
253
- `Slog` works perfectly with Logstash or Franz when configured to treat the log
254
- file as JSON; they'll add the other fields necessary for reconstruction.
255
-
256
- More than anything, structured logging has changed how I approach logs. Instead
257
- of writing *everything* to file, Franz strives to log useful events that contain
258
- metadata. Instead of every request, an occasional digest. Instead of a paragraph
259
- of text, a simple summary. Franz uses different log levels appropriately,
260
- allowing end users to control verbosity.
261
-
262
-
263
- ### Execution Path
264
-
265
- Franz is implemented as a series of stages connected via bounded queues:
266
-
267
- Input -> Discover -> Watch -> Tail -> Agg -> Output
268
-
269
- Each of these stages is a class under the `Franz` namespace, and they run up
270
- to a couple `Thread`s, typically a worker and maybe a helper (e.g. multiline
271
- flush). Communicating via `SizedQueue`s helps ensure correctness and constrain
272
- memory usage under high load.
273
-
274
- 0. `Input`: Actually wires together `Discover`, `Watch`, `Tail`, and `Agg`.
275
- 1. `Discover`: Performs half of file existence detection by expanding globs and
276
- keeping track of files known to Franz.
277
- 2. `Watch`: Works in tandem with `Discover` to maintain a list of known files and
278
- their status. Events are generated when a file is created, destroyed, or
279
- modified (including appended, truncated, and replaced).
280
- 3. `Tail`: Receives low-level file events from a `Watch` and handles the actual
281
- reading of files, providing a stream of lines.
282
- 4. `Agg`: Mostly aggregates `Tail` events by applying the multiline filter, but it
283
- also applies the `host` and `type` fields. Basically, it does all the post
284
- processing after we've retreived a line from a file.
285
- 5. `Output`: RabbitMQ output for Franz, based on the really-very-good [Bunny](https://github.com/ruby-amqp/bunny)
286
- client. You must declare an `x-consistent-hash` exchange, as we generate a
287
- random `Integer` for routing. Such is life.
data/Rakefile DELETED
@@ -1,32 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- require 'rake'
4
-
5
-
6
- require 'rake/testtask'
7
- Rake::TestTask.new(:test) do |test|
8
- test.libs << 'lib' << 'test'
9
- test.test_files = FileList['test/test*.rb']
10
- test.verbose = true
11
- end
12
-
13
- task :default => :test
14
-
15
-
16
- require 'yard'
17
- YARD::Rake::YardocTask.new do |t|
18
- t.files = %w[ --readme Readme.md lib/**/*.rb - VERSION ]
19
- end
20
-
21
-
22
- require 'rubygems/tasks'
23
- Gem::Tasks.new({
24
- sign: {}
25
- }) do |tasks|
26
- tasks.console.command = 'pry'
27
- end
28
- Gem::Tasks::Sign::Checksum.new sha2: true
29
-
30
-
31
- require 'rake/version_task'
32
- Rake::VersionTask.new
data/franz.gemspec DELETED
@@ -1,29 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path(File.join('..', 'lib'), __FILE__)
3
- require 'franz/metadata'
4
-
5
- Gem::Specification.new do |s|
6
- s.name = 'franz'
7
- s.version = Franz::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.license = Franz::LICENSE
10
- s.homepage = Franz::HOMEPAGE
11
- s.author = Franz::AUTHOR
12
- s.email = Franz::EMAIL
13
- s.summary = Franz::SUMMARY
14
- s.description = Franz::SUMMARY + '.'
15
-
16
- s.add_runtime_dependency 'slog', '~> 1.1.0'
17
- s.add_runtime_dependency 'bunny', '~> 1.6.0'
18
- s.add_runtime_dependency 'trollop', '~> 2.1.0'
19
- s.add_runtime_dependency 'colorize', '~> 0.7.0'
20
- s.add_runtime_dependency 'deep_merge', '~> 1.0.0'
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'
24
-
25
- s.files = `git ls-files`.split("\n")
26
- s.test_files = `git ls-files -- test/*`.split("\n")
27
- s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File::basename(f) }
28
- s.require_paths = %w[ lib ]
29
- end