tapsilog 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,2 +1,8 @@
1
- v0.0.1 Initial fork from internally forked analogger.
1
+ v0.2.0
2
+ Improved command line interface
3
+ Includes tapsilog_tail utility
4
+ Added support for autocreate
5
+ Added proxy backend
2
6
 
7
+ v0.1.0
8
+ Includes mongoDB support, and support for tagging logs.
data/INSTALL ADDED
@@ -0,0 +1,60 @@
1
+ Swiftcore Analogger 0.5
2
+
3
+ Homepage:: http://analogger.swiftcore.org
4
+ Copyright:: (C) 2007 by Kirk Haines. All Rights Reserved.
5
+ Email:: wyhaines@gmail.com
6
+
7
+
8
+ Installation
9
+ ------------
10
+
11
+ To install analogger:
12
+
13
+ ruby setup.rb
14
+
15
+ The analogger executable will be installed into the ruby installation's
16
+ bindir, along with server and client libraries into the site_lib. The
17
+ rdoc documentation will also be generated.
18
+
19
+ ruby setup.rb --help
20
+
21
+ to se a full list of options.
22
+
23
+
24
+ Quickstart
25
+ ----------
26
+
27
+ To start an Analogger instance, first create a configuration file:
28
+
29
+ port: 6766
30
+ host: 127.0.0.1
31
+ default_log: /var/log/weblogs/default
32
+ daemonize: true
33
+ syncinterval: 60
34
+ logs:
35
+ - service: bigapp
36
+ logfile: /var/log/bigapp
37
+ cull: true
38
+ - service:
39
+ - smallapp1
40
+ - smallapp2
41
+ logfile: /var/log/smallapps
42
+ cull: true
43
+ - service: newsletter_sender
44
+ logfile: /var/log/newsletter.log
45
+ cull: false
46
+
47
+
48
+ Then start the analogger:
49
+
50
+ /usr/bin/env analogger -c config_file
51
+
52
+
53
+ To use the client library to connect to an Analogger instance and send
54
+ logging messages to it:
55
+
56
+ require 'swiftcore/Analogger/Client'
57
+
58
+ logger = Swiftcdore::Analogger::Client.new('smallapp1','127.0.0.1','6766')
59
+
60
+ logger.log('info','This is a log message.')
data/Manifest CHANGED
@@ -1,13 +1,16 @@
1
1
  CHANGELOG
2
+ INSTALL
2
3
  Manifest
3
4
  README.md
4
5
  Rakefile
5
6
  bin/tapsilog
7
+ bin/tapsilog_tail
6
8
  lib/palmade/tapsilog.rb
7
9
  lib/palmade/tapsilog/adapters.rb
8
10
  lib/palmade/tapsilog/adapters/base_adapter.rb
9
11
  lib/palmade/tapsilog/adapters/file_adapter.rb
10
12
  lib/palmade/tapsilog/adapters/mongo_adapter.rb
13
+ lib/palmade/tapsilog/adapters/proxy_adapter.rb
11
14
  lib/palmade/tapsilog/client.rb
12
15
  lib/palmade/tapsilog/conn.rb
13
16
  lib/palmade/tapsilog/logger.rb
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ## Tapsilog, an asynchronous logging service
1
+ # Tapsilog, an asynchronous logging service
2
2
 
3
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
4
  Currently, Tapsilog supports files and mongodb as storage backend.
@@ -7,6 +7,7 @@
7
7
 
8
8
  - file - Logs to files, STDOUT or STDERR
9
9
  - mongo - Logs to mongoDB
10
+ - proxy - Forwards logs to another tapsilog server
10
11
 
11
12
  **Gems required for mongoDB support**
12
13
 
@@ -14,48 +15,79 @@
14
15
  - bson
15
16
  - bson_ext
16
17
 
18
+ **Compatibility with analogger**
19
+
20
+ Tapsilog is mostly compatible with analogger client. Though there is a known quirk.
21
+ When using the analogger client, text after a colon will be interpreted as a tag.
22
+ Tapsilog URL encodes and decodes messages to circumvent this.
23
+
17
24
  ## Usage
18
25
 
19
26
  **Tapsilog Server**
20
27
 
21
- The best way to run tapsilog is to write a config file and call:
22
-
23
- tapsilog -c /path/to/config_file.yml
28
+ See tapsilog --help for details
24
29
 
25
- **Sample Config**
30
+ **Sample File/Mongo Config**
26
31
 
27
32
  port: 19080
