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 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: