right_support 2.12.1 → 2.13.3

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: e7074bbc2d6d01fc7eb2bb07e1feeef2ba2f53ed
4
- data.tar.gz: 95c557ae302fce0f8612aa6db52f5c4741bf8748
3
+ metadata.gz: 88f99381f01a8163458dfbb3428605aa10504ecf
4
+ data.tar.gz: c15fde263a3abf66adf8a665b7aabb27a530a89a
5
5
  SHA512:
6
- metadata.gz: ffb1c16256704d39e72a2661d8a3fb4c7cda875cc3de50eab7d6825ae42cb4b178c3ca01110d28d6655bc0d40674478e6b6565f605cd045c56ca438cb993f1f0
7
- data.tar.gz: 62082378411fc6876b5d76f06bc8ec1a48960521ef4221e24aee9b6e187fa2e2df394364fb58b34cdc317a5ada5b7fa433c69b13c6670752d3e2caf8cf27ca04
6
+ metadata.gz: c54a1945a13c506c38e2cfc337db07c9d68c2691fc2e501dc68ec16f1fa95670797927f384dd6bb2635c6b01d39b27c5a66994e8ffc910dded8ff505010c15e6
7
+ data.tar.gz: 08b6bac68cc7b5ebbb12a319fac566b904ef993f452956ac39c9a8371961c2c0fabe1849f407a5681560f627fe80e2c1b588089653db8ee9ef1a3a4e68fe287b
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.12.1
1
+ 2.13.3
@@ -486,5 +486,81 @@ module RightSupport::Data
486
486
  raise NoJson, "JSON is unavailable"
487
487
  end
488
488
  end
489
+
490
+ # provides a canonical representation of a header key that is acceptable to
491
+ # Rack, etc. the web standard for canonical header keys is all lowercase
492
+ # except with the first character capitalized at the start and after each
493
+ # dash. underscores are converted to dash characters.
494
+ def self.canonicalize_header_key(key)
495
+ key.to_s.downcase.gsub('_', '-').gsub(/(^|-)(.)/) { $1 + $2.upcase }
496
+ end
497
+
498
+ # @return [String] header value by canonical key name, if present.
499
+ def self.header_value_get(headers, key)
500
+ return nil unless headers
501
+ canonical_key = canonicalize_header_key(key)
502
+ value = nil
503
+ headers.each do |k, v|
504
+ if canonicalize_header_key(k) == canonical_key
505
+ value = v
506
+ break
507
+ end
508
+ end
509
+ value
510
+ end
511
+
512
+ # safely sets a header value by first locating any existing key by canonical
513
+ # search. if you know that the header hash is already canonical then you can
514
+ # alternatively set by using the canonicalized key.
515
+ #
516
+ # @param [Hash] headers to modify
517
+ # @param [String] key for header
518
+ # @param [String] value for header
519
+ #
520
+ # @return [Hash] updated headers
521
+ def self.header_value_set(headers, key, value)
522
+ headers ||= {}
523
+ canonical_key = canonicalize_header_key(key)
524
+ headers.each do |k, v|
525
+ if canonicalize_header_key(k) == canonical_key
526
+ key = k
527
+ break
528
+ end
529
+ end
530
+ headers[key] = value
531
+ headers
532
+ end
533
+
534
+ # duplicates the given headers hash after canonicalizing header keys.
535
+ #
536
+ # @param [Hash] headers to canonicalize
537
+ #
538
+ # @return [Hash] canonicalized headers
539
+ def self.canonicalize_headers(headers)
540
+ headers.inject({}) do |h, (k, v)|
541
+ h[canonicalize_header_key(k)] = v
542
+ h
543
+ end
544
+ end
545
+
546
+ # merges source headers hash into the duplicated target headers canonically.
547
+ # also supports removal of a target header when the source value is nil.
548
+ #
549
+ # @param [Hash] target to merge to
550
+ # @param [String] source to merge from
551
+ # @return [Hash] merged headers
552
+ def self.merge_headers(target, source)
553
+ target = canonicalize_headers(target)
554
+ (source || {}).each do |k, v|
555
+ canonical_key = canonicalize_header_key(k)
556
+ if v.nil?
557
+ target.delete(canonical_key)
558
+ else
559
+ target[canonical_key] = v
560
+ end
561
+ end
562
+ target
563
+ end
564
+
489
565
  end
