carnivore-files 0.1.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 +7 -0
- data/CHANGELOG.md +2 -0
- data/README.md +46 -0
- data/carnivore-files.gemspec +19 -0
- data/lib/carnivore-files.rb +14 -0
- data/lib/carnivore-files/file.rb +74 -0
- data/lib/carnivore-files/util/fetcher.rb +90 -0
- data/lib/carnivore-files/util/nio.rb +105 -0
- data/lib/carnivore-files/util/penguin.rb +101 -0
- data/lib/carnivore-files/version.rb +6 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 751a352aa27f264731f03db94c22669e2d823642
|
4
|
+
data.tar.gz: 59fdf94c07eed89ada05c7b2350b5f2af1bc25d9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 05a82a0a715c750b9eae0c785d493e53a32741996fdc24f21e34d14f3d82b4c61cf7a11f3db102458d59dc7d0df5e42b6603f53b17fa4616c73155dd86d7dc49
|
7
|
+
data.tar.gz: ec5cfd66f96523885ec06655832dda10c0a78d95c381e6281194590244d7c3295a2526046c801605a2684e4cd2303977e37d93e1b6debc247ecb0ad4b5f1c227
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Carnivore Files
|
2
|
+
|
3
|
+
Provides File `Carnivore::Source`
|
4
|
+
|
5
|
+
# Usage
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
require 'carnivore'
|
9
|
+
require 'carnivore-files'
|
10
|
+
|
11
|
+
Carnivore.configure do
|
12
|
+
source = Carnivore::Source.build(
|
13
|
+
:type => :file, :args => {:path => '/var/log/app.log'}
|
14
|
+
)
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
The `File` source is built on two "foundations", `sleepy_penguin`
|
19
|
+
and `nio4r`. The optimal foundation will be selected based on
|
20
|
+
the current Ruby in use (`nio4r` for JRuby, `sleepy_penguin` for
|
21
|
+
everything else). If you want to force the foundation:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'carnivore'
|
25
|
+
require 'carnivore-files'
|
26
|
+
|
27
|
+
Carnivore.configure do
|
28
|
+
source = Carnivore::Source.build(
|
29
|
+
:type => :file, :args => {
|
30
|
+
:path => '/var/log/app.log',
|
31
|
+
:foundation => :nio
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Important note
|
38
|
+
|
39
|
+
The underlying foundations are not installed by this gem. Be sure
|
40
|
+
include the dependency within your application dependencies (nio4r
|
41
|
+
or sleepy_penguin).
|
42
|
+
|
43
|
+
# Info
|
44
|
+
* Carnivore: https://github.com/carnivore-rb/carnivore
|
45
|
+
* Repository: https://github.com/carnivore-rb/carnivore-files
|
46
|
+
* IRC: Freenode @ #carnivore
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'carnivore-files/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'carnivore-files'
|
5
|
+
s.version = Carnivore::Files::VERSION.version
|
6
|
+
s.summary = 'Message processing helper'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'code@chrisroberts.org'
|
9
|
+
s.homepage = 'https://github.com/carnivore-rb/carnivore-files'
|
10
|
+
s.description = 'Carnivore file source'
|
11
|
+
s.license = 'Apache 2.0'
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.add_dependency 'carnivore', '>= 0.1.8'
|
14
|
+
# support both but don't want to install both
|
15
|
+
# update to be setup like carnivore-rabbitmq
|
16
|
+
# s.add_dependency 'nio4r'
|
17
|
+
s.add_dependency 'sleepy_penguin'
|
18
|
+
s.files = Dir['{lib}/**/**/*'] + %w(carnivore-files.gemspec README.md CHANGELOG.md)
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'carnivore-files/version'
|
2
|
+
require 'carnivore'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
# Carnivore source module for files
|
6
|
+
module Files
|
7
|
+
# Utilities for files
|
8
|
+
module Util
|
9
|
+
autoload :Fetcher, 'carnivore-files/util/fetcher'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Carnivore::Source.provide(:file, 'carnivore-files/file')
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'carnivore'
|
2
|
+
|
3
|
+
module Carnivore
|
4
|
+
class Source
|
5
|
+
# Carnivore source for consumption from files
|
6
|
+
class File < Source
|
7
|
+
|
8
|
+
# @return [String] path to file
|
9
|
+
attr_reader :path
|
10
|
+
# @return [Symbol] registry name of fetcher
|
11
|
+
attr_reader :fetcher_name
|
12
|
+
|
13
|
+
# Setup source
|
14
|
+
#
|
15
|
+
# @param args [Hash]
|
16
|
+
# @option args [String] :path path to file
|
17
|
+
# @option args [Symbol] :foundation underlying file interaction library
|
18
|
+
# @option args [Celluloid::Actor] :notify_actor actor to notify on line receive
|
19
|
+
def setup(args={})
|
20
|
+
@path = ::File.expand_path(args[:path])
|
21
|
+
@fetcher_name = "log_fetcher_#{name}".to_sym
|
22
|
+
unless(args[:foundation])
|
23
|
+
args[:foundation] = RUBY_ENGINE == 'jruby' ? :nio4r : :penguin
|
24
|
+
end
|
25
|
+
case args[:foundation].to_sym
|
26
|
+
when :nio, :nio4r
|
27
|
+
callback_supervisor.supervise_as(fetcher_name, Carnivore::Files::Util::Fetcher::Nio,
|
28
|
+
args.merge(:notify_actor => current_actor)
|
29
|
+
)
|
30
|
+
else
|
31
|
+
callback_supervisor.supervise_as(fetcher_name, Carnivore::Files::Util::Fetcher::Penguin,
|
32
|
+
args.merge(:notify_actor => current_actor)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Carnivore::Files::Util::Fetcher] line fetcher
|
38
|
+
def fetcher
|
39
|
+
callback_supervisor[fetcher_name]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Start the line fetcher
|
43
|
+
def connect
|
44
|
+
fetcher.async.start_fetcher
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Array<Hash>] return messages
|
48
|
+
def receive(*args)
|
49
|
+
wait(:new_log_lines)
|
50
|
+
fetcher.return_lines.map do |l|
|
51
|
+
format_message(l)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Send payload
|
56
|
+
#
|
57
|
+
# @param payload [Object] payload to transmit
|
58
|
+
def transmit(payload, *args)
|
59
|
+
fetcher.write_line(payload)
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Format message into customized Hash
|
65
|
+
#
|
66
|
+
# @param m [Object] payload
|
67
|
+
# @return [Hash]
|
68
|
+
def format_message(m)
|
69
|
+
Smash.new(:path => path, :content => m)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Carnivore
|
2
|
+
module Files
|
3
|
+
# Helper utilities
|
4
|
+
module Util
|
5
|
+
# Fetch lines from file
|
6
|
+
class Fetcher
|
7
|
+
|
8
|
+
autoload :Nio, 'carnivore-files/util/nio'
|
9
|
+
autoload :Penguin, 'carnivore-files/util/penguin'
|
10
|
+
|
11
|
+
include Celluloid
|
12
|
+
include Carnivore::Utils::Logging
|
13
|
+
|
14
|
+
# @return [String] path to file
|
15
|
+
attr_reader :path
|
16
|
+
# @return [String] string to split messages on
|
17
|
+
attr_reader :delimiter
|
18
|
+
# @return [Celluloid::Actor] actor to notify on new messages
|
19
|
+
attr_reader :notify_actor
|
20
|
+
|
21
|
+
# @return [Array] messages
|
22
|
+
attr_accessor :messages
|
23
|
+
# @return [IO] underlying IO instance
|
24
|
+
attr_accessor :io
|
25
|
+
|
26
|
+
# Create new instance
|
27
|
+
#
|
28
|
+
# @param args [Hash] initialization args
|
29
|
+
# @option args [String] :path path to file
|
30
|
+
# @option args [String] :delimiter string delimiter to break messages
|
31
|
+
# @option args [Celluloid::Actor] :notify_actor actor to be notified on new messages
|
32
|
+
def initialize(args={})
|
33
|
+
@leftover = ''
|
34
|
+
@path = ::File.expand_path(args[:path])
|
35
|
+
@delimiter = args.fetch(:delimiter, "\n")
|
36
|
+
@notify_actor = args[:notify_actor]
|
37
|
+
@messages = []
|
38
|
+
end
|
39
|
+
|
40
|
+
# Start the line fetcher
|
41
|
+
def start_fetcher
|
42
|
+
defer do
|
43
|
+
loop do
|
44
|
+
build_socket
|
45
|
+
messages = nil
|
46
|
+
selector.select.each do |mon|
|
47
|
+
self.messages += retrieve_lines
|
48
|
+
end
|
49
|
+
notify_actor.signal(:new_logs_lines) unless self.messages.empty?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Array<String>] current lines
|
55
|
+
def return_lines
|
56
|
+
msgs = messages.dup
|
57
|
+
messages.clear
|
58
|
+
msgs
|
59
|
+
end
|
60
|
+
|
61
|
+
# Write line to IO
|
62
|
+
#
|
63
|
+
# @param line [String]
|
64
|
+
# @return [Integer] bytes written
|
65
|
+
def write_line(line)
|
66
|
+
if(io)
|
67
|
+
io.puts(line)
|
68
|
+
else
|
69
|
+
raise 'No IO detected! Failed to write.'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Retreive lines from file
|
74
|
+
def retrieve_lines
|
75
|
+
if(io)
|
76
|
+
while(data = io.read(4096))
|
77
|
+
@leftover << data
|
78
|
+
end
|
79
|
+
result = @leftover.split(delimiter)
|
80
|
+
@leftover.replace @leftover.end_with?(delimiter) ? '' : result.pop.to_s
|
81
|
+
result
|
82
|
+
else
|
83
|
+
[]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'nio'
|
2
|
+
|
3
|
+
module Carnivore
|
4
|
+
module Files
|
5
|
+
module Util
|
6
|
+
class Fetcher
|
7
|
+
|
8
|
+
# NIO based fetcher
|
9
|
+
class Nio < Fetcher
|
10
|
+
|
11
|
+
# @return [NIO::Monitor]
|
12
|
+
attr_accessor :monitor
|
13
|
+
# @return [NIO::Selector]
|
14
|
+
attr_accessor :selector
|
15
|
+
|
16
|
+
# Create new instance
|
17
|
+
#
|
18
|
+
# @param args [Hash] initialization arguments (unused)
|
19
|
+
def initialize(args={})
|
20
|
+
super
|
21
|
+
@selector = NIO::Selector.new
|
22
|
+
every(5) do
|
23
|
+
check_file
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Start the fetcher
|
28
|
+
def start_fetcher
|
29
|
+
defer do
|
30
|
+
loop do
|
31
|
+
build_io
|
32
|
+
messages = nil
|
33
|
+
selector.select.each do |mon|
|
34
|
+
self.messages += retrieve_lines
|
35
|
+
end
|
36
|
+
notify_actor.signal(:new_log_lines) unless self.messages.empty?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Check for file and destroy monitor if file has changed
|
44
|
+
def check_file
|
45
|
+
if(io)
|
46
|
+
begin
|
47
|
+
unless(io.stat.ino == ::File.stat(path).ino)
|
48
|
+
destroy_io
|
49
|
+
end
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
destroy_io
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Build the IO instance if found
|
57
|
+
#
|
58
|
+
# @return [TrueClass]
|
59
|
+
def build_io
|
60
|
+
unless(monitor)
|
61
|
+
if(::File.exists?(path))
|
62
|
+
unless(io)
|
63
|
+
@io = ::File.open(path, 'r')
|
64
|
+
@io.seek(0, ::IO::SEEK_END) # fast-forward to EOF
|
65
|
+
end
|
66
|
+
@monitor = selector.register(io, :r)
|
67
|
+
else
|
68
|
+
wait_for_file
|
69
|
+
build_io
|
70
|
+
end
|
71
|
+
end
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
# Destroy the IO instance and monitor
|
76
|
+
#
|
77
|
+
# @return [TrueClass]
|
78
|
+
def destroy_io
|
79
|
+
if(monitor)
|
80
|
+
selector.deregister(monitor)
|
81
|
+
@monitor = nil
|
82
|
+
end
|
83
|
+
if(io)
|
84
|
+
io.close
|
85
|
+
@io = nil
|
86
|
+
end
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
# Wait helper for file to appear (5 sleep second intervals)
|
91
|
+
#
|
92
|
+
# @return [TrueClass]
|
93
|
+
def wait_for_file
|
94
|
+
warn "Waiting for file to appear (#{path})"
|
95
|
+
until(::File.exists?(path))
|
96
|
+
sleep(5)
|
97
|
+
end
|
98
|
+
info "File has appeared (#{path})!"
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'sleepy_penguin/sp'
|
2
|
+
|
3
|
+
module Carnivore
|
4
|
+
module Files
|
5
|
+
module Util
|
6
|
+
class Fetcher
|
7
|
+
|
8
|
+
|
9
|
+
# NIO based fetcher
|
10
|
+
class Penguin < Fetcher
|
11
|
+
# @return [SP::Inotify]
|
12
|
+
attr_accessor :notify
|
13
|
+
# @return [Hash] registered file descriptors
|
14
|
+
attr_accessor :notify_descriptors
|
15
|
+
|
16
|
+
# Create new instance
|
17
|
+
#
|
18
|
+
# @param args [Hash] initialization arguments (unused)
|
19
|
+
def initialize(args={})
|
20
|
+
super
|
21
|
+
@notify = SP::Inotify.new
|
22
|
+
@notify_descriptors = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Start the fetcher
|
26
|
+
def start_fetcher
|
27
|
+
defer do
|
28
|
+
loop do
|
29
|
+
build_io
|
30
|
+
notify.each do |event|
|
31
|
+
event.events.each do |ev|
|
32
|
+
case ev
|
33
|
+
when :MODIFY
|
34
|
+
self.messages += retrieve_lines
|
35
|
+
when :MOVE_SELF, :DELETE_SELF, :ATTRIB
|
36
|
+
destroy_io
|
37
|
+
end
|
38
|
+
end
|
39
|
+
notify_actor.signal(:new_log_lines) unless messages.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Build the IO and monitor
|
48
|
+
#
|
49
|
+
# @return [TrueClass]
|
50
|
+
def build_io
|
51
|
+
unless(io)
|
52
|
+
if(::File.exists?(path))
|
53
|
+
notify_descriptors[:file_watch] = notify.add_watch(path, :ALL_EVENTS)
|
54
|
+
@io = ::File.open(path, 'r')
|
55
|
+
@io.seek(0, ::IO::SEEK_END) # fast-forward to EOF
|
56
|
+
else
|
57
|
+
wait_for_file
|
58
|
+
build_io
|
59
|
+
end
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
# Destroy the IO and monitor
|
65
|
+
#
|
66
|
+
# @return [TrueClass]
|
67
|
+
def destroy_io
|
68
|
+
if(io)
|
69
|
+
notify.rm_watch(notify_descriptors.delete(:file_watch))
|
70
|
+
@io.close
|
71
|
+
@io = nil
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Wait helper for file to appear (waits for expected notification)
|
78
|
+
#
|
79
|
+
# @return [TrueClass]
|
80
|
+
def wait_for_file
|
81
|
+
until(::File.exists?(path))
|
82
|
+
notified = false
|
83
|
+
directory = ::File.dirname(path)
|
84
|
+
notify_descriptors[:file_wait] = notify.add_watch(directory, :OPEN)
|
85
|
+
until(notified)
|
86
|
+
warn "Waiting for file to appear (#{path})"
|
87
|
+
event = notify.take
|
88
|
+
if(event.name)
|
89
|
+
notified = ::File.expand_path(event.name) == path
|
90
|
+
end
|
91
|
+
end
|
92
|
+
notify.rm_watch(notify_descriptors.delete(:file_wait))
|
93
|
+
end
|
94
|
+
info "File has appeared (#{path})!"
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: carnivore-files
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Roberts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: carnivore
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sleepy_penguin
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Carnivore file source
|
42
|
+
email: code@chrisroberts.org
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- CHANGELOG.md
|
48
|
+
- README.md
|
49
|
+
- carnivore-files.gemspec
|
50
|
+
- lib/carnivore-files.rb
|
51
|
+
- lib/carnivore-files/file.rb
|
52
|
+
- lib/carnivore-files/util/fetcher.rb
|
53
|
+
- lib/carnivore-files/util/nio.rb
|
54
|
+
- lib/carnivore-files/util/penguin.rb
|
55
|
+
- lib/carnivore-files/version.rb
|
56
|
+
homepage: https://github.com/carnivore-rb/carnivore-files
|
57
|
+
licenses:
|
58
|
+
- Apache 2.0
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.2.2
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Message processing helper
|
80
|
+
test_files: []
|
81
|
+
has_rdoc:
|