api_hammer 0.6.3 → 0.7.1
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 +4 -4
- data/CHANGELOG.md +5 -0
- data/bin/hc +4 -0
- data/lib/api_hammer/check_required_params.rb +4 -4
- data/lib/api_hammer/halt.rb +19 -3
- data/lib/api_hammer/version.rb +1 -1
- data/lib/logstash/filters/active_support_tags.rb +27 -0
- data/lib/logstash/filters/api_hammer_request.rb +49 -0
- data/lib/logstash/filters/request_bodies_parsed.rb +34 -0
- data/lib/logstash/filters/ruby_logger.rb +28 -0
- data/lib/logstash/filters/sidekiq.rb +33 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2efd3cea2d6c262bff720e983354d944f0de16da
|
4
|
+
data.tar.gz: 9611db21a814571891d2d17ed017c82d2048b52e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b0dbb63040d700c2c137e24c4d84eb4804d2a234a68f3b02fdc001a78ff9364cb04975466e571bac801436be222174cd7fb7def879766c1af15afadddc1a093
|
7
|
+
data.tar.gz: 01d61498c3db5d7ef844b6c4c8bd4524a7f98c5d2b8bd738b3a8d5ac42e952c11eab9e85c48d794e9e9d1c8fdbdbff5f2317b78d95b8f4e3af954b35729228e9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# v0.7.1
|
2
|
+
- logstash filters for sidekiq, activesupport tags, and of course ApiHammer's request logging
|
3
|
+
- use i18n for errors and add error_message to response
|
4
|
+
- hc assumes http if no protocol specified
|
5
|
+
|
1
6
|
# v0.6.3
|
2
7
|
- add request role to the request logging
|
3
8
|
|
data/bin/hc
CHANGED
@@ -94,6 +94,10 @@ end
|
|
94
94
|
|
95
95
|
httpmethod, url, body = *ARGV
|
96
96
|
|
97
|
+
unless url['://']
|
98
|
+
url = 'http://' + url
|
99
|
+
end
|
100
|
+
|
97
101
|
unless Faraday::Connection::METHODS.map{|m| m.to_s.downcase }.include?(httpmethod.downcase)
|
98
102
|
abort "Unrecognized HTTP method given: #{httpmethod}\n\n" + opt_parser.help
|
99
103
|
end
|
@@ -29,7 +29,7 @@ module ApiHammer::Rails
|
|
29
29
|
key = parents.join('#')
|
30
30
|
add_error = proc { |message| errors[key] << message unless errors[key].include?(message) }
|
31
31
|
if subparams.nil?
|
32
|
-
add_error.call("is required but was not provided")
|
32
|
+
add_error.call(I18n.t(:"errors.required_params.not_provided", :default => "%{key} is required but was not provided", :key => key))
|
33
33
|
elsif check
|
34
34
|
case check
|
35
35
|
when Array
|
@@ -40,17 +40,17 @@ module ApiHammer::Rails
|
|
40
40
|
check_required_params_helper(subcheck, subparams[key], errors, parents + [key])
|
41
41
|
end
|
42
42
|
else
|
43
|
-
add_error.call("must be a Hash")
|
43
|
+
add_error.call(I18n.t(:"errors.required_params.must_be_hash", :default => "%{key} must be a Hash", :key => key))
|
44
44
|
end
|
45
45
|
when Class
|
46
46
|
unless subparams.is_a?(check)
|
47
|
-
add_error.call("must be a
|
47
|
+
add_error.call(I18n.t(:"errors.required_params.must_be_type", :default => "%{key} must be a %{type}", :key => key, :type => check.name))
|
48
48
|
end
|
49
49
|
else
|
50
50
|
if subparams.is_a?(Hash)
|
51
51
|
check_required_params_helper(nil, subparams[check], errors, parents + [check])
|
52
52
|
else
|
53
|
-
add_error.call("must be a Hash")
|
53
|
+
add_error.call(I18n.t(:"errors.required_params.must_be_hash", :default => "%{key} must be a Hash", :key => key))
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
data/lib/api_hammer/halt.rb
CHANGED
@@ -44,7 +44,7 @@ module ApiHammer::Rails
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# halt and render the given errors in the body on the 'errors' key
|
47
|
-
def halt_error(status, errors,
|
47
|
+
def halt_error(status, errors, options = {})
|
48
48
|
errors_as_json = errors.respond_to?(:as_json) ? errors.as_json : errors
|
49
49
|
unless errors_as_json.is_a?(Hash)
|
50
50
|
raise ArgumentError, "errors be an object representable in JSON as a Hash; got errors = #{errors.inspect}"
|
@@ -55,7 +55,19 @@ module ApiHammer::Rails
|
|
55
55
|
unless errors_as_json.values.all? { |v| v.is_a?(Array) && v.all? { |e| e.is_a?(String) } }
|
56
56
|
raise ArgumentError, "errors values must all be arrays of strings; got errors = #{errors.inspect}"
|
57
57
|
end
|
58
|
-
|
58
|
+
render_options = options.dup.with_indifferent_access
|
59
|
+
body = {'errors' => errors}
|
60
|
+
error_message = render_options.delete('error_message') || begin
|
61
|
+
error_values = errors.values.inject([], &:+)
|
62
|
+
if error_values.size <= 1
|
63
|
+
error_values.first
|
64
|
+
else
|
65
|
+
# sentencify with periods
|
66
|
+
error_values.map { |v| v =~ /\.\s*\z/ ? v : v + '.' }.join(' ')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
body['error_message'] = error_message if error_message
|
70
|
+
halt(status, body, render_options)
|
59
71
|
end
|
60
72
|
|
61
73
|
# attempts to find and return the given model (an ActiveRecord::Base subclass) with the given attributes.
|
@@ -73,7 +85,11 @@ module ApiHammer::Rails
|
|
73
85
|
attributes = find_attrs.map{|attr, val| "#{attr}: #{val}" }.join(", ")
|
74
86
|
model_name = model.table_name
|
75
87
|
model_name = model_name.singularize if model_name.respond_to?(:singularize)
|
76
|
-
|
88
|
+
message = I18n.t(:"errors.unknown_record_with_attributes", :default => "Unknown %{model_name}! %{attributes}",
|
89
|
+
:model_name => model_name,
|
90
|
+
:attributes => attributes
|
91
|
+
)
|
92
|
+
halt_error(options[:status], {model_name => [message]})
|
77
93
|
end
|
78
94
|
record
|
79
95
|
end
|
data/lib/api_hammer/version.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "logstash/filters/base"
|
2
|
+
require "logstash/namespace"
|
3
|
+
|
4
|
+
class LogStash::Filters::ActiveSupportTags < LogStash::Filters::Base
|
5
|
+
config_name "active_support_tags"
|
6
|
+
milestone 1
|
7
|
+
|
8
|
+
config :consume, :validate => :boolean, :default => true
|
9
|
+
config :source, :validate => :string, :default => 'message'
|
10
|
+
|
11
|
+
public
|
12
|
+
def register
|
13
|
+
end
|
14
|
+
|
15
|
+
public
|
16
|
+
def filter(event)
|
17
|
+
as_tags = []
|
18
|
+
message = event[@source]
|
19
|
+
while message =~ /\A\[([^\]]+?)\]\s+/
|
20
|
+
as_tags << $1
|
21
|
+
message = $'
|
22
|
+
end
|
23
|
+
event['as_tags'] = as_tags if as_tags.any?
|
24
|
+
|
25
|
+
event[@source] = message if @consume
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "logstash/filters/base"
|
2
|
+
require "logstash/namespace"
|
3
|
+
|
4
|
+
class LogStash::Filters::ApiHammerRequest < LogStash::Filters::Base
|
5
|
+
config_name "api_hammer_request"
|
6
|
+
milestone 1
|
7
|
+
|
8
|
+
config :consume, :validate => :boolean, :default => true
|
9
|
+
config :source, :validate => :string, :default => 'message'
|
10
|
+
|
11
|
+
public
|
12
|
+
def register
|
13
|
+
end
|
14
|
+
|
15
|
+
public
|
16
|
+
def filter(event)
|
17
|
+
# discard the request status line for humans - always followed by json which we'll parse
|
18
|
+
col = /[\e\[\dm]*/.source
|
19
|
+
human_request = [/\A/, /[<>]/, /\s/, /\d+/, / : /, /\w+/, / /, /[^\e]+/, / @ /, /[^\e]+/, /\z/].map(&:source).join(col)
|
20
|
+
event.cancel if event[@source] =~ /#{human_request}/
|
21
|
+
|
22
|
+
begin
|
23
|
+
parsed_message = JSON.parse(event[@source])
|
24
|
+
if @consume
|
25
|
+
# replace the source with a brief human-readable message
|
26
|
+
role = parsed_message['request_role']
|
27
|
+
dir = role == 'server' ? '<' : role == 'client' ? '>' : '*'
|
28
|
+
status = parsed_message['response'] && parsed_message['response']['status']
|
29
|
+
request_method = parsed_message['request'] && parsed_message['request']['method']
|
30
|
+
request_uri = parsed_message['request'] && parsed_message['request']['uri']
|
31
|
+
now_s = Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')
|
32
|
+
event[@source] = "#{dir} #{status} : #{request_method} #{request_uri} @ #{now_s}"
|
33
|
+
end
|
34
|
+
rescue JSON::ParserError
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
if parsed_message
|
39
|
+
if parsed_message.is_a?(Hash)
|
40
|
+
event.to_hash.update(parsed_message)
|
41
|
+
if parsed_message['processing'].is_a?(Hash) && parsed_message['processing']['began_at'].is_a?(Integer)
|
42
|
+
event['@timestamp'] = Time.at(parsed_message['processing']['began_at']).utc
|
43
|
+
end
|
44
|
+
else
|
45
|
+
event['parsed_message'] = parsed_message
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "logstash/filters/base"
|
2
|
+
require "logstash/namespace"
|
3
|
+
require 'rack'
|
4
|
+
require 'cgi'
|
5
|
+
require 'json'
|
6
|
+
class LogStash::Filters::RequestBodiesParsed < LogStash::Filters::Base
|
7
|
+
config_name "request_bodies_parsed"
|
8
|
+
milestone 1
|
9
|
+
|
10
|
+
public
|
11
|
+
def register
|
12
|
+
end
|
13
|
+
|
14
|
+
public
|
15
|
+
def filter(event)
|
16
|
+
%w(request response).each do |re|
|
17
|
+
if event[re].is_a?(Hash) && event[re]['body'].is_a?(String)
|
18
|
+
_, content_type = event[re].detect { |(k,_)| k =~ /\Acontent.type\z/i }
|
19
|
+
if event[re]['headers'].is_a?(Hash) && !content_type
|
20
|
+
_, content_type = event[re]['headers'].detect { |(k,_)| k =~ /\Acontent.type\z/i }
|
21
|
+
end
|
22
|
+
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
23
|
+
body_parsed = begin
|
24
|
+
if media_type == 'application/json'
|
25
|
+
JSON.parse(event[re]['body']) rescue nil
|
26
|
+
elsif media_type == 'application/x-www-form-urlencoded'
|
27
|
+
CGI.parse(event[re]['body']).map { |k, vs| {k => vs.last} }.inject({}, &:update)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
event[re]['body_parsed'] = body_parsed if body_parsed
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "logstash/filters/base"
|
2
|
+
require "logstash/namespace"
|
3
|
+
|
4
|
+
class LogStash::Filters::RubyLogger < LogStash::Filters::Base
|
5
|
+
config_name "ruby_logger"
|
6
|
+
milestone 1
|
7
|
+
|
8
|
+
config :consume, :validate => :boolean, :default => true
|
9
|
+
config :source, :validate => :string, :default => 'message'
|
10
|
+
|
11
|
+
public
|
12
|
+
def register
|
13
|
+
end
|
14
|
+
|
15
|
+
public
|
16
|
+
def filter(event)
|
17
|
+
ruby_logged = /\A(?<severity_letter>\w), +\[(?<time>[\d\-T.:]+) +#(?<pid>\d+)\] +(?<severity>(?i:DEBUG|INFO|WARN|ERROR|FATAL|UNKNOWN|ANY)) +-- +(?<progname>.*?): /
|
18
|
+
if ruby_log_match = event[@source].match(ruby_logged)
|
19
|
+
uninteresting_names = %w(severity_letter time)
|
20
|
+
interesting_names = ruby_log_match.names - uninteresting_names
|
21
|
+
event.to_hash.update(interesting_names.map { |name| {name => ruby_log_match[name]} }.inject({}, &:update))
|
22
|
+
|
23
|
+
event['@timestamp'] = Time.parse(ruby_log_match['time']).utc
|
24
|
+
|
25
|
+
event[@source] = ruby_log_match.post_match if @consume
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "logstash/filters/base"
|
2
|
+
require "logstash/namespace"
|
3
|
+
|
4
|
+
class LogStash::Filters::Sidekiq < LogStash::Filters::Base
|
5
|
+
config_name "sidekiq"
|
6
|
+
milestone 1
|
7
|
+
|
8
|
+
config :consume, :validate => :boolean, :default => true
|
9
|
+
config :source, :validate => :string, :default => 'message'
|
10
|
+
|
11
|
+
public
|
12
|
+
def register
|
13
|
+
end
|
14
|
+
|
15
|
+
public
|
16
|
+
def filter(event)
|
17
|
+
sidekiq_logged = /\A(?<time>[\d\-]+T[\d:]+Z) (?<pid>\d+) TID-(?<tid>\w+)(?<context>.*?) (?<severity>(?i:DEBUG|INFO|WARN|ERROR|FATAL|UNKNOWN|ANY)): /
|
18
|
+
if sidekiq_match = event[@source].match(sidekiq_logged)
|
19
|
+
event['sidekiq'] ||= {}
|
20
|
+
event['sidekiq'].update(sidekiq_match.names.map { |name| {name => sidekiq_match[name]} }.inject({}, &:update))
|
21
|
+
|
22
|
+
event['@timestamp'] = Time.parse(sidekiq_match['time']).utc
|
23
|
+
|
24
|
+
# extract more info from context
|
25
|
+
job_context = /\A\s*(?<job_name>.+) JID-(?<jid>\w+)\z/
|
26
|
+
if context_match = sidekiq_match['context'].match(job_context)
|
27
|
+
event['sidekiq']['context'] = context_match.names.map { |name| {name => context_match[name]} }.inject({}, &:update)
|
28
|
+
end
|
29
|
+
|
30
|
+
event[@source] = sidekiq_match.post_match if @consume
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_hammer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -255,6 +255,11 @@ files:
|
|
255
255
|
- lib/api_hammer/unmunged_request_params.rb
|
256
256
|
- lib/api_hammer/version.rb
|
257
257
|
- lib/api_hammer/weblink.rb
|
258
|
+
- lib/logstash/filters/active_support_tags.rb
|
259
|
+
- lib/logstash/filters/api_hammer_request.rb
|
260
|
+
- lib/logstash/filters/request_bodies_parsed.rb
|
261
|
+
- lib/logstash/filters/ruby_logger.rb
|
262
|
+
- lib/logstash/filters/sidekiq.rb
|
258
263
|
- test/active_record_cache_find_by_test.rb
|
259
264
|
- test/check_required_params_test.rb
|
260
265
|
- test/faraday_request_logger_test.rb
|