nfagent 0.0.1

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.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-09-22
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,22 @@
1
+ Rakefile
2
+ lib/nfagent.rb
3
+ lib/nfagent.init
4
+ lib/nfagent
5
+ lib/nfagent/cli.rb
6
+ lib/nfagent/base64.rb
7
+ lib/nfagent/encoder.rb
8
+ lib/nfagent/server.rb
9
+ lib/nfagent/chunk_handler.rb
10
+ lib/nfagent/log.rb
11
+ lib/nfagent/event.rb
12
+ lib/nfagent/chunk.rb
13
+ lib/nfagent/config.rb
14
+ lib/nfagent/tail.rb
15
+ lib/nfagent/submitter.rb
16
+ bin/nfagent
17
+ bin/squid_log_writer
18
+ PostInstall.txt
19
+ History.txt
20
+ Manifest.txt
21
+ nfagent.init
22
+ nfagent.conf
@@ -0,0 +1 @@
1
+ For more information on nfagent, see http://nfagent.rubyforge.org
@@ -0,0 +1,31 @@
1
+ %w[rubygems rake rake/clean hoe fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/nfagent'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.spec('nfagent') do |p|
7
+ p.version = NFAgent::VERSION
8
+ p.summary = "Logging Agent for NetFox Online"
9
+ p.developer('Daniel Draper', 'daniel@netfox.com')
10
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
11
+ p.post_install_message = 'PostInstall.txt'
12
+ p.rubyforge_name = p.name
13
+ p.extra_deps = [
14
+ ['svutil','>= 0.0.3'],
15
+ ]
16
+ p.extra_dev_deps = [
17
+ ['newgem', ">= #{::Newgem::VERSION}"]
18
+ ]
19
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
20
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
21
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
22
+ p.rsync_args = '-av --delete --ignore-errors'
23
+ p.readme_file = "README.txt"
24
+ p.spec_extras[:default_executable] = 'nfagent'
25
+ end
26
+
27
+ require 'newgem/tasks' # load /tasks/*.rake
28
+ Dir['tasks/**/*.rake'].each { |t| load t }
29
+
30
+ # TODO - want other tests/tasks run by default? Add them to the list
31
+ # task :default => [:spec, :features]
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'nfagent'
4
+ NFAgent::CLI.new
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+
5
+ class Client
6
+ def initialize(host, port)
7
+ @host = host
8
+ @port = port
9
+ end
10
+
11
+ def write_safely(data)
12
+ connect_socket unless @connected
13
+ begin
14
+ @client.puts(data)
15
+ rescue
16
+ @connected = false
17
+ end
18
+ end
19
+
20
+ def close
21
+ @client.close if @client && @connected
22
+ end
23
+
24
+ private
25
+ def connect_socket
26
+ begin
27
+ @client = TCPSocket.new(@host, @port)
28
+ @connected = true
29
+ rescue
30
+ @connected = false
31
+ end
32
+ end
33
+ end
34
+
35
+ client = Client.new("127.0.0.1", "10000")
36
+
37
+ while !$stdin.eof?
38
+ line = $stdin.readline
39
+ client.write_safely(line)
40
+ $stdout.flush
41
+ end
42
+ client.close
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+
3
+ # nfagent This shell script takes care of starting and stopping
4
+ #
5
+ # chkconfig: 345 98 98
6
+ # description: The NetFox logging agent
7
+
8
+ start () {
9
+
10
+ }
@@ -0,0 +1,28 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'svutil'
6
+
7
+ require 'thread'
8
+ require 'fileutils'
9
+ require 'logger'
10
+ require 'pp'
11
+ require 'uri'
12
+ require 'net/http'
13
+ require 'eventmachine'
14
+
15
+ require 'nfagent/chunk'
16
+ require 'nfagent/chunk_handler'
17
+ require 'nfagent/submitter'
18
+ require 'nfagent/encoder'
19
+ require 'nfagent/config'
20
+ require 'nfagent/log'
21
+ require 'nfagent/tail'
22
+ require 'nfagent/event'
23
+ require 'nfagent/server'
24
+ require 'nfagent/cli'
25
+
26
+ module NFAgent
27
+ VERSION = '0.0.1'
28
+ end
@@ -0,0 +1,7 @@
1
+ module NFAgent
2
+ class Base64
3
+ def self.encode64url(str)
4
+ [str].pack('m').tr("+/","-_")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ require 'zlib'
2
+ require 'digest'
3
+
4
+ module NFAgent
5
+ class Chunk
6
+ attr_reader :created_at
7
+
8
+ ::DEFAULT_TIME_OUT = 60
9
+
10
+ def initialize(max_size = 500)
11
+ @max_size = max_size
12
+ @created_at = Time.now
13
+ @array = []
14
+ @submitter = Submitter.new(Config.client_key)
15
+ end
16
+
17
+ def <<(line)
18
+ @array << line
19
+ end
20
+
21
+ def full?
22
+ @array.size >= @max_size
23
+ end
24
+
25
+ def expired?
26
+ (Time.now - @created_at > ::DEFAULT_TIME_OUT) && !@array.empty?
27
+ end
28
+
29
+ def dump
30
+ puts @array.join("\n")
31
+ payload = Encoder.encode64url(Zlib::Deflate.deflate(@array.join("\n"), Zlib::BEST_COMPRESSION))
32
+ checksum = Digest::SHA1.hexdigest(payload)
33
+ Log.info "Submitting chunk to server (#{checksum})"
34
+ [ payload, checksum ]
35
+ end
36
+
37
+ def clear
38
+ @array.clear
39
+ end
40
+
41
+ def submit_to_server
42
+ payload, checksum = dump
43
+ @submitter.submit(payload, checksum)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,57 @@
1
+ module NFAgent
2
+ class ChunkHandler
3
+
4
+ def initialize(chunk_size = 500)
5
+ @mutex = Mutex.new
6
+ @chunk_size = chunk_size
7
+ make_new_chunk
8
+ end
9
+
10
+ def submit(line)
11
+ # if current day is > day of last entry on current_chunk
12
+ # then submit and reset the chunk before adding the line
13
+ current_day = Time.now.day
14
+ if current_day != current_chunk.created_at.day
15
+ Log.info("Expiring chunk due to date rollover")
16
+ reset_chunk
17
+ end
18
+ current_chunk << line
19
+ end
20
+
21
+ def periodically_check_expired
22
+ Thread.new do
23
+ loop do
24
+ check_full_or_expired
25
+ sleep 5
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+ def check_full_or_expired
32
+ if current_chunk.full? || current_chunk.expired?
33
+ reset_chunk
34
+ end
35
+ end
36
+
37
+ def reset_chunk
38
+ outgoing_chunk = current_chunk
39
+ make_new_chunk
40
+ Thread.new do
41
+ outgoing_chunk.submit_to_server
42
+ end
43
+ end
44
+
45
+ def make_new_chunk
46
+ @mutex.synchronize do
47
+ @current_chunk = Chunk.new(@chunk_size)
48
+ end
49
+ end
50
+
51
+ def current_chunk
52
+ @mutex.synchronize do
53
+ return @current_chunk
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,14 @@
1
+ module NFAgent
2
+ class CLI
3
+ include NFAgent
4
+ include SVUtil
5
+
6
+ def initialize
7
+ Config.load_and_parse
8
+ @process = ProcessManager.new(Server)
9
+ @process.start
10
+ end
11
+ end
12
+ end
13
+
14
+ # TODO Run EventMachine here later to allow clients to connect for real time log display
@@ -0,0 +1,36 @@
1
+ module NFAgent
2
+ class Proxy
3
+ attr_accessor :host, :port, :user, :password
4
+ end
5
+
6
+ class Config < SVUtil::Config
7
+ @@proxy = Proxy.new
8
+
9
+ def self.proxy
10
+ @@proxy
11
+ end
12
+
13
+ class << self
14
+ def validate
15
+ unless dump_dir and File.exists?(dump_dir) and File.directory?(dump_dir)
16
+ raise "Dump dir (#{dump_dir}) must exist and be a directory"
17
+ end
18
+ super
19
+ end
20
+
21
+ def process_options
22
+ parse_options do |opts|
23
+ opts.on("-k", "--client-key [key]", "Service client key") do |key|
24
+ Config.client_key = key
25
+ end
26
+ opts.on("-l", "--debug-log [log-file]", "Debug Log File") do |log|
27
+ Config.log_file = log
28
+ end
29
+ opts.on("-D", "--dump-dir [dir]", "Dump directory for failed chunks") do |dir|
30
+ Config.dump_dir = dir
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ module NFAgent
2
+ class Encoder
3
+ def self.encode64url(str)
4
+ [str].pack('m').tr("+/","-_")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module NFAgent
2
+ class Event < EventMachine::Connection
3
+ def initialize(chunk_handler)
4
+ @handler = chunk_handler
5
+ end
6
+
7
+ def post_init
8
+ Log.info "Client Connected"
9
+ end
10
+
11
+ def receive_data(data)
12
+ if data && data.length > 2
13
+ @handler.submit(data)
14
+ end
15
+ send_data('OK')
16
+ end
17
+
18
+ def unbind
19
+ Log.info "Disconnected"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module NFAgent
2
+ class Log
3
+ class << self
4
+ %w(info warning error).each do |level|
5
+ define_method(level) do |arg|
6
+ log(level, arg)
7
+ end
8
+ end
9
+
10
+ def log(level, arg)
11
+ STDOUT.puts "#{Time.now}: (#{level}) #{arg}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module NFAgent
2
+ class Server
3
+ def initialize
4
+ Log.info("Starting up")
5
+ Submitter.start_resubmission_thread
6
+ chunk_handler = ChunkHandler.new
7
+ chunk_handler.periodically_check_expired
8
+
9
+ EM.run {
10
+ EM.start_server "0.0.0.0", 10000, Event, chunk_handler
11
+ }
12
+
13
+ #Tail.tail(Config.proxy_log) do |line|
14
+ # chunk_handler.submit(line)
15
+ #end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ module NFAgent
2
+ class Submitter
3
+ attr_accessor :host
4
+
5
+ def initialize(key)
6
+ @service_host = "collector.service.netfox.com"
7
+ @key = key
8
+ end
9
+
10
+ def submit(payload, checksum)
11
+ puts "submitting paylod: #{payload}"
12
+ proxy_class = Net::HTTP::Proxy(Config.proxy.host, Config.proxy.port, Config.proxy.user, Config.proxy.password)
13
+ proxy_class.start(@service_host, 80) do |http|
14
+ req = Net::HTTP::Post.new('/collector')
15
+ req.set_form_data({ "payload" => payload, "checksum" => checksum, "key" => @key })
16
+ response, body = http.request(req)
17
+ raise body unless Net::HTTPOK === response
18
+ end
19
+ rescue
20
+ Log.error "Submission Failed: #{$!}"
21
+ write_failed_dump(payload, checksum)
22
+ end
23
+
24
+ def write_failed_dump(payload, checksum)
25
+ File.open(File.join(Config.dump_dir, checksum), "w") do |file|
26
+ file << payload
27
+ end
28
+ end
29
+
30
+ def self.start_resubmission_thread
31
+ Thread.new do
32
+ loop do
33
+ self.resubmit_failed_dumps
34
+ sleep 60
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.resubmit_failed_dumps
40
+ submitter = Submitter.new(Config.client_key)
41
+ dump_dir = Dir.new(Config.dump_dir)
42
+ dump_dir.entries.select { |e| not e =~ /^\./ }.each do |entry|
43
+ Log.info "Resubmitting #{entry }"
44
+ payload = ""
45
+ ref = File.join(dump_dir.path, entry)
46
+ File.open(ref, "r") do |file|
47
+ payload << file.read
48
+ end
49
+ FileUtils.rm(ref)
50
+ submitter.submit(payload, entry)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,129 @@
1
+ ##!/usr/bin/env ruby1.9
2
+
3
+ # NOTE: There is a bug in Ruby 1.8.6p111 which prevents this from working properly - 1.9 is known to work
4
+
5
+ module LogClient
6
+ class Tail
7
+ class BufferError < StandardError; end
8
+
9
+ ::BUFFER_SIZE = 1024
10
+ ::MAX_BUFFER_SIZE = 32768
11
+
12
+ def self.tail(filename)
13
+ loop do
14
+ Tail.new(filename).run do |line|
15
+ unless line.nil? || line.empty?
16
+ if block_given?
17
+ yield line
18
+ else
19
+ puts line
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def initialize(filename)
27
+ @buffer = ""
28
+ @filename = filename
29
+ reset_buffer_size
30
+ seek_and_setup
31
+ end
32
+
33
+ # The real work
34
+ # Start at next start. Read until we get a new line and yield the line
35
+ # If we don't get a new line then just ignore and try again until we do
36
+ def run
37
+ begin
38
+ yield @first_line if block_given?
39
+ while (size = File::Stat.new(@filename).size) >= @next_start
40
+ size = @file.stat.size
41
+ reset_buffer_size
42
+ begin
43
+ line = ""
44
+ @file.seek(@next_start, File::SEEK_SET)
45
+ @file.read(@buf_size, @buffer)
46
+ buffer_start = @next_start
47
+ found_new_line = false
48
+ 0.upto(@buffer.size - 1) do |index|
49
+ line << @buffer[index]
50
+ if @buffer[index].chr == "\n"
51
+ yield(line) if block_given?
52
+ line = ""
53
+ found_new_line = true
54
+ @next_start = buffer_start + index + 1
55
+ end
56
+ end
57
+ unless found_new_line || @buffer.empty?
58
+ raise BufferError
59
+ end
60
+ rescue BufferError
61
+ increment_buffer_size
62
+ retry
63
+ end
64
+ sleep 0.01
65
+ end
66
+ rescue Errno::ENOENT
67
+ # Wait until the file is recreated
68
+ while !File.exists?(@filename)
69
+ sleep 0.05
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+ def seek_and_setup
76
+ @file = File.open(@filename, "r+")
77
+ size = @file.stat.size
78
+ pos = size
79
+ begin
80
+ if size >= @buf_size
81
+ @file.seek(size - @buf_size, File::SEEK_SET)
82
+ end
83
+ @buffer = @file.read
84
+ if @buffer.size > 0
85
+ index = @buffer.size - 1
86
+ # Find the last new line in the @buffer
87
+ # Make sure we got a new line somewhere
88
+ while (char = @buffer[index].chr) != "\n" && index >= 0
89
+ index -= 1
90
+ end
91
+ # Start reading from here next time
92
+ @next_start = pos - (@buf_size - index) + 1
93
+ @next_start = 0 if @next_start < 0
94
+ line = ""
95
+ if index > 1
96
+ index -= 1
97
+ # Go back to the previous new line (or the start of the @buffer)
98
+ while (char = @buffer[index].chr) != "\n"
99
+ line << char
100
+ index -= 1
101
+ end
102
+ end
103
+ # If index is 0 and the buffer is smaller than the file
104
+ # then we can try reading more chars until we find a new line
105
+ if index <= 0 and @buf_size < size
106
+ raise BufferError
107
+ end
108
+ @first_line = line.reverse
109
+ else
110
+ @next_start = pos
111
+ end
112
+ rescue BufferError
113
+ increment_buffer_size
114
+ retry
115
+ end
116
+ end
117
+
118
+ def increment_buffer_size
119
+ @buf_size = @buf_size * 2
120
+ if @buf_size > ::MAX_BUFFER_SIZE
121
+ raise BufferError, "Maximum buffer size exceeded"
122
+ end
123
+ end
124
+
125
+ def reset_buffer_size
126
+ @buf_size = ::BUFFER_SIZE
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,5 @@
1
+ client_key = 1234
2
+ proxy_log = /tmp/access.log
3
+ dump_dir = /tmp/dumps
4
+ log_file = /tmp/debug
5
+ pid_file = /tmp/nfagent.pid
@@ -0,0 +1,49 @@
1
+ #! /bin/sh
2
+
3
+ ### BEGIN INIT INFO
4
+ # Provides: nfagent
5
+ # Required-Start: $network $local_fs $remote_fs
6
+ # Required-Stop:
7
+ # Default-Start: 2 3 4 5
8
+ # Default-Stop: 0 1 6
9
+ # Short-Description: NetFox Agent
10
+ ### END INIT INFO
11
+
12
+ set -e
13
+ . /lib/lsb/init-functions
14
+
15
+ # /etc/init.d/pgship: start and stop the PGShip WAL Shipping Service
16
+
17
+ PGSHIP_OPTS="-f /etc/nfagent.conf -d"
18
+
19
+ # Are we running from init?
20
+ run_by_init() {
21
+ ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ]
22
+ }
23
+
24
+ export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
25
+
26
+ case "$1" in
27
+ start)
28
+ log_daemon_msg "NetFox Agent" "nfagent"
29
+ if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/pgship.pid --exec /usr/sbin/pgship.bin -- $PGSHIP_OPTS; then
30
+ log_end_msg 0
31
+ else
32
+ log_end_msg 1
33
+ fi
34
+ ;;
35
+ stop)
36
+ log_daemon_msg "NetFox Agent" "nfagent"
37
+ if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/pgship.pid; then
38
+ log_end_msg 0
39
+ else
40
+ log_end_msg 1
41
+ fi
42
+ ;;
43
+
44
+ *)
45
+ log_action_msg "Usage: /etc/init.d/nfagent {start|stop}"
46
+ exit 1
47
+ esac
48
+
49
+ exit 0
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nfagent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Draper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-22 00:00:00 +09:30
13
+ default_executable: nfagent
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: svutil
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: newgem
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.5.2
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.3
44
+ version:
45
+ description: ""
46
+ email:
47
+ - daniel@netfox.com
48
+ executables:
49
+ - nfagent
50
+ - squid_log_writer
51
+ extensions: []
52
+
53
+ extra_rdoc_files:
54
+ - PostInstall.txt
55
+ - History.txt
56
+ - Manifest.txt
57
+ files:
58
+ - Rakefile
59
+ - lib/nfagent.rb
60
+ - lib/nfagent.init
61
+ - lib/nfagent/cli.rb
62
+ - lib/nfagent/base64.rb
63
+ - lib/nfagent/encoder.rb
64
+ - lib/nfagent/server.rb
65
+ - lib/nfagent/chunk_handler.rb
66
+ - lib/nfagent/log.rb
67
+ - lib/nfagent/event.rb
68
+ - lib/nfagent/chunk.rb
69
+ - lib/nfagent/config.rb
70
+ - lib/nfagent/tail.rb
71
+ - lib/nfagent/submitter.rb
72
+ - bin/nfagent
73
+ - bin/squid_log_writer
74
+ - PostInstall.txt
75
+ - History.txt
76
+ - Manifest.txt
77
+ - nfagent.init
78
+ - nfagent.conf
79
+ has_rdoc: true
80
+ homepage:
81
+ licenses: []
82
+
83
+ post_install_message: PostInstall.txt
84
+ rdoc_options:
85
+ - --main
86
+ - README.txt
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ version:
101
+ requirements: []
102
+
103
+ rubyforge_project: nfagent
104
+ rubygems_version: 1.3.3
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Logging Agent for NetFox Online
108
+ test_files: []
109
+