490
566
  end
@@ -403,7 +403,17 @@ module RightSupport::Net
403
403
  stats = get_stats
404
404
  exceptions.each_pair do |endpoint, list|
405
405
  summary = []
406
- list.each { |e| summary << e.class }
406
+ list.each do |e|
407
+ if e.message.to_s.empty?
408
+ summary << e.class.name
409
+ else
410
+ message_top = e.message.to_s.lines.first.chomp
411
+ if message_top.length > 128
412
+ message_top = message_top[0, 124] + ' ...'
413
+ end
414
+ summary << "#{e.class.name}: #{message_top}"
415
+ end
416
+ end
407
417
  health = stats[endpoint] if stats[endpoint] != 'n/a'
408
418
  if hostname = lookup_hostname(endpoint)
409
419
  msg << "'#{hostname}' (#{endpoint}#{", "+health if health}) => [#{summary.uniq.join(', ')}]"
@@ -99,7 +99,6 @@ class RightSupport::Notifier::Utility::BacktraceDecoder
99
99
  frames = []
100
100
  trace ||= []
101
101
  trace = trace[@backtrace_offset..-1] if @backtrace_offset > 0
102
- gems_finder = '/gems/'
103
102
  seen_root_path = false
104
103
  trace.each do |t|
105
104
  has_root_path = false
@@ -134,8 +133,29 @@ class RightSupport::Notifier::Utility::BacktraceDecoder
134
133
  # rubygems or '/vendor/bundle/.../gems/' prefixes are only noise that
135
134
  # makes the trace harder to read for a human. '/gems/' may also appear
136
135
  # more than once so use the last found.
137
- if gems_offset = file.rindex(gems_finder)
138
- file = file[gems_offset + gems_finder.length..-1]
136
+ #
137
+ # also note that vendored gitted gems and their binstubs can appear
138
+ # directly under the '<app root>/vendor/bundle|cache/' directory and
139
+ # so are not explicitly under a '/gems/' directory.
140
+ founder = nil
141
+ founder_offset = nil
142
+ %w(
143
+ /gems/
144
+ /vendor/cache/
145
+ /vendor/bundle/
146
+ ).each do |finder|
147
+ if founder_offset = file.rindex(finder)
148
+ founder = finder
149
+ break
150
+ end
151
+ end
152
+ if founder
153
+ file = file[founder_offset + founder.length..-1]
154
+
155
+ # note that vendored gems are also technically on the 'root path'
156
+ # but we do not want to include them in the 'seen root path'
157
+ # logic because we want to see the trace back to real app code.
158
+ has_root_path = false
139
159
  end
140
160
  end
141
161
  else
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2011 RightScale Inc
2
+ # Copyright (c) 2011-2016 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -25,11 +25,10 @@ module RightSupport
25
25
  # A namespace for Rack middleware and other enhancements.
26
26
  #
27
27
  module Rack
28
-
28
+ autoload :LogSetter, 'right_support/rack/log_setter'
29
+ autoload :RequestLogger, 'right_support/rack/request_logger'
30
+ autoload :RequestTracker, 'right_support/rack/request_tracker'
31
+ autoload :Runtime, 'right_support/rack/runtime'
32
+ autoload :SharedEnvironment, 'right_support/rack/shared_environment'
29
33
  end
30
34
  end
31
-
32
- require 'right_support/rack/log_setter'
33
- require 'right_support/rack/request_logger'
34
- require 'right_support/rack/request_tracker'
35
- require 'right_support/rack/runtime'
@@ -185,44 +185,21 @@ module RightSupport::Rack
185
185
  raise
186
186
  end
187
187
 
188
- # provides a canonical representation of a header key that is acceptable to
189
- # Rack, etc.
188
+ # @deprecated in favor of RightSupport::Data::HashTools.canonicalize_header_key
190
189
  def self.canonicalize_header_key(key)
190
+ # the legacy implementation is train-case but HashTools uses the more
191
+ # commonly accepted Canonical-Key style (preferred by golang, etc).
191
192
  key.downcase.gsub('_', '-')
192
193
  end
193
194
 
194
- # @return [String] header value by canonical key name, if present.
195
+ # @deprecated in favor of RightSupport::Data::HashTools.header_value_get
195
196
  def self.header_value_get(headers, key)
196
- return nil unless headers
197
- key = canonicalize_header_key(key)
198
- value = nil
199
- headers.each do |k, v|
200
- if canonicalize_header_key(k) == key
201
- value = v
202
- break
203
- end
204
- end
205
- value
197
+ return ::RightSupport::Data::HashTools.header_value_get(headers, key)
206
198
  end
207
199
 
208
- # safely sets a header value by first locating any existing key by canonical
209
- # search.
210
- #
211
- # @param [Hash] headers to modify
212
- # @param [String] key for header
213
- # @param [String] value for header
214
- # @return [Hash] updated headers
200
+ # @deprecated in favor of RightSupport::Data::HashTools.header_value_set
215
201
  def self.header_value_set(headers, key, value)
216
- headers ||= {}
217
- key = canonicalize_header_key(key)
218
- headers.each do |k, v|
219
- if canonicalize_header_key(k) == key
220
- key = k
221
- break
222
- end
223
- end
224
- headers[key] = value
225
- headers
202
+ return ::RightSupport::Data::HashTools.header_value_set(headers, key, value)
226
203
  end
227
204
 
228
205
  # Formats a concise error message with limited backtrace, etc.
@@ -342,7 +319,7 @@ module RightSupport::Rack
342
319
  # @return [TrueClass] always true
343
320
  def log_request_end(logger, env, status, headers, body, began_at)
344
321
  duration = (Time.now - began_at) * 1000
345
- unless content_length = self.class.header_value_get(headers, 'Content-Length')
322
+ unless content_length = ::RightSupport::Data::HashTools.header_value_get(headers, 'Content-Length')
346
323
  case body
347
324
  when Array
348
325
  content_length = body.reduce(0) {|accum, e| accum += e.bytesize}
@@ -49,6 +49,13 @@ module RightSupport::Rack
49
49
  # value to finding and replacing with _id in all cases.
50
50
  REQUEST_UUID_ENV_NAME = 'rack.request_uuid'.freeze
51
51
 
52
+ # records count of additional API calls made by an application while
53
+ # handling a request, etc. this is useful for generating an incremental
54
+ # request [UU]ID to be forwarded to other services for additional API calls.
55
+ #
56
+ # @see RightSupport::Rack::RequestTracker.next_request_uuid
57
+ API_CALL_COUNTER_ENV_KEY = 'request_tracker.api_call_counter'
58
+
52
59
  # @deprecated do not send the lineage header as support may go away.
53
60
  REQUEST_LINEAGE_UUID_HEADER = 'HTTP_X_REQUEST_LINEAGE_UUID'.freeze
54
61
 
@@ -118,23 +125,57 @@ module RightSupport::Rack
118
125
  end
119
126
 
120
127
  # copies the request [UU]ID from the request environment to the given hash,
121
- # if present. does nothing if request [UU]ID is not set (because middleware
122
- # was not present, etc.)
128
+ # if present.
123
129
  #
124
- # @param [Hash] from_env as source
130
+ # @param [Hash] from_env as source hash
125
131
  # @param [Hash] to_headers as target
132
+ # @param [Hash] options
133
+ # @option options [Proc|Method] :generator as callback to generate a new
134
+ # request uuid when not present or nil
135
+ # @option options [Proc|Method] :modifier as callback to modify request uuid
136
+ # before it is finally inserted in headers or nil. this can be useful for
137
+ # appending a counter for each new API call made by a request handler, etc.
138
+ # only called if identifier was retrieved from the environment hash because
139
+ # an explicity-set (or generated) ID value is used without modification.
140
+ # @option options [TrueClass|FalseClass] :return_request_uuid as true to
141
+ # return a tuple of [to_headers, request_uuid] instead. false to only
142
+ # return updated to_headers (default).
126
143
  #
127
- # @return [Hash] updated headers
128
- def self.copy_request_uuid(from_env, to_headers)
144
+ # @return [Hash|Array] updated to_headers (default) or tuple of
145
+ # [to_headers, request_uuid]. see :return_request_uuid option.
146
+ def self.copy_request_uuid(from_env, to_headers, options = {})
147
+ options = {
148
+ generator: nil,
149
+ modifier: nil,
150
+ return_request_uuid: false
151
+ }.merge(options)
152
+ from_env ||= {}
129
153
  to_headers ||= {}
130
- if from_env
154
+
155
+ # keep existing ID value, if any.
156
+ request_uuid = ::RightSupport::Data::HashTools.header_value_get(
157
+ to_headers,
158
+ REQUEST_ID_HEADER)
159
+ unless request_uuid
160
+ # attempt to find ID key in environment hash.
131
161
  if request_uuid = from_env[REQUEST_UUID_ENV_NAME]
132
- # note we always forward the _ID header as the standard. none of the
133
- # RS code ever actually accepted the _UUID header so that is a
134
- # non-issue.
135
- to_headers[REQUEST_ID_HEADER] = request_uuid
162
+ # use modifier, if any.
163
+ if modifier = options[:modifier]
164
+ request_uuid = modifier.call(request_uuid)
165
+ end
166
+ else
167
+ # use generator, if any.
168
+ if generator = options[:generator]
169
+ request_uuid = generator.call
170
+ end
136
171
  end
172
+
173
+ # note that we will always forward the _ID header as the standard.
174
+ # none of the RS code ever actually accepted the _UUID header so that
175
+ # is a non-issue.
176
+ to_headers[REQUEST_ID_HEADER] = request_uuid if request_uuid
137
177
  end
178
+ return [to_headers, request_uuid] if options[:return_request_uuid]
138
179
  to_headers
139
180
  end
140
181
 
@@ -146,5 +187,13 @@ module RightSupport::Rack
146
187
  Generator.generate
147
188
  end
148
189
 
190
+ # @return [String] next request [UU]ID
191
+ def self.next_request_uuid(env, base_request_uuid = nil)
192
+ base_request_uuid ||= env[REQUEST_UUID_ENV_NAME] || 'missing'
193
+ env[API_CALL_COUNTER_ENV_KEY] ||= 0
194
+ n = env[API_CALL_COUNTER_ENV_KEY] += 1
195
+ "#{base_request_uuid}_#{n}"
196
+ end
197
+
149
198
  end # RequestTracker
150
199
  end # RightSupport::Rack
