Skip to content

Commit

Permalink
feat(httpigeon): [XAPI-1353] Gemify HTTPigeon library
Browse files Browse the repository at this point in the history
  • Loading branch information
2k-joker committed Jun 15, 2023
1 parent b4e3e53 commit e8e9cc7
Show file tree
Hide file tree
Showing 19 changed files with 678 additions and 2 deletions.
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## GENERAL
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
.DS_Store

## BUNDLER
*.gem
/.bundle/
Gemfile.lock
vendor/bundle

## IDEs
.idea/
.yardoc
/.yardoc
/_yardoc/
97 changes: 97 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
AllCops:
TargetRubyVersion: 3.1.2
Exclude:
- 'bin/**/*'
- 'vendor/**/*'
NewCops: disable

# Let ruby do it's thing when the time comes
Style/FrozenStringLiteralComment:
Enabled: false

# Decision whether to use alias or alias_method is not stylistic
# See: https://blog.bigbinary.com/2012/01/08/alias-vs-alias-method.html
Style/Alias:
Enabled: false

# This still common in Rails and usually doesn't result in problems.
Style/ClassAndModuleChildren:
Enabled: false

Style/CollectionMethods:
Description: Preferred collection methods.
StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size
Enabled: true
PreferredMethods:
collect: map
collect!: map!
find: detect
find_all: select
reduce: inject

# We don't enforce per-class documentation.
Style/Documentation:
Enabled: false

# We don't mind two-line empty methods as they're easier to start editing and
# pretty common in auto-generated Rails controllers.
Style/EmptyMethod:
Enabled: false

# There's no statistical difference between single and double quotes
# performance.
# See: https://www.viget.com/articles/just-use-double-quoted-ruby-strings/
Style/StringLiterals:
Enabled: false

# Ditto for above.
Style/StringLiteralsInInterpolation:
Enabled: false

# Methods should be easy to read, enforcing an arbitrary metric as number
# of lines is not the way to do it though.
Metrics/MethodLength:
Enabled: false

Metrics/ClassLength:
Enabled: false

Layout/DotPosition:
Enabled: true
EnforcedStyle: trailing

Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented_relative_to_receiver
Enabled: false
IndentationWidth: ~

# This rule does not detect string interpolations reliably,
# e.g. accuses 'full_messages.join(", ")'
Layout/SpaceInsideStringInterpolation:
Enabled: false

# Allow arbitrary symbol names
Naming/VariableNumber:
CheckSymbols: false

Metrics/AbcSize:
Max: 20
Exclude:
- 'test/**/*.rb'

Metrics/BlockLength:
Exclude:
- 'test/**/*'

# Allow long lines with comments
Layout/LineLength:
Max: 160
AllowedPatterns: ['(\A|\s)#']

# Allow for multiple expectations on a block
RSpec/MultipleExpectations:
Enabled: false

# Allow for big examples
RSpec/ExampleLength:
Enabled: false
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## [Unreleased]

## [0.1.0] - 2023-06-14

- Initial release
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

source "https://rubygems.org"

# Specify your gem's dependencies in httpigeon-ruby.gemspec
gemspec
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
# httpigeon-ruby
Simple client for making and logging HTTP requests and responses
# Httpigeon::Ruby

