nfagent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+