roqua-support 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4aa0d44014dfb644f0d1c89f6d1fc7bf161bd69d
4
+ data.tar.gz: 480bec91ad5b3b5687c365c82c642302a2fe8064
5
+ SHA512:
6
+ metadata.gz: 45cdd38e1b846d5456efe2eb76d9cb5757ed6f0d25badb43e74bcb7db2e0626118986077cda6e588a48e416c76fd2e0b56002f70a97c58e09c55e4f81cab963f
7
+ data.tar.gz: 87bbbd4f07f04b89f9f07674ee2253d3de81ab078c445c7d34d167869e74b999f200f83c62ca02cb39f356e66fe2c3aabb171f9eb22192b8131e68c32b9f7922
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in roqua-support.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ roqua-support (0.1.0)
5
+ activesupport (~> 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (3.2.16)
11
+ i18n (~> 0.6, >= 0.6.4)
12
+ multi_json (~> 1.0)
13
+ diff-lcs (1.1.3)
14
+ i18n (0.6.8)
15
+ multi_json (1.8.2)
16
+ rake (10.0.3)
17
+ rspec (2.12.0)
18
+ rspec-core (~> 2.12.0)
19
+ rspec-expectations (~> 2.12.0)
20
+ rspec-mocks (~> 2.12.0)
21
+ rspec-core (2.12.0)
22
+ rspec-expectations (2.12.0)
23
+ diff-lcs (~> 1.1.3)
24
+ rspec-mocks (2.12.0)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ bundler (~> 1.0)
31
+ rake
32
+ roqua-support!
33
+ rspec (~> 2.12.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 RoQua
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Roqua::Support
2
+
3
+ This gem contains all sorts of support utilities and helper methods that are
4
+ useful to have in RoQua's applications, but have nothing to with the domain.
5
+
6
+ ## Usage
7
+
8
+ ### Logging
9
+
10
+ ```ruby
11
+ class Example
12
+ include Roqua::Logging
13
+
14
+ def methodname
15
+ # This writes a single line to the event log with
16
+ # the given event name and parameters as key=value format.
17
+ eventlog.info 'example.eventname', optional: 'extra parameters'
18
+ end
19
+
20
+ def another
21
+ # This automatically emits two lines, one for when the
22
+ # block begins, one for when the block ends. ':started',
23
+ # ':finished', ':failed' are appended to the event name
24
+ # given, and the duration of the block is logged with
25
+ # the :finished log line.
26
+ eventlog.lifecycle 'example.lifecycle', optional: 'params' do
27
+ sleep 5
28
+ end
29
+ end
30
+
31
+ def third
32
+ # This example is the same as the `another` example.
33
+ sleep 5
34
+ end
35
+ log :third, 'example.lifecycle', optional: 'params'
36
+ end
37
+ ```
38
+
39
+ ### Rails logger
40
+
41
+ You can also add an additional request logger by adding this to `config/initializers/request_logger.rb`:
42
+
43
+ ```ruby
44
+ require 'roqua/support/request_logger'
45
+ Roqua::Support::RequestLogger.attach_to :action_controller
46
+ ```
47
+
48
+ ## Contributing
49
+
50
+ 1. Fork it
51
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
52
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
53
+ 4. Push to the branch (`git push origin my-new-feature`)
54
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'bundler'
6
+ rescue LoadError => e
7
+ warn e.message
8
+ warn "Run `gem install bundler` to install Bundler."
9
+ exit -1
10
+ end
11
+
12
+ begin
13
+ Bundler.setup(:development)
14
+ rescue Bundler::BundlerError => e
15
+ warn e.message
16
+ warn "Run `bundle install` to install missing gems."
17
+ exit e.status_code
18
+ end
19
+
20
+ require "bundler/gem_tasks"
21
+ require 'rspec/core/rake_task'
22
+
23
+ RSpec::Core::RakeTask.new(:spec)
24
+ task :default => :spec
@@ -0,0 +1,2 @@
1
+ require "roqua-support/version"
2
+ require "roqua/support"
@@ -0,0 +1,5 @@
1
+ module Roqua
2
+ module Support
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,37 @@
1
+ class Array
2
+ # Method for stably sorting elements in an array on multiple attributes.
3
+ #
4
+ # * Pass the method a block with two arrays containing the attributes for which the
5
+ # elements should be subsequently sorted. The first attribute is applied last.
6
+ # If for some attribute the sort order should be reversed, the parameters x and y can
7
+ # be exchanged between the arrays.
8
+ #
9
+ # ==== Example
10
+ # my_array.stable_sort_by{|x, y| [
11
+ # x.attribute1,
12
+ # y.attribute2,
13
+ # y.attribute3,
14
+ # y.attribute4
15
+ # ] <=> [
16
+ # y.attribute1,
17
+ # x.attribute2,
18
+ # x.attribute3,
19
+ # x.attribute4
20
+ # ]}
21
+ #
22
+ def stable_sort_by
23
+ sort do |x, y|
24
+ if not x
25
+ -1
26
+ elsif not y
27
+ 1
28
+ else
29
+ if block_given?
30
+ yield x, y
31
+ else
32
+ x <=> y
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ module Enumerable
2
+ def sort_by_alphanum
3
+ sort do |a, b|
4
+ if block_given?
5
+ grouped_compare(yield(a), yield(b))
6
+ else
7
+ grouped_compare(a, b)
8
+ end
9
+ end
10
+ end
11
+
12
+ def sort_by_alphanum!
13
+ sort! do |a, b|
14
+ if block_given?
15
+ grouped_compare(yield(a), yield(b))
16
+ else
17
+ grouped_compare(a, b)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def grouped_compare(a, b)
25
+ loop {
26
+ a_chunk, a = extract_alpha_or_number_group(a)
27
+ b_chunk, b = extract_alpha_or_number_group(b)
28
+
29
+ ret = if a_chunk =~ /\d/ and b_chunk =~ /\d/
30
+ a_chunk.to_i <=> b_chunk.to_i
31
+ else
32
+ a_chunk <=> b_chunk
33
+ end
34
+
35
+ return -1 if a_chunk == ''
36
+ return ret if ret != 0
37
+ }
38
+ end
39
+
40
+ def extract_alpha_or_number_group(item)
41
+ matchdata = /([A-Za-z]+|[\d]+)/.match(item)
42
+
43
+ if matchdata.nil?
44
+ ["", ""]
45
+ else
46
+ [matchdata[0], item = item[matchdata.offset(0)[1] .. -1]]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ class Fabricate
2
+ @singletons = {}
3
+
4
+ def self.singleton(name, options={}, &block)
5
+ @singletons[name] ||= Fabricate(name, options={}, &block)
6
+ return @singletons[name]
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class Fixnum
2
+ def clamp(low, high)
3
+ raise "Low (#{low}) must be lower than high (#{high})" unless low < high
4
+ return low if self < low
5
+ return high if self > high
6
+ self
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ require 'logger'
2
+ require 'roqua/support/logging'
3
+
4
+ module Roqua
5
+ class << self
6
+ def logger
7
+ @logger ||= LogWrapper.new(Logger.new(STDOUT))
8
+ end
9
+
10
+ def logger=(logger)
11
+ @logger = LogWrapper.new(logger)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ require 'pty'
2
+
3
+ module CommandRunner
4
+ def self.run_command_and_print(cmd, output)
5
+ output.puts "Executing #{cmd}\n\n"
6
+
7
+ PTY.spawn(cmd) do |read_stream, write_stream, pid|
8
+ begin
9
+ while chars = read_stream.read(1)
10
+ output.print chars
11
+ end
12
+ rescue Errno::EIO
13
+ end
14
+ Process.wait(pid)
15
+ end
16
+ output.puts "\n\n\n"
17
+
18
+ if $?
19
+ exit 1 if $?.exitstatus > 0
20
+ else
21
+ raise "Huh?! We didn't get an exit status from that last one: #{cmd}"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ module Roqua
4
+ class LogWrapper
5
+ attr_reader :logger
6
+
7
+ def initialize(logger)
8
+ @logger = logger
9
+ end
10
+
11
+ def add(level, message, options = {})
12
+ logger.send(level, "#{message} #{options.to_json}".strip)
13
+ end
14
+
15
+ [:fatal, :error, :warn, :info, :debug].each do |level|
16
+ define_method(level) do |*args|
17
+ add(level, *args)
18
+ end
19
+ end
20
+
21
+ def lifecycle(message, options = {})
22
+ started_at = Time.now.to_f
23
+ info("#{message}:started", options)
24
+ value = yield
25
+ finished_at = Time.now.to_f
26
+ duration = finished_at - started_at
27
+ info("#{message}:finished", {duration: duration}.merge(options))
28
+ value
29
+ rescue => e
30
+ error("#{message}:failed", {exception: e.class, message: e.message}.merge(options))
31
+ raise
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_support/core_ext/module/aliasing'
2
+ require 'roqua/support/log_wrapper'
3
+
4
+ module Roqua
5
+ module Logging
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def log(method_name, message, options = {})
12
+ define_method(:"#{method_name}_with_log") do |*args, &block|
13
+ eventlog.lifecycle(message, options) do
14
+ send(:"#{method_name}_without_log", *args, &block)
15
+ end
16
+ end
17
+
18
+ alias_method_chain method_name, 'log'
19
+ end
20
+ end
21
+
22
+ def eventlog
23
+ Roqua.logger
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,108 @@
1
+ require 'roqua/support'
2
+ require 'active_support/log_subscriber'
3
+
4
+ module Roqua
5
+ module Support
6
+ module RequestLogging
7
+ def add_log_information(key, value)
8
+ Thread.current[:roqua_request_log] ||= {}
9
+ Thread.current[:roqua_request_log][key] = value
10
+ end
11
+ end
12
+
13
+ class RequestLogger < ActiveSupport::LogSubscriber
14
+ include Roqua::Logging
15
+
16
+ def process_action(event)
17
+ payload = event.payload
18
+ extra_logged_information = Thread.current[:roqua_request_log] || {}
19
+ Thread.current[:roqua_request_log] = {}
20
+
21
+ data = extract_request_id(event)
22
+ data.merge! extract_request(payload)
23
+ data.merge! extract_status(payload)
24
+ data.merge! extract_parameters(payload)
25
+ data.merge! redirect_information
26
+ data.merge! extra_logged_information
27
+ data.merge! runtimes(event)
28
+
29
+ #eventlog.info event.inspect
30
+ eventlog.info 'roqua.web', data
31
+ rescue Exception => e
32
+ eventlog.info 'roqua.web:logerror', {class: e.class, message: e.message}
33
+ raise
34
+ end
35
+
36
+ def redirect_to(event)
37
+ # Unfortunately, when a redirect is triggered by your application's code,
38
+ # ActionController fires two events. One for the redirect itself, and
39
+ # another one when the request is finished. Unfortunately the final event
40
+ # doesn't include the redirect, so we store the redirect URL as a
41
+ # thread-local attribute and refers to it in process_action.
42
+ Thread.current[:roqua_request_log_redirect] = event.payload[:location]
43
+ end
44
+
45
+ private
46
+
47
+ def extract_request_id(event)
48
+ {uuid: event.transaction_id}
49
+ end
50
+
51
+ def extract_request(payload)
52
+ {
53
+ :method => payload[:method],
54
+ :path => extract_path(payload),
55
+ :format => extract_format(payload),
56
+ :controller => payload[:params]['controller'],
57
+ :action => payload[:params]['action']
58
+ }
59
+ end
60
+
61
+ def extract_path(payload)
62
+ payload[:path].split("?").first
63
+ end
64
+
65
+ def extract_format(payload)
66
+ payload[:format]
67
+ end
68
+
69
+ def extract_status(payload)
70
+ if payload[:status]
71
+ { :status => payload[:status].to_i }
72
+ elsif payload[:exception]
73
+ exception, message = payload[:exception]
74
+ { :status => 500, :error => "#{exception}:#{message}" }
75
+ else
76
+ { :status => 0 }
77
+ end
78
+ end
79
+
80
+ def redirect_information
81
+ if location = Thread.current[:roqua_request_log_redirect]
82
+ Thread.current[:roqua_request_log_redirect] = nil
83
+ {location: location}
84
+ else
85
+ {}
86
+ end
87
+ end
88
+
89
+ def extract_parameters(payload)
90
+ filtered_params = payload[:params].reject do |key, value|
91
+ key == 'controller' or key == 'action'
92
+ end
93
+ {params: filtered_params}
94
+ end
95
+
96
+ def runtimes(event)
97
+ {
98
+ :duration => event.duration,
99
+ :view => event.payload[:view_runtime],
100
+ :db => event.payload[:db_runtime]
101
+ }.inject({}) do |runtimes, (name, runtime)|
102
+ runtimes[name] = runtime.to_f.round(2) if runtime
103
+ runtimes
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'roqua-support/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "roqua-support"
8
+ gem.version = Roqua::Support::VERSION
9
+ gem.summary = %q{Helper objects and proxies used by a lot of RoQua applications}
10
+ gem.description = %q{Logging backend, freedom patches, }
11
+ gem.license = "MIT"
12
+ gem.authors = ["Marten Veldthuis"]
13
+ gem.email = "marten@roqua.nl"
14
+ gem.homepage = "https://github.com/roqua/healthy"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency 'activesupport', '~> 3.2'
22
+
23
+ gem.add_development_dependency 'bundler', '~> 1.0'
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'rspec', '~> 2.12.0'
26
+ end
@@ -0,0 +1,40 @@
1
+ require 'roqua/core_ext/array/stable_sort_by'
2
+
3
+ describe Array do
4
+ describe "#stable_sort_by" do
5
+ it "wraps #sort" do
6
+ array = []
7
+ array.should_receive(:sort)
8
+ array.stable_sort_by
9
+ end
10
+
11
+ it "sorts nil values before all others" do
12
+ [1, nil, 3].stable_sort_by.should == [nil, 1, 3]
13
+ end
14
+
15
+ it "defaults to regular comparison" do
16
+ [1, 3, 2].stable_sort_by.should == [1, 2, 3]
17
+ end
18
+
19
+ it "accepts a block to do complex comparison" do
20
+ [{a: 2, b: 2, c: 3},
21
+ {a: 2, b: 2, c: 4},
22
+ {a: 1, b: 1, c: 6}].stable_sort_by do |x, y|
23
+ [x[:a], x[:b], x[:c]] <=> [y[:a], y[:b], y[:c]]
24
+ end.should == [{a: 1, b: 1, c: 6},
25
+ {a: 2, b: 2, c: 3},
26
+ {a: 2, b: 2, c: 4}]
27
+ end
28
+
29
+ it "leaves items in original order if they are the same" do
30
+ [{a: 2, b: 2, c: 4},
31
+ {a: 2, b: 1, c: 3},
32
+ {a: 1, b: 3, c: 6}].sort do |x, y|
33
+ [x[:a], x[:b]] <=> [y[:a], y[:b]]
34
+ end.should == [{a: 1, b: 3, c: 6},
35
+ {a: 2, b: 1, c: 3},
36
+ {a: 2, b: 2, c: 4}]
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ require 'roqua/core_ext/enumerable/sort_by_alphanum'
2
+
3
+ describe Enumerable do
4
+ describe '#sort_by_alphanum' do
5
+ it 'sorts by chunks' do
6
+ ["004some11thing",
7
+ "004some10thing",
8
+ "3another"].sort_by_alphanum.should == ["3another", "004some10thing", "004some11thing"]
9
+ end
10
+
11
+ it 'can take a block which can transform values before comparison' do
12
+ ["004some11thing",
13
+ "004some10thing",
14
+ "3another"].sort_by_alphanum(&:reverse).should == ["004some10thing", "004some11thing", "3another"]
15
+ end
16
+
17
+ it 'compares number chunks as integers' do
18
+ %w(004 3).sort_by_alphanum.should == %w(3 004)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require 'roqua/core_ext/fabrication/singleton'
2
+
3
+ def Fabricate(name, overrides={}, &block)
4
+ rand
5
+ end
6
+
7
+ describe Fabricate do
8
+ it "returns singleton objects" do
9
+ Fabricate.singleton(:one).should == Fabricate.singleton(:one)
10
+ end
11
+
12
+ it 'maintains multiple singletons' do
13
+ Fabricate.singleton(:one).should_not == Fabricate.singleton(:two)
14
+ end
15
+ end
16
+
@@ -0,0 +1,23 @@
1
+ require 'roqua/core_ext/fixnum/clamp'
2
+
3
+ describe Fixnum do
4
+ describe '#clamp' do
5
+ it "returns self if within bounds" do
6
+ 5.clamp(1,10).should == 5
7
+ end
8
+
9
+ it "returns the lower bound if self < low" do
10
+ 5.clamp(8,10).should == 8
11
+ end
12
+
13
+ it "returns the upper bound if self > high" do
14
+ 5.clamp(1,3).should == 3
15
+ end
16
+
17
+ it "should raise an exception if the lower bound is greater than the upper bound" do
18
+ expect {
19
+ 5.clamp(10,1)
20
+ }.to raise_error
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ require 'roqua/support/logging'
2
+ require 'logger'
3
+ require 'stringio'
4
+
5
+ module Roqua
6
+ describe LogWrapper do
7
+ let(:logstream) { StringIO.new }
8
+ let(:logger) { Logger.new(logstream) }
9
+ let(:logwrapper) { LogWrapper.new(logger) }
10
+
11
+ def log
12
+ logstream.string
13
+ end
14
+
15
+ describe '#add' do
16
+ it 'writes event name to log' do
17
+ logwrapper.add :info, "testevent"
18
+ log.should include("testevent {}\n")
19
+ end
20
+
21
+ it 'writes given parameters as json hash' do
22
+ logwrapper.add :info, "testevent", extra: 'params', go: 'here'
23
+ log.should include('testevent {"extra":"params","go":"here"}' + "\n")
24
+ end
25
+
26
+ it 'escapes newline characters in params' do
27
+ logwrapper.add :info, "testevent", param: "this\nshould not have newlines"
28
+ log.should include('testevent {"param":"this\nshould not have newlines"')
29
+ end
30
+ end
31
+
32
+ describe '#lifecycle' do
33
+ it 'logs the start and finish lifecycle of a block' do
34
+ logwrapper.lifecycle 'testevent', extra: 'params' do
35
+ 1 + 1
36
+ end
37
+ log.should include('testevent:started {"extra":"params"}')
38
+ log.should match(/testevent:finished.*"extra":"params"/)
39
+ end
40
+
41
+ it 'logs the duration of the block with the finished event' do
42
+ logwrapper.lifecycle('testevent') { 1 + 1 }
43
+ log.should match(/testevent:finished.*"duration":/)
44
+ end
45
+
46
+ it 'returns the value returned by the block' do
47
+ logwrapper.lifecycle('testevent') { 1 + 1 }.should == 2
48
+ end
49
+
50
+ it 'logs the start and failure of a block if it raises' do
51
+ logwrapper.lifecycle 'testevent' do
52
+ raise StandardError, "Foo"
53
+ end rescue nil
54
+ log.should include('testevent:started')
55
+ log.should include('testevent:failed {"exception":"StandardError","message":"Foo"}')
56
+ end
57
+
58
+ it 'reraises the exception' do
59
+ expect {
60
+ logwrapper.lifecycle 'testevent' do
61
+ raise "Foo"
62
+ end
63
+ }.to raise_error('Foo')
64
+ end
65
+ end
66
+
67
+ describe '.lifecycle' do
68
+ it 'wraps given method' do
69
+ ::Roqua.stub(:logger => logwrapper)
70
+
71
+ test = Class.new do
72
+ include Logging
73
+
74
+ def foo
75
+ 'bar'
76
+ end
77
+ log :foo, 'roqua.testevent.foo'
78
+ end
79
+
80
+ test.new.foo.should == 'bar'
81
+ log.should include('roqua.testevent.foo:started')
82
+ log.should include('roqua.testevent.foo:finished')
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,147 @@
1
+ require 'roqua/support/logging'
2
+ require 'roqua/support/request_logger'
3
+ require 'active_support/notifications'
4
+ require 'active_support/core_ext/string'
5
+
6
+ describe Roqua::Support::RequestLogger do
7
+ let(:logstream) { StringIO.new }
8
+ let(:logger) { Logger.new(logstream) }
9
+ let(:logwrapper) { Roqua::LogWrapper.new(logger) }
10
+
11
+ before { Roqua.stub(logger: logwrapper) }
12
+
13
+ def log
14
+ logstream.string
15
+ end
16
+
17
+ let(:subscriber) { Roqua::Support::RequestLogger.new }
18
+
19
+ context 'when processing a request' do
20
+ let(:event) do
21
+ ActiveSupport::Notifications::Event.new('process_action.action_controller',
22
+ Time.new(2013, 02, 28, 12, 34, 56),
23
+ Time.new(2013, 02, 28, 12, 34, 57), 2,
24
+ status: 200, format: 'application/json', method: 'GET', path: '/home?foo=bar',
25
+ params: {'controller' => 'home', 'action' => 'index', 'foo' => 'bar'},
26
+ db_runtime: 0.02, view_runtime: 0.01
27
+ )
28
+ end
29
+
30
+ it "logs the URL" do
31
+ subscriber.process_action(event)
32
+ logstream.string.should include('/home')
33
+ end
34
+
35
+ it "does not log the query string" do
36
+ subscriber.process_action(event)
37
+ logstream.string.should_not include('?foo=bar')
38
+ end
39
+
40
+ it "logs the HTTP method" do
41
+ subscriber.process_action(event)
42
+ logstream.string.should include('"method":"GET"')
43
+ end
44
+
45
+ it "logs the status code returned" do
46
+ subscriber.process_action(event)
47
+ logstream.string.should include('"status":200')
48
+ end
49
+
50
+ it "logs the controller and action" do
51
+ subscriber.process_action(event)
52
+ logstream.string.should include('"controller":"home","action":"index"')
53
+ end
54
+
55
+ it 'logs request parameters' do
56
+ subscriber.process_action(event)
57
+ logstream.string.should include('"params":{"foo":"bar"}')
58
+ end
59
+
60
+ it "logs how long the request took" do
61
+ subscriber.process_action(event)
62
+ logstream.string.should =~ /"duration":1000.0/
63
+ end
64
+
65
+ it "logs the view rendering time" do
66
+ subscriber.process_action(event)
67
+ logstream.string.should =~ /"view":0.01/
68
+ end
69
+
70
+ it "logs the database rendering time" do
71
+ subscriber.process_action(event)
72
+ logstream.string.should =~ /"db":0.02/
73
+ end
74
+
75
+ it 'logs extra information added in the controller' do
76
+ controller = Class.new do
77
+ include Roqua::Support::RequestLogging
78
+
79
+ def index
80
+ add_log_information 'current_user', 'johndoe'
81
+ end
82
+ end
83
+ controller.new.index
84
+ subscriber.process_action(event)
85
+ logstream.string.should include('"current_user":"johndoe"')
86
+
87
+ # next request should not still maintain this data
88
+ logstream.truncate 0
89
+ subscriber.process_action(event)
90
+ logstream.string.should_not include('current_user')
91
+ end
92
+ end
93
+
94
+ context 'when an exception occured processing the request' do
95
+ let(:event) do
96
+ ActiveSupport::Notifications::Event.new('process_action.action_controller',
97
+ Time.now, Time.now, 2,
98
+ status: nil, format: 'application/json', method: 'GET', path: '/home?foo=bar',
99
+ exception: ['AbstractController::ActionNotFound', 'Route not found'],
100
+ params: {'controller' => 'home', 'action' => 'index', 'foo' => 'bar'},
101
+ db_runtime: 0.02, view_runtime: 0.01
102
+ )
103
+ end
104
+
105
+ it "logs the 500 status when an exception occurred" do
106
+ subscriber.process_action(event)
107
+ logstream.string.should =~ /"status":500/
108
+ logstream.string.should =~ /"error":"AbstractController::ActionNotFound:Route not found"/
109
+ end
110
+
111
+ it "should return an unknown status when no status or exception is found" do
112
+ event.payload[:status] = nil
113
+ event.payload[:exception] = nil
114
+ subscriber.process_action(event)
115
+ logstream.string.should =~ /"status":0/
116
+ end
117
+ end
118
+
119
+ context 'when the request redirected' do
120
+ let(:event) do
121
+ ActiveSupport::Notifications::Event.new('process_action.action_controller',
122
+ Time.new(2013, 02, 28, 12, 34, 56),
123
+ Time.new(2013, 02, 28, 12, 34, 57), 2,
124
+ status: 200, format: 'application/json', method: 'GET', path: '/home?foo=bar',
125
+ params: {'controller' => 'home', 'action' => 'index', 'foo' => 'bar'},
126
+ db_runtime: 0.02, view_runtime: 0.01
127
+ )
128
+ end
129
+
130
+ let(:redirect) {
131
+ ActiveSupport::Notifications::Event.new(
132
+ 'redirect_to.action_controller', Time.now, Time.now, 1, location: 'http://example.com', status: 302
133
+ )
134
+ }
135
+
136
+ it 'logs the redirect' do
137
+ subscriber.redirect_to(redirect)
138
+ subscriber.process_action(event)
139
+ log.should include('"location":"http://example.com"')
140
+
141
+ # next request should no longer get location
142
+ logstream.truncate 0
143
+ subscriber.process_action(event)
144
+ log.should_not include('location')
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,19 @@
1
+ require 'roqua/support'
2
+
3
+ describe Roqua do
4
+ describe '#logger' do
5
+ it 'has a default' do
6
+ Roqua.logger.should be_an_instance_of(Roqua::LogWrapper)
7
+ end
8
+ end
9
+
10
+ describe '#logger=' do
11
+ let(:logger) { stub }
12
+
13
+ it 'wraps a given logger' do
14
+ Roqua.logger = logger
15
+ Roqua.logger.should be_an_instance_of(Roqua::LogWrapper)
16
+ Roqua.logger.logger.should == logger
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roqua-support
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marten Veldthuis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.12.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 2.12.0
69
+ description: 'Logging backend, freedom patches, '
70
+ email: marten@roqua.nl
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - .gitignore
76
+ - .rspec
77
+ - .travis.yml
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - lib/roqua-support.rb
84
+ - lib/roqua-support/version.rb
85
+ - lib/roqua/core_ext/array/stable_sort_by.rb
86
+ - lib/roqua/core_ext/enumerable/sort_by_alphanum.rb
87
+ - lib/roqua/core_ext/fabrication/singleton.rb
88
+ - lib/roqua/core_ext/fixnum/clamp.rb
89
+ - lib/roqua/support.rb
90
+ - lib/roqua/support/command_runner.rb
91
+ - lib/roqua/support/log_wrapper.rb
92
+ - lib/roqua/support/logging.rb
93
+ - lib/roqua/support/request_logger.rb
94
+ - roqua-support.gemspec
95
+ - spec/roqua/core_ext/array/stable_sort_by_spec.rb
96
+ - spec/roqua/core_ext/enumerable/sort_by_alphanum_spec.rb
97
+ - spec/roqua/core_ext/fabrication/singleton_spec.rb
98
+ - spec/roqua/core_ext/fixnum/clamp_spec.rb
99
+ - spec/roqua/support/logging_spec.rb
100
+ - spec/roqua/support/request_logger_spec.rb
101
+ - spec/roqua/support_spec.rb
102
+ homepage: https://github.com/roqua/healthy
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.0.6
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Helper objects and proxies used by a lot of RoQua applications
126
+ test_files:
127
+ - spec/roqua/core_ext/array/stable_sort_by_spec.rb
128
+ - spec/roqua/core_ext/enumerable/sort_by_alphanum_spec.rb
129
+ - spec/roqua/core_ext/fabrication/singleton_spec.rb
130
+ - spec/roqua/core_ext/fixnum/clamp_spec.rb
131
+ - spec/roqua/support/logging_spec.rb
132
+ - spec/roqua/support/request_logger_spec.rb
133
+ - spec/roqua/support_spec.rb