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 +7 -0
- data/.gitignore +16 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +22 -0
- data/README.md +54 -0
- data/Rakefile +24 -0
- data/lib/roqua-support.rb +2 -0
- data/lib/roqua-support/version.rb +5 -0
- data/lib/roqua/core_ext/array/stable_sort_by.rb +37 -0
- data/lib/roqua/core_ext/enumerable/sort_by_alphanum.rb +49 -0
- data/lib/roqua/core_ext/fabrication/singleton.rb +8 -0
- data/lib/roqua/core_ext/fixnum/clamp.rb +8 -0
- data/lib/roqua/support.rb +14 -0
- data/lib/roqua/support/command_runner.rb +24 -0
- data/lib/roqua/support/log_wrapper.rb +34 -0
- data/lib/roqua/support/logging.rb +26 -0
- data/lib/roqua/support/request_logger.rb +108 -0
- data/roqua-support.gemspec +26 -0
- data/spec/roqua/core_ext/array/stable_sort_by_spec.rb +40 -0
- data/spec/roqua/core_ext/enumerable/sort_by_alphanum_spec.rb +21 -0
- data/spec/roqua/core_ext/fabrication/singleton_spec.rb +16 -0
- data/spec/roqua/core_ext/fixnum/clamp_spec.rb +23 -0
- data/spec/roqua/support/logging_spec.rb +86 -0
- data/spec/roqua/support/request_logger_spec.rb +147 -0
- data/spec/roqua/support_spec.rb +19 -0
- metadata +133 -0
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
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
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,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,24 @@
|
|
1
|
+
require 'pty'
|
2
|
+
|
3
|
+
module CommandRunner
|
4
|
+
def self.run_command_and_print(cmd, output)
|
5
|
+
output.puts "[1mExecuting #{cmd}[0m\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
|