Client library that simplifies making and logging HTTP requests and responses. This library is built as an abstraction on top of the [Faraday](https://github.com/lostisland/faraday) client.

## Installation

Add this line to your application's Gemfile:
```
gem 'httpigeon-ruby'
```

## Usage

TODO: Write usage instructions here

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/httpigeon-ruby.
16 changes: 16 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
end

require "rubocop/rake_task"

RuboCop::RakeTask.new

task default: %i[test rubocop]
15 changes: 15 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "httpigeon"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start(__FILE__)
8 changes: 8 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here
42 changes: 42 additions & 0 deletions httpigeon-ruby.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require_relative "lib/httpigeon/version"

Gem::Specification.new do |spec|
spec.name = "httpigeon"
spec.version = HTTPigeon::VERSION
spec.authors = ["2k-joker"]
spec.email = ["[email protected]"]

spec.summary = "Simple, easy way to make and log HTTP requests and responses"
spec.description = "Client library that simplifies making and logging HTTP requests and responses. This library is built as an abstraction on top of the Faraday ruby client."
spec.homepage = "https://github.com/dailypay/httpigeon-ruby"
spec.required_ruby_version = Gem::Requirement.new("~> 3.1.0")

spec.metadata["allowed_push_host"] = "https://rubygems.pkg.github.com/dailypay"

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
end

spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency "faraday", "~> 2.7.6"
spec.add_dependency "activesupport", "~> 7.0.4"

spec.add_development_dependency "rake", "~> 13.0"
spec.add_development_dependency "minitest", "~> 5.15"
spec.add_development_dependency "rubocop", "~> 1.21"
spec.add_development_dependency "pry", "~> 0.13.1"
spec.add_development_dependency "minitest-stub_any_instance"

# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
end
26 changes: 26 additions & 0 deletions lib/httpigeon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require "active_support/core_ext/module/delegation"

require "httpigeon/configuration"
require "httpigeon/version"
require "httpigeon/logger"
require "httpigeon/request"

module HTTPigeon
extend self

delegate :default_event_type, :default_filter_keys, :event_logger, :notify_all_exceptions, :exception_notifier, to: :configuration

def configure
@config = HTTPigeon::Configuration.new

yield(@config)

@config.freeze
end

private

def configuration
@configuration ||= @config || HTTPigeon::Configuration.new
end
end
13 changes: 13 additions & 0 deletions lib/httpigeon/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module HTTPigeon
class Configuration
attr_accessor :default_event_type, :default_filter_keys, :event_logger, :notify_all_exceptions, :exception_notifier

def initialize
@default_event_type = 'http.outbound'
@default_filter_keys = []
@event_logger = nil
@notify_all_exceptions = false
@exception_notifier = nil
end
end
end
105 changes: 105 additions & 0 deletions lib/httpigeon/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
require "active_support/core_ext/hash"
require "active_support/core_ext/object/deep_dup"

module HTTPigeon
class Logger
def initialize(event_type: nil, additional_filter_keys: nil)
@event_type = event_type || HTTPigeon.default_event_type
@additional_filter_keys = additional_filter_keys.to_a.map(&:to_s)
end

def log(faraday_env, data = {})
log_data = build_log_data(faraday_env, data)

HTTPigeon.event_logger.nil? ? log_to_stdout : HTTPigeon.event_logger.new(event_type).log(log_data)
rescue StandardError => e
HTTPigeon.exception_notifier.notify_exception(e) if HTTPigeon.notify_all_exceptions
raise e if ['development', 'test'].include?(ENV['RAILS_ENV'].to_s)
end

def on_request_start
@start_time = Time.current
end

def on_request_finish
@end_time = Time.current
end

private

attr_reader :event_type, :header_log_keys, :additional_filter_keys, :start_time, :end_time

def build_log_data(env, data)
log_data = data.deep_dup
request_id = env.request_headers.transform_keys(&:downcase)['x-request-id']
request_latency = end_time - start_time if end_time.present? && start_time.present?

log_data[:request] = {
method: env.method,
url: env.url.to_s,
headers: filter(env.request_headers),
body: filter(env.request_body),
host: env.url.host,
path: env.url.path
}

log_data[:response] = {
headers: filter(env.response_headers),
body: filter(env.response_body),
status: env.status,
}

log_data[:metadata] = {
latency: request_latency,
identifier: request_id,
protocol: env.url.scheme
}

if log_data[:error].present?
error = log_data.delete(:error)
log_data[:error] = {
type: error.class.name,
message: error.message,
backtrace: error.backtrace.last(10)
}
end

log_data
end

def filter_keys
@filter_keys ||= (HTTPigeon.default_filter_keys + additional_filter_keys).map(&:downcase)
end

def filter(body)
return {} if body.blank?

body = JSON.parse(body) if body.is_a?(String)
filter_hash(body)
rescue JSON::ParserError
body
end

def filter_hash(data)
if data.is_a?(Array)
data.map { |datum| filter_hash(datum) }
elsif !data.is_a?(Hash)
data
else
data.to_h do |k, v|
v = '[FILTERED]' if filter_keys.include?(k.to_s.downcase)

if v.is_a?(Hash) || v.is_a?(Array)
[k, filter_hash(v)]
else
[k, v]
end
end
end
end

def log_to_stdout
Logger.new($stdout).log(1, { event_type: event_type, data: log_data }.to_json)
end
end
end
Loading

0 comments on commit e8e9cc7

Please sign in to comment.