28
33
  host:
29
34
  - 127.0.0.1
30
35
  socket:
31
36
  - /tmp/tapsilog.sock
32
- daemonize: false
33
- key: some_serious_key
37
+ daemonize: true
38
+ key: the_real_logger
34
39
 
35
40
  syncinterval: 1
36
41
 
37
- logs:
38
- #Currently supports file or mongo
42
+ backend:
43
+ # Can be mongo or file
39
44
  adapter: mongo
40
45
 
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
-
46
+ # Services not listed in logs section below will automatically be created under this collection (autocreate_namespace.service_name)
47
+ # If autocreate is off and an unknown service is requested, tapsilog uses the service named 'default'.
48
+ # If the service 'default' is not specified, tapsilog ignores the request
49
+ #
50
+ # If file adapter is used, this is used to specify the directory where log files named by the service name are created.
51
+ #autocreate: development
52
+
53
+ # You can leave these blank and tapsilog connects using mongodb connection defaults
44
54
  #host: 127.0.0.1
45
55
  #port: 1234
46
56
  #user: root
47
57
  #password: somepassword
48
- database: tapsilog
58
+ #database: tapsilog
49
59
 
50
- services:
51
- - service: default
52
- target: default # This is the mongodb namespace. For file backend, use the path of log file
60
+ # For mongo adapter, target refers to the mongodb collection
61
+ # For file adapter, specify the path of the log file. You can also use stdout and stderr
62
+ logs:
63
+ - service: default
64
+ target: default
65
+
66
+ - service: access
67
+ target: access
68
+
69
+ - service: bizsupport
70
+ target: bizsupport
53
71
 
54
- - service: dev.access
55
- target: dev.access
72
+ **Sample Proxy Config**
73
+
74
+ socket:
75
+ - /tmp/tapsilog_proxy.sock
76
+ daemonize: true
77
+ key: some_serious_key
78
+
79
+ syncinterval: 1
80
+
81
+ backend:
82
+ adapter: proxy
83
+
84
+ # You can connect to the destination tapsilog instance via tcpip or unix domain socket
85
+ #host: 127.0.0.1
86
+ #port: 19080
87
+ #socket: /tmp/tapsilog.sock
56
88
 
57
- - service: dev.bizsupport
58
- target: dev.bizsupport
89
+ # Specify the authorization key of the tapsilog server to connect to
90
+ key: the_real_logger
59
91
 
60
92
  **Tapsilog Client**
61
93
 
data/Rakefile CHANGED
@@ -1,10 +1,16 @@
1
1
  require 'echoe'
2
2
 
3
- Echoe.new('tapsilog', '0.1.0') do |p|
3
+ Echoe.new('tapsilog', '0.2.0') do |p|
4
4
  p.author = "Palmade"
5
5
  p.project = "tapsilog"
6
6
  p.summary = "Hydrid app-level logger from Palmade. Analogger fork."
7
7
 
8
8
  p.dependencies = ["eventmachine"]
9
9
  p.ignore_pattern = ["tmp/*"]
10
+
11
+ p.need_tar_gz = false
12
+ p.need_tgz = true
13
+
14
+ p.clean_pattern += [ "pkg", "lib/*.bundle", "*.gem", ".config" ]
15
+ p.rdoc_pattern = [ 'README', 'LICENSE', 'COPYING', 'lib/**/*.rb', 'doc/**/*.rdoc' ]
10
16
  end
data/bin/tapsilog CHANGED
@@ -2,68 +2,101 @@
2
2
 
3
3
  require 'yaml'
4
4
  require 'optparse'
5
- require 'palmade/tapsilog'
5
+ require 'rubygems'
6
6
 
7
- module Palmade
7
+ relative_tapsilog_path = File.expand_path(File.join(File.dirname(__FILE__), '../lib/palmade'))
8
+ if File.exists?(relative_tapsilog_path)
9
+ require File.join(relative_tapsilog_path, 'tapsilog')
10
+ else
11
+ gem 'tapsilog'
12
+ require 'palmade/tapsilog'
13
+ end
14
+
15
+ module Palmade::Tapsilog
8
16
  class TapsilogExec
9
17
 
10
- def self.run
11
- @@parsed_options ||= parse_options
12
- Palmade::Tapsilog::Server.start(@@parsed_options)
18
+ @@config = {}
19
+
20
+ def self.start
21
+ loop do
22
+ catch(:hup) {
23
+ if File.exists?(@@config[:configfile])
24
+ config = Utils.symbolize_keys(YAML.load(File.read(@@config[:configfile])))
25
+ @@config.merge!(config)
26
+ end
27
+ Palmade::Tapsilog::Server.start(@@config)
28
+ }
29
+ end
30
+ end
31
+
32
+ def self.stop
33
+ if Utils.pidf_running?(@@config[:pidfile])
34
+ pid = Utils.pidf_read(@@config[:pidfile])
35
+ puts "Sending QUIT to #{pid}"
36
+ Utils.pidf_kill(@@config[:pidfile])
37
+ else
38
+ puts "Tapsilog is not running"
39
+ Utils.pidf_clean(@@config[:pidfile])
40
+ end
41
+ end
42
+
43
+ def self.restart
44
+ stop
45
+ Utils.pidf_clean(@@config[:pidfile])
46
+ start
47
+ end
48
+
49
+ def self.status
50
+ if Utils.pidf_running?(@@config[:pidfile])
51
+ pid = Utils.pidf_read(@@config[:pidfile])
52
+ puts "Tapsilog is running with pid #{pid}"
53
+ else
54
+ puts "Tapsilog is not running"
55
+ Utils.pidf_clean(@@config[:pidfile])
56
+ end
13
57
  end
14
58
 
15
59
  def self.parse_options(config = {})
60
+ option_parser.parse!
61
+ @@config
62
+ end
63
+
64
+ def self.option_parser
16
65
  OptionParser.new do |opts|
17
- opts.banner = 'Usage: tapsilog [options]'
66
+ opts.banner = 'Usage: tapsilog [options] {start|stop|restart|status}'
67
+
18
68
  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
69
+
70
+ @@config[:configfile] = "/etc/tapsilog.yml"
71
+ opts.on('-c', '--config CONFFILE', "The configuration file to read. (/etc/tapsilog.yml)") do |conf|
72
+ @@config[:configfile] = conf
36
73
  end
37
- opts.on('-s','--syncinterval [INTERVAL]',String,"The interval between queue syncs. Defaults to 60 seconds.") do |interval|
38
- config[:syncinterval] = interval
74
+
75
+ @@config[:pidfile] = "/var/run/tapsilog.pid"
76
+ opts.on('-w', '--writepid FILENAME', "The filename to write a PID file to. (/var/run/tapsilog.pid)") do |pidfile|
77
+ @@config[:pidfile] = pidfile
39
78
  end
40
- opts.on('-d','--default [PATH]',String,"The default log destination. Defaults to stdout.") do |default|
41
- config[:default_log] = default
79
+
80
+ opts.on('-h', '--help', "Show this message") do
81
+ puts opts
82
+ exit
42
83
  end
43
- opts.on('-x','--daemonize',"Tell the Analogger to daemonize itself.") do
44
- config[:daemonize] = true
84
+ opts.on('-v', '--version', "Show version") do
85
+ puts 'tapsilog 0.2.0'
86
+ exit
45
87
  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
88
 
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
- }
89
+ end
60
90
  end
61
91
 
62
92
  end
63
93
  end
64
94
 
65
- loop do
66
- catch(:hup) {
67
- Palmade::TapsilogExec.run
68
- }
95
+ config = Palmade::Tapsilog::TapsilogExec.parse_options
96
+
97
+ if ARGV.length == 0
98
+ puts Palmade::Tapsilog::TapsilogExec.option_parser.help
99
+ else
100
+ action = ARGV.shift
101
+ Palmade::Tapsilog::TapsilogExec.send(action)
69
102
  end
data/bin/tapsilog_tail ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'optparse'
5
+ require 'rubygems'
6
+
7
+ config = {}
8
+
9
+ optparse = OptionParser.new do |opts|
10
+ opts.banner = 'Usage: tapsilog_tail [options] ServiceName'
11
+
12
+ opts.separator ''
13
+
14
+ config[:configfile] = "/etc/tapsilog.yml"
15
+ opts.on('-c', '--config CONFFILE', "The configuration file to read. (/etc/tapsilog.yml)") do |conf|
16
+ config[:configfile] = conf
17
+ end
18
+
19
+ opts.on('-f', '--follow', "Output appended data as the file grows.") do
20
+ config[:follow] = true
21
+ end
22
+
23
+ config[:lines] = 10
24
+ opts.on('-n', '--lines LINES', "Output the last N lines, instead of the last 10.") do |lines|
25
+ config[:lines] = lines
26
+ end
27
+
28
+ config[:sleep] = 1
29
+ opts.on('-s', '--sleep S', "With -f, sleep for approximately S seconds. (default 1.0)") do |sec|
30
+ config[:sleep] = sec
31
+ end
32
+
33
+ opts.on('-h', '--help', "Show this message") do
34
+ puts opts
35
+ exit
36
+ end
37
+ opts.on('-v', '--version', "Show version") do
38
+ puts 'tapsilog_tail 0.2.0'
39
+ exit
40
+ end
41
+ end
42
+ optparse.parse!
43
+
44
+ if ARGV.length == 0
45
+ puts optparse.help
46
+ else
47
+ tapsilog_config = YAML.load(File.read(config[:configfile]))
48
+ service = ARGV.shift
49
+
50
+ tapsilog_config['backend'] ||= {}
51
+ tapsilog_backend = tapsilog_config['backend']
52
+ tapsilog_config['logs'] ||= []
53
+ adapter = tapsilog_config['backend']['adapter'] || 'file'
54
+
55
+ if tapsilog_config['default_log']
56
+ tapsilog_config['logs'].push({'service' => 'default', 'target' => tapsilog_config['default_log']})
57
+ end
58
+
59
+ services = {}
60
+ tapsilog_config['logs'].each do |srv|
61
+ service_name = srv['service']
62
+ target = srv['target'] || srv['logfile']
63
+
64
+ services[service_name] = target
65
+ end
66
+
67
+ if adapter == 'file'
68
+ command = "tail"
69
+ command += " -f " if config[:follow]
70
+ command += " -n #{config[:lines]} " if config[:lines]
71
+
72
+ if services[service]
73
+ tail_target = services[service]
74
+ else
75
+ if autocreate = tapsilog_config['autocreate'] || tapsilog_backend['autocreate']
76
+ tail_target = File.join(autocreate, service)
77
+ else
78
+ tail_target = services['default']
79
+ end
80
+ end
81
+
82
+ if tail_target.nil?
83
+ puts "Uknown service #{service}!"
84
+ exit
85
+ end
86
+
87
+ if tail_target == "stdout" or tail_target == "stderr"
88
+ puts "Cannot tail #{tail_target}!"
89
+ exit
90
+ end
91
+
92
+ command += " #{tail_target} "
93
+ system command
94
+ elsif adapter == 'mongo'
95
+ require 'mongo'
96
+
97
+ mongo_conn = Mongo::Connection.new(tapsilog_backend['host'], tapsilog_backend['port'])
98
+ db_name = tapsilog_backend['database'] || 'tapsilog'
99
+
100
+ db = mongo_conn.db(db_name)
101
+ if tapsilog_backend['user'] and tapsilog_backend['password']
102
+ db.authenticate(tapsilog_backend['user'], tapsilog_backend['password'])
103
+ end
104
+
105
+ if services[service]
106
+ tail_target = services[service]
107
+ else
108
+ if autocreate = tapsilog_config['autocreate'] || tapsilog_backend['autocreate']
109
+ tail_target = "#{autocreate}.#{service}"
110
+ else
111
+ tail_target = services['default']
112
+ end
113
+ end
114
+
115
+ if tail_target.nil?
116
+ puts "Unknown service #{service}!"
117
+ exit
118
+ end
119
+
120
+ last_id = nil
121
+ print_entries = lambda do |log_entries|
122
+ log_entries.each do |log|
123
+ last_id = log['_id']
124
+
125
+ message = [log['timestamp'], log['service'], log['pid'], log['message']]
126
+ message.push(log['tags'].inspect) unless log['tags'].nil?
127
+ puts message.join("|")
128
+ end
129
+ end
130
+
131
+ last_entries = db[tail_target].find({}, :sort => ['_id', :desc], :limit => config[:lines].to_i).to_a.reverse
132
+ print_entries.call(last_entries)
133
+
134
+ if config[:follow]
135
+ trap("INT") { exit }
136
+
137
+ loop do
138
+ query = last_id ? {'_id' => {'$gt' => last_id}} : {}
139
+ new_entries = db[tail_target].find(query).to_a
140
+ print_entries.call(new_entries)
141
+
142
+ sleep(config[:sleep].to_i)
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -4,6 +4,7 @@ module Palmade::Tapsilog
4
4
  autoload :BaseAdapter, File.join(File.dirname(__FILE__), 'adapters/base_adapter')
5
5
  autoload :FileAdapter, File.join(File.dirname(__FILE__), 'adapters/file_adapter')
6
6
  autoload :MongoAdapter, File.join(File.dirname(__FILE__), 'adapters/mongo_adapter')
7
+ autoload :ProxyAdapter, File.join(File.dirname(__FILE__), 'adapters/proxy_adapter')
7
8
 
8
9
  end
9
10
  end
@@ -22,8 +22,10 @@ module Palmade::Tapsilog::Adapters
22
22
 
23
23
  @config[:services].each do |service|
24
24
  service_name = service['service']
25
+ target = service['target'] || service['logfile']
26
+
25
27
  @services[service_name] = {
26
- :target => service['target']
28
+ :target => target
27
29
  }
28
30
  end
29
31
  end
@@ -3,8 +3,14 @@ module Palmade::Tapsilog::Adapters
3
3
 
4
4
  def write(log_message)
5
5
  service = log_message[1]
6
+ log_message[5] = Palmade::Tapsilog::Utils.hash_to_query_string(log_message[5])
7
+
6
8
  file = get_file_descriptor(service)
7
- file.puts(log_message.join("|"))
9
+ if file
10
+ file.puts(log_message.join("|"))
11
+ else
12
+ STDERR.puts "Unknown service: #{service}"
13
+ end
8
14
  end
9
15
 
10
16
  def flush
@@ -28,16 +34,30 @@ module Palmade::Tapsilog::Adapters
28
34
  protected
29
35
 
30
36
  def get_file_descriptor(service_name)
31
- service_name = (@services[service_name].nil?) ? 'default' : service_name
37
+ service_name = resolve_service_name(service_name)
32
38
  service = @services[service_name]
33
39
 
40
+ return nil if service.nil?
41
+
34
42
  if service[:file].nil?
35
43
  service[:file] = open_file_descriptor(service)
36
44
  end
37
-
38
45
  service[:file]
39
46
  end
40
47
 
48
+ def resolve_service_name(service_name)
49
+ if @services[service_name].nil?
50
+ if @config[:autocreate]
51
+ @services[service_name] = {
52
+ :target => File.join(@config[:autocreate], service_name)
53
+ }
54
+ else
55
+ return 'default'
56
+ end
57
+ end
58
+ service_name
59
+ end
60
+
41
61
  def open_file_descriptor(service)
42
62
  logfile = service[:target]
43
63
 
@@ -7,7 +7,11 @@ module Palmade::Tapsilog::Adapters
7
7
  service = log_message[1]
8
8
  coll = get_collection(service)
9
9
 
10
- coll.insert(log_to_hash(log_message))
10
+ if coll
11
+ coll.insert(log_to_hash(log_message))
12
+ else
13
+ STDERR.puts "Unknown service: #{service}"
14
+ end
11
15
  end
12
16
 
13
17
  def close
@@ -19,12 +23,27 @@ module Palmade::Tapsilog::Adapters
19
23
  protected
20
24
 
21
25
  def get_collection(service_name)
22
- service_name = (@services[service_name].nil?) ? 'default' : service_name
26
+ service_name = resolve_service_name(service_name)
23
27
  service = @services[service_name]
24
28
 
29
+ return nil if service.nil?
30
+
25
31
  db_conn[service[:target]]
26
32
  end
27
33
 
34
+ def resolve_service_name(service_name)
35
+ if @services[service_name].nil?
36
+ if @config[:autocreate]
37
+ @services[service_name] = {
38
+ :target => "#{@config[:autocreate]}.#{service_name}"
39
+ }
40
+ else
41
+ return 'default'
42
+ end
43
+ end
44
+ service_name
45
+ end
46
+
28
47
  def log_to_hash(log_message)
29
48
  timestamp, service, pid, severity, message, tags = log_message
30
49
  log_hash = {
@@ -36,7 +55,7 @@ module Palmade::Tapsilog::Adapters
36
55
  :created_at => Time.now
37
56
  }
38
57
  unless tags.nil? or tags.empty?
39
- log_hash[:tags] = Palmade::Tapsilog::Utils::query_string_to_hash(tags)
58
+ log_hash[:tags] = tags
40
59
  end
41
60
 
42
61
  log_hash
@@ -0,0 +1,42 @@
1
+ module Palmade::Tapsilog::Adapters
2
+ class ProxyAdapter < BaseAdapter
3
+
4
+ def initialize(config)
5
+ super(config)
6
+ end
7
+
8
+ def write(log_message)
9
+ service = log_message[1]
10
+ instance_key = log_message[2]
11
+ severity = log_message[3]
12
+ message = log_message[4]
13
+ tags = log_message[5]
14
+
15
+ conn.log(service, instance_key, severity, message, tags)
16
+ end
17
+
18
+ def flush
19
+ conn.flush
20
+ end
21
+
22
+ def close
23
+ conn.close
24
+ end
25
+
26
+ protected
27
+
28
+ def conn
29
+ if @conn.nil?
30
+ if @config[:socket]
31
+ target = @config[:socket]
32
+ else
33
+ target = "#{@config[:host]}:#{@config[:port]}"
34
+ end
35
+ @conn = Palmade::Tapsilog::Conn.new(target, @config[:key])
36
+ @conn.max_tries = -1
37
+ end
38
+ @conn
39
+ end
40
+
41
+ end
42
+ end
@@ -3,8 +3,7 @@ require 'cgi'
3
3
  module Palmade::Tapsilog
4
4
  class Conn
5
5
  attr_reader :socket
6
-
7
- MAX_TRIES = 6
6
+ attr_accessor :max_tries
8
7
 
9
8
  def initialize(target, key, buffered = false)
10
9
  if target =~ /(.+)\:(\d+)$/i
@@ -17,6 +16,7 @@ module Palmade::Tapsilog
17
16
  @key = key
18
17
  @socket = nil
19
18
  @buffered = buffered
19
+ @max_tries = 6
20
20
  end
21
21
 
22
22
  def connect(host, port)
@@ -33,7 +33,8 @@ module Palmade::Tapsilog
33
33
  connect(@host, @port)
34
34
 
35
35
  ts = Time.now if ts.nil?
36
- tag_string = Utils::hash_to_query_string(tags)
36
+ tag_string = Utils.hash_to_query_string(tags)
37
+ message = CGI.escape(message)
37
38
 
38
39
  fullmsg = ":#{service}:#{instance_key}:#{severity}:#{message}:#{tag_string}"
39
40
 
@@ -47,9 +48,9 @@ module Palmade::Tapsilog
47
48
  write "#{len}#{len}#{fullmsg}"
48
49
  rescue Exception => e
49
50
  STDERR.puts "Failed to write to server! Retrying... (#{tries})" +
50
- "#{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"
51
+ "#{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}" unless @max_tries == -1
51
52
 
52
- if tries < MAX_TRIES
53
+ if @max_tries == -1 || tries < @max_tries
53
54
  tries += 1
54
55
  close_possibly_dead_conn(tries)
55
56
  reconnect
@@ -94,9 +95,9 @@ module Palmade::Tapsilog
94
95
  raise "Unable to create socket!" if @socket.nil?
95
96
  rescue Exception => e
96
97
  STDERR.puts "Failed to establish connection with server! Retrying... (#{tries})" +
97
- " #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"
98
+ " #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}" unless @max_tries == -1
98
99
 
99
- if tries < MAX_TRIES
100
+ if @max_tries == -1 || tries < @max_tries
100
101
  tries += 1
101
102
  close_possibly_dead_conn(tries)
102
103
  retry
@@ -1,3 +1,5 @@
1
+ require 'cgi'
2
+
1
3
  module Palmade::Tapsilog
2
4
  class Protocol < EventMachine::Connection
3
5
  Ci = 'i'.freeze
@@ -64,6 +66,8 @@ module Palmade::Tapsilog
64
66
  msg.shift
65
67
 
66
68
  msg[0] = msg[0].to_s.gsub(/[^a-zA-Z0-9\-\_\.]\s/, '').strip
69
+ msg[3] = CGI.unescape(msg[3].to_s)
70
+ msg[4] = Utils::query_string_to_hash(msg[4].to_s)
67
71
 
68
72
  LoggerClass.add_log(msg)
69
73
  @length = nil
@@ -25,31 +25,54 @@ module Palmade::Tapsilog
25
25
  protected
26
26
 
27
27
  def self.boot
28
- write_pid_file if @config[:pidfile]
29
- daemonize if @config[:daemonize]
30
-
31
28
  load_adapter
32
29
 
33
30
  trap("INT") { exit }
34
31
  trap("TERM") { exit }
35
32
  trap("HUP") { throw :hup }
36
33
 
34
+ prepare_server
35
+ daemonize if @config[:daemonize]
36
+ write_pid_file if @config[:pidfile]
37
37
  start_server
38
38
  end
