log_spy 0.0.1

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: a3109d5c0b67ca1205a4c268d3d00a2c14551823
4
+ data.tar.gz: 1ee795e98a6c2b25fa610fe74b7e6538127aa28b
5
+ SHA512:
6
+ metadata.gz: 739791004933ab3e1079d18a44813bb4765e0a517ef20f36243f2612bf69bef1a175fd0af392f6e953bf95192d1080f3ae6488433ced5829619d687d9469b2b2
7
+ data.tar.gz: 377744523bcb6bc2268ca28427836d3cf03eea916f39545bc685bdfe1d51fd71eab5f7c69007aaa9556c7ed652b890acede8427dd0aad0ddbad9c2d8556da4ce
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ -I ./spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in log_spy.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Yang-Hsing Lin
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,82 @@
1
+ # LogSpy
2
+
3
+ LogSpy is a Rack middleware sending request log to [Amazon SQS](http://aws.amazon.com/sqs/).
4
+ After each request, `log_spy` opens a new thread and sends the request log payload as a `json` string onto [AWS SQS](http://aws.amazon.com/sqs/).
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'log_spy'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install log_spy
19
+
20
+ ## Usage
21
+
22
+ require and use the middleware:
23
+
24
+ - bare rack:
25
+
26
+ ```ruby
27
+ require 'log_spy'
28
+ use LogSpy::Spy, 'aws-sqs-url'
29
+ ```
30
+
31
+ - rails:
32
+ ```ruby
33
+ # application.rb
34
+ config.middleware.use LogSpy::Spy, 'aws-sqs-url', :reigon => 'ap-southeast-1'
35
+ ```
36
+
37
+ ## API Documents:
38
+
39
+ ### to `use` the middleware:
40
+ - usage: `use LogSpy::Spy, <aws-sqs-url>[, <options>]`
41
+ - params:
42
+ - `aws-sqs-url`(required): the [Queue URL](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/ImportantIdentifiers.html) of SQS, which identifies the queue.
43
+ - `options`(optional): if given, `log_spy` would pass it to initialize [`AWS::SQS`](http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SQS.html)
44
+
45
+ ### the payload format sends to AWS SQS:
46
+
47
+ ```json
48
+ {
49
+ "path": "/the/request/path",
50
+ "status": 200,
51
+ "execution_time": 145.3, // in ms
52
+ "request": {
53
+ "content_type": "application/json",
54
+ "request_method": "post",
55
+ "ip": "123.1.1.1",
56
+ "query_string": "query-key=query-val&hello=world",
57
+ "body": "body-key=body-val"
58
+ },
59
+
60
+ // if got exception
61
+ "error": {
62
+ "message": "the exception message",
63
+ "backtrace": [ "exception back trace" ]
64
+ }
65
+ }
66
+ ```
67
+
68
+ - `error`: `error` would not be included in the payload if no exception was raised
69
+ - `request.body`: if the request `Content-Type` is of `multipart`, the body would be an empty string
70
+
71
+ ## Testing:
72
+ `$ bundle install`
73
+ `$ bundle exec rspec spec/`
74
+
75
+
76
+ ## Contributing
77
+
78
+ 1. Fork it ( https://github.com/[my-github-username]/log_spy/fork )
79
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
80
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
81
+ 4. Push to the branch (`git push origin my-new-feature`)
82
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+ class LogSpy::Payload
3
+ def initialize req, res, error = nil
4
+ @req = req
5
+ @res = res
6
+ @error = error
7
+ end
8
+
9
+ def to_json
10
+ req_body = @req.content_type =~ /multipart/ ? '' : @req.body.read
11
+
12
+ hash = {
13
+ :path => @req.path,
14
+ :status => @res.status,
15
+ :execution_time => @res.duration,
16
+ :request => {
17
+ :content_type => @req.content_type,
18
+ :request_method => @req.request_method,
19
+ :ip => @req.ip,
20
+ :query_string => @req.query_string,
21
+ :body => req_body
22
+ }
23
+ }
24
+
25
+ if @error
26
+ hash[:error] = { :message => @error.message, :backtrace => @error.backtrace }
27
+ end
28
+
29
+ hash.to_json
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ require 'aws-sdk'
2
+ require 'rack'
3
+ require 'log_spy/payload'
4
+ require 'json'
5
+ require 'ostruct'
6
+
7
+ class LogSpy::Spy
8
+ attr_reader :sqs_thread
9
+ def initialize(app, sqs_url, options = {})
10
+ @app = app
11
+ @sqs_url = sqs_url
12
+ @options = options
13
+ end
14
+
15
+ def call env
16
+ @start_time = Time.now.to_f
17
+ @req = Rack::Request.new env
18
+ @status, @header, @body = @app.call(env)
19
+
20
+ @sqs_thread = send_sqs_async
21
+ [ @status, @header, @body ]
22
+ rescue Exception => err
23
+ @sqs_thread = send_sqs_async(err)
24
+
25
+ raise err
26
+ end
27
+
28
+ def send_sqs_async(err = nil)
29
+ @sqs_thread = Thread.new do
30
+ status = err ? 500 : @status
31
+ sqs = AWS::SQS.new(@options)
32
+ duration = ( (Time.now.to_f - @start_time) * 1000 ).round(0)
33
+ res = OpenStruct.new({
34
+ :duration => duration,
35
+ :status => status
36
+ })
37
+ payload = ::LogSpy::Payload.new(@req, res, err)
38
+
39
+ sqs.queues[@sqs_url].send_message(payload.to_json)
40
+ end
41
+ end
42
+ private :send_sqs_async
43
+ end
@@ -0,0 +1,3 @@
1
+ module LogSpy
2
+ VERSION = "0.0.1"
3
+ end
data/lib/log_spy.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "log_spy/version"
2
+ require "log_spy/spy"
3
+
4
+ module LogSpy
5
+ # Your code goes here...
6
+ end
data/log_spy.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'log_spy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "log_spy"
8
+ spec.version = LogSpy::VERSION
9
+ spec.authors = ["Yang-Hsing Lin"]
10
+ spec.email = ["yanghsing.lin@gmail.com"]
11
+ spec.summary = %q{ send rack application log to Amazon SQS }
12
+ spec.description = %q{ LogSpy is a rack middleware sending request log to Amazon SQS }
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "aws-sdk"
22
+ spec.add_dependency "rack"
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require 'log_spy/payload'
3
+
4
+ describe LogSpy::Payload do
5
+ let(:path) { '/the/request/path' }
6
+ let(:request_method) { 'post' }
7
+ let(:ip) { '123.1.1.1' }
8
+ let(:query_string) { 'query-key=query-val' }
9
+ let(:status) { 200 }
10
+ let(:duration) { 335 }
11
+ let(:body) { double(:body, :read => 'the-raw-body') }
12
+ let(:content_type) { 'application/json' }
13
+
14
+ let(:request) do
15
+ double(:request,
16
+ :path => path,
17
+ :content_type => content_type,
18
+ :request_method => request_method,
19
+ :ip => ip,
20
+ :query_string => query_string,
21
+ :body => body)
22
+ end
23
+
24
+ let(:response) do
25
+ double(:response,
26
+ :status => status,
27
+ :duration => duration)
28
+ end
29
+
30
+ let(:error) { Exception.new 'the-message' }
31
+
32
+ describe '::new(request, response[, error = nil])' do
33
+ it 'takes a request, response and an optional error to init' do
34
+ payload = LogSpy::Payload.new request, response
35
+ payload_with_err = LogSpy::Payload.new(request, response, error)
36
+ end
37
+ end
38
+
39
+ describe '#to_json' do
40
+ let(:expected_hash) do
41
+ {
42
+ :path => path,
43
+ :status => status,
44
+ :execution_time => duration,
45
+ :request => {
46
+ :content_type => content_type,
47
+ :request_method => request_method,
48
+ :ip => ip,
49
+ :query_string => query_string,
50
+ :body => body.read
51
+ }
52
+ }
53
+ end
54
+
55
+ context "if no error" do
56
+ let(:payload) { LogSpy::Payload.new request, response }
57
+ it 'returns correct format' do
58
+ expect(payload.to_json).to eq(expected_hash.to_json)
59
+ end
60
+
61
+ it 'returns no body if request content_type is multipart' do
62
+ allow(request).to receive_messages(:content_type => 'multipart/form-data')
63
+ expected_hash[:request][:content_type] = 'multipart/form-data'
64
+ expected_hash[:request][:body] = ''
65
+ expect(payload.to_json).to eq(expected_hash.to_json)
66
+ end
67
+ end
68
+
69
+ context "if with error" do
70
+ let(:payload) { LogSpy::Payload.new request, response, error }
71
+ it 'returns error with message and backtrace' do
72
+ expected_hash[:error] = {
73
+ :message => error.message,
74
+ :backtrace => error.backtrace
75
+ }
76
+
77
+ expect(payload.to_json).to eq(expected_hash.to_json)
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,2 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
data/spec/spy_spec.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+ require 'log_spy/spy'
3
+
4
+ describe LogSpy::Spy do
5
+ let(:app) { double(:app) }
6
+ let(:sqs_url) { 'the-sqs-url' }
7
+ let(:options) { { :reigon => 'ap-southeast-1' } }
8
+
9
+ describe '#new(app, sqs_url [, options = {}])' do
10
+ it 'takes an app, an sqs_url, and an optional options to init' do
11
+ middleware = LogSpy::Spy.new(app, sqs_url)
12
+ middleware_w_options = LogSpy::Spy.new(app, sqs_url, options)
13
+ end
14
+ end
15
+
16
+ describe 'call' do
17
+ let(:sqs) { double(:sqs, :queues => double(:[] => queue)) }
18
+ let(:queue) { double(:queue, :send_message => true) }
19
+ let(:call_result) { [200, { 'Content-Type' => 'application/json' }, [ 'body' ]] }
20
+ let(:env) { {} }
21
+
22
+ let(:request) { double(:request) }
23
+ let(:payload) { double(:payload, :to_json => {'key' => 'val'}.to_json) }
24
+
25
+ let(:middleware) { LogSpy::Spy.new(app, sqs_url, options) }
26
+ let(:duration) { 20.12345 }
27
+ let(:now) { Time.now }
28
+ let(:three_sec_later) { now + duration }
29
+
30
+ before :each do
31
+ allow(AWS::SQS).to receive_messages(:new => sqs)
32
+ allow(app).to receive_messages(:call => call_result)
33
+ allow(Rack::Request).to receive_messages(:new => request)
34
+ allow(LogSpy::Payload).to receive_messages(:new => payload)
35
+ allow(Time).to receive(:now).and_return(now, three_sec_later)
36
+ end
37
+
38
+ it 'config sqs with options' do
39
+ expect(AWS::SQS).to receive(:new).with(options)
40
+
41
+ middleware.call env
42
+ middleware.sqs_thread.join
43
+ end
44
+
45
+ it 'sends payload json json to sqs' do
46
+ expect(Rack::Request).to receive(:new).with(env)
47
+
48
+ expect(queue).to receive(:send_message).with(payload.to_json)
49
+ middleware.call env
50
+ middleware.sqs_thread.join
51
+ end
52
+
53
+ it 'builds payload with request, status, request_time' do
54
+ expect(LogSpy::Payload).to receive(:new) do |req, res|
55
+ expect(req).to be(request)
56
+ expect(res.status).to eq(200)
57
+ expect(res.duration).to eq((duration * 1000).round(0))
58
+ end
59
+
60
+ middleware.call env
61
+ middleware.sqs_thread.join
62
+ end
63
+
64
+ it 'returns original result' do
65
+ expect(middleware.call(env)).to eq(call_result)
66
+ middleware.sqs_thread.join
67
+ end
68
+
69
+ context "if `app.call raises`" do
70
+ let(:error) { Exception.new }
71
+
72
+ before :each do
73
+ allow(app).to receive(:call).and_raise(error)
74
+ end
75
+
76
+ it 'build payload with error' do
77
+ expect(LogSpy::Payload).to receive(:new) do |req, res, err|
78
+ expect(req).to be(request)
79
+ expect(res.status).to eq(500)
80
+ expect(res.duration).to eq(( duration * 1000 ).round(0))
81
+ expect(err).to eq(error)
82
+ end
83
+
84
+ begin
85
+ middleware.call(env)
86
+ rescue Exception => e
87
+ end
88
+
89
+ middleware.sqs_thread.join
90
+ end
91
+
92
+ it 'forward to error' do
93
+ expect {
94
+ middleware.call(env)
95
+ }.to raise_error(error)
96
+
97
+ middleware.sqs_thread.join
98
+ end
99
+ end
100
+
101
+ end
102
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: log_spy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Yang-Hsing Lin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ' LogSpy is a rack middleware sending request log to Amazon SQS '
84
+ email:
85
+ - yanghsing.lin@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .rspec
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - lib/log_spy.rb
97
+ - lib/log_spy/payload.rb
98
+ - lib/log_spy/spy.rb
99
+ - lib/log_spy/version.rb
100
+ - log_spy.gemspec
101
+ - spec/payload_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/spy_spec.rb
104
+ homepage: ''
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.2.2
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: send rack application log to Amazon SQS
128
+ test_files:
129
+ - spec/payload_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/spy_spec.rb