logbook 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ tmp/*.log
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ree@logbook_gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in logbook.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ logbook (0.0.1)
5
+ addressable
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ addressable (2.2.2)
11
+ diff-lcs (1.1.2)
12
+ fakeweb (1.3.0)
13
+ rspec (2.0.0)
14
+ rspec-core (= 2.0.0)
15
+ rspec-expectations (= 2.0.0)
16
+ rspec-mocks (= 2.0.0)
17
+ rspec-core (2.0.0)
18
+ rspec-expectations (2.0.0)
19
+ diff-lcs (>= 1.1.2)
20
+ rspec-mocks (2.0.0)
21
+ rspec-core (= 2.0.0)
22
+ rspec-expectations (= 2.0.0)
23
+ timecop (0.3.5)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ addressable
30
+ fakeweb
31
+ logbook!
32
+ rspec
33
+ timecop
data/README.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ = What's logbook?
2
+
3
+ Logbook is a hosted service for collecting log entries from your application.
4
+
5
+ = Requirements
6
+
7
+ Works in Rails 3 only at the moment. Check back later for more framework / versions.
8
+
9
+ = Getting started
10
+
11
+ * Go to http://logbook.me and create an account
12
+ * Download logbook.yml that it genereated for you to the config dir
13
+ * Add this gem to your Gemfile:
14
+
15
+ gem 'logbook'
16
+
17
+ = Usage
18
+
19
+ Anywhere in your application:
20
+
21
+ Logbook.debug 'facility', { :my => 'data' }
22
+
23
+ Logbook has a method for every severity supported by the standard Ruby logger (in order of priority: debug < info < warn < error < fatal < unknown). Thefirst parameter it takes specifies "facility". It's a linux syslog analogy and allows to have different "types" of log entries so that you could filter them in the web interface more easily later. The second parameter needs to be a hash with any data that you want to log. Passing a hash is good because it allows to log structured data, which again makes it possible to filter records in the web interface.
24
+
25
+ = Authors
26
+
27
+ We created this service and gem during Rails Rumble 2010: http://railsrumble.com/teams/48-hours-of-unpaid-work-and-5-cookies
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/lib/logbook.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'logbook/agent'
2
+ require 'logbook/client'
3
+ require 'logbook/railtie'
4
+
5
+ module Logbook
6
+ class << self
7
+ attr_accessor :api_key
8
+ attr_accessor :logger
9
+ attr_accessor :delay_between_requests
10
+
11
+ def start
12
+ agent.start
13
+ end
14
+
15
+ def stop
16
+ agent.stop
17
+ end
18
+
19
+ %W(debug info warn error fatal unknown).each do |severity|
20
+ define_method severity do |facility, payload|
21
+ agent.add_entry(severity.to_sym, facility, payload)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def agent
28
+ @agent ||= Logbook::Agent.instance
29
+ end
30
+ end
31
+
32
+ # Default values
33
+ self.delay_between_requests = 5
34
+ end
@@ -0,0 +1,79 @@
1
+ require 'singleton'
2
+
3
+ require 'logger'
4
+ class Logbook::Agent
5
+ include Singleton
6
+
7
+ attr_reader :client, :entries, :stop_processing
8
+
9
+ def initialize
10
+ @client = Logbook::Client.new(Logbook.api_key)
11
+ @entries = []
12
+ @semaphore = Mutex.new
13
+ Logbook.logger.info('Logbook agent started')
14
+ end
15
+
16
+ def start
17
+ @keep_running = true
18
+ @loop = Thread.new do
19
+ run_loop
20
+ end
21
+ @loop['label'] = 'Logbook loop'
22
+ end
23
+
24
+ def stop
25
+ Logbook.logger.info 'Stopping logbook agent. Checking if there is anything to send'
26
+ @keep_running = false
27
+ @loop.join
28
+ end
29
+
30
+ def reset
31
+ entries.clear
32
+ end
33
+
34
+ def run_loop
35
+ while keep_running?
36
+ process
37
+ sleep Logbook.delay_between_requests
38
+ end
39
+ end
40
+
41
+ def process
42
+ @semaphore.synchronize {
43
+ return if entries.empty?
44
+
45
+ # Copying the queue
46
+ @entries_to_send = entries.dup
47
+ Logbook.logger.info("Sending #{@entries_to_send.size} entries to the server")
48
+
49
+ begin
50
+ @client.send_entries(@entries_to_send)
51
+ rescue => e
52
+ Logbook.logger.error("Error when sending data to the server: #{e.message}")
53
+
54
+ case e
55
+ when Logbook::Client::NotAuthorized
56
+ @stop_processing = true
57
+ end
58
+ ensure
59
+ # Removing sent items from the queue anyway
60
+ entries.shift(@entries_to_send.size)
61
+ end
62
+ }
63
+ end
64
+
65
+ def add_entry(severity, facility, payload)
66
+ entries.push(
67
+ :severity => severity,
68
+ :facility => facility,
69
+ :payload => payload,
70
+ :timestamp => Time.now.to_s
71
+ )
72
+ end
73
+
74
+ private
75
+
76
+ def keep_running?
77
+ @keep_running && !@stop_processing
78
+ end
79
+ end
@@ -0,0 +1,49 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'addressable/uri'
4
+
5
+ class Logbook::Client
6
+
7
+ API_URL = "https://logbook.me/entries"
8
+
9
+ class NotAuthorized < StandardError ; end
10
+ class ServerError < StandardError ; end
11
+
12
+ attr_reader :api_key
13
+
14
+ def initialize(key)
15
+ @api_key = key
16
+ end
17
+
18
+ def send_entries(entries)
19
+ uri = URI.parse API_URL
20
+
21
+ req = Net::HTTP::Post.new(uri.path)
22
+ req.content_type = 'application/x-www-form-urlencoded'
23
+ req.body = request_body(entries)
24
+
25
+ http = Net::HTTP.new(uri.host, uri.port)
26
+ http.use_ssl = true
27
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
28
+
29
+ response = http.start {|session| session.request(req) }
30
+
31
+ case response
32
+ when Net::HTTPSuccess
33
+ true
34
+ when Net::HTTPUnauthorized
35
+ raise NotAuthorized
36
+ else
37
+ raise ServerError
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def request_body(entries)
44
+ hash = Addressable::URI.new
45
+ processed = entries.map { |entry| entry.inject({}) { |r, e| r[e.first] = e.last.to_s; r } }
46
+ hash.query_values = {:entries => processed, :api_key => @api_key}
47
+ hash.query
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ if defined?(Rails)
2
+ module Logbook
3
+ class Railtie < Rails::Railtie
4
+ initializer "logbook.start_agent" do
5
+ Logbook.logger = ActiveSupport::BufferedLogger.new(File.join(Rails.root, 'log', 'logbook.log'))
6
+
7
+ config_file = File.join(Rails.root, 'config', 'logbook.yml')
8
+
9
+ if !File.exists?(config_file)
10
+ Logbook.logger.error('config/logbook.yml does not exist, shutting down')
11
+ next
12
+ end
13
+
14
+ config = YAML.load_file(config_file)[Rails.env.to_s] || {}
15
+
16
+ if !config['enabled']
17
+ Logbook.logger.info("Logbook is not enabled in #{Rails.env}, shutting down")
18
+ next
19
+ end
20
+
21
+ Logbook.api_key = config['api_key']
22
+ Logbook.start
23
+ at_exit { Logbook.stop }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Logbook
2
+ VERSION = "0.0.1"
3
+ end
data/logbook.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "logbook/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "logbook"
7
+ s.version = Logbook::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["48 hours of unpaid work and 5 cookies Rails Rumble 2010 Team"]
10
+ s.email = ["support@logbook.me"]
11
+ s.homepage = "http://logbook.me"
12
+ s.summary = %q{Logbook is a hosted service to collect log entries from your application}
13
+ s.description = %q{A gem to integrate your Rails 3 app with http://logbook.me}
14
+
15
+ s.rubyforge_project = "logbook"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency 'rspec'
23
+ s.add_development_dependency 'timecop'
24
+ s.add_development_dependency 'fakeweb'
25
+
26
+ s.add_dependency 'addressable'
27
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Logbook::Agent do
4
+ subject { Logbook::Agent.instance }
5
+
6
+ before(:each) do
7
+ subject.client.stub!(:send_entries)
8
+ end
9
+
10
+ after(:each) do
11
+ subject.reset
12
+ end
13
+
14
+ def process
15
+ subject.start; subject.stop
16
+ end
17
+
18
+ it "should set api_key for the Client" do
19
+ subject.client.api_key.should == 'test_key'
20
+ end
21
+
22
+ context 'when adding a new entry to the queue' do
23
+ it 'should add a timestamp to every entry' do
24
+ time = Time.now
25
+ Timecop.freeze(time)
26
+ subject.add_entry :debug, :test, 'test'
27
+ subject.entries.first[:timestamp].should == time.to_s
28
+ end
29
+ end
30
+
31
+ context 'when processing the queue' do
32
+ it 'should empty the queue' do
33
+ subject.add_entry :debug, :test, 'test'
34
+ process
35
+ subject.entries.should be_empty
36
+ end
37
+
38
+ it 'should send each entry to the server' do
39
+ subject.add_entry :debug, :test, 'test'
40
+ subject.client.should_receive(:send_entries).with(subject.entries.dup)
41
+ process
42
+ end
43
+
44
+ context 'when server errors out' do
45
+ it 'should still clear the queue' do
46
+ subject.add_entry :debug, :test, 'test'
47
+ subject.client.stub!(:send_entries).and_raise(Logbook::Client::ServerError)
48
+ process
49
+ subject.entries.should be_empty
50
+ end
51
+
52
+ it 'should stop processing the queue if it gets a NotAuthorized exception' do
53
+ subject.client.stub!(:send_entries).and_raise(Logbook::Client::NotAuthorized)
54
+ subject.add_entry :debug, :test, 'test'
55
+ process
56
+ subject.stop_processing.should == true
57
+ end
58
+ end
59
+ end
60
+
61
+ it 'should clear the queue on reset' do
62
+ subject.add_entry :debug, :test, 'test'
63
+ subject.reset
64
+ subject.entries.should be_empty
65
+ end
66
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Logbook::Client do
4
+
5
+ subject { Logbook::Client.new('key') }
6
+
7
+ context "when making a request" do
8
+ let(:request) { FakeWeb.last_request }
9
+
10
+ before(:each) do
11
+ FakeWeb.register_uri(:post, Logbook::Client::API_URL, {})
12
+ end
13
+
14
+ it "should make a POST request" do
15
+ subject.send_entries([])
16
+ request.should_not be_nil
17
+ end
18
+
19
+ it "should set content_type to 'text/plain' " do
20
+ subject.send_entries([])
21
+ request.content_type.should == 'application/x-www-form-urlencoded'
22
+ end
23
+
24
+ it "should send entries in the request" do
25
+ entries = [{:params => 'params'}]
26
+ subject.send_entries(entries)
27
+ request.body.should include("entries[0][params]=params")
28
+ end
29
+
30
+ it 'should handle non-sting data' do
31
+ expect { subject.send_entries([{:time => Time.now}]) }.
32
+ to_not raise_error
33
+ end
34
+
35
+ it "should send api_key in the request" do
36
+ subject.send_entries([])
37
+ request.body.should include("api_key=key")
38
+ end
39
+ end
40
+
41
+ context "when processing a response" do
42
+ it "should not raise anything if response status is 200" do
43
+ FakeWeb.register_uri(:post, Logbook::Client::API_URL, {:status => ['200', 'Ok']})
44
+ expect { subject.send_entries([]) }.to_not raise_error
45
+ end
46
+
47
+ it "should raise NotAuthorized exception if response status is 401" do
48
+ FakeWeb.register_uri(:post, Logbook::Client::API_URL, {:status => ['401', 'Not Authorized']})
49
+ expect { subject.send_entries([]) }.to raise_error(Logbook::Client::NotAuthorized)
50
+ end
51
+
52
+ it "should raise ServerError exception if response status is 500" do
53
+ FakeWeb.register_uri(:post, Logbook::Client::API_URL, {:status => ['500', 'Server Error']})
54
+ expect { subject.send_entries([]) }.to raise_error(Logbook::Client::ServerError)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Logbook do
4
+ let(:agent) do
5
+ Logbook::Agent.instance
6
+ end
7
+
8
+ before(:each) do
9
+ agent.client.stub!(:send_entries)
10
+ end
11
+
12
+ context 'when starting' do
13
+ it 'should start the agent' do
14
+ agent.should_receive(:start)
15
+ Logbook.start
16
+ end
17
+ end
18
+
19
+ it 'should add an entry to the queue' do
20
+ expect { Logbook.debug :facility, :data => :value }.
21
+ to change { agent.entries.size }.by(1)
22
+ end
23
+
24
+ context 'when stopping' do
25
+ it 'should stop the agent' do
26
+ agent.should_receive(:stop)
27
+ Logbook.stop
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ require 'logbook'
2
+ require 'timecop'
3
+ require 'fakeweb'
4
+
5
+ FakeWeb.allow_net_connect = false
6
+
7
+ # No need to wait in tests
8
+ Logbook.delay_between_requests = 0
9
+ Logbook.api_key = 'test_key'
10
+ Logbook.logger = Logger.new(File.dirname(__FILE__) + '/../tmp/test.log')
data/tmp/.keep ADDED
File without changes
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logbook
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - 48 hours of unpaid work and 5 cookies Rails Rumble 2010 Team
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-17 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: timecop
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: fakeweb
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: addressable
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ description: A gem to integrate your Rails 3 app with http://logbook.me
78
+ email:
79
+ - support@logbook.me
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - .gitignore
88
+ - .rvmrc
89
+ - Gemfile
90
+ - Gemfile.lock
91
+ - README.rdoc
92
+ - Rakefile
93
+ - lib/logbook.rb
94
+ - lib/logbook/agent.rb
95
+ - lib/logbook/client.rb
96
+ - lib/logbook/railtie.rb
97
+ - lib/logbook/version.rb
98
+ - logbook.gemspec
99
+ - spec/agent_spec.rb
100
+ - spec/client_spec.rb
101
+ - spec/logbook_spec.rb
102
+ - spec/spec_helper.rb
103
+ - tmp/.keep
104
+ has_rdoc: true
105
+ homepage: http://logbook.me
106
+ licenses: []
107
+
108
+ post_install_message:
109
+ rdoc_options: []
110
+
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project: logbook
134
+ rubygems_version: 1.3.7
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Logbook is a hosted service to collect log entries from your application
138
+ test_files: []
139
+