@@ -0,0 +1,112 @@
1
+ #
2
+ # Copyright (c) 2016 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport::Rack
24
+
25
+ # provides shared access to the Rack request environment hash for any method
26
+ # executing on the request thread without needing to pass the hash explicitly.
27
+ # explicit parameter passing is preferred when writing new code, of course,
28
+ # but it can be difficult to update old code with new parameters going several
29
+ # layers deep. this middleware attempts to simplify those changes.
30
+ #
31
+ # note that only the request thread receives the environment hash so any
32
+ # threads spawned by the request thread will need to explicitly reference any
33
+ # needed hash values and/or the entire hash using the accessors.
34
+ class SharedEnvironment
35
+
36
+ # the symbol that represents the Rack request environment hash in TLS.
37
+ ENVIRONMENT_HASH_SYMBOL = :rack_request_environment_hash
38
+
39
+ # @param [Object] app to call
40
+ # @param [Hash] options
41
+ # @option options [TrueClass|FalseClass] :fiber_local as false to set the
42
+ # environment hash visible to all fibers of the current thread (default) or
43
+ # true to set a fiber-local environment hash. if your app does not attempt
44
+ # to use fibers then either will work as every thread has a root fiber.
45
+ # if your app creates fibers while handling requests then the default of
46
+ # thread-local is probably best because the environment hash is shared with
47
+ # all fibers. keep in mind that the thread and its root fiber keep distinct
48
+ # hashes so you must reference one or the other.
49
+ def initialize(app, options = {})
50
+ options = {
51
+ fiber_local: false
52
+ }.merge(options)
53
+ @app = app
54
+ @fiber_local = !!options[:fiber_local]
55
+ end
56
+
57
+ def call(env)
58
+ # sanity check in case the application developer is unaware that the
59
+ # execution model is using fibers instead of threads, etc.
60
+ fail 'Environment hash is already set.' unless get_environment_hash.empty?
61
+ set_environment_hash(env)
62
+ return @app.call(env)
63
+ ensure
64
+ # only intended for use by the application; middleware already has env
65
+ # available on each call. reset to nil before falling back.
66
+ # also note that there is no such animal as thread_variable_delete, etc.
67
+ set_environment_hash(nil)
68
+ end
69
+
70
+ # @return [Hash] environment hash or empty
71
+ def get_environment_hash
72
+ return self.class.get_environment_hash(@fiber_local)
73
+ end
74
+
75
+ # @param [Hash] env as environment hash or nil or empty
76
+ #
77
+ # @return [TrueClass] always true
78
+ def set_environment_hash(env)
79
+ return self.class.set_environment_hash(env, @fiber_local)
80
+ end
81
+
82
+ # gets the environment hash for current thread or fiber.
83
+ #
84
+ # @return [Hash] environment hash or empty
85
+ def self.get_environment_hash(fiber_local = false)
86
+ current = ::Thread.current
87
+ if fiber_local
88
+ # note that in ruby 1.8 this would have referred to thread-local storage
89
+ # but in ruby 1.9+ it refers to fiber-local storage.
90
+ current[ENVIRONMENT_HASH_SYMBOL] || {}
91
+ else
92
+ # in ruby 1.9+ you must explicitly call for thread-local variables.
93
+ current.thread_variable_get(ENVIRONMENT_HASH_SYMBOL) || {}
94
+ end
95
+ end
96
+
97
+ # sets the environment hash for current thread or fiber.
98
+ #
99
+ # @param [Hash] env as environment hash or nil or empty
100
+ #
101
+ # @return [TrueClass] always true
102
+ def self.set_environment_hash(env, fiber_local = false)
103
+ current = ::Thread.current
104
+ if fiber_local
105
+ current[ENVIRONMENT_HASH_SYMBOL] = env
106
+ else
107
+ current.thread_variable_set(ENVIRONMENT_HASH_SYMBOL, env)
108
+ end
109
+ end
110
+
111
+ end
112
+ end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: right_support 2.12.1 ruby lib
5
+ # stub: right_support 2.13.3 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "right_support"
9
- s.version = "2.12.1"
9
+ s.version = "2.13.3"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Tony Spataro", "Sergey Sergyenko", "Ryan Williamson", "Lee Kirchhoff", "Alexey Karpik", "Scott Messier"]
14
- s.date = "2016-08-04"
14
+ s.date = "2016-09-12"
15
15
  s.description = "A toolkit of useful, reusable foundation code created by RightScale."
16
16
  s.email = "support@rightscale.com"
17
17
  s.extra_rdoc_files = [
@@ -88,6 +88,7 @@ Gem::Specification.new do |s|
88
88
  "lib/right_support/rack/request_logger.rb",
89
89
  "lib/right_support/rack/request_tracker.rb",
90
90
  "lib/right_support/rack/runtime.rb",
91
+ "lib/right_support/rack/shared_environment.rb",
91
92
  "lib/right_support/ruby.rb",
92
93
  "lib/right_support/ruby/easy_singleton.rb",
93
94
  "lib/right_support/ruby/object_extensions.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_support
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.1
4
+ version: 2.13.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Spataro
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2016-08-11 00:00:00.000000000 Z
16
+ date: 2016-09-13 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: rake
@@ -162,6 +162,7 @@ files:
162
162
  - lib/right_support/rack/request_logger.rb
163
163
  - lib/right_support/rack/request_tracker.rb
164
164
  - lib/right_support/rack/runtime.rb
165
+ - lib/right_support/rack/shared_environment.rb
165
166
  - lib/right_support/ruby.rb
166
167
  - lib/right_support/ruby/easy_singleton.rb
167
168
  - lib/right_support/ruby/object_extensions.rb