carnivore-files 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
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
@@ -0,0 +1,6 @@
1
+ module Carnivore
2
+ module Files
3
+ # Current version of library
4
+ VERSION = Gem::Version.new('0.1.0')
5
+ end
6
+ 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: