tapsilog 0.1.0 → 0.2.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 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