39
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"
40
+ def self.load_adapter
41
+ @config[:backend] ||= {}
42
+
43
+ adapter_name = @config[:backend][:adapter] || "file"
44
+ class_name = "#{adapter_name.capitalize}Adapter"
45
+ adapter = Palmade::Tapsilog::Adapters.const_get(class_name)
46
+
47
+ adapter_config = @config[:backend]
48
+ adapter_config[:autocreate] = @config[:autocreate] if @config[:autocreate]
49
+ adapter_config[:services] = @config[:logs] || []
50
+
51
+ if @config[:default_log]
52
+ adapter_config[:services].push({'service' => 'default', 'target' => @config[:default_log]})
53
+ end
54
+
55
+ @adapter = adapter.new(adapter_config)
56
+ end
57
+
58
+ def self.prepare_server
59
+ if @config[:socket]
60
+ @usocks = @config[:socket]
61
+ @usocks = [ @usocks ] unless @usocks.is_a? Array
62
+
63
+ @usocks.each do |usock|
64
+ raise "Socket file already exists! (#{usock})" if File.exists?(usock)
49
65
  end
50
66
  end
51
67
 
52
- File.open(@config[Cpidfile],'w+') {|fh| fh.puts $$}
68
+ if @config[:host]
69
+ @tsocks = @config[:host]
70
+ @tsocks = [ @tsocks ] unless @tsocks.is_a? Array
71
+
72
+ @tsocks.each do |tsock|
73
+ raise "Port already in use! #{tsock}:#{@config[:port]}" if Utils.is_port_open?(tsock, @config[:port])
74
+ end
75
+ end
53
76
  end
54
77
 
55
78
  def self.daemonize
@@ -63,19 +86,23 @@ module Palmade::Tapsilog
63
86
  puts "Platform (#{RUBY_PLATFORM}) does not appear to support fork/setsid; skipping"
64
87
  end
65
88
 
66
- def self.load_adapter
67
- raise "Missing logs section in config file" unless @config[:logs]
89
+ def self.write_pid_file
90
+ if pid = Utils.pidf_read(@config[:pidfile])
91
+ if Utils.process_running?(pid)
92
+ raise "Another instance of tapsilog seems to be running! (#{pid})"
93
+ else
94
+ Utils.pidf_clean(@config[:pidfile])
95
+ STDERR.puts "Stale PID (#{pid}) removed"
96
+ end
97
+ end
68
98
 
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])
99
+ File.open(@config[:pidfile],'w+') {|fh| fh.puts $$}
73
100
  end
74
101
 
75
102
  def self.start_server
76
- prepare_server
77
103
  begin
78
104
  EventMachine.run {
105
+ update_now
79
106
  start_servers
80
107
  EventMachine.add_periodic_timer(1) { update_now }
81
108
  EventMachine.add_periodic_timer(@config[:interval]) { write_queue }
@@ -86,26 +113,6 @@ module Palmade::Tapsilog
86
113
  end
87
114
  end
88
115
 
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
116
  def self.start_servers
110
117
  @usocks.each do |usock|
111
118
  raise "Socket file already exists! (#{usock})" if File.exists?(usock)
@@ -2,7 +2,16 @@ require 'cgi'
2
2
 
3
3
  module Palmade::Tapsilog
4
4
  class Utils
5
-
5
+
6
+ def self.symbolize_keys(hash)
7
+ hash.inject({}){|result, (key, value)|
8
+ new_key = key.kind_of?(String) ? key.to_sym : key
9
+ new_value = value.kind_of?(Hash) ? symbolize_keys(value) : value
10
+ result[new_key] = new_value
11
+ result
12
+ }
13
+ end
14
+
6
15
  def self.is_port_open?(ip, port)
7
16
  begin
8
17
  ::Timeout::timeout(1) do
@@ -20,6 +29,70 @@ module Palmade::Tapsilog
20
29
  return false
21
30
  end
22
31
 
32
+ def self.process_running?(pid)
33
+ Process.getpgid(pid) != -1
34
+ rescue Errno::ESRCH
35
+ false
36
+ end
37
+
38
+ def self.pidf_running?(pid_file)
39
+ if pid = pidf_read(pid_file)
40
+ process_running?(pid) ? pid : false
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def self.pidf_read(pid_file)
47
+ if File.exists?(pid_file) && File.file?(pid_file) && pid = File.read(pid_file)
48
+ pid.to_i
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ def self.pidf_kill(pid_file, timeout = 30)
55
+ if timeout == 0
56
+ pidf_send_signal('INT', pid_file, timeout)
57
+ else
58
+ pidf_send_signal('QUIT', pid_file, timeout)
59
+ end
60
+ end
61
+
62
+ def self.pidf_send_signal(signal, pid_file, timeout = 30)
63
+ if pid = pidf_read(pid_file)
64
+ Process.kill(signal, pid)
65
+ ::Timeout.timeout(timeout) do
66
+ sleep 0.1 while process_running?(pid)
67
+ end
68
+ pid
69
+ else
70
+ nil
71
+ end
72
+ rescue ::Timeout::Error
73
+ pidf_force_kill pid_file
74
+ rescue Interrupt
75
+ pidf_force_kill pid_file
76
+ rescue Errno::ESRCH # No such process
77
+ pidf_force_kill pid_file
78
+ end
79
+
80
+ def self.pidf_force_kill(pid_file)
81
+ if pid = pidf_read(pid_file)
82
+ Process.kill("KILL", pid)
83
+ File.delete(pid_file) if File.exist?(pid_file)
84
+ pid
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ def self.pidf_clean(pid_file)
91
+ unless pidf_running?(pid_file)
92
+ File.delete(pid_file) if File.exists?(pid_file)
93
+ end
94
+ end
95
+
23
96
  # Taken from github accumulator/uri
24
97
 
25
98
  def self.query_string_to_hash(query_string, options={})
data/tapsilog.gemspec CHANGED
@@ -2,17 +2,16 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{tapsilog}
5
- s.version = "0.1.0"
5
+ s.version = "0.2.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Palmade"]
9
- s.date = %q{2010-09-03}
10
- s.default_executable = %q{tapsilog}
9
+ s.date = %q{2010-09-08}
11
10
  s.description = %q{Hydrid app-level logger from Palmade. Analogger fork.}
