tracker_hub-request 1.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: 6f9665a1b80b7c8db64b40ae258eea6b6872c100
4
+ data.tar.gz: 7f645fc08bf692b2f9ea759495401ea9c1db90fa
5
+ SHA512:
6
+ metadata.gz: 0903a3b964a765540d1bbe24069a42ad9327efa9dea128d0db8248b10b63bf8902e98588d4f982d04737c8557e9cc1f9a73aa1167bf94ee67f410f6e8e1adc0b
7
+ data.tar.gz: de367b9ff0845498841c922a5ba62dd3657dcdad4b2e16a6ad01834a5627ee8dabf688aa7c1522424fb196597ebafc98a900bb3dfc7d0d49de412ebac8f3133b
data/.codeclimate.yml ADDED
@@ -0,0 +1,19 @@
1
+ ---
2
+ engines:
3
+ reek:
4
+ enabled: true
5
+ duplication:
6
+ enabled: true
7
+ config:
8
+ languages:
9
+ - ruby
10
+ fixme:
11
+ enabled: true
12
+ rubocop:
13
+ enabled: true
14
+ ratings:
15
+ paths:
16
+ - '**.rb'
17
+ exclude_paths:
18
+ - pkg/
19
+ - spec/
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
11
+ /coverage/
12
+ /rdoc/
13
+ /.rvmrc
data/.hound.yml ADDED
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ config_file: .rubocop.yml
data/.reek ADDED
@@ -0,0 +1,14 @@
1
+ ---
2
+ TooManyStatements:
3
+ enabled: true
4
+ exclude:
5
+ - initialize
6
+ max_statements: 10
7
+
8
+ Attribute:
9
+ exclude:
10
+ - TrackerHub::Request::Config
11
+
12
+ # Directories below will not be scanned at all
13
+ exclude_paths:
14
+ - spec
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/**/*'
4
+
5
+ Metrics/LineLength:
6
+ Max: 100
7
+ # To make it possible to copy or click on URIs in the code, we allow lines
8
+ # containing a URI to be longer than Max.
9
+ AllowHeredoc: true
10
+ AllowURI: true
11
+ URISchemes:
12
+ - http
13
+ - https
14
+
15
+ Metrics/MethodLength:
16
+ CountComments: false # count full line comments?
17
+ Max: 15
data/.travis.yml ADDED
@@ -0,0 +1,31 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache:
4
+ - bundler
5
+
6
+ rvm:
7
+ - jruby-head
8
+ - rbx-2
9
+ - 2.0.0
10
+ - 2.1.9
11
+ - 2.2.5
12
+ - 2.3.1
13
+ - ruby-head
14
+
15
+ addons:
16
+ code_climate:
17
+ repo_token: 54662312136af39c0814c981c7693a7baf17fbeb09e154806a08b97f467c9392
18
+
19
+ before_install:
20
+ - gem install bundler
21
+
22
+ before_script:
23
+ - bundle update
24
+
25
+ script:
26
+ - bundle exec rake spec
27
+
28
+ matrix:
29
+ allow_failures:
30
+ - rvm: rbx-2
31
+ - rvm: jruby-head
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Change Log
2
+
3
+ ## [1.0.1](https://github.com/SparkHub/gs-tracking-requests/tree/1.0.1) (2016-10-07)
4
+
5
+ **Fixed bugs:**
6
+ - Fix HipChat argument conflicts on #send_message ([mchaisse](https://github.com/mchaisse))
7
+
8
+
9
+ ## [1.0.0](https://github.com/SparkHub/gs-tracking-requests/tree/1.0.0) (2016-10-04)
10
+
11
+ **Implemented enhancements:**
12
+ - Initial version :tada: ([mchaisse](https://github.com/mchaisse))
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at maxime.chaisseleal@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tracker_hub-requests.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'rubocop', require: false
8
+ gem 'brakeman', require: false
9
+ if RUBY_VERSION >= '2.1.0'
10
+ gem 'reek', require: false
11
+ else
12
+ gem 'reek', '~> 3.11', require: false
13
+ end
14
+ end
15
+
16
+ group :test do
17
+ gem 'simplecov', require: false
18
+ gem 'codeclimate-test-reporter', require: false if RUBY_VERSION >= '2.0'
19
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Maxime Chaisse-Leal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ [![Gem Version](https://badge.fury.io/rb/tracker_hub-request.svg)](http://badge.fury.io/rb/tracker_hub-request)
2
+ [![Build Status](https://travis-ci.org/SparkHub/gs-tracking-requests.svg?branch=master)](https://travis-ci.org/SparkHub/gs-tracking-requests)
3
+ [![Code Climate](https://codeclimate.com/github/SparkHub/gs-tracking-requests/badges/gpa.svg)](https://codeclimate.com/github/SparkHub/gs-tracking-requests)
4
+ [![Test Coverage](https://codeclimate.com/github/SparkHub/gs-tracking-requests/badges/coverage.svg)](https://codeclimate.com/github/SparkHub/gs-tracking-requests/coverage)
5
+ [![Dependency Status](https://gemnasium.com/SparkHub/gs-tracking-requests.svg)](https://gemnasium.com/SparkHub/gs-tracking-requests)
6
+ [![Inline docs](http://inch-ci.org/github/SparkHub/gs-tracking-requests.svg)](http://inch-ci.org/github/SparkHub/gs-tracking-requests)
7
+
8
+ # TrackerHub::Request
9
+
10
+ Part of __TrackerHub__, __Request__ is a gem tracking every requests on the backend side. Some keys will be filtered and the request data will finally be saved into a logfile. This logfile will be automatically managed and rotated by the [logging](https://github.com/TwP/logging) gem.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'tracker_hub-request', git: 'git@github.com:SparkHub/gs-tracking-requests.git'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ ## Configuration _(optional)_
25
+
26
+ In an initializer, you can configure the tracker:
27
+
28
+ ```ruby
29
+ # ./config/initializers/request_tracker.rb
30
+ TrackerHub::Request.setup do |config|
31
+ config.app_version = '1.0'
32
+ config.required_keys = %w(my rack env keys to log)
33
+ config.logger = ActiveSupport::Logger.new('requests.log')
34
+ config.notification = TrackerHub::Request::Notification.new(TrackerHub::Request::Notification::HipChat.new('my_token', 'my_room', 'my_username'))
35
+ end
36
+ ```
37
+
38
+ __Note:__
39
+
40
+ - __logger__
41
+
42
+ Here you can define your own configured logger. The default logger is [logging](https://github.com/TwP/logging). Feel free to add your own, or use another gem!
43
+
44
+ - __notification__
45
+
46
+ If an error occure, it will be catched and a notification will be sent to the service of your choice. The list of available services are defined [here](https://github.com/SparkHub/gs-tracking-requests/tree/master/lib/tracker_hub/request/notification).
47
+
48
+ ## Usage
49
+
50
+ Add the middleware to your environment:
51
+
52
+ ```ruby
53
+ config.middleware.insert_after ActionDispatch::DebugExceptions, TrackerHub::Request::Middleware
54
+ ```
55
+
56
+ Activate the TrackerHub by adding to your environment (or `.env`):
57
+
58
+ $ TRACKER=true
59
+
60
+ Get your request logs:
61
+
62
+ $ tail -f ./log/tracker/requests.log
63
+
64
+ ## Running tests
65
+
66
+ To run tests:
67
+
68
+ $ bundle exec rake spec
69
+
70
+ ## Contributing
71
+
72
+ Bug reports and pull request are welcome on GitHub at https://github.com/SparkHub/gs-tracking-requests. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
73
+
74
+
75
+ ## License
76
+
77
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'tracking_hub/requests'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,45 @@
1
+ require 'logging'
2
+
3
+ require_relative 'request/version'
4
+ require_relative 'request/middleware'
5
+ require_relative 'request/setup'
6
+
7
+ module TrackerHub
8
+ # Format and log an incoming request
9
+ class Request
10
+ extend Setup
11
+ include Format::Logger
12
+
13
+ private
14
+
15
+ # @return [Utils::Env] the rack environment object built from the response object
16
+ # @api private
17
+ attr_accessor :response
18
+
19
+ # @return [Integer] the request status (can be a Fixnum)
20
+ # @api private
21
+ attr_accessor :status
22
+
23
+ # @return [Hash] request header
24
+ # @api private
25
+ attr_accessor :request
26
+
27
+ # Instantiate a request tracker object
28
+ #
29
+ # @param [Utils::Env] env Rack environement object (full response)
30
+ # @param [Integer] status Request status
31
+ # @param [Hash] headers Request header
32
+ #
33
+ # @example
34
+ # > status, headers, body = @app.call(env)
35
+ # > new_env = Utils::Env.new(env)
36
+ # > TrackerHub::Request.new(new_env, status, headers)
37
+ #
38
+ # @api private
39
+ def initialize(env, status, headers)
40
+ self.request = headers
41
+ self.status = status
42
+ self.response = env
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'config/env_keys'
2
+ require_relative 'config/logger'
3
+ require_relative 'format/logger'
4
+ require_relative 'notification'
5
+
6
+ module TrackerHub
7
+ class Request
8
+ # TrackerHub::Request configuration object, can be setup in an initializer
9
+ class Config
10
+ # @return [undefined] logger object to log the request data with
11
+ # @api public
12
+ attr_accessor :logger
13
+
14
+ # @return [Array<String>] rack env keys to log
15
+ # @api public
16
+ attr_accessor :required_keys
17
+
18
+ # @return [TrackerHub::Request::Notification] service to send a
19
+ # notification to if request log process fails
20
+ # @api public
21
+ attr_accessor :notification
22
+
23
+ # @return [String] version of the application logging the request data
24
+ # @api public
25
+ attr_accessor :app_version
26
+
27
+ private
28
+
29
+ # Instanciate a Config object with default values
30
+ #
31
+ # @todo Extract logger logic to be able to store data in another way
32
+ # (ex: database)
33
+ #
34
+ # @return [TrackerHub::Request::Config]
35
+ #
36
+ # @example
37
+ # > TrackerHub::Request::Config.new
38
+ #
39
+ # @api private
40
+ def initialize
41
+ self.app_version = ''
42
+ self.logger = Logger.default_config
43
+ self.required_keys = EnvKeys.default_config
44
+ self.notification = Request::Notification.new
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,71 @@
1
+ module TrackerHub
2
+ class Request
3
+ class Config
4
+ # Rack environment keys to log
5
+ module EnvKeys
6
+ class << self
7
+ # Rack env keys to extract and log
8
+ #
9
+ # @return [Array<String>]
10
+ #
11
+ # @example
12
+ # > TrackerHub::Request::Config::EnvKeys.default_config
13
+ #
14
+ # @api public
15
+ def default_config
16
+ root_keys + rack_keys + action_dispatch_keys
17
+ end
18
+
19
+ private
20
+
21
+ # Request keys in the rack env
22
+ #
23
+ # @return [Array<String>]
24
+ #
25
+ # @api private
26
+ def root_keys
27
+ %w(GATEWAY_INTERFACE PATH_INFO QUERY_STRING REMOTE_ADDR REMOTE_HOST REQUEST_METHOD
28
+ REQUEST_URI SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
29
+ HTTP_HOST HTTP_CONNECTION HTTP_UPGRADE_INSECURE_REQUESTS HTTP_USER_AGENT HTTP_ACCEPT
30
+ HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_COOKIE HTTP_VERSION REQUEST_PATH
31
+ ORIGINAL_FULLPATH ORIGINAL_SCRIPT_NAME)
32
+ end
33
+
34
+ # Rack keys in the rack env
35
+ #
36
+ # @return [Array<String>]
37
+ #
38
+ # @api private
39
+ def rack_keys
40
+ %w(version multithread multiprocess run_once url_scheme hijack? hijack_io
41
+ timestamp request.query_string request.query_hash request.cookie_hash
42
+ request.cookie_string).map do |key|
43
+ "rack.#{key}"
44
+ end
45
+ end
46
+
47
+ # Action dispatch keys in the rack env
48
+ #
49
+ # @return [Array<String>]
50
+ #
51
+ # @api private
52
+ def action_dispatch_keys
53
+ bases = %w(parameter_filter redirect_filter secret_token secret_key_base show_exceptions
54
+ show_detailed_exceptions http_auth_salt signed_cookie_salt
55
+ encrypted_cookie_salt encrypted_signed_cookie_salt cookies_serializer
56
+ cookies_digest request_id).map do |key|
57
+ "action_dispatch.#{key}"
58
+ end
59
+
60
+ requests = %w(path_parameters content_type request_parameters query_parameters
61
+ parameters formats unsigned_session_cookie).map do |key|
62
+ "action_dispatch.request.#{key}"
63
+ end
64
+
65
+ bases + requests
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,103 @@
1
+ module TrackerHub
2
+ class Request
3
+ class Config
4
+ # Default customizable configurations for normal/rolling logger
5
+ module Logger
6
+ ROLLING_PATTERN = '%Y-%m-%d-%H-%M-%S'.freeze
7
+ ROLLING_AGE = 'daily'.freeze
8
+ ROLLING_SIZE = 1.gigabyte.freeze
9
+ LOGGER_NAME = 'requests'.freeze
10
+ LOGFILE_NAME = "#{LOGGER_NAME}.log".freeze
11
+
12
+ class << self
13
+ # Template for the rolling logger configuration
14
+ # Note: for additional options, please refer to:
15
+ # http://www.rubydoc.info/gems/logging/Logging/Appenders/RollingFile:initialize
16
+ #
17
+ # @option args [String] :log_path ('/log') Path of the log file
18
+ # @option args [String] :logger_name ('requests') Name of the log file
19
+ # @option args [String] :logfile_pattern ('requests.log.%Y-%m-%d-%H-%M-%S')
20
+ # Pattern to roll log files
21
+ # @option args [String|Integer] :age ('daily') The maximum age (in seconds)
22
+ # of a log file before it is rolled.
23
+ # @option args [String] :size (1073741824) The maximum allowed size (in bytes)
24
+ # of a log file before it is rolled.
25
+ # @return [Logging::Logger]
26
+ #
27
+ # @example
28
+ # > options = { logger_name: 'my_logger' }
29
+ # > logger = TrackerHub::Request::Config::Logger.rolling_logger(options)
30
+ # > logger.info('log text')
31
+ #
32
+ # @api public
33
+ def rolling_logger(args = {})
34
+ options = extract_rolling_options(args)
35
+
36
+ build_rolling_logger(options)
37
+ end
38
+
39
+ # Default configuration for the logger
40
+ #
41
+ # @return [ActiveSupport::Logger]
42
+ #
43
+ # @example
44
+ # > logger = TrackerHub::Request::Config::Logger.default_config
45
+ # > logger.info('log text')
46
+ #
47
+ # @api public
48
+ def default_config
49
+ ::ActiveSupport::Logger.new(File.join(log_path, LOGFILE_NAME))
50
+ end
51
+
52
+ private
53
+
54
+ # Extract the given rolling logger arguments and merge with defaults
55
+ #
56
+ # @param [Hash] args Options to configure the rolling logger
57
+ # @return [Hash]
58
+ #
59
+ # @api private
60
+ def extract_rolling_options(args)
61
+ {
62
+ log_path: log_path,
63
+ logger_name: LOGGER_NAME,
64
+ logfile_pattern: "#{LOGFILE_NAME}{{.#{ROLLING_PATTERN}}}",
65
+
66
+ age: ROLLING_AGE,
67
+ size: ROLLING_SIZE
68
+ }.merge(args)
69
+ end
70
+
71
+ # Build a rolling logger based on the given options
72
+ #
73
+ # @param [Hash] options Options to configure the rolling logger
74
+ # @return [Logging]
75
+ #
76
+ # @api private
77
+ def build_rolling_logger(options)
78
+ logfile_path = File.join(options.delete(:log_path),
79
+ options.delete(:logfile_pattern))
80
+
81
+ logger = Logging.logger.new(options.delete(:logger_name))
82
+ rolling = Logging::Appenders::RollingFile.new(logfile_path, options)
83
+ logger.add_appenders(rolling)
84
+
85
+ logger
86
+ end
87
+
88
+ # Get the current rails log path
89
+ #
90
+ # @return [String]
91
+ #
92
+ # @api private
93
+ #
94
+ # :nocov:
95
+ def log_path
96
+ Pathname.new(Rails.application.config.paths['log'].first).parent.to_s
97
+ end
98
+ # :nocov:
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,66 @@
1
+ module TrackerHub
2
+ class Request
3
+ module Format
4
+ # Logger module formatting and rendering the request object
5
+ module Logger
6
+ # Format the response to the logger
7
+ #
8
+ # @return [JSON]
9
+ #
10
+ # @example
11
+ # > status, headers, body = @app.call(env)
12
+ # > track = TrackerHub::Request.new(env, status, headers)
13
+ # > track.to_logger
14
+ #
15
+ # @api public
16
+ def to_logger
17
+ {
18
+ status: status,
19
+ request: request,
20
+ response: cleaned_env,
21
+ app_version: Request.config.app_version,
22
+ tracker_version: Request::VERSION
23
+ }.to_json
24
+ end
25
+
26
+ # Extract the rack env keys (see TrackerHub::Request::Config#required_keys)
27
+ # and convert them
28
+ #
29
+ # @return [Utils::Env] cleaned rack env object
30
+ #
31
+ # @example
32
+ # > status, headers, body = @app.call(env)
33
+ # > track = TrackerHub::Request.new(env, status, headers)
34
+ # > track.cleaned_env
35
+ #
36
+ # @api public
37
+ def cleaned_env
38
+ response.slice(*Request.config.required_keys).merge(serialized_env)
39
+ end
40
+
41
+ # Serialize objects before jsonifying them
42
+ #
43
+ # @return [Hash]
44
+ #
45
+ # @example
46
+ # > status, headers, body = @app.call(env)
47
+ # > track = TrackerHub::Request.new(env, status, headers)
48
+ # > track.serialized_env
49
+ #
50
+ # @api public
51
+ #
52
+ # rubocop:disable AbcSize
53
+ def serialized_env
54
+ {
55
+ 'action_dispatch.logger' => response['action_dispatch.logger'].formatter.session_info,
56
+ 'action_dispatch.remote_ip' => response['action_dispatch.remote_ip'].to_s,
57
+ 'rack.session' => response['rack.session'].try(:to_hash),
58
+ 'rack.session.options' => response['rack.session.options'].try(:to_hash),
59
+ 'http_accept_language.parser' => response['http_accept_language.parser'].header
60
+ }
61
+ end
62
+ # rubocop:enable AbcSize
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'utils/env'
3
+ require_relative 'utils/exception'
4
+
5
+ module TrackerHub
6
+ class Request
7
+ # Middleware to include in a Rails stack to log every incoming requests
8
+ class Middleware
9
+ # Method called by the middleware stack to run the request tracker
10
+ #
11
+ # @param [Hash] env Rack env
12
+ # @return [Array]
13
+ #
14
+ # @api public
15
+ def call(env)
16
+ # execute application to get more data (injected into env)
17
+ status, headers, body = @app.call(env)
18
+ # save logs from env
19
+ track(env, status, headers)
20
+ # release the process to other middlewares
21
+ [status, headers, body]
22
+ end
23
+
24
+ private
25
+
26
+ # Instanciate the request tracker middleware
27
+ #
28
+ # @param [undefined] app Previous middelware
29
+ # @return [undefined]
30
+ #
31
+ # @api private
32
+ def initialize(app)
33
+ @logger = ::TrackerHub::Request.config.logger
34
+ @app = app
35
+ end
36
+
37
+ # Initiate the request tracker to store formated requests in a log file.
38
+ # If an internal error occurs while logging the request, and if a
39
+ # notification has been configured (see TrackerHub::Request::Notification),
40
+ # then a notification is sent to the configured service
41
+ #
42
+ # @param [Hash] env Rack env
43
+ # @param [Integer] status HTTP request status
44
+ # @param [Hash] headers Header of the HTTP request
45
+ # @return [undefined]
46
+ #
47
+ # @example
48
+ # > status, headers, body = @app.call(env)
49
+ # > track(env, status, headers)
50
+ #
51
+ # @api private
52
+ def track(env, status, headers)
53
+ new_env = Utils::Env.new(env)
54
+ return unless new_env.trackable?
55
+
56
+ @logger.info ::TrackerHub::Request.new(new_env, status, headers).to_logger
57
+ rescue StandardError => exception
58
+ notifier = ::TrackerHub::Request.config.notification
59
+ Utils::Exception.new(exception).report(notifier)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,95 @@
1
+ require_relative 'notification/hip_chat'
2
+
3
+ module TrackerHub
4
+ class Request
5
+ # Notify a configured service when an error occured during the request log process
6
+ class Notification
7
+ # Key to cache the timelapse between 2 notifications
8
+ KEY_CACHE = 'trackinghub_request_notification'.freeze
9
+
10
+ # @return [Object] the notification adapter
11
+ # @api public
12
+ attr_reader :notifier
13
+
14
+ # @return [Integer|ActiveSupport::Duration] timelapse between 2 notifications
15
+ # @api public
16
+ attr_reader :timelapse
17
+
18
+ # @return [Hash] notification and notifier options
19
+ # @api public
20
+ attr_reader :options
21
+
22
+ # Trigger a notification paused by a timelapse if specified
23
+ #
24
+ # @param [String] message Notification's message
25
+ # @param [Hash] args Notifier adapter's options (if there is any)
26
+ # @return [undefined]
27
+ #
28
+ # @example
29
+ # > notifier = TrackerHub::Request::Notification::HipChat.new(token, 'room', 'username')
30
+ # > options = { timelapse: 10.minutes }
31
+ # > notification = TrackerHub::Request::Notification.new(notifier, options)
32
+ # > notification.notify('my message')
33
+ #
34
+ # @api public
35
+ def notify(message, args = {})
36
+ timelapser do
37
+ notifier.send_message(message, options.merge(args))
38
+ end
39
+ rescue
40
+ false
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Object] the notification adapter
46
+ # @api private
47
+ attr_writer :notifier
48
+
49
+ # @return [Integer|ActiveSupport::Duration] timelapse between 2 notifications
50
+ # @api private
51
+ attr_writer :timelapse
52
+
53
+ # @return [Hash] notification and notifier options
54
+ # @api private
55
+ attr_writer :options
56
+
57
+ # Instantiate a notification object to add the ability to send a notification
58
+ # to any service adapter (see in request/notification). If a timelapse is
59
+ # given in parameter, there will be a pause between 2 notifications.
60
+ # Please checkout the available adapters for more :args: options
61
+ #
62
+ # @param [Object] notifier The notification adapter
63
+ # @option args [Integer] :timelapse Time between 2 notifications
64
+ #
65
+ # @example
66
+ # > notifier = TrackerHub::Request::Notification::HipChat.new(token, 'room', 'username')
67
+ # > options = { timelapse: 10.minutes }
68
+ # > TrackerHub::Request::Notification.new(notifier, options)
69
+ #
70
+ # @api private
71
+ def initialize(notifier = nil, args = {})
72
+ defaults = {
73
+ notify: true
74
+ }
75
+
76
+ self.notifier = notifier
77
+ self.timelapse = args.delete(:timelapse)
78
+ self.options = defaults.merge(args)
79
+ end
80
+
81
+ # Execute an action in a timelapse
82
+ #
83
+ # @return [Boolean]
84
+ #
85
+ # @api private
86
+ def timelapser
87
+ return yield unless timelapse
88
+
89
+ Rails.cache.fetch(KEY_CACHE, expires_in: timelapse) do
90
+ yield
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,68 @@
1
+ module TrackerHub
2
+ class Request
3
+ class Notification
4
+ # Service to send notifications to when an error occured during the log process
5
+ class HipChat
6
+ # HipChat API version to call by default
7
+ API_VERSION = 'v2'.freeze
8
+
9
+ # Send a notification message
10
+ #
11
+ # @param [String] msg Message to send
12
+ # @param [Hash] args See more options at
13
+ # https://www.hipchat.com/docs/apiv2/method/send_room_notification
14
+ # @return [Boolean]
15
+ #
16
+ # @example
17
+ # > notifier = TrackerHub::Request::Notification::HipChat.new(token, 'room', 'username')
18
+ # > notifier.send_message('my message')
19
+ #
20
+ # @api public
21
+ def send_message(msg, args = {})
22
+ client[room].send(username, msg, options.merge(args))
23
+ end
24
+
25
+ private
26
+
27
+ # @return [HipChat::Client] the hipchat client
28
+ # @api private
29
+ attr_accessor :client
30
+
31
+ # @return [String] user's name sending the message
32
+ # @api private
33
+ attr_accessor :username
34
+
35
+ # @return [String] room's name to send the message to
36
+ # @api private
37
+ attr_accessor :room
38
+
39
+ # @return [Hash] hipchat send method options
40
+ # @api private
41
+ attr_accessor :options
42
+
43
+ # Instantiate a HipChat notifier. For more options, please checkout
44
+ # https://www.hipchat.com/docs/apiv2/method/send_room_notification
45
+ #
46
+ # @param [String] token API key to talk with hipchat services
47
+ # @param [String] room Room's name to send the message
48
+ # @param [String] username User's name sending the message
49
+ # @option options [String] :api_version Version of the hipchat API
50
+ # @return [TrackerHub::Request::Notification::HipChat]
51
+ #
52
+ # @example
53
+ # > TrackerHub::Request::Notification::HipChat.new(token, 'room', 'username')
54
+ #
55
+ # @api private
56
+ def initialize(token, room, username, options = {})
57
+ api_version = options.delete(:api_version) || API_VERSION
58
+
59
+ self.room = room
60
+ self.username = username
61
+ self.options = options
62
+
63
+ self.client = ::HipChat::Client.new(token, api_version: api_version)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'config'
2
+
3
+ module TrackerHub
4
+ class Request
5
+ # Configure the request tracker
6
+ module Setup
7
+ # Get the current configuration, instantiate a new object if nil
8
+ # Note: each attribute can also be written through the config object
9
+ #
10
+ # @return [TrackerHub::Request::Config] configuration object
11
+ #
12
+ # @example
13
+ # > # get the current configuration
14
+ # > TrackerHub::Request.config
15
+ # > => #<TrackerHub::Request::Config:0x007fa574ad7390 ...>
16
+ # >
17
+ # > # set a specific value in the config
18
+ # > TrackerHub::Request.config.app_version = '4.2'
19
+ #
20
+ # @api public
21
+ def config
22
+ @config ||= self::Config.new
23
+ end
24
+
25
+ # Setup configuration in block
26
+ #
27
+ # @yield (see TrackerHub::Request::Config#initialize)
28
+ # @return [TrackerHub::Request::Config]
29
+ #
30
+ # @example
31
+ # > TrackerHub::Request.setup do |config|
32
+ # > config.app_version = '4.2'
33
+ # > end
34
+ #
35
+ # @api public
36
+ def setup
37
+ yield(config)
38
+ end
39
+
40
+ private
41
+
42
+ # @return [TrackerHub::Request::Config] the tracking request configuration
43
+ # @api private
44
+ attr_writer :config
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module TrackerHub
3
+ class Request
4
+ module Utils
5
+ # SimpleDelegator wrapping class for rack environment
6
+ class Env < SimpleDelegator
7
+ # Should the request tracker log the current request
8
+ #
9
+ # @return [Boolean] true if should track the current request,
10
+ # false if should not track the current request
11
+ #
12
+ # @example
13
+ # > new_env = Utils::Env.new(env)
14
+ # > new_env.trackable?
15
+ #
16
+ # @api public
17
+ def trackable?
18
+ '/assets' != self['SCRIPT_NAME']
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module TrackerHub
3
+ class Request
4
+ module Utils
5
+ # SimpleDelegator wrapping class for exceptions
6
+ class Exception < SimpleDelegator
7
+ # Send a report of the exception with a given notification
8
+ #
9
+ # @param [TrackerHub::Request::Notification] notification See in request/notification
10
+ # for a full list of available notifiers
11
+ # @param [undefined] framework Used framework to retreive the rack environment
12
+ # @return [Boolean]
13
+ #
14
+ # @example
15
+ # > notifier = TrackerHub::Request::Notification::HipChat.new(token, 'room', 'username')
16
+ # > notification = TrackerHub::Request::Notification.new(notifier)
17
+ # > new_exception = Utils::Exception.new(exception)
18
+ # > new_exception.report(notification)
19
+ #
20
+ # @api public
21
+ def report(notification, framework = Rails)
22
+ formatted_backtrace = backtrace.join("\n")
23
+ msg = "[#{framework.env}]\n#{message}\n#{formatted_backtrace}"
24
+ notification.notify(msg)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module TrackerHub
2
+ class Request
3
+ VERSION = '1.0.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tracker_hub/request/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'tracker_hub-request'
8
+ spec.version = TrackerHub::Request::VERSION
9
+ spec.authors = ['Maxime Chaisse-Leal']
10
+ spec.email = ['maxime.chaisseleal@gmail.com']
11
+
12
+ spec.summary = 'Track all the incoming requests and store them in log files.'
13
+ spec.description = 'Track all the incoming requests and store them in log files.'
14
+ spec.homepage = 'https://github.com/SparkHub/gs-tracking-request'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'logging', '~> 2.1'
25
+ spec.add_dependency 'hipchat', '~> 1.5'
26
+
27
+ if RUBY_VERSION >= '2.2.2'
28
+ spec.add_dependency 'activesupport'
29
+ else
30
+ spec.add_dependency 'activesupport', '< 5'
31
+ end
32
+
33
+ spec.add_development_dependency 'bundler', '~> 1.13'
34
+ spec.add_development_dependency 'rake', '~> 11.3'
35
+ spec.add_development_dependency 'rspec', '~> 3.0'
36
+
37
+ if RUBY_VERSION >= '2.2.2'
38
+ spec.add_development_dependency 'rack'
39
+ else
40
+ spec.add_development_dependency 'rack', '~> 1.6'
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tracker_hub-request
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Maxime Chaisse-Leal
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logging
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hipchat
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '11.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '11.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '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'
111
+ description: Track all the incoming requests and store them in log files.
112
+ email:
113
+ - maxime.chaisseleal@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".codeclimate.yml"
119
+ - ".gitignore"
120
+ - ".hound.yml"
121
+ - ".reek"
122
+ - ".rspec"
123
+ - ".rubocop.yml"
124
+ - ".travis.yml"
125
+ - CHANGELOG.md
126
+ - CODE_OF_CONDUCT.md
127
+ - Gemfile
128
+ - LICENSE.txt
129
+ - README.md
130
+ - Rakefile
131
+ - bin/console
132
+ - bin/setup
133
+ - lib/tracker_hub/request.rb
134
+ - lib/tracker_hub/request/config.rb
135
+ - lib/tracker_hub/request/config/env_keys.rb
136
+ - lib/tracker_hub/request/config/logger.rb
137
+ - lib/tracker_hub/request/format/logger.rb
138
+ - lib/tracker_hub/request/middleware.rb
139
+ - lib/tracker_hub/request/notification.rb
140
+ - lib/tracker_hub/request/notification/hip_chat.rb
141
+ - lib/tracker_hub/request/setup.rb
142
+ - lib/tracker_hub/request/utils/env.rb
143
+ - lib/tracker_hub/request/utils/exception.rb
144
+ - lib/tracker_hub/request/version.rb
145
+ - tracker_hub-request.gemspec
146
+ homepage: https://github.com/SparkHub/gs-tracking-request
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.5.1
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: Track all the incoming requests and store them in log files.
170
+ test_files: []