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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 002d518d717450a30af8921565ffe152625133aa
4
- data.tar.gz: 9bae35c5c3917d005ec60b662923d3bcf69317e8
3
+ metadata.gz: 2efd3cea2d6c262bff720e983354d944f0de16da
4
+ data.tar.gz: 9611db21a814571891d2d17ed017c82d2048b52e
5
5
  SHA512:
6
- metadata.gz: 166eefca239f0809baf6a54eb4d0de4b43ae5f2944ac57dcdab980df6be1be21572868575d3af3a15b07380ef367811b4e005c047e491cceab6c8c66f5a291f4
7
- data.tar.gz: e60133dfd3a348b375e34ac6075435ce7e4784d8d8dae5ff68428398f2340ba78653f168eea849b82d9c3db086f4e3f2cfaef43fa2a2c7a9d8c901606bf38d4f
6
+ metadata.gz: 8b0dbb63040d700c2c137e24c4d84eb4804d2a234a68f3b02fdc001a78ff9364cb04975466e571bac801436be222174cd7fb7def879766c1af15afadddc1a093
7
+ data.tar.gz: 01d61498c3db5d7ef844b6c4c8bd4524a7f98c5d2b8bd738b3a8d5ac42e952c11eab9e85c48d794e9e9d1c8fdbdbff5f2317b78d95b8f4e3af954b35729228e9
@@ -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 #{check.name}")
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
@@ -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, render_options = {})
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
- halt(status, {'errors' => errors}, render_options)
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
- halt_error(options[:status], {model_name => ["Unknown #{model_name}! #{attributes}"]})
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
@@ -1,3 +1,3 @@
1
1
  module ApiHammer
2
- VERSION = "0.6.3"
2
+ VERSION = "0.7.1"
3
3
  end
@@ -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.6.3
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-09-22 00:00:00.000000000 Z
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