12
11
  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"]
12
+ s.executables = ["tapsilog", "tapsilog_tail"]
13
+ s.extra_rdoc_files = ["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/adapters/proxy_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"]
14
+ s.files = ["CHANGELOG", "INSTALL", "Manifest", "README.md", "Rakefile", "bin/tapsilog", "bin/tapsilog_tail", "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/adapters/proxy_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
15
  s.homepage = %q{}
17
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tapsilog", "--main", "README.md"]
18
17
  s.require_paths = ["lib"]
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Palmade
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-03 00:00:00 +08:00
17
+ date: 2010-09-08 00:00:00 +08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -34,17 +34,16 @@ description: Hydrid app-level logger from Palmade. Analogger fork.
34
34
  email: ""
35
35
  executables:
36
36
  - tapsilog
37
+ - tapsilog_tail
37
38
  extensions: []
38
39
 
39
40
  extra_rdoc_files:
40
- - CHANGELOG
41
- - README.md
42
- - bin/tapsilog
43
41
  - lib/palmade/tapsilog.rb
44
42
  - lib/palmade/tapsilog/adapters.rb
45
43
  - lib/palmade/tapsilog/adapters/base_adapter.rb
46
44
  - lib/palmade/tapsilog/adapters/file_adapter.rb
47
45
  - lib/palmade/tapsilog/adapters/mongo_adapter.rb
46
+ - lib/palmade/tapsilog/adapters/proxy_adapter.rb
48
47
  - lib/palmade/tapsilog/client.rb
49
48
  - lib/palmade/tapsilog/conn.rb
50
49
  - lib/palmade/tapsilog/logger.rb
@@ -53,15 +52,18 @@ extra_rdoc_files:
53
52
  - lib/palmade/tapsilog/utils.rb
54
53
  files:
55
54
  - CHANGELOG
55
+ - INSTALL
56
56
  - Manifest
57
57
  - README.md
58
58
  - Rakefile
59
59
  - bin/tapsilog
60
+ - bin/tapsilog_tail
60
61
  - lib/palmade/tapsilog.rb
61
62
  - lib/palmade/tapsilog/adapters.rb
62
63
  - lib/palmade/tapsilog/adapters/base_adapter.rb
63
64
  - lib/palmade/tapsilog/adapters/file_adapter.rb
64
65
  - lib/palmade/tapsilog/adapters/mongo_adapter.rb
66
+ - lib/palmade/tapsilog/adapters/proxy_adapter.rb
65
67
  - lib/palmade/tapsilog/client.rb
66
68
  - lib/palmade/tapsilog/conn.rb
67
69
  - lib/palmade/tapsilog/logger.rb