analogger 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ # A support module for the test suite. This provides a win32 aware
2
+ # mechanism for doing fork/exec operations. It requires win32/process
3
+ # to be installed, however.
4
+ #
5
+ module SwiftcoreTestSupport
6
+ @run_modes = []
7
+
8
+ def self.create_process(args)
9
+ @fork_ok = true unless @fork_ok == false
10
+ pid = nil
11
+ begin
12
+ raise NotImplementedError unless @fork_ok
13
+ unless pid = fork
14
+ Dir.chdir args[:dir]
15
+ exec(*args[:cmd])
16
+ end
17
+ rescue NotImplementedError
18
+ @fork_ok = false
19
+ begin
20
+ require 'rubygems'
21
+ rescue LoadError
22
+ end
23
+
24
+ begin
25
+ require 'win32/process'
26
+ rescue LoadError
27
+ raise "Please install win32-process to run all tests on a Win32 platform. 'gem install win32-process' or http://rubyforge.org/projects/win32utils"
28
+ end
29
+ cwd = Dir.pwd
30
+ Dir.chdir args[:dir]
31
+ pid = Process.create(:app_name => args[:cmd].join(' '))
32
+ Dir.chdir cwd
33
+ end
34
+ pid
35
+ end
36
+
37
+ def self.test_dir(dir)
38
+ File.dirname(File.expand_path(dir))
39
+ end
40
+
41
+ def self.cd_to_test_dir(dir)
42
+ Dir.chdir(File.dirname(File.expand_path(dir)))
43
+ end
44
+
45
+ def self.set_src_dir
46
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../src'))
47
+ end
48
+
49
+ @announcements = {}
50
+ def self.announce(section,msg)
51
+ unless @announcements.has_key?(section)
52
+ puts "\n\n"
53
+ puts msg,"#{'=' * msg.length}\n\n"
54
+ @announcements[section] = true
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,26 @@
1
+ #!ruby
2
+
3
+ basedir = File.dirname(__FILE__)
4
+ $:.push(basedir)
5
+
6
+ require 'external/package'
7
+ require 'rbconfig'
8
+ begin
9
+ require 'rubygems'
10
+ rescue LoadError
11
+ end
12
+
13
+ Dir.chdir(basedir)
14
+ Package.setup("1.0") {
15
+ name "Swiftcore Analogger"
16
+
17
+ translate(:lib, 'src/' => '')
18
+ translate(:bin, 'bin/' => '')
19
+ lib(*Dir["src/swiftcore/**/*.rb"])
20
+ ri(*Dir["src/swiftcore/**/*.rb"])
21
+ bin "bin/analogger"
22
+
23
+ unit_test "test/TC_Analogger.rb"
24
+
25
+ true
26
+ }
@@ -0,0 +1,293 @@
1
+ require 'socket'
2
+ begin
3
+ load_attempted ||= false
4
+ require 'eventmachine'
5
+ rescue LoadError => e
6
+ unless load_attempted
7
+ load_attempted = true
8
+ require 'rubygems'
9
+ retry
10
+ end
11
+ raise e
12
+ end
13
+
14
+ module Swiftcore
15
+ class Analogger
16
+ C_colon = ':'.freeze
17
+ C_bar = '|'.freeze
18
+ Ccull = 'cull'.freeze
19
+ Cdaemonize = 'daemonize'.freeze
20
+ Cdefault = 'default'.freeze
21
+ Cdefault_log = 'default_log'.freeze
22
+ Chost = 'host'.freeze
23
+ Cinterval = 'interval'.freeze
24
+ Ckey = 'key'.freeze
25
+ Clogfile = 'logfile'.freeze
26
+ Clogs = 'logs'.freeze
27
+ Cport = 'port'.freeze
28
+ Csecret = 'secret'.freeze
29
+ Cservice = 'service'.freeze
30
+ Clevels = 'levels'.freeze
31
+ Csyncinterval = 'syncinterval'.freeze
32
+ Cpidfile = 'pidfile'.freeze
33
+ DefaultSeverityLevels = ['debug','info','warn','error','fatal'].inject({}){|h,k|h[k]=true;h}
34
+
35
+ class NoPortProvided < Exception; def to_s; "The port to bind to was not provided."; end; end
36
+ class BadPort < Exception
37
+ def initialize(port)
38
+ @port = port
39
+ end
40
+
41
+ def to_s; "The port provided (#{@port}) is invalid."; end
42
+ end
43
+
44
+ class << self
45
+ def start(config,protocol = AnaloggerProtocol)
46
+ @config = config
47
+ daemonize if @config[Cdaemonize]
48
+ File.open(@config[Cpidfile],'w+') {|fh| fh.puts $$} if @config[Cpidfile]
49
+ @logs = Hash.new {|h,k| h[k] = new_log(k)}
50
+ @queue = Hash.new {|h,k| h[k] = []}
51
+ postprocess_config_load
52
+ check_config_settings
53
+ populate_logs
54
+ set_config_defaults
55
+ @rcount = 0
56
+ @wcount = 0
57
+ trap("INT") {cleanup;exit}
58
+ trap("TERM") {cleanup;exit}
59
+ trap("HUP") {cleanup;throw :hup}
60
+ EventMachine.run {
61
+ EventMachine.start_server @config[Chost], @config[Cport], protocol
62
+ EventMachine.add_periodic_timer(1) {Analogger.update_now}
63
+ EventMachine.add_periodic_timer(@config[Cinterval]) {write_queue}
64
+ EventMachine.add_periodic_timer(@config[Csyncinterval]) {flush_queue}
65
+ }
66
+ end
67
+
68
+ def daemonize
69
+ if (child_pid = fork)
70
+ puts "PID #{child_pid}" unless @config[Cpidfile]
71
+ exit!
72
+ end
73
+ Process.setsid
74
+
75
+ rescue Exception
76
+ puts "Platform (#{RUBY_PLATFORM}) does not appear to support fork/setsid; skipping"
77
+ end
78
+
79
+ def new_log(facility = Cdefault, levels = @config[Clevels] || DefaultSeverityLevels, log = @config[Cdefault_log], cull = true)
80
+ Log.new({Cservice => facility, Clevels => levels, Clogfile => log, Ccull => cull})
81
+ end
82
+
83
+ def cleanup
84
+ @logs.each do |service,l|
85
+ l.logfile.fsync if !l.logfile.closed? and l.logfile.fileno > 2
86
+ l.logfile.close unless l.logfile.closed? or l.logfile.fileno < 3
87
+ end
88
+ end
89
+
90
+ def update_now
91
+ @now = Time.now.strftime('%Y/%m/%d %H:%M:%S')
92
+ end
93
+
94
+ def config
95
+ @config
96
+ end
97
+
98
+ def config=(conf)
99
+ @config = conf
100
+ end
101
+
102
+ def populate_logs
103
+ @config[Clogs].each do |log|
104
+ next unless log[Cservice]
105
+ if Array === log[Cservice]
106
+ log[Cservice].each do |loglog|
107
+ @logs[loglog] = new_log(loglog,log[Clevels],logfile_destination(log[Clogfile]),log[Ccull])
108
+ end
109
+ else
110
+ @logs[log[Cservice]] = new_log(log[Cservice],log[Clevels],logfile_destination(log[Clogfile]),log[Ccull])
111
+ end
112
+ end
113
+ end
114
+
115
+ def postprocess_config_load
116
+ @config[Clogs] ||= []
117
+ if @config[Clevels]
118
+ @config[Clevels] = normalize_levels(@config[Clevels])
119
+ end
120
+
121
+ @config[Clogs].each do |log|
122
+ log[Clevels] = normalize_levels(log[Clevels])
123
+ end
124
+ end
125
+
126
+ def normalize_levels(levels)
127
+ if String === levels and levels =~ /,/
128
+ levels.split(/,/).inject({}) {|h,k| h[k.to_s] = true; h}
129
+ elsif Array === levels
130
+ levels.inject({}) {|h,k| h[k.to_s] = true; h}
131
+ elsif levels.nil?
132
+ DefaultSeverityLevels
133
+ elsif !(Hash === levels)
134
+ [levels.to_s => true]
135
+ else
136
+ levels
137
+ end
138
+ end
139
+
140
+ def check_config_settings
141
+ raise NoPortProvided unless @config[Cport]
142
+ raise BadPort.new(@config[Cport]) unless @config[Cport].to_i > 0
143
+ end
144
+
145
+ def set_config_defaults
146
+ @config[Chost] ||= '127.0.0.1'
147
+ @config[Cinterval] ||= 1
148
+ @config[Csyncinterval] ||= 60
149
+ @config[Csyncinterval] = nil if @config[Csyncinterval] == 0
150
+ @config[Cdefault_log] = @config[Cdefault_log].nil? || @config[Cdefault_log] == '-' ? 'STDOUT' : @config[Cdefault_log]
151
+ @config[Cdefault_log] = logfile_destination(@config[Cdefault_log])
152
+ @logs['default'] = new_log
153
+ end
154
+
155
+ def logfile_destination(logfile)
156
+ if logfile =~ /^STDOUT$/i
157
+ $stdout
158
+ elsif logfile =~ /^STDERR$/i
159
+ $stderr
160
+ else
161
+ File.open(logfile,'ab+')
162
+ end
163
+ end
164
+
165
+ def add_log(log)
166
+ @queue[log.first] << log
167
+ @rcount += 1
168
+ end
169
+
170
+ def write_queue
171
+ @queue.each do |service,q|
172
+ last_sv = nil
173
+ last_m = nil
174
+ last_count = 0
175
+ next unless log = @logs[service]
176
+ lf = log.logfile
177
+ cull = log.cull
178
+ levels = log.levels
179
+ q.each do |m|
180
+ next unless levels.has_key?(m[1])
181
+ if cull
182
+ if m.last == last_m and m[0..1] == last_sv
183
+ last_count += 1
184
+ next
185
+ elsif last_count > 0
186
+ lf.puts "#{@now}|#{last_sv.join(C_bar)}|Last message repeated #{last_count} times"
187
+ last_sv = last_m = nil
188
+ last_count = 0
189
+ end
190
+ lf.puts "#{@now}|#{m.join(C_bar)}"
191
+ last_m = m.last
192
+ last_sv = m[0..1]
193
+ else
194
+ lf.puts "#{@now}|#{m.join(C_bar)}"
195
+ end
196
+ @wcount += 1
197
+ end
198
+ lf.puts "#{@now}|#{last_sv.join(C_bar)}|Last message repeated #{last_count} times" if cull and last_count > 0
199
+ end
200
+ @queue.each {|service,q| q.clear}
201
+ end
202
+
203
+ def flush_queue
204
+ @logs.each_value {|l| l.logfile.fsync if l.logfile.fileno > 2}
205
+ end
206
+
207
+ def key
208
+ @config[Ckey].to_s
209
+ end
210
+
211
+ end
212
+
213
+ end
214
+
215
+ class Log
216
+ attr_reader :service, :levels, :logfile, :cull
217
+
218
+ def initialize(spec)
219
+ @service = spec[Analogger::Cservice]
220
+ @levels = spec[Analogger::Clevels]
221
+ @logfile = spec[Analogger::Clogfile]
222
+ @cull = spec[Analogger::Ccull]
223
+ end
224
+
225
+ def to_s
226
+ "service: #{@service}\nlevels: #{@levels.inspect}\nlogfile: #{@logfile}\ncull: #{@cull}\n"
227
+ end
228
+ end
229
+
230
+ class AnaloggerProtocol < EventMachine::Connection
231
+ Ci = 'i'.freeze
232
+ Rcolon = /:/
233
+ MaxMessageLength = 8192
234
+
235
+ LoggerClass = Analogger
236
+
237
+ def post_init
238
+ setup
239
+ end
240
+
241
+ def setup
242
+ @length = nil
243
+ @logchunk = ''
244
+ @authenticated = nil
245
+ end
246
+
247
+ def receive_data data
248
+ @logchunk << data
249
+ decompose = true
250
+ while decompose
251
+ unless @length
252
+ if @logchunk.length > 7
253
+ l = @logchunk[0..3].unpack(Ci).first
254
+ ck = @logchunk[4..7].unpack(Ci).first
255
+ if l == ck and l < MaxMessageLength
256
+ @length = l + 7
257
+ else
258
+ decompose = false
259
+ peer = get_peername
260
+ peer = peer ? ::Socket.unpack_sockaddr_in(peer)[1] : 'UNK'
261
+ if l == ck
262
+ LoggerClass.add_log([:default,:error,"Max Length Exceeded from #{peer} -- #{l}/#{MaxMessageLength}"])
263
+ close_connection
264
+ else
265
+ LoggerClass.add_log([:default,:error,"checksum failed from #{peer} -- #{l}/#{ck}"])
266
+ close_connection
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ if @length and @logchunk.length > @length
273
+ msg = @logchunk.slice!(0..@length).split(Rcolon,4)
274
+ unless @authenticated
275
+ if msg.last == LoggerClass.key
276
+ @authenticated = true
277
+ else
278
+ close_connection
279
+ end
280
+ else
281
+ msg[0] = nil
282
+ msg.shift
283
+ LoggerClass.add_log(msg)
284
+ end
285
+ @length = nil
286
+ else
287
+ decompose = false
288
+ end
289
+ end
290
+ end
291
+
292
+ end
293
+ end
@@ -0,0 +1,103 @@
1
+ require 'socket'
2
+ include Socket::Constants
3
+
4
+ module Swiftcore
5
+ module Analogger
6
+
7
+ # Swift::Analogger::Client is the client library for writing logging
8
+ # messages to the Swift Analogger asynchronous logging server.
9
+ #
10
+ # To use the Analogger client, instantiate an instance of the Client
11
+ # class.
12
+ #
13
+ # logger = Swift::Analogger::Client.new(:myapplog,'127.0.0.1',12345)
14
+ #
15
+ # Four arguments are accepted when a new Client is created. The first
16
+ # is the name of the logging facility that this Client will write to.
17
+ # The second is the hostname where the Analogger process is running,
18
+ # and the third is the port number that it is listening on for
19
+ # connections.
20
+ #
21
+ # The fourth argument is optional. Analogger can require an
22
+ # authentication key before it will allow logging clients to use its
23
+ # facilities. If the Analogger that one is connecting to requires
24
+ # an authentication key, it must be passed to the new() call as the
25
+ # fourth argument. If the key is incorrect, the connection will be
26
+ # closed.
27
+ #
28
+ # If a Client connects to the Analogger using a facility that is
29
+ # undefined in the Analogger, the log messages will still be accepted,
30
+ # but they will be dumped to the default logging destination.
31
+ #
32
+ # Once connected, the Client is ready to deliver messages to the
33
+ # Analogger. To send a messagine, the log() method is used:
34
+ #
35
+ # logger.log(:debug,"The logging client is now connected.")
36
+ #
37
+ # The log() method takes two arguments. The first is the severity of
38
+ # the message, and the second is the message itself. The default
39
+ # Analogger severity levels are the same as in the standard Ruby
40
+ #
41
+ class Client
42
+ Cauthentication = 'authentication'.freeze
43
+ Ci = 'i'.freeze
44
+
45
+ def initialize(service = 'default', host = '127.0.0.1' , port = 6766, key = nil)
46
+ @service = service.to_s
47
+ @key = key
48
+ @host = host
49
+ @port = port
50
+ connect(host,port)
51
+ end
52
+
53
+ def connect(host,port)
54
+ tries ||= 0
55
+ @socket = Socket.new(AF_INET,SOCK_STREAM,0)
56
+ sockaddr = Socket.pack_sockaddr_in(port,host)
57
+ @socket.connect(sockaddr)
58
+ log(Cauthentication,"#{@key}")
59
+ rescue Exception => e
60
+ if tries < 3
61
+ tries += 1
62
+ @socket.close unless @socket.closed?
63
+ @socket = nil
64
+ select(nil,nil,nil,tries * 0.2) if tries > 0
65
+ retry
66
+ else
67
+ raise e
68
+ end
69
+ end
70
+
71
+ def reconnect
72
+ connect(@host,@port)
73
+ end
74
+
75
+ def log(severity,msg)
76
+ tries ||= 0
77
+ fullmsg = ":#{@service}:#{severity}:#{msg}"
78
+ len = [fullmsg.length].pack(Ci)
79
+ @socket.write "#{len}#{len}#{fullmsg}"
80
+ rescue Exception => e
81
+ if tries < 3
82
+ tries += 1
83
+ @socket.close unless @socket.closed?
84
+ @socket = nil
85
+ select(nil,nil,nil,tries) if tries > 0
86
+ reconnect
87
+ retry
88
+ else
89
+ raise e
90
+ end
91
+ end
92
+
93
+ def close
94
+ @socket.close
95
+ end
96
+
97
+ def closed?
98
+ @socket.closed?
99
+ end
100
+
101
+ end
102
+ end
103
+ end