progstr-ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +20 -0
- data/README.md +71 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/progstr/client.rb +41 -0
- data/lib/progstr/config.rb +22 -0
- data/lib/progstr/httpclient.rb +61 -0
- data/lib/progstr/log_message.rb +33 -0
- data/lib/progstr/logger.rb +14 -0
- data/lib/progstr/logger_device.rb +37 -0
- data/lib/progstr/rails_logger.rb +18 -0
- data/lib/progstr/threadpool.rb +130 -0
- data/lib/progstr.rb +17 -0
- data/script/test-post.rb +10 -0
- data/test/helper.rb +18 -0
- data/test/test_formatter.rb +24 -0
- data/test/test_progstr.rb +50 -0
- metadata +136 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
gem "json"
|
9
|
+
gem "multi_json"
|
10
|
+
|
11
|
+
group :development do
|
12
|
+
gem "shoulda", ">= 0"
|
13
|
+
gem "bundler", "~> 1.0.0"
|
14
|
+
gem "jeweler", "~> 1.6.2"
|
15
|
+
gem "rcov", ">= 0"
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.6.2)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
json (1.5.3)
|
10
|
+
multi_json (1.0.3)
|
11
|
+
rake (0.9.2)
|
12
|
+
rcov (0.9.9)
|
13
|
+
shoulda (2.11.3)
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
ruby
|
17
|
+
|
18
|
+
DEPENDENCIES
|
19
|
+
bundler (~> 1.0.0)
|
20
|
+
jeweler (~> 1.6.2)
|
21
|
+
json
|
22
|
+
multi_json
|
23
|
+
rcov
|
24
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Hristo Deshev
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
Progstr » logger is a service that collects and manages programmer log entries in the cloud. Most web applications log errors and special external events generated by users or third party systems. Progstr » logger takes away the pain of complex logging system configurations, provides a centralized location for all your log entries and helps you analyze the data you've collected over time. *progstr-ruby* is the Ruby client library that collects log entries and transports them to the Progstr data store.
|
2
|
+
|
3
|
+
#Installation
|
4
|
+
|
5
|
+
Install the `progstr-ruby` gem on your system:
|
6
|
+
|
7
|
+
gem install progstr-ruby
|
8
|
+
|
9
|
+
Or even better - add it to your bundler configuration by adding this to your Gemfile:
|
10
|
+
|
11
|
+
gem "progstr-ruby"
|
12
|
+
|
13
|
+
*progstr-ruby* is a regular Ruby gem that you add to your application. Here is how you integrate it in your project:
|
14
|
+
|
15
|
+
* Sign up for progstr » logger and obtain an API token. That token is used to identify you against the service. Keep it secret and do not share it with anyone unless you want to let them log messages on your behalf.
|
16
|
+
* Install the *progstr-ruby* gem on your system or add it to your bundler configuration.
|
17
|
+
* Configure the API token by setting the `Progstr.api_key` property before you start logging:
|
18
|
+
<pre>
|
19
|
+
Progstr.api_key = "6f413b64-a8e1-4e25-b9e6-d83acf26ccba"
|
20
|
+
</pre>
|
21
|
+
* (Optional) Set up Rails, so that it sends logs to the Progstr service by changing your respective environment configuration. For example, add this to your `config/environments/production.rb`:
|
22
|
+
<pre>
|
23
|
+
Progstr.api_key = "6f413b64-a8e1-4e25-b9e6-d83acf26ccba"
|
24
|
+
Progstr::RailsLogger.start config
|
25
|
+
</pre>
|
26
|
+
|
27
|
+
#Getting started
|
28
|
+
|
29
|
+
There are four log severity levels that you can use to log events of different importance:
|
30
|
+
|
31
|
+
* Info: used to log general information events that can be used for reference or troubleshooting if needed.
|
32
|
+
* Warning: something odd happened and somebody needs to know about it.
|
33
|
+
* Error: something failed and needs to be fixed.
|
34
|
+
* Fatal: the entire application or a critical part of it is not working at all.
|
35
|
+
|
36
|
+
To log an event you need to create a `Progstr::Logger` object and call some of its info/warn/error/fatal methods. You need to provide the logger source name as a constructor parameter. That will be used to categorize logs and make it easier for you to find specific entries:
|
37
|
+
|
38
|
+
require 'progstr'
|
39
|
+
...
|
40
|
+
...
|
41
|
+
home_log = Progstr::Logger.new("HomeController")
|
42
|
+
...
|
43
|
+
home_log.info(message);
|
44
|
+
...
|
45
|
+
home_log.warn(message);
|
46
|
+
...
|
47
|
+
home_log.error(message);
|
48
|
+
...
|
49
|
+
home_log.fatal(message);
|
50
|
+
|
51
|
+
A `Progstr::Logger` object is really a standard Ruby [Logger](http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/classes/Logger.html). That means you can also check the current log level or do conditional logging using blocks that are evaluated only if the respective level has been enabled. For example, to construct a log message and log it only if info logging has been enabled, you can do this:
|
52
|
+
|
53
|
+
home_log.info { "Assemble " + "a complex log message here: #{user.name}" }
|
54
|
+
|
55
|
+
Alternatively, you can override the source name by passing the `progname` parameter to log methods:
|
56
|
+
|
57
|
+
home_log.warn("other-source") { "Oops, I need to warn somebody!" }
|
58
|
+
|
59
|
+
If you have configured Rails to send all logs to our service, you can simply use the `Rails.logger` object to log:
|
60
|
+
|
61
|
+
Rails.logger.info("HomeController#index called")
|
62
|
+
|
63
|
+
Note: Ruby `Logger` objects also support debug-level logs. `progstr-ruby` will convert those to info logs.
|
64
|
+
|
65
|
+
# Supported platforms
|
66
|
+
|
67
|
+
* Ruby 1.8.7 and later
|
68
|
+
|
69
|
+
# Documentation
|
70
|
+
|
71
|
+
Available online [here](http://docs.progstr.com).
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "progstr-ruby"
|
18
|
+
gem.homepage = "http://github.com/progstr/progstr-ruby"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{progstr.log Ruby client library}
|
21
|
+
gem.description = %Q{progstr.log application logging service API client library.}
|
22
|
+
gem.email = "hristo@deshev.com"
|
23
|
+
gem.authors = ["Hristo Deshev"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "progstr #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Progstr
|
2
|
+
module Client
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def send(message)
|
6
|
+
raise NoApiKeyError if Progstr.api_key.nil?
|
7
|
+
|
8
|
+
pool.schedule do
|
9
|
+
begin
|
10
|
+
execute(message)
|
11
|
+
rescue Timeout::Error,
|
12
|
+
Errno::EINVAL,
|
13
|
+
Errno::ECONNRESET,
|
14
|
+
Errno::ECONNREFUSED,
|
15
|
+
EOFError,
|
16
|
+
SocketError,
|
17
|
+
Net::HTTPBadResponse,
|
18
|
+
Net::HTTPHeaderSyntaxError,
|
19
|
+
Net::ProtocolError => error
|
20
|
+
#puts "Progstr::Client.send: #{error.message}"
|
21
|
+
#puts error.backtrace.join("\r\n")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def pool
|
29
|
+
if (@pool.nil?)
|
30
|
+
@pool = ThreadPool.new(2)
|
31
|
+
at_exit { @pool.shutdown }
|
32
|
+
end
|
33
|
+
@pool
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(message)
|
37
|
+
json = MultiJson.encode(message)
|
38
|
+
Progstr::HttpClient::post("v1/log", json)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Progstr
|
2
|
+
class << self
|
3
|
+
attr_accessor :host, :path_prefix, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout, :proxy_host, :proxy_port, :proxy_user, :proxy_pass
|
4
|
+
|
5
|
+
def host
|
6
|
+
@host ||= "api.progstr.com"
|
7
|
+
end
|
8
|
+
def port
|
9
|
+
@port || 80
|
10
|
+
end
|
11
|
+
def path_prefix
|
12
|
+
@path_prefix ||= '/'
|
13
|
+
end
|
14
|
+
def http_open_timeout
|
15
|
+
@http_open_timeout ||= 5
|
16
|
+
end
|
17
|
+
|
18
|
+
def http_read_timeout
|
19
|
+
@http_read_timeout ||= 15
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Progstr
|
2
|
+
module HttpClient
|
3
|
+
HEADERS = {
|
4
|
+
'Content-type' => 'application/json',
|
5
|
+
'Accept' => 'application/json'
|
6
|
+
}
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def post(path, data = '')
|
10
|
+
handle_response(http.post(url_path(path), data, headers))
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def protocol
|
16
|
+
"http"
|
17
|
+
end
|
18
|
+
|
19
|
+
def url
|
20
|
+
URI.parse("#{protocol}://#{Progstr.host}:#{Progstr.port}/")
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_response(response)
|
24
|
+
# do nothing even on errors
|
25
|
+
end
|
26
|
+
|
27
|
+
def headers
|
28
|
+
@headers ||= HEADERS.merge({ "X-Progstr-Token" => Progstr.api_key.to_s })
|
29
|
+
end
|
30
|
+
|
31
|
+
def url_path(path)
|
32
|
+
Progstr.path_prefix + path
|
33
|
+
end
|
34
|
+
|
35
|
+
def http
|
36
|
+
if Thread.current[:http].nil?
|
37
|
+
Thread.current[:http] = build_http
|
38
|
+
end
|
39
|
+
Thread.current[:http]
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_http
|
43
|
+
http = Net::HTTP::Proxy(Progstr.proxy_host,
|
44
|
+
Progstr.proxy_port,
|
45
|
+
Progstr.proxy_user,
|
46
|
+
Progstr.proxy_pass).new(url.host, url.port)
|
47
|
+
|
48
|
+
http.read_timeout = Progstr.http_read_timeout
|
49
|
+
http.open_timeout = Progstr.http_open_timeout
|
50
|
+
http.use_ssl = false
|
51
|
+
http
|
52
|
+
end
|
53
|
+
|
54
|
+
def error_message(response_body)
|
55
|
+
Progstr::Json.decode(response_body)["Message"]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Progstr
|
2
|
+
class LogMessage
|
3
|
+
LEVELS = { :info => 0, :warning => 1, :error => 2, :fatal => 3 }
|
4
|
+
LEVELS.default = 0
|
5
|
+
|
6
|
+
attr_reader :text, :source, :host, :level, :time
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@text = params[:text]
|
10
|
+
@source = params[:source]
|
11
|
+
@host = params[:host]
|
12
|
+
@level = params[:level]
|
13
|
+
@time = params[:time]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
{:text => @text, :source => @source, :host => get_host(@host), :level => LEVELS[@level], :time => encode_time(@time)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_json
|
21
|
+
to_hash.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
def encode_time(time)
|
25
|
+
time ||= Time.now
|
26
|
+
(time.to_f * 1000).to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_host(host)
|
30
|
+
host || Socket.gethostname || ENV["HOST"] || "Unknown"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Progstr
|
2
|
+
class LoggerDevice
|
3
|
+
LEVELS_MAP = {
|
4
|
+
"INFO" => :info,
|
5
|
+
"WARN" => :warning,
|
6
|
+
"ERROR" => :error,
|
7
|
+
"FATAL" => :fatal
|
8
|
+
}
|
9
|
+
def initialize(source = nil)
|
10
|
+
@source = source || "Unknown"
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(message)
|
14
|
+
Progstr::Client::send(message)
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
end
|
19
|
+
|
20
|
+
def formatter
|
21
|
+
proc do |severity, datetime, progname, msg|
|
22
|
+
Progstr::LogMessage.new :text => msg,
|
23
|
+
:level => resolve_level(severity),
|
24
|
+
:time => datetime,
|
25
|
+
:source => resolve_source(progname)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def resolve_level(severity)
|
30
|
+
LEVELS_MAP[severity] || :info
|
31
|
+
end
|
32
|
+
|
33
|
+
def resolve_source(progname)
|
34
|
+
progname || @source
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Progstr
|
2
|
+
class RailsLogger < Progstr::Logger
|
3
|
+
def initialize
|
4
|
+
super("Rails")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.start(rails_config)
|
8
|
+
rails_config.after_initialize do
|
9
|
+
Rails.logger = rails_config.logger = Progstr::RailsLogger.new
|
10
|
+
|
11
|
+
#specific loggers for ActionController and ActiveRecord
|
12
|
+
ActionController::Base.logger = Progstr::Logger.new("ActionController")
|
13
|
+
ActiveRecord::Base.logger = Progstr::Logger.new("ActiveRecord")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Credit goes to Kim Burgestrand:
|
2
|
+
# http://burgestrand.se/articles/quick-and-simple-ruby-thread-pool.html
|
3
|
+
#
|
4
|
+
# Ruby Thread Pool
|
5
|
+
# ================
|
6
|
+
# A thread pool is useful when you wish to do some work in a thread, but do
|
7
|
+
# not know how much work you will be doing in advance. Spawning one thread
|
8
|
+
# for each task is potentially expensive, as threads are not free.
|
9
|
+
#
|
10
|
+
# In this case, it might be more beneficial to start a predefined set of
|
11
|
+
# threads and then hand off work to them as it becomes available. This is
|
12
|
+
# the pure essence of what a thread pool is: an array of threads, all just
|
13
|
+
# waiting to do some work for you!
|
14
|
+
#
|
15
|
+
# Prerequisites
|
16
|
+
# -------------
|
17
|
+
|
18
|
+
# We need the [Queue](http://rdoc.info/stdlib/thread/1.9.2/Queue), as our
|
19
|
+
# thread pool is largely dependent on it. Thanks to this, the implementation
|
20
|
+
# becomes very simple!
|
21
|
+
# require 'thread'
|
22
|
+
|
23
|
+
# Public Interface
|
24
|
+
# ----------------
|
25
|
+
|
26
|
+
# `ThreadPool` is our thread pool class. It will allow us to do three operations:
|
27
|
+
#
|
28
|
+
# - `.new(size)` creates a thread pool of a given size
|
29
|
+
# - `#schedule(*args, &job)` schedules a new job to be executed
|
30
|
+
# - `#shutdown` shuts down all threads (after letting them finish working, of course)
|
31
|
+
class ThreadPool
|
32
|
+
|
33
|
+
# ### initialization, or `ThreadPool.new(size)`
|
34
|
+
# Creating a new `ThreadPool` involves a certain amount of work. First, however,
|
35
|
+
# we need to define its’ `size`. It defines how many threads we will have
|
36
|
+
# working internally.
|
37
|
+
#
|
38
|
+
# Which size is best for you is hard to answer. You do not want it to be
|
39
|
+
# too low, as then you won’t be able to do as many things concurrently.
|
40
|
+
# However, if you make it too high Ruby will spend too much time switching
|
41
|
+
# between threads, and that will also degrade performance!
|
42
|
+
def initialize(size)
|
43
|
+
# Before we do anything else, we need to store some information about
|
44
|
+
# our pool. `@size` is useful later, when we want to shut our pool down,
|
45
|
+
# and `@jobs` is the heart of our pool that allows us to schedule work.
|
46
|
+
@size = size
|
47
|
+
@jobs = Queue.new
|
48
|
+
|
49
|
+
# #### Creating our pool of threads
|
50
|
+
# Once preparation is done, it’s time to create our pool of threads.
|
51
|
+
# Each thread store its’ index in a thread-local variable, in case we
|
52
|
+
# need to know which thread a job is executing in later on.
|
53
|
+
@pool = Array.new(@size) do |i|
|
54
|
+
Thread.new do
|
55
|
+
Thread.current[:id] = i
|
56
|
+
|
57
|
+
# We start off by defining a `catch` around our worker loop. This
|
58
|
+
# way we’ve provided a method for graceful shutdown of our threads.
|
59
|
+
# Shutting down is merely a `#schedule { throw :exit }` away!
|
60
|
+
catch(:exit) do
|
61
|
+
# The worker thread life-cycle is very simple. We continuously wait
|
62
|
+
# for tasks to be put into our job `Queue`. If the `Queue` is empty,
|
63
|
+
# we will wait until it’s not.
|
64
|
+
loop do
|
65
|
+
# Once we have a piece of work to be done, we will pull out the
|
66
|
+
# information we need and get to work.
|
67
|
+
job, args = @jobs.pop
|
68
|
+
job.call(*args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# ### Work scheduling
|
76
|
+
|
77
|
+
# To schedule a piece of work to be done is to say to the `ThreadPool` that you
|
78
|
+
# want something done.
|
79
|
+
def schedule(*args, &block)
|
80
|
+
# Your given task will not be run immediately; rather, it will be put
|
81
|
+
# into the work `Queue` and executed once a thread is ready to work.
|
82
|
+
@jobs << [block, args]
|
83
|
+
end
|
84
|
+
|
85
|
+
# ### Graceful shutdown
|
86
|
+
|
87
|
+
# If you ever wish to close down your application, I took the liberty of
|
88
|
+
# making it easy for you to wait for any currently executing jobs to finish
|
89
|
+
# before you exit.
|
90
|
+
def shutdown
|
91
|
+
# A graceful shutdown involves threads exiting cleanly themselves, and
|
92
|
+
# since we’ve defined a `catch`-handler around the threads’ worker loop
|
93
|
+
# it is simply a matter of throwing `:exit`. Thus, if we throw one `:exit`
|
94
|
+
# for each thread in our pool, they will all exit eventually!
|
95
|
+
@size.times do
|
96
|
+
schedule { throw :exit }
|
97
|
+
end
|
98
|
+
|
99
|
+
# And now one final thing: wait for our `throw :exit` jobs to be run on
|
100
|
+
# all our worker threads. This call will not return until all worker threads
|
101
|
+
# have exited.
|
102
|
+
@pool.map(&:join)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Demonstration
|
107
|
+
# -------------
|
108
|
+
# Running this file will display how the thread pool works.
|
109
|
+
if $0 == __FILE__
|
110
|
+
# - First, we create a new thread pool with a size of 10. This number is
|
111
|
+
# lower than our planned amount of work, to show that threads do not
|
112
|
+
# exit once they have finished a task.
|
113
|
+
p = ThreadPool.new(10)
|
114
|
+
|
115
|
+
# - Next we simulate some workload by scheduling a large amount of work
|
116
|
+
# to be done. The actual time taken for each job is randomized. This
|
117
|
+
# is to demonstrate that even if two tasks are scheduled approximately
|
118
|
+
# at the same time, the one that takes less time to execute is likely
|
119
|
+
# to finish before the other one.
|
120
|
+
20.times do |i|
|
121
|
+
p.schedule do
|
122
|
+
sleep rand(4) + 2
|
123
|
+
puts "Job #{i} finished by thread #{Thread.current[:id]}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# - Finally, register an `at_exit`-hook that will wait for our thread pool
|
128
|
+
# to properly shut down before allowing our script to completely exit.
|
129
|
+
at_exit { p.shutdown }
|
130
|
+
end
|
data/lib/progstr.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "multi_json"
|
2
|
+
require "socket"
|
3
|
+
require "net/http"
|
4
|
+
require "net/https"
|
5
|
+
require "thread"
|
6
|
+
require "logger"
|
7
|
+
|
8
|
+
require "progstr/config"
|
9
|
+
require "progstr/threadpool"
|
10
|
+
require "progstr/log_message"
|
11
|
+
require "progstr/client"
|
12
|
+
require "progstr/httpclient"
|
13
|
+
require "progstr/logger_device"
|
14
|
+
require "progstr/logger"
|
15
|
+
require "progstr/rails_logger"
|
16
|
+
|
17
|
+
class NoApiKeyError < StandardError; end
|
data/script/test-post.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "progstr"
|
2
|
+
|
3
|
+
Progstr.api_key = "DEMO"
|
4
|
+
|
5
|
+
logger = Progstr::Logger.new("console-script")
|
6
|
+
logger.info("Something funny")
|
7
|
+
logger.info("source-override") { "Something funny with custom source" }
|
8
|
+
|
9
|
+
rails_logger = Progstr::RailsLogger.new
|
10
|
+
rails_logger.info("Something funny in Rails-land.")
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'progstr'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestFormatter < Test::Unit::TestCase
|
4
|
+
should "serialize level" do
|
5
|
+
device = Progstr::LoggerDevice.new
|
6
|
+
formatter = device.formatter
|
7
|
+
|
8
|
+
yesterday = Time.now - 1 * 60 * 60 * 24
|
9
|
+
message = formatter.call("WARN", yesterday, "progname", "message body")
|
10
|
+
assert_equal "progname", message.source
|
11
|
+
assert_equal :warning, message.level
|
12
|
+
assert_equal "message body", message.text
|
13
|
+
assert_equal yesterday, message.time
|
14
|
+
end
|
15
|
+
|
16
|
+
should "infer progname from constructor" do
|
17
|
+
device = Progstr::LoggerDevice.new("default-progname")
|
18
|
+
formatter = device.formatter
|
19
|
+
|
20
|
+
yesterday = Time.now - 1 * 60 * 60 * 24
|
21
|
+
message = formatter.call("WARN", yesterday, nil, "message body")
|
22
|
+
assert_equal "default-progname", message.source
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestProgstrRuby < Test::Unit::TestCase
|
4
|
+
should "serialize text and source" do
|
5
|
+
message = Progstr::LogMessage.new :text => "test message",
|
6
|
+
:source => "progstr.something",
|
7
|
+
:host => "dev-machine"
|
8
|
+
assert_equal "test message", message.to_hash[:text]
|
9
|
+
assert_equal "progstr.something", message.to_hash[:source]
|
10
|
+
assert_equal "dev-machine", message.to_hash[:host]
|
11
|
+
end
|
12
|
+
|
13
|
+
should "serialize level" do
|
14
|
+
message = Progstr::LogMessage.new :level => :info
|
15
|
+
assert_equal 0, message.to_hash[:level]
|
16
|
+
end
|
17
|
+
|
18
|
+
should "serialize time in millis since epoch start (UTC)" do
|
19
|
+
now = Time.now
|
20
|
+
nowMillis = (now.to_f * 1000).to_i
|
21
|
+
message = Progstr::LogMessage.new :time => now
|
22
|
+
assert_equal nowMillis, message.to_hash[:time]
|
23
|
+
end
|
24
|
+
|
25
|
+
should "get current time if not given" do
|
26
|
+
message = Progstr::LogMessage.new({})
|
27
|
+
assert message.to_hash[:time] > 0, "time not valid"
|
28
|
+
end
|
29
|
+
|
30
|
+
should "get machine name" do
|
31
|
+
message = Progstr::LogMessage.new({})
|
32
|
+
assert message.to_hash[:host].to_s.size > 0, "host name not inferred"
|
33
|
+
end
|
34
|
+
|
35
|
+
should "serialize to json" do
|
36
|
+
newYear = Time.utc(2011, 1, 1)
|
37
|
+
message = Progstr::LogMessage.new :text => "test message",
|
38
|
+
:source => "progstr.something",
|
39
|
+
:host => "dev-machine",
|
40
|
+
:level => :error,
|
41
|
+
:time => newYear
|
42
|
+
|
43
|
+
json = MultiJson.encode(message)
|
44
|
+
assert_match /"source":"progstr.something"/, json
|
45
|
+
assert_match /"host":"dev-machine"/, json
|
46
|
+
assert_match /"text":"test message"/, json
|
47
|
+
assert_match /"level":2/, json
|
48
|
+
assert_match /"time":\d+/, json
|
49
|
+
end
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: progstr-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Hristo Deshev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-12 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: &15006000 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *15006000
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: multi_json
|
27
|
+
requirement: &15005520 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *15005520
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: shoulda
|
38
|
+
requirement: &15005040 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *15005040
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: &15004560 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *15004560
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: jeweler
|
60
|
+
requirement: &15004080 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.6.2
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *15004080
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rcov
|
71
|
+
requirement: &15003600 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *15003600
|
80
|
+
description: progstr.log application logging service API client library.
|
81
|
+
email: hristo@deshev.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files:
|
85
|
+
- LICENSE.txt
|
86
|
+
- README.md
|
87
|
+
files:
|
88
|
+
- .document
|
89
|
+
- Gemfile
|
90
|
+
- Gemfile.lock
|
91
|
+
- LICENSE.txt
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- VERSION
|
95
|
+
- lib/progstr.rb
|
96
|
+
- lib/progstr/client.rb
|
97
|
+
- lib/progstr/config.rb
|
98
|
+
- lib/progstr/httpclient.rb
|
99
|
+
- lib/progstr/log_message.rb
|
100
|
+
- lib/progstr/logger.rb
|
101
|
+
- lib/progstr/logger_device.rb
|
102
|
+
- lib/progstr/rails_logger.rb
|
103
|
+
- lib/progstr/threadpool.rb
|
104
|
+
- script/test-post.rb
|
105
|
+
- test/helper.rb
|
106
|
+
- test/test_formatter.rb
|
107
|
+
- test/test_progstr.rb
|
108
|
+
homepage: http://github.com/progstr/progstr-ruby
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
hash: -1547365430839715510
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 1.8.6
|
133
|
+
signing_key:
|
134
|
+
specification_version: 3
|
135
|
+
summary: progstr.log Ruby client library
|
136
|
+
test_files: []
|