roqua-support 0.1.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.
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