right_support 2.12.1 → 2.13.3

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 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