tapsilog 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.
- data/CHANGELOG +2 -0
- data/Manifest +17 -0
- data/README.md +69 -0
- data/Rakefile +10 -0
- data/bin/tapsilog +69 -0
- data/lib/palmade/tapsilog.rb +19 -0
- data/lib/palmade/tapsilog/adapters.rb +9 -0
- data/lib/palmade/tapsilog/adapters/base_adapter.rb +32 -0
- data/lib/palmade/tapsilog/adapters/file_adapter.rb +54 -0
- data/lib/palmade/tapsilog/adapters/mongo_adapter.rb +61 -0
- data/lib/palmade/tapsilog/client.rb +48 -0
- data/lib/palmade/tapsilog/conn.rb +132 -0
- data/lib/palmade/tapsilog/logger.rb +87 -0
- data/lib/palmade/tapsilog/protocol.rb +87 -0
- data/lib/palmade/tapsilog/server.rb +151 -0
- data/lib/palmade/tapsilog/utils.rb +138 -0
- data/tapsilog.gemspec +35 -0
- metadata +111 -0
data/CHANGELOG
ADDED
data/Manifest
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
Manifest
|
3
|
+
README.md
|
4
|
+
Rakefile
|
5
|
+
bin/tapsilog
|
6
|
+
lib/palmade/tapsilog.rb
|
7
|
+
lib/palmade/tapsilog/adapters.rb
|
8
|
+
lib/palmade/tapsilog/adapters/base_adapter.rb
|
9
|
+
lib/palmade/tapsilog/adapters/file_adapter.rb
|
10
|
+
lib/palmade/tapsilog/adapters/mongo_adapter.rb
|
11
|
+
lib/palmade/tapsilog/client.rb
|
12
|
+
lib/palmade/tapsilog/conn.rb
|
13
|
+
lib/palmade/tapsilog/logger.rb
|
14
|
+
lib/palmade/tapsilog/protocol.rb
|
15
|
+
lib/palmade/tapsilog/server.rb
|
16
|
+
lib/palmade/tapsilog/utils.rb
|
17
|
+
tapsilog.gemspec
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
## Tapsilog, an asynchronous logging service
|
2
|
+
|
3
|
+
Tapsilog is a super customized fork of Analogger. Tapsilog allows you to attach tags to log messages so that it can be searched easily.
|
4
|
+
Currently, Tapsilog supports files and mongodb as storage backend.
|
5
|
+
|
6
|
+
**Supported adapters**
|
7
|
+
|
8
|
+
- file - Logs to files, STDOUT or STDERR
|
9
|
+
- mongo - Logs to mongoDB
|
10
|
+
|
11
|
+
**Gems required for mongoDB support**
|
12
|
+
|
13
|
+
- mongo
|
14
|
+
- bson
|
15
|
+
- bson_ext
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
**Tapsilog Server**
|
20
|
+
|
21
|
+
The best way to run tapsilog is to write a config file and call:
|
22
|
+
|
23
|
+
tapsilog -c /path/to/config_file.yml
|
24
|
+
|
25
|
+
**Sample Config**
|
26
|
+
|
27
|
+
port: 19080
|
28
|
+
host:
|
29
|
+
- 127.0.0.1
|
30
|
+
socket:
|
31
|
+
- /tmp/tapsilog.sock
|
32
|
+
daemonize: false
|
33
|
+
key: some_serious_key
|
34
|
+
|
35
|
+
syncinterval: 1
|
36
|
+
|
37
|
+
logs:
|
38
|
+
#Currently supports file or mongo
|
39
|
+
adapter: mongo
|
40
|
+
|
41
|
+
#These options are used for mongo backend.
|
42
|
+
#You can leave the host, port, user and password blank and tapsilog connects to your local mongo installation by default
|
43
|
+
|
44
|
+
#host: 127.0.0.1
|
45
|
+
#port: 1234
|
46
|
+
#user: root
|
47
|
+
#password: somepassword
|
48
|
+
database: tapsilog
|
49
|
+
|
50
|
+
services:
|
51
|
+
- service: default
|
52
|
+
target: default # This is the mongodb namespace. For file backend, use the path of log file
|
53
|
+
|
54
|
+
- service: dev.access
|
55
|
+
target: dev.access
|
56
|
+
|
57
|
+
- service: dev.bizsupport
|
58
|
+
target: dev.bizsupport
|
59
|
+
|
60
|
+
**Tapsilog Client**
|
61
|
+
|
62
|
+
The tapsilog Logger class quacks like the ruby standard Logger.
|
63
|
+
|
64
|
+
**Sample**
|
65
|
+
|
66
|
+
logger = Palmade::Tapsilog::Logger.new('default', '/tmp/tapsilog.sock', 'some_serious_key')
|
67
|
+
logger.level = Palmade::Tapsilog::Logger::DEBUG # defaults to INFO
|
68
|
+
logger.info("I am logging a message.", {:my_name => "tapsilog", :my_number => 2})
|
69
|
+
|
data/Rakefile
ADDED
data/bin/tapsilog
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'optparse'
|
5
|
+
require 'palmade/tapsilog'
|
6
|
+
|
7
|
+
module Palmade
|
8
|
+
class TapsilogExec
|
9
|
+
|
10
|
+
def self.run
|
11
|
+
@@parsed_options ||= parse_options
|
12
|
+
Palmade::Tapsilog::Server.start(@@parsed_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse_options(config = {})
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.banner = 'Usage: tapsilog [options]'
|
18
|
+
opts.separator ''
|
19
|
+
opts.on('-c','--config CONFFILE',"The configuration file to read.") do |conf|
|
20
|
+
config = symbolize_keys(YAML.load(File.read(conf)))
|
21
|
+
end
|
22
|
+
opts.on('-p','--port [PORT]',Integer,"The port to receive connections on.") do |port|
|
23
|
+
config[:port] = port
|
24
|
+
end
|
25
|
+
opts.on('-h','--host [HOST]',String,"The host to bind the connection to.") do |host|
|
26
|
+
config[:host] = host
|
27
|
+
end
|
28
|
+
opts.on('-t','--socket [SOCKET]',String,"The unix domain socket to bind connection to.") do |socket|
|
29
|
+
config[:socket] = socket
|
30
|
+
end
|
31
|
+
opts.on('-k','--key [KEY]',String,"The secret key that authenticates a valid client session.") do |secret|
|
32
|
+
config[:key] = secret
|
33
|
+
end
|
34
|
+
opts.on('-i','--interval [INTERVAL]',String,"The interval between queue writes. Defaults to 1 second.") do |interval|
|
35
|
+
config[:interval] = interval
|
36
|
+
end
|
37
|
+
opts.on('-s','--syncinterval [INTERVAL]',String,"The interval between queue syncs. Defaults to 60 seconds.") do |interval|
|
38
|
+
config[:syncinterval] = interval
|
39
|
+
end
|
40
|
+
opts.on('-d','--default [PATH]',String,"The default log destination. Defaults to stdout.") do |default|
|
41
|
+
config[:default_log] = default
|
42
|
+
end
|
43
|
+
opts.on('-x','--daemonize',"Tell the Analogger to daemonize itself.") do
|
44
|
+
config[:daemonize] = true
|
45
|
+
end
|
46
|
+
opts.on('-w','--writepid [FILENAME]',"The filename to write a PID file to.") do |pidfile|
|
47
|
+
config[:pidfile] = pidfile
|
48
|
+
end.parse!
|
49
|
+
end
|
50
|
+
config
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.symbolize_keys(hash)
|
54
|
+
hash.inject({}){|result, (key, value)|
|
55
|
+
new_key = key.kind_of?(String) ? key.to_sym : key
|
56
|
+
new_value = value.kind_of?(Hash) ? symbolize_keys(value) : value
|
57
|
+
result[new_key] = new_value
|
58
|
+
result
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
loop do
|
66
|
+
catch(:hup) {
|
67
|
+
Palmade::TapsilogExec.run
|
68
|
+
}
|
69
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module Palmade
|
6
|
+
module Tapsilog
|
7
|
+
class Timeout < Exception; end
|
8
|
+
|
9
|
+
autoload :Protocol, File.join(File.dirname(__FILE__), 'tapsilog/protocol')
|
10
|
+
autoload :Server, File.join(File.dirname(__FILE__), 'tapsilog/server')
|
11
|
+
autoload :Adapters, File.join(File.dirname(__FILE__), 'tapsilog/adapters')
|
12
|
+
autoload :Utils, File.join(File.dirname(__FILE__), 'tapsilog/utils')
|
13
|
+
|
14
|
+
autoload :Conn, File.join(File.dirname(__FILE__), 'tapsilog/conn')
|
15
|
+
autoload :Client, File.join(File.dirname(__FILE__), 'tapsilog/client')
|
16
|
+
autoload :Logger, File.join(File.dirname(__FILE__), 'tapsilog/logger')
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Palmade::Tapsilog
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
autoload :BaseAdapter, File.join(File.dirname(__FILE__), 'adapters/base_adapter')
|
5
|
+
autoload :FileAdapter, File.join(File.dirname(__FILE__), 'adapters/file_adapter')
|
6
|
+
autoload :MongoAdapter, File.join(File.dirname(__FILE__), 'adapters/mongo_adapter')
|
7
|
+
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Palmade::Tapsilog::Adapters
|
2
|
+
class BaseAdapter
|
3
|
+
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
initialize_services
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(log_message)
|
10
|
+
end
|
11
|
+
|
12
|
+
def flush
|
13
|
+
end
|
14
|
+
|
15
|
+
def close
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def initialize_services
|
21
|
+
@services = {}
|
22
|
+
|
23
|
+
@config[:services].each do |service|
|
24
|
+
service_name = service['service']
|
25
|
+
@services[service_name] = {
|
26
|
+
:target => service['target']
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Palmade::Tapsilog::Adapters
|
2
|
+
class FileAdapter < BaseAdapter
|
3
|
+
|
4
|
+
def write(log_message)
|
5
|
+
service = log_message[1]
|
6
|
+
file = get_file_descriptor(service)
|
7
|
+
file.puts(log_message.join("|"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def flush
|
11
|
+
@services.each do |name, service|
|
12
|
+
fd = service[:file]
|
13
|
+
unless fd.nil?
|
14
|
+
fd.fsync if fd.fileno > 2
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
@services.each do |name, service|
|
21
|
+
fd = service[:file]
|
22
|
+
unless fd.nil?
|
23
|
+
fd.close unless fd.closed?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def get_file_descriptor(service_name)
|
31
|
+
service_name = (@services[service_name].nil?) ? 'default' : service_name
|
32
|
+
service = @services[service_name]
|
33
|
+
|
34
|
+
if service[:file].nil?
|
35
|
+
service[:file] = open_file_descriptor(service)
|
36
|
+
end
|
37
|
+
|
38
|
+
service[:file]
|
39
|
+
end
|
40
|
+
|
41
|
+
def open_file_descriptor(service)
|
42
|
+
logfile = service[:target]
|
43
|
+
|
44
|
+
if logfile =~ /^STDOUT$/i
|
45
|
+
$stdout
|
46
|
+
elsif logfile =~ /^STDERR$/i
|
47
|
+
$stderr
|
48
|
+
else
|
49
|
+
File.open(logfile, 'ab+')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
|
3
|
+
module Palmade::Tapsilog::Adapters
|
4
|
+
class MongoAdapter < BaseAdapter
|
5
|
+
|
6
|
+
def write(log_message)
|
7
|
+
service = log_message[1]
|
8
|
+
coll = get_collection(service)
|
9
|
+
|
10
|
+
coll.insert(log_to_hash(log_message))
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
unless @conn.nil?
|
15
|
+
@conn.close if @conn.connected?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def get_collection(service_name)
|
22
|
+
service_name = (@services[service_name].nil?) ? 'default' : service_name
|
23
|
+
service = @services[service_name]
|
24
|
+
|
25
|
+
db_conn[service[:target]]
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_to_hash(log_message)
|
29
|
+
timestamp, service, pid, severity, message, tags = log_message
|
30
|
+
log_hash = {
|
31
|
+
:timestamp => timestamp,
|
32
|
+
:service => service,
|
33
|
+
:pid => pid,
|
34
|
+
:severity => severity,
|
35
|
+
:message => message,
|
36
|
+
:created_at => Time.now
|
37
|
+
}
|
38
|
+
unless tags.nil? or tags.empty?
|
39
|
+
log_hash[:tags] = Palmade::Tapsilog::Utils::query_string_to_hash(tags)
|
40
|
+
end
|
41
|
+
|
42
|
+
log_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def db_conn
|
46
|
+
if @db.nil?
|
47
|
+
mongo_conn = Mongo::Connection.new(@config[:host], @config[:port])
|
48
|
+
db_name = @config[:database] || 'tapsilog'
|
49
|
+
|
50
|
+
@db = mongo_conn.db(db_name)
|
51
|
+
if @config[:user] and @config[:password]
|
52
|
+
@db.authenticate(@config[:user], @config[:password])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@db
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Palmade::Tapsilog
|
2
|
+
class Client
|
3
|
+
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
:async => false
|
6
|
+
}
|
7
|
+
|
8
|
+
def initialize(service = 'default', target = '127.0.0.1:19070', key = nil, instance_key = nil, options = { })
|
9
|
+
@service = service.to_s
|
10
|
+
@instance_key = instance_key
|
11
|
+
|
12
|
+
@target = target
|
13
|
+
@key = key
|
14
|
+
|
15
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
16
|
+
@conn = Palmade::Tapsilog::Conn.new(@target, @key, @options[:async])
|
17
|
+
end
|
18
|
+
|
19
|
+
def log(severity, msg, tags = {}, ts = nil)
|
20
|
+
ts = Time.now if ts.nil?
|
21
|
+
conn_log(severity, msg, tags, ts)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def flush
|
26
|
+
@conn.flush
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
@conn.close
|
31
|
+
end
|
32
|
+
|
33
|
+
def closed?
|
34
|
+
@conn.closed?
|
35
|
+
end
|
36
|
+
|
37
|
+
def reconnect
|
38
|
+
@conn.reconnect!
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def conn_log(severity, msg, tags = {}, ts = nil)
|
44
|
+
ts = Time.now if ts.nil?
|
45
|
+
@conn.log(@service, @instance_key || $$, severity, msg, tags, ts)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Palmade::Tapsilog
|
4
|
+
class Conn
|
5
|
+
attr_reader :socket
|
6
|
+
|
7
|
+
MAX_TRIES = 6
|
8
|
+
|
9
|
+
def initialize(target, key, buffered = false)
|
10
|
+
if target =~ /(.+)\:(\d+)$/i
|
11
|
+
@host = $~[1]
|
12
|
+
@port = $~[2]
|
13
|
+
else
|
14
|
+
@host = target
|
15
|
+
@port = nil
|
16
|
+
end
|
17
|
+
@key = key
|
18
|
+
@socket = nil
|
19
|
+
@buffered = buffered
|
20
|
+
end
|
21
|
+
|
22
|
+
def connect(host, port)
|
23
|
+
if @socket.nil? || @socket.closed?
|
24
|
+
real_connect(host, port)
|
25
|
+
log('default', $$, 'authentication', @key)
|
26
|
+
else
|
27
|
+
@socket
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def log(service, instance_key, severity, message, tags = {}, ts = nil)
|
32
|
+
tries = 0
|
33
|
+
connect(@host, @port)
|
34
|
+
|
35
|
+
ts = Time.now if ts.nil?
|
36
|
+
tag_string = Utils::hash_to_query_string(tags)
|
37
|
+
|
38
|
+
fullmsg = ":#{service}:#{instance_key}:#{severity}:#{message}:#{tag_string}"
|
39
|
+
|
40
|
+
# Truncate below the 8192 limit on Tapsilog service
|
41
|
+
fullmsg = fullmsg[0,8190] if fullmsg.size > 8190
|
42
|
+
|
43
|
+
len = [fullmsg.length].pack('i')
|
44
|
+
|
45
|
+
begin
|
46
|
+
# first 8-bytes, is len and checksum
|
47
|
+
write "#{len}#{len}#{fullmsg}"
|
48
|
+
rescue Exception => e
|
49
|
+
STDERR.puts "Failed to write to server! Retrying... (#{tries})" +
|
50
|
+
"#{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"
|
51
|
+
|
52
|
+
if tries < MAX_TRIES
|
53
|
+
tries += 1
|
54
|
+
close_possibly_dead_conn(tries)
|
55
|
+
reconnect
|
56
|
+
retry
|
57
|
+
else
|
58
|
+
raise e
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
len
|
63
|
+
end
|
64
|
+
|
65
|
+
def flush
|
66
|
+
@socket.flush unless @socket.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def close
|
70
|
+
@socket.nil? ? nil : @socket.close rescue nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def closed?
|
74
|
+
@socket.nil? ? true : @socket.closed?
|
75
|
+
end
|
76
|
+
|
77
|
+
def reconnect!
|
78
|
+
close unless closed?
|
79
|
+
connect(@host, @port)
|
80
|
+
end
|
81
|
+
alias :reconnect :reconnect!
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def real_connect(host, port)
|
86
|
+
tries = 0
|
87
|
+
begin
|
88
|
+
if host =~ /^\/(.+)/
|
89
|
+
@socket = UNIXSocket.new(host)
|
90
|
+
else
|
91
|
+
@socket = TCPSocket.new(host, port)
|
92
|
+
@socket.sync = !@buffered
|
93
|
+
end
|
94
|
+
raise "Unable to create socket!" if @socket.nil?
|
95
|
+
rescue Exception => e
|
96
|
+
STDERR.puts "Failed to establish connection with server! Retrying... (#{tries})" +
|
97
|
+
" #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"
|
98
|
+
|
99
|
+
if tries < MAX_TRIES
|
100
|
+
tries += 1
|
101
|
+
close_possibly_dead_conn(tries)
|
102
|
+
retry
|
103
|
+
else
|
104
|
+
raise e
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
@socket
|
109
|
+
end
|
110
|
+
|
111
|
+
def write(msg, flush = false)
|
112
|
+
conn_timeout do
|
113
|
+
wrtlen = @socket.write(msg)
|
114
|
+
end
|
115
|
+
self.flush if flush
|
116
|
+
@socket
|
117
|
+
end
|
118
|
+
|
119
|
+
def close_possibly_dead_conn(tries = 0)
|
120
|
+
close unless @socket.nil? || closed?
|
121
|
+
|
122
|
+
@socket = nil
|
123
|
+
select(nil,nil,nil, tries * 0.2) if tries > 0
|
124
|
+
@socket
|
125
|
+
end
|
126
|
+
|
127
|
+
def conn_timeout(&block)
|
128
|
+
::Timeout::timeout(6, Palmade::Tapsilog::Timeout, &block)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Palmade::Tapsilog
|
2
|
+
class Logger < Client
|
3
|
+
LOG_LEVEL_TEXT = [ 'debug', 'info', 'warn', 'error', 'fatal', 'unknown' ]
|
4
|
+
|
5
|
+
DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN = (0..5).to_a
|
6
|
+
|
7
|
+
attr_accessor :level
|
8
|
+
|
9
|
+
def initialize(*args, &block)
|
10
|
+
super(*args, &block)
|
11
|
+
@level = INFO
|
12
|
+
end
|
13
|
+
|
14
|
+
def info?
|
15
|
+
@level <= INFO
|
16
|
+
end
|
17
|
+
|
18
|
+
def info(progname = nil, tags = {}, &block)
|
19
|
+
add(INFO, nil, progname, tags, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def debug?
|
23
|
+
@level <= DEBUG
|
24
|
+
end
|
25
|
+
|
26
|
+
def debug(progname = nil, tags = {}, &block)
|
27
|
+
add(DEBUG, nil, progname, tags, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def error?
|
31
|
+
@level <= ERROR
|
32
|
+
end
|
33
|
+
|
34
|
+
def error(progname = nil, tags = {}, &block)
|
35
|
+
add(ERROR, nil, progname, tags, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def fatal?
|
39
|
+
@level <= FATAL
|
40
|
+
end
|
41
|
+
|
42
|
+
def fatal(progname = nil, tags = {}, &block)
|
43
|
+
add(FATAL, nil, progname, tags, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def warn?
|
47
|
+
@level <= WARN
|
48
|
+
end
|
49
|
+
|
50
|
+
def warn(progname = nil, tags = {}, &block)
|
51
|
+
add(WARN, nil, progname, tags, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def add(severity, message = nil, progname = nil, tags = {}, &block)
|
55
|
+
case severity
|
56
|
+
when 'authentication'
|
57
|
+
return log_without_rails_extensions(severity, message)
|
58
|
+
when String, Symbol
|
59
|
+
severity = LOG_LEVEL_TEXT.index(severity.to_s.downcase) || UNKNOWN
|
60
|
+
when nil
|
61
|
+
severity = UNKNOWN
|
62
|
+
end
|
63
|
+
|
64
|
+
if severity < @level
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
68
|
+
log_level_text = LOG_LEVEL_TEXT[severity]
|
69
|
+
progname ||= @service
|
70
|
+
message = if message.nil?
|
71
|
+
if block_given?
|
72
|
+
message = yield
|
73
|
+
else
|
74
|
+
progname
|
75
|
+
end
|
76
|
+
else
|
77
|
+
message.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
log_without_rails_extensions(log_level_text, message, tags)
|
81
|
+
end
|
82
|
+
|
83
|
+
alias_method :log_without_rails_extensions, :log
|
84
|
+
alias_method :log, :add
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Palmade::Tapsilog
|
2
|
+
class Protocol < EventMachine::Connection
|
3
|
+
Ci = 'i'.freeze
|
4
|
+
Rcolon = /:/
|
5
|
+
MaxMessageLength = 8192
|
6
|
+
|
7
|
+
LoggerClass = Palmade::Tapsilog::Server
|
8
|
+
|
9
|
+
def post_init
|
10
|
+
setup
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@length = nil
|
15
|
+
@logchunk = ''
|
16
|
+
@authenticated = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive_data(data)
|
20
|
+
@logchunk << data
|
21
|
+
|
22
|
+
while @length.nil? and @logchunk.length > 7
|
23
|
+
return false unless get_length
|
24
|
+
|
25
|
+
if @length and @logchunk.length > @length
|
26
|
+
get_message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def get_length
|
34
|
+
l = @logchunk[0..3].unpack(Ci).first
|
35
|
+
ck = @logchunk[4..7].unpack(Ci).first
|
36
|
+
|
37
|
+
if l == ck and l < MaxMessageLength
|
38
|
+
@length = l +7
|
39
|
+
return true
|
40
|
+
else
|
41
|
+
peer = get_peername
|
42
|
+
peer = (peer ? ::Socket.unpack_sockaddr_in(peer)[1] : 'UNK') rescue 'UNK'
|
43
|
+
|
44
|
+
if l == ck
|
45
|
+
LoggerClass.add_log([:default, $$.to_s, :error, "Max Length Exceeded from #{peer} -- #{l}/#{MaxMessageLength}"])
|
46
|
+
else
|
47
|
+
LoggerClass.add_log([:default, $$.to_s, :error, "Checksum failed from #{peer} -- #{l}/#{ck}"])
|
48
|
+
end
|
49
|
+
|
50
|
+
close_connection
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_message
|
56
|
+
msg = @logchunk.slice!(0..@length).split(Rcolon, 6)
|
57
|
+
|
58
|
+
unless @authenticated
|
59
|
+
@authenticated = authenticate_message(msg)
|
60
|
+
end
|
61
|
+
|
62
|
+
if @authenticated
|
63
|
+
msg[0] = nil
|
64
|
+
msg.shift
|
65
|
+
|
66
|
+
msg[0] = msg[0].to_s.gsub(/[^a-zA-Z0-9\-\_\.]\s/, '').strip
|
67
|
+
|
68
|
+
LoggerClass.add_log(msg)
|
69
|
+
@length = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def authenticate_message(msg)
|
74
|
+
if msg[4] == LoggerClass.key
|
75
|
+
return true
|
76
|
+
else
|
77
|
+
peer = get_peername
|
78
|
+
peer = (peer ? ::Socket.unpack_sockaddr_in(peer)[1] : 'UNK') rescue 'UNK'
|
79
|
+
|
80
|
+
LoggerClass.add_log([:default, $$.to_s, :error, "Invalid key from #{peer} -- #{msg.last}"])
|
81
|
+
close_connection
|
82
|
+
return false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Palmade::Tapsilog
|
2
|
+
class Server
|
3
|
+
|
4
|
+
SeverityLevels = [:debug, :info, :warn, :error, :fatal]
|
5
|
+
|
6
|
+
attr_reader :now
|
7
|
+
|
8
|
+
def self.start(config, protocol = Palmade::Tapsilog::Protocol)
|
9
|
+
@config = config
|
10
|
+
@protocol = protocol
|
11
|
+
@tsocks = []
|
12
|
+
@usocks = []
|
13
|
+
@queue = []
|
14
|
+
boot
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.add_log(log)
|
18
|
+
@queue << ([@now] + log)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.key
|
22
|
+
@config[:key].to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def self.boot
|
28
|
+
write_pid_file if @config[:pidfile]
|
29
|
+
daemonize if @config[:daemonize]
|
30
|
+
|
31
|
+
load_adapter
|
32
|
+
|
33
|
+
trap("INT") { exit }
|
34
|
+
trap("TERM") { exit }
|
35
|
+
trap("HUP") { throw :hup }
|
36
|
+
|
37
|
+
start_server
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.write_pid_file
|
41
|
+
if File.exists?(@config[:pidfile])
|
42
|
+
begin
|
43
|
+
pid = File.read(@config[:pidfile]).strip
|
44
|
+
Process.kill(0, pid.to_i)
|
45
|
+
raise "Another instance of tapsilog seems to be running! (#{pid})"
|
46
|
+
rescue Errno::ESRCH
|
47
|
+
File.delete(@config[Cpidfile])
|
48
|
+
STDERR.puts "Stale PID (#{pid}) removed"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
File.open(@config[Cpidfile],'w+') {|fh| fh.puts $$}
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.daemonize
|
56
|
+
if (child_pid = fork)
|
57
|
+
puts "Forked PID #{child_pid}"
|
58
|
+
exit!
|
59
|
+
end
|
60
|
+
Process.setsid
|
61
|
+
|
62
|
+
rescue Exception
|
63
|
+
puts "Platform (#{RUBY_PLATFORM}) does not appear to support fork/setsid; skipping"
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.load_adapter
|
67
|
+
raise "Missing logs section in config file" unless @config[:logs]
|
68
|
+
|
69
|
+
adapter_name = @config[:logs][:adapter] || "file"
|
70
|
+
class_name = "#{adapter_name.capitalize}Adapter"
|
71
|
+
adapter = Palmade::Tapsilog::Adapters.const_get(class_name)
|
72
|
+
@adapter = adapter.new(@config[:logs])
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.start_server
|
76
|
+
prepare_server
|
77
|
+
begin
|
78
|
+
EventMachine.run {
|
79
|
+
start_servers
|
80
|
+
EventMachine.add_periodic_timer(1) { update_now }
|
81
|
+
EventMachine.add_periodic_timer(@config[:interval]) { write_queue }
|
82
|
+
EventMachine.add_periodic_timer(@config[:syncinterval]) { flush_queue }
|
83
|
+
}
|
84
|
+
ensure
|
85
|
+
cleanup
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.prepare_server
|
90
|
+
if @config[:socket]
|
91
|
+
@usocks = @config[:socket]
|
92
|
+
@usocks = [ @usocks ] unless @usocks.is_a? Array
|
93
|
+
|
94
|
+
@usocks.each do |usock|
|
95
|
+
raise "Socket file already exists! (#{usock})" if File.exists?(usock)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if @config[:host]
|
100
|
+
@tsocks = @config[:host]
|
101
|
+
@tsocks = [ @tsocks ] unless @tsocks.is_a? Array
|
102
|
+
|
103
|
+
@tsocks.each do |tsock|
|
104
|
+
raise "Port already in use! #{tsock}:#{@config[:port]}" if Utils.is_port_open?(tsock, @config[:port])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.start_servers
|
110
|
+
@usocks.each do |usock|
|
111
|
+
raise "Socket file already exists! (#{usock})" if File.exists?(usock)
|
112
|
+
|
113
|
+
STDERR.puts "Listening to: #{usock}"
|
114
|
+
EventMachine.start_unix_domain_server(usock, @protocol)
|
115
|
+
File.chmod(0777, usock)
|
116
|
+
end
|
117
|
+
|
118
|
+
@tsocks.each do |tsock|
|
119
|
+
raise "Port already in use! #{tsock}:#{@config[:port]}" if Utils.is_port_open?(tsock, @config[:port])
|
120
|
+
|
121
|
+
STDERR.puts "Listening to: #{tsock}:#{@config[:port]}"
|
122
|
+
EventMachine.start_server(tsock, @config[:port], @protocol)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.cleanup
|
127
|
+
@adapter.close unless @adapter.nil?
|
128
|
+
@usocks.each do |usock|
|
129
|
+
File.delete(usock) if File.exists?(usock)
|
130
|
+
end
|
131
|
+
File.delete(@config[:pidfile]) if @config[:pidfile] and File.exists?(@config[:pidfile])
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.update_now
|
135
|
+
@now = Time.now.strftime('%Y/%m/%d %H:%M:%S')
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.write_queue
|
139
|
+
@queue.each do |log_message|
|
140
|
+
next unless SeverityLevels.include?(log_message[3].to_sym)
|
141
|
+
@adapter.write(log_message)
|
142
|
+
end
|
143
|
+
@queue.clear
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.flush_queue
|
147
|
+
@adapter.flush
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Palmade::Tapsilog
|
4
|
+
class Utils
|
5
|
+
|
6
|
+
def self.is_port_open?(ip, port)
|
7
|
+
begin
|
8
|
+
::Timeout::timeout(1) do
|
9
|
+
begin
|
10
|
+
s = TCPSocket.new(ip, port)
|
11
|
+
s.close
|
12
|
+
return true
|
13
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
rescue ::Timeout::Error
|
18
|
+
end
|
19
|
+
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Taken from github accumulator/uri
|
24
|
+
|
25
|
+
def self.query_string_to_hash(query_string, options={})
|
26
|
+
defaults = {:notation => :subscript}
|
27
|
+
options = defaults.merge(options)
|
28
|
+
if ![:flat, :dot, :subscript].include?(options[:notation])
|
29
|
+
raise ArgumentError,
|
30
|
+
"Invalid notation. Must be one of: [:flat, :dot, :subscript]."
|
31
|
+
end
|
32
|
+
dehash = lambda do |hash|
|
33
|
+
hash.each do |(key, value)|
|
34
|
+
if value.kind_of?(Hash)
|
35
|
+
hash[key] = dehash.call(value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
|
39
|
+
hash.sort.inject([]) do |accu, (key, value)|
|
40
|
+
accu << value; accu
|
41
|
+
end
|
42
|
+
else
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return nil if query_string == nil
|
47
|
+
return ((query_string.split("&").map do |pair|
|
48
|
+
pair.split("=", -1) if pair && pair != ""
|
49
|
+
end).compact.inject({}) do |accumulator, (key, value)|
|
50
|
+
value = true if value.nil?
|
51
|
+
key = CGI::unescape(key)
|
52
|
+
if value != true
|
53
|
+
value = CGI::unescape(value).gsub(/\+/, " ")
|
54
|
+
end
|
55
|
+
if options[:notation] == :flat
|
56
|
+
if accumulator[key]
|
57
|
+
raise ArgumentError, "Key was repeated: #{key.inspect}"
|
58
|
+
end
|
59
|
+
accumulator[key] = value
|
60
|
+
else
|
61
|
+
if options[:notation] == :dot
|
62
|
+
array_value = false
|
63
|
+
subkeys = key.split(".")
|
64
|
+
elsif options[:notation] == :subscript
|
65
|
+
array_value = !!(key =~ /\[\]$/)
|
66
|
+
subkeys = key.split(/[\[\]]+/)
|
67
|
+
end
|
68
|
+
current_hash = accumulator
|
69
|
+
for i in 0...(subkeys.size - 1)
|
70
|
+
subkey = subkeys[i]
|
71
|
+
current_hash[subkey] = {} unless current_hash[subkey]
|
72
|
+
current_hash = current_hash[subkey]
|
73
|
+
end
|
74
|
+
if array_value
|
75
|
+
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
|
76
|
+
current_hash[subkeys.last] << value
|
77
|
+
else
|
78
|
+
current_hash[subkeys.last] = value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
accumulator
|
82
|
+
end).inject({}) do |accumulator, (key, value)|
|
83
|
+
accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
|
84
|
+
accumulator
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.hash_to_query_string(hash)
|
89
|
+
# Check for frozenness
|
90
|
+
if hash == nil
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
if !hash.respond_to?(:to_hash)
|
94
|
+
raise TypeError, "Can't convert #{hash.class} into Hash."
|
95
|
+
end
|
96
|
+
hash = hash.to_hash
|
97
|
+
hash = hash.map do |key, value|
|
98
|
+
key = key.to_s if key.kind_of?(Symbol)
|
99
|
+
[key, value]
|
100
|
+
end
|
101
|
+
hash.sort! # Useful default for OAuth and caching
|
102
|
+
|
103
|
+
# Algorithm shamelessly stolen from Julien Genestoux, slightly modified
|
104
|
+
buffer = ""
|
105
|
+
stack = []
|
106
|
+
e = lambda do |component|
|
107
|
+
CGI::escape(component.to_s)
|
108
|
+
end
|
109
|
+
hash.each do |key, value|
|
110
|
+
if value.kind_of?(Hash)
|
111
|
+
stack << [key, value]
|
112
|
+
elsif value.kind_of?(Array)
|
113
|
+
stack << [
|
114
|
+
key,
|
115
|
+
value.inject({}) { |accu, x| accu[accu.size.to_s] = x; accu }
|
116
|
+
]
|
117
|
+
elsif value == true
|
118
|
+
buffer << "#{e.call(key)}&"
|
119
|
+
else
|
120
|
+
buffer << "#{e.call(key)}=#{e.call(value)}&"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
stack.each do |(parent, hash)|
|
124
|
+
(hash.sort_by { |key| key.to_s }).each do |(key, value)|
|
125
|
+
if value.kind_of?(Hash)
|
126
|
+
stack << ["#{parent}[#{key}]", value]
|
127
|
+
elsif value == true
|
128
|
+
buffer << "#{parent}[#{e.call(key)}]&"
|
129
|
+
else
|
130
|
+
buffer << "#{parent}[#{e.call(key)}]=#{e.call(value)}&"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
buffer.chop
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
data/tapsilog.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{tapsilog}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Palmade"]
|
9
|
+
s.date = %q{2010-09-03}
|
10
|
+
s.default_executable = %q{tapsilog}
|
11
|
+
s.description = %q{Hydrid app-level logger from Palmade. Analogger fork.}
|
12
|
+
s.email = %q{}
|
13
|
+
s.executables = ["tapsilog"]
|
14
|
+
s.extra_rdoc_files = ["CHANGELOG", "README.md", "bin/tapsilog", "lib/palmade/tapsilog.rb", "lib/palmade/tapsilog/adapters.rb", "lib/palmade/tapsilog/adapters/base_adapter.rb", "lib/palmade/tapsilog/adapters/file_adapter.rb", "lib/palmade/tapsilog/adapters/mongo_adapter.rb", "lib/palmade/tapsilog/client.rb", "lib/palmade/tapsilog/conn.rb", "lib/palmade/tapsilog/logger.rb", "lib/palmade/tapsilog/protocol.rb", "lib/palmade/tapsilog/server.rb", "lib/palmade/tapsilog/utils.rb"]
|
15
|
+
s.files = ["CHANGELOG", "Manifest", "README.md", "Rakefile", "bin/tapsilog", "lib/palmade/tapsilog.rb", "lib/palmade/tapsilog/adapters.rb", "lib/palmade/tapsilog/adapters/base_adapter.rb", "lib/palmade/tapsilog/adapters/file_adapter.rb", "lib/palmade/tapsilog/adapters/mongo_adapter.rb", "lib/palmade/tapsilog/client.rb", "lib/palmade/tapsilog/conn.rb", "lib/palmade/tapsilog/logger.rb", "lib/palmade/tapsilog/protocol.rb", "lib/palmade/tapsilog/server.rb", "lib/palmade/tapsilog/utils.rb", "tapsilog.gemspec"]
|
16
|
+
s.homepage = %q{}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tapsilog", "--main", "README.md"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{tapsilog}
|
20
|
+
s.rubygems_version = %q{1.3.7}
|
21
|
+
s.summary = %q{Hydrid app-level logger from Palmade. Analogger fork.}
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tapsilog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Palmade
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-03 00:00:00 +08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: eventmachine
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Hydrid app-level logger from Palmade. Analogger fork.
|
34
|
+
email: ""
|
35
|
+
executables:
|
36
|
+
- tapsilog
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- CHANGELOG
|
41
|
+
- README.md
|
42
|
+
- bin/tapsilog
|
43
|
+
- lib/palmade/tapsilog.rb
|
44
|
+
- lib/palmade/tapsilog/adapters.rb
|
45
|
+
- lib/palmade/tapsilog/adapters/base_adapter.rb
|
46
|
+
- lib/palmade/tapsilog/adapters/file_adapter.rb
|
47
|
+
- lib/palmade/tapsilog/adapters/mongo_adapter.rb
|
48
|
+
- lib/palmade/tapsilog/client.rb
|
49
|
+
- lib/palmade/tapsilog/conn.rb
|
50
|
+
- lib/palmade/tapsilog/logger.rb
|
51
|
+
- lib/palmade/tapsilog/protocol.rb
|
52
|
+
- lib/palmade/tapsilog/server.rb
|
53
|
+
- lib/palmade/tapsilog/utils.rb
|
54
|
+
files:
|
55
|
+
- CHANGELOG
|
56
|
+
- Manifest
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/tapsilog
|
60
|
+
- lib/palmade/tapsilog.rb
|
61
|
+
- lib/palmade/tapsilog/adapters.rb
|
62
|
+
- lib/palmade/tapsilog/adapters/base_adapter.rb
|
63
|
+
- lib/palmade/tapsilog/adapters/file_adapter.rb
|
64
|
+
- lib/palmade/tapsilog/adapters/mongo_adapter.rb
|
65
|
+
- lib/palmade/tapsilog/client.rb
|
66
|
+
- lib/palmade/tapsilog/conn.rb
|
67
|
+
- lib/palmade/tapsilog/logger.rb
|
68
|
+
- lib/palmade/tapsilog/protocol.rb
|
69
|
+
- lib/palmade/tapsilog/server.rb
|
70
|
+
- lib/palmade/tapsilog/utils.rb
|
71
|
+
- tapsilog.gemspec
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: ""
|
74
|
+
licenses: []
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options:
|
78
|
+
- --line-numbers
|
79
|
+
- --inline-source
|
80
|
+
- --title
|
81
|
+
- Tapsilog
|
82
|
+
- --main
|
83
|
+
- README.md
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
segments:
|
100
|
+
- 1
|
101
|
+
- 2
|
102
|
+
version: "1.2"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project: tapsilog
|
106
|
+
rubygems_version: 1.3.7
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Hydrid app-level logger from Palmade. Analogger fork.
|
110
|
+
test_files: []
|
111
|
+
|