api_hammer 0.6.3 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|