rack-access-capture 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: 8911f1418898cc356642bffbe5b174629cd546c0
4
+ data.tar.gz: 2125d9a0db9613d0a2afcbe56a50bba311e5cb2f
5
+ SHA512:
6
+ metadata.gz: 973e5287ae69c3ef18be156caf7da4fbe49c95dee8b4b11058da91a22b54ec3230791743fd93e0a0a5de1ad074cf02bca1dbf6da2af3fd8553dcfd9bd7244e07
7
+ data.tar.gz: 03d054e1e7ecdb9236bb448560bd2a6ffaeb1bddd2fa8a698df70656b6407a0f46d86a4eccaafecb2ae26896432fb05bc70ed366b96e7c669aeb10b7479fd20a
@@ -0,0 +1,20 @@
1
+ module Rack
2
+ module Access
3
+ module Capture
4
+ module Collector
5
+
6
+ class AbstractAdapter
7
+
8
+ def collect?(env)
9
+ false
10
+ end
11
+
12
+ # This is meant to be implemented by the adapters that support access log collect.
13
+ def collect
14
+ # this should be overridden by concrete adapters
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ require 'rack/access/capture/collector/console_adapter'
2
+ require 'rack/access/capture/collector/fluentd_adapter'
3
+
4
+ module Rack
5
+ module Access
6
+ module Capture
7
+ module Collector
8
+
9
+ class Adapters
10
+
11
+ BUILTIN_ADAPTERS = [:console, :fluentd].freeze
12
+ COLLECTOR_ADAPTER_METHODS = ["collect?", "collect"].freeze
13
+ SUFFIX_CLASS_NAME = "Adapter".freeze
14
+
15
+ private_constant :BUILTIN_ADAPTERS, :COLLECTOR_ADAPTER_METHODS, :SUFFIX_CLASS_NAME
16
+
17
+ class << self
18
+
19
+ def interpret_collector(config)
20
+ name_or_class = config["adapter"] if !config.nil? && config.is_a?(Hash) && config.key?("adapter")
21
+ case name_or_class
22
+ when Symbol
23
+ collector_class = lookup(name_or_class)
24
+ collector_class.nil? ? lookup(:console).new : collector_class.new(config["config"])
25
+ when String
26
+ console_adapter_in_case_of_name_error do
27
+ builtin_klass = lookup(name_or_class)
28
+ obj = builtin_klass.nil? ? Object.const_get(name_or_class).new(config["config"]) : builtin_klass.new(config["config"])
29
+ collector_adapter?(obj) ? obj : lookup(:console).new
30
+ end
31
+ else
32
+ lookup(:console).new
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def collector_adapter?(object)
39
+ COLLECTOR_ADAPTER_METHODS.all? { |method_name| object.respond_to?(method_name) }
40
+ end
41
+
42
+ def lookup(name)
43
+ return nil unless BUILTIN_ADAPTERS.include? name.to_sym
44
+ const_get("Rack::Access::Capture::Collector::#{name.capitalize}#{SUFFIX_CLASS_NAME}")
45
+ end
46
+
47
+ def console_adapter_in_case_of_name_error(&block)
48
+ proc {
49
+ begin
50
+ yield
51
+ rescue NameError
52
+ lookup(:console).new
53
+ end
54
+ }.call
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ require 'json'
2
+ require "rack/access/capture/collector/abstract_adapter"
3
+
4
+ module Rack
5
+ module Access
6
+ module Capture
7
+ module Collector
8
+
9
+ class ConsoleAdapter < AbstractAdapter
10
+
11
+ def initialize(config = {})
12
+ end
13
+
14
+ def collect?(env)
15
+ true
16
+ end
17
+
18
+ def collect(log)
19
+ $stdout.puts log.to_json
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ require 'fluent-logger'
2
+ require "rack/access/capture/collector/abstract_adapter"
3
+
4
+ module Rack
5
+ module Access
6
+ module Capture
7
+ module Collector
8
+
9
+ class FluentdAdapter < AbstractAdapter
10
+
11
+ attr_reader :tag
12
+
13
+ def initialize(options = {})
14
+ config = options || {}
15
+ @tag = config["tag"] || 'development'
16
+ tag_prefix = config["tag_prefix"]
17
+ host = config["host"] || 'localhost'
18
+ port = config["port"] || 24224
19
+ handler = proc { |messages| BufferOverflowHandler.new(config["log_file_path"]).flush(messages) }
20
+ buffer_limit = config["buffer_limit"] || 131072 # Buffer limit of the standard is 128.kilobyte
21
+ log_reconnect_error_threshold = config["log_reconnect_error_threshold"] || Fluent::Logger::FluentLogger::RECONNECT_WAIT_MAX_COUNT
22
+ options = { host: host,
23
+ port: port,
24
+ buffer_limit: buffer_limit,
25
+ buffer_overflow_handler: handler,
26
+ log_reconnect_error_threshold: log_reconnect_error_threshold }
27
+ @logger = config["logger"] || Fluent::Logger::FluentLogger.new(tag_prefix, options)
28
+ exclude_request = config["exclude_request"] || []
29
+ @collect = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, LINK, UNLINK, TRACE] - exclude_request
30
+ end
31
+
32
+ def collect?(env)
33
+ @collect.include?(env["REQUEST_METHOD"])
34
+ end
35
+
36
+ def collect(log)
37
+ @logger.post(@tag, log)
38
+ end
39
+ end
40
+
41
+ class BufferOverflowHandler
42
+
43
+ def initialize(log_dirirectory_path)
44
+ @log_directory_path = log_dirirectory_path
45
+ end
46
+
47
+ def flush(messages)
48
+ return if @log_directory_path.nil?
49
+
50
+ MessagePack::Unpacker.new.feed_each(messages) do |msg|
51
+ open("#{@log_directory_path}/#{msg[0]}_#{msg[1]}.json", 'w') do |io|
52
+ JSON.dump(msg[2], io)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,70 @@
1
+ require 'rack/access/capture/collector/adapters'
2
+ require 'rack/access/capture/watcher/adapters'
3
+ require 'rack/access/capture/watcher/internal_watcher_adapter'
4
+
5
+ module Rack
6
+ module Access
7
+ module Capture
8
+ class Config
9
+
10
+ attr_reader :_collector, :_watcher, :_internal_watcher
11
+
12
+ class << self
13
+ def config_accessor(*names)
14
+ names.each do |name|
15
+ class_eval <<-METHOD
16
+ def #{name}
17
+ @#{name}
18
+ end
19
+
20
+ def #{name}=(value)
21
+ @#{name} = value
22
+ if "#{name}" == "filter"
23
+ @_internal_watcher = Rack::Access::Capture::Watcher::InternalWatcherAdapter.new(convert_hash(value))
24
+ else
25
+ @_#{name} = Object.const_get("Rack::Access::Capture::#{name.capitalize}::Adapters").send("interpret_#{name}", convert_hash(value))
26
+ end
27
+ end
28
+ METHOD
29
+ end
30
+ end
31
+
32
+ private :config_accessor
33
+ end
34
+
35
+ config_accessor :collector, :watcher, :filter
36
+
37
+ def initialize(config = {})
38
+ convertd_config = convert_hash(config)
39
+
40
+ self.collector = convertd_config["collector"] if convertd_config.key?("collector")
41
+ self.watcher = convertd_config["watcher"] if convertd_config.key?("watcher")
42
+ self.filter = convertd_config["filter"]
43
+ end
44
+
45
+ private
46
+
47
+ def convert_hash(hash)
48
+ return {} if hash.nil?
49
+ return hash unless hash.is_a?(Hash)
50
+
51
+ converted_hash = {}
52
+
53
+ hash.each do |key, value|
54
+ key = key.to_s if key.is_a?(Symbol)
55
+
56
+ if value.is_a?(Hash)
57
+ value = convert_hash(value)
58
+ elsif value.is_a?(Array)
59
+ value = value.map { |v| v.is_a?(Hash) ? convert_hash(v) : v }
60
+ end
61
+
62
+ converted_hash[key] = value
63
+ end
64
+
65
+ converted_hash
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ require 'rack/access/capture/config'
2
+
3
+ module Rack
4
+ module Access
5
+ module Capture
6
+ class Manager
7
+
8
+ attr_accessor :config
9
+
10
+ def initialize(app, options = {})
11
+ @app = app
12
+ @config = Rack::Access::Capture::Config.new(options)
13
+ yield @config if block_given?
14
+ end
15
+
16
+ def call(env)
17
+ captured_request = @config._watcher.nil? ? {} : @config._watcher.request_capture(env)
18
+ status_code, header, body = @app.call(env)
19
+ captured_response = @config._watcher.nil? ? {} : @config._watcher.response_capture(env, status_code, header)
20
+
21
+ if !@config._collector.nil? && @config._collector.collect?(env)
22
+ access_log = @config._internal_watcher.access_log(env, status_code, header)
23
+ captured_access_log = merge_logs(captured_request, captured_response, access_log)
24
+ @config._collector.collect(captured_access_log)
25
+ end
26
+
27
+ [status_code, header, body]
28
+ end
29
+
30
+ private
31
+
32
+ def merge_logs(captured_request, captured_response, access_log = {})
33
+ if captured_request && captured_request.is_a?(Hash)
34
+ access_log.merge!(captured_request)
35
+ else
36
+ access_log.merge!(request: captured_request.to_s) unless captured_request.nil?
37
+ end
38
+
39
+ if captured_response && captured_response.is_a?(Hash)
40
+ access_log.merge!(captured_response)
41
+ else
42
+ access_log.merge!(response: captured_response.to_s) unless captured_response.nil?
43
+ end
44
+
45
+ access_log
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ module Rack
2
+ module Access
3
+ module Capture
4
+ VERSION = "0.0.1".freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module Rack
2
+ module Access
3
+ module Capture
4
+ module Watcher
5
+
6
+ class Adapters
7
+
8
+ WATCHER_ADAPTER_METHODS = %w(request_capture response_capture).freeze
9
+
10
+ private_constant :WATCHER_ADAPTER_METHODS
11
+
12
+ class << self
13
+ def interpret_watcher(config)
14
+ class_name = config["adapter"] if !config.nil? && config.is_a?(Hash) && config.key?("adapter")
15
+
16
+ if class_name.is_a?(String)
17
+ with_ignoring_name_error do
18
+ object = Object.const_get(class_name).new
19
+ watcher_adapter?(object) ? object : nil
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def watcher_adapter?(object)
27
+ WATCHER_ADAPTER_METHODS.all? { |method_name| object.respond_to?(method_name) }
28
+ end
29
+
30
+ def with_ignoring_name_error(&block)
31
+ proc {
32
+ begin
33
+ yield
34
+ rescue NameError
35
+ nil
36
+ end
37
+ }.call
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module Rack
2
+ module Access
3
+ module Capture
4
+ module Watcher
5
+ class BaseAdapter
6
+
7
+ def request_capture(env)
8
+ # this should be overridden by concrete adapters
9
+ end
10
+
11
+ def response_capture(env, http_status_code, header)
12
+ # this should be overridden by concrete adapters
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ require 'woothee'
2
+ require 'rack/access/capture/watcher/base_adapter'
3
+
4
+ module Rack
5
+ module Access
6
+ module Capture
7
+ module Watcher
8
+ class InternalWatcherAdapter < BaseAdapter
9
+
10
+ attr_reader :filter_params
11
+
12
+ def initialize(config = {})
13
+ params = config.is_a?(Hash) && config.key?("params") ? config["params"] : []
14
+ @filter_params = initialize_filter_params(params)
15
+ end
16
+
17
+ def access_log(env, status_code, header)
18
+ request = Rack::Request.new(env)
19
+ log = {}
20
+ log["status"] = status_code
21
+ log["path"] = request.path
22
+ log["method"] = request.request_method
23
+ log["params"] = params_filter(request.params).to_s
24
+ ua = Woothee.parse(request.user_agent)
25
+ log["device"] = ua[:category]
26
+ log["os"] = ua[:os]
27
+ log["browser"] = ua[:name]
28
+ log["browser_ver"] = ua[:version]
29
+ log["user_agent"] = request.user_agent
30
+ log["remote_ip"] = request.ip
31
+ access_time = Time.now.to_i
32
+ log["time"] = access_time
33
+ log["accessed_at"] = access_time
34
+ log
35
+ end
36
+
37
+ private
38
+
39
+ def initialize_filter_params(filter_items)
40
+ filter_strings = []
41
+ filter_strings << Rack::Access::Capture::DEFAULT_FILTER_PARAMS
42
+ filter_strings << filter_items.map(&:to_s)
43
+ filter_strings.flatten!.uniq
44
+ filter_strings.map { |item| Regexp.compile(Regexp.escape(item.to_s)) }
45
+ end
46
+
47
+ def params_filter(params)
48
+ filtered_params = {}
49
+
50
+ params.each do |key, value|
51
+ if @filter_params.any? { |filter| key =~ filter }
52
+ value = Rack::Access::Capture::FILTERED
53
+ elsif value.is_a?(Hash)
54
+ value = params_filter(value)
55
+ elsif value.is_a?(Array)
56
+ value = value.map { |v| v.is_a?(Hash) ? params_filter(v) : v }
57
+ end
58
+
59
+ filtered_params[key] = value
60
+ end
61
+
62
+ filtered_params
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,26 @@
1
+ require "rack/access/capture/version"
2
+ require "rack/access/capture/config"
3
+ require "rack/access/capture/manager"
4
+ require "rack/access/capture/collector/abstract_adapter"
5
+ require "rack/access/capture/watcher/base_adapter"
6
+
7
+ module Rack
8
+ module Access
9
+ module Capture
10
+ # parameter filter
11
+ FILTERED = '[FILTERED]'.freeze
12
+ DEFAULT_FILTER_PARAMS = %w(password authenticity_token).freeze
13
+ # HTTP method
14
+ GET = 'GET'.freeze
15
+ POST = 'POST'.freeze
16
+ PUT = 'PUT'.freeze
17
+ PATCH = 'PATCH'.freeze
18
+ DELETE = 'DELETE'.freeze
19
+ HEAD = 'HEAD'.freeze
20
+ OPTIONS = 'OPTIONS'.freeze
21
+ LINK = 'LINK'.freeze
22
+ UNLINK = 'UNLINK'.freeze
23
+ TRACE = 'TRACE'.freeze
24
+ end
25
+ end
26
+ end
@@ -0,0 +1 @@
1
+ require 'rack/access/capture'
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rack/access/capture/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rack-access-capture'
7
+ spec.version = Rack::Access::Capture::VERSION
8
+ spec.authors = ['moonstruckdrops']
9
+ spec.email = ['moonstruckdrops@gmail.com']
10
+
11
+ spec.summary = 'To capture the request and response in the rack middleware, you can be output to any destination.'
12
+ spec.description = 'To capture the request and response in the rack middleware, you can be output to any destination.'
13
+ spec.homepage = 'https://github.com/livesense-inc/rack-access-capture'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = ['rack-access-capture.gemspec'].concat(Dir.glob('lib/**/*').reject { |f| File.directory?(f) || f =~ /~$/ })
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '~> 2.0'
21
+
22
+ spec.add_dependency 'woothee', '~> 1.4'
23
+ spec.add_dependency 'fluent-logger', '~> 0.5.1'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.12'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
28
+ spec.add_development_dependency 'rack-test', '~> 0.6.3'
29
+ spec.add_development_dependency 'rubocop', '~> 0.40.0'
30
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-access-capture
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - moonstruckdrops
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: woothee
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fluent-logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.1
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.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.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: 3.4.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.4.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.6.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.6.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.40.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.40.0
111
+ description: To capture the request and response in the rack middleware, you can be
112
+ output to any destination.
113
+ email:
114
+ - moonstruckdrops@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - lib/rack-access-capture.rb
120
+ - lib/rack/access/capture.rb
121
+ - lib/rack/access/capture/collector/abstract_adapter.rb
122
+ - lib/rack/access/capture/collector/adapters.rb
123
+ - lib/rack/access/capture/collector/console_adapter.rb
124
+ - lib/rack/access/capture/collector/fluentd_adapter.rb
125
+ - lib/rack/access/capture/config.rb
126
+ - lib/rack/access/capture/manager.rb
127
+ - lib/rack/access/capture/version.rb
128
+ - lib/rack/access/capture/watcher/adapters.rb
129
+ - lib/rack/access/capture/watcher/base_adapter.rb
130
+ - lib/rack/access/capture/watcher/internal_watcher_adapter.rb
131
+ - rack-access-capture.gemspec
132
+ homepage: https://github.com/livesense-inc/rack-access-capture
133
+ licenses:
134
+ - MIT
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '2.0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.5.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: To capture the request and response in the rack middleware, you can be output
156
+ to any destination.
157
+ test_files: []