right_support 2.10.1 → 2.11.2

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.
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2012 RightScale Inc
2
+ # Copyright (c) 2012-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
@@ -35,20 +35,60 @@ module RightSupport::Rack
35
35
  # with Rack::Logger or another middleware that provides logging services.
36
36
  class RequestLogger
37
37
 
38
- # Initialize an instance of the middleware, optionally providing a whitelist (:only) or
39
- # a blacklist (:except) of path regexps to which request logging will apply.
40
- #
41
- # @param [Object] app inner application or middleware layer; must respond to #call
38
+ # limit exception backtrace length; usually only the first few lines are
39
+ # significant. the same error dumped repeatedly can make grepping difficult.
40
+ BACKTRACE_LIMIT = 10
41
+
42
+ # for debug-mode only logging. debug-mode can also be enabled by the
43
+ # "X-Debug=<anything>" header.
44
+ DEBUG_MODE_ENV_KEY = 'rack.debug-mode'.freeze
45
+ DEBUG_MODE = ::ENV['DEBUG_MODE'] == 'true'
46
+ DEBUG_MODE_HTTP_HEADER = 'HTTP_X_DEBUG'
47
+
48
+ # defaults to STDERR by Rack but usually overridden to use syslog, etc.
49
+ RACK_LOGGER = 'rack.logger'.freeze
50
+
51
+ # indicates whether current request is enabled for logging by filter, etc.
52
+ RACK_LOGGING_ENABLED = 'rack.logging.enabled'.freeze
53
+
54
+ # list of status codes that are 'normalized' when :normalize_40x=true
55
+ # normalization means stripping any developer-provided details to avoid
56
+ # leaking information about the service. overridden by DEBUG_MODE=true but
57
+ # specifically *not* affected by the 'X-Debug' header.
58
+ NORMALIZE_40X = [401, 403, 404]
59
+
60
+ # Initialize an instance of the middleware, optionally providing a whitelist
61
+ # (:only) or a blacklist (:except) of path regexps to which request logging
62
+ # will apply.
42
63
  #
43
- # @option options [Array] :only log for these path Regexps unless in debug mode
44
- # @option options [Array] :except log except for these path Regexps unless in debug mode; this option is ignored if :only is provided
45
- # @option options [Array] :include_query_string log the contents of the query string
64
+ # @param [Object] app inner application or middleware layer; must respond to
65
+ # #call.
66
+ # @param [Hash] options
67
+ # @option options [Array] :only log for these path Regexps unless in debug
68
+ # mode.
69
+ # @option options [Array] :except log except for these path Regexps unless
70
+ # in debug mode; this option is ignored if :only is provided.
71
+ # @option options [Array] :include_query_string log the contents of the
72
+ # query string.
73
+ # @option options [Logger] :logger to override any previously set logger or
74
+ # nil. Rack sets a STDERR logger by default or it may be set by other
75
+ # framework middleware so this is only for when those are not used.
76
+ # @option options [TrueClass|FalseClass] :normalize_40x to remove detail
77
+ # from some 40x responses for security reasons. default=false.
78
+ # @option options [TrueClass|FalseClass] :normalize_40x_set_cookie to
79
+ # override the usual global session behavior of always responding with a
80
+ # 'Set-Cookie' header regardless of status code. defaults to having the
81
+ # same value as the :normalize_40x option.
46
82
  def initialize(app, options = {})
47
83
  @app = app
48
84
  @only = options[:only] || []
49
85
  @except = options[:except] || []
50
86
  # by default we don't log the query string contents because it may have sensitive data.
51
87
  @include_query_string = options[:include_query_string] || false
88
+ @logger = options[:logger]
89
+ @normalize_40x = !!options[:normalize_40x]
90
+ @normalize_40x_set_cookie = options[:normalize_40x_set_cookie]
91
+ @normalize_40x_set_cookie = @normalize_40x if @normalize_40x_set_cookie.nil?
52
92
  end
53
93
 
54
94
  # Add a logger to the Rack environment and call the next middleware.
@@ -67,25 +107,154 @@ module RightSupport::Rack
67
107
  # @return [Object] always returns whatever value is returned by the next
68
108
  # layer of middleware
69
109
  def call(env)
70
- logger = env["rack.logger"]
71
- env["rack.logging.enabled"] = enabled = logging_enabled?(logger, env)
110
+ # forward-declare in case of exception.
111
+ enabled = true
72
112
 
113
+ # override logger, if necessary.
114
+ if @logger
115
+ logger = env[RACK_LOGGER] = @logger
116
+ else
117
+ logger = env[RACK_LOGGER]
118
+ end
119
+
120
+ # determine if debug-mode is enabled.
121
+ env[DEBUG_MODE_ENV_KEY] = debug_enabled?(env)
122
+
123
+ # log request only when enabled.
124
+ env[RACK_LOGGING_ENABLED] = enabled = logging_enabled?(logger, env)
73
125
  began_at = Time.now
74
126
  log_request_begin(logger, env) if enabled
75
- status, header, body = @app.call(env)
127
+
128
+ # next
129
+ status, headers, body = @app.call(env)
130
+
131
+ # log exception, if necessary.
76
132
  if env['sinatra.error'] && !env['sinatra.error.handled']
133
+ if !enabled
134
+ # after the fact but better than logging exception without its request.
135
+ log_request_begin(logger, env)
136
+ enabled = true
137
+ end
77
138
  log_exception(logger, env['sinatra.error'])
139
+ elsif !enabled && env[RACK_LOGGING_ENABLED]
140
+ # controller can conditionally enable logging for the current request
141
+ # even though by default such requests are not logged. an example is a
142
+ # periodic request for a listing (scheduled tasks, etc.) that is only
143
+ # logged when not empty.
144
+ log_request_begin(logger, env)
145
+ enabled = true
78
146
  end
79
- log_request_end(logger, env, status, header, body, began_at) if enabled
80
147
 
81
- return [status, header, body]
148
+ # respond identically to some 40Xs; do not reveal any internals by
149
+ # varying the response headers or body (as developers tend to do for
150
+ # debugging reasons). public APIs will receive such requests frequently
151
+ # but this is generally not needed for internal APIs.
152
+ #
153
+ # note that it is important to *not* accept the "X-Debug" header override
154
+ # and show more information in response ;)
155
+ if @normalize_40x && !DEBUG_MODE && NORMALIZE_40X.include?(status)
156
+ # note that ::Rack::CONTENT_LENGTH was not defined before rack v1.6+ :@
157
+ headers = { 'Content-Length' => '0' }
158
+ body = []
159
+ env[DEBUG_MODE_ENV_KEY] = false # disable any verbose debugging
160
+ end
161
+
162
+ # defeat global session renew, update and set-cookie for normalized 40x.
163
+ # has no effect if the app is not using global session middleware.
164
+ if @normalize_40x_set_cookie && NORMALIZE_40X.include?(status)
165
+ env['global_session.req.renew'] = false
166
+ env['global_session.req.update'] = false
167
+ end
168
+
169
+ # log response only when enabled.
170
+ if enabled
171
+ log_request_end(logger, env, status, headers, body, began_at)
172
+ end
173
+
174
+ return [status, headers, body]
82
175
  rescue Exception => e
176
+ if !enabled
177
+ # after the fact but better than logging exception without its request.
178
+ log_request_begin(logger, env)
179
+ end
83
180
  log_exception(logger, e)
84
181
  raise
85
182
  end
86
183
 
184
+ # provides a canonical representation of a header key that is acceptable to
185
+ # Rack, etc.
186
+ def self.canonicalize_header_key(key)
187
+ key.downcase.gsub('_', '-')
188
+ end
189
+
190
+ # @return [String] header value by canonical key name, if present.
191
+ def self.header_value_get(headers, key)
192
+ return nil unless headers
193
+ key = canonicalize_header_key(key)
194
+ value = nil
195
+ headers.each do |k, v|
196
+ if canonicalize_header_key(k) == key
197
+ value = v
198
+ break
199
+ end
200
+ end
201
+ value
202
+ end
203
+
204
+ # safely sets a header value by first locating any existing key by canonical
205
+ # search.
206
+ #
207
+ # @param [Hash] headers to modify
208
+ # @param [String] key for header
209
+ # @param [String] value for header
210
+ # @return [Hash] updated headers
211
+ def self.header_value_set(headers, key, value)
212
+ headers ||= {}
213
+ key = canonicalize_header_key(key)
214
+ headers.each do |k, v|
215
+ if canonicalize_header_key(k) == key
216
+ key = k
217
+ break
218
+ end
219
+ end
220
+ headers[key] = value
221
+ headers
222
+ end
223
+
224
+ # Formats a concise error message with limited backtrace, etc.
225
+ #
226
+ # @param [Exception] e to format
227
+ #
228
+ # @return [String] formatted error
229
+ def self.format_exception_message(e)
230
+ trace = e.backtrace || []
231
+ if trace.size > BACKTRACE_LIMIT
232
+ trace = trace[0, BACKTRACE_LIMIT] << '...'
233
+ end
234
+ kind = e.class.name
235
+ if (msg = e.message) && !msg.empty? && msg != kind
236
+ kind = "#{kind}: #{msg}"
237
+ end
238
+ [kind, *trace].join("\n")
239
+ end
240
+
241
+ # @return [String] accepted/generated request UUID or else 'missing' to
242
+ # indicate that the RequestTracker middleware is missing.
243
+ def self.request_uuid_from_env(env)
244
+ env[RequestTracker::REQUEST_UUID_ENV_NAME] || 'missing'
245
+ end
246
+
87
247
  private
88
248
 
249
+ # determines if debug-mode is enabled for the current request by environment
250
+ # variable override or by specific X-Debug header in request.
251
+ #
252
+ # note that the legacy code accepted any value for the debug header so long
253
+ # as it was not nil.
254
+ def debug_enabled?(env)
255
+ DEBUG_MODE || !env[DEBUG_MODE_HTTP_HEADER].nil?
256
+ end
257
+
89
258
  # Determine whether logging enabled for given request
90
259
  #
91
260
  # @param [Object] logger for Rack
@@ -96,7 +265,7 @@ module RightSupport::Rack
96
265
  path = env["PATH_INFO"]
97
266
 
98
267
  # the request can always ask for debugging
99
- if env["HTTP_X_DEBUG"]
268
+ if env[DEBUG_MODE_ENV_KEY]
100
269
  true
101
270
  # some apps have a blacklist of boring requests (e.g. health checks)
102
271
  elsif @except.any? { |e| e.is_a?(Regexp) ? (path =~ e) : (path == e) }
@@ -110,6 +279,12 @@ module RightSupport::Rack
110
279
  end
111
280
  end
112
281
 
282
+ # @return [String] accepted/generated request UUID or else 'missing' to
283
+ # indicate that the RequestTracker middleware is missing.
284
+ def request_uuid_from_env(env)
285
+ self.class.request_uuid_from_env(env)
286
+ end
287
+
113
288
  # Log beginning of request
114
289
  #
115
290
  # @param [Object] logger for Rack
@@ -137,24 +312,20 @@ module RightSupport::Rack
137
312
  cookie = env['global_session']
138
313
  info = [ env['global_session'].id,
139
314
  cookie.keys.map{|k| %{"#{k}"=>"#{cookie[k]}"} }.join(', ') ]
140
- sess = %Q{Session ID: %s Session Data: {%s}} % info
315
+ sess = %Q{Session ID: %s, Session Data: {%s}, } % info
141
316
  else
142
317
  sess = ""
143
318
  end
144
319
 
145
- shard_info = 'Shard: ' + (env['HTTP_X_SHARD'] || 'default').to_s + ';'
146
-
147
- params = [
320
+ logger.info '[%s] Started %s "%s%s" (for %s), %sShard: %s' % [
321
+ request_uuid_from_env(env),
148
322
  env['REQUEST_METHOD'],
149
323
  env['PATH_INFO'],
150
324
  query_info,
151
325
  remote_addr,
152
326
  sess,
153
- shard_info,
154
- env['rack.request_uuid'] || ''
327
+ env['HTTP_X_SHARD'] || 'default'
155
328
  ]
156
-
157
- logger.info 'Processing %s "%s%s" (for %s) %s %s Request ID: %s' % params
158
329
  end
159
330
 
160
331
  # Log end of request
@@ -167,30 +338,39 @@ module RightSupport::Rack
167
338
  # @return [TrueClass] always true
168
339
  def log_request_end(logger, env, status, headers, body, began_at)
169
340
  duration = (Time.now - began_at) * 1000
170
-
171
- # Downcase keys in the headers Hash for case-insensitive comparisons
172
- safe_headers = Hash[*headers.map {|k,v| [k.downcase, v]}.flatten]
173
-
174
- content_length = if safe_headers['content-length']
175
- safe_headers['content-length']
176
- else
177
- if body.is_a?(Array)
178
- body.reduce(0) {|accum, e| accum += e.bytesize}
179
- elsif body.is_a?(String)
180
- body.bytesize
341
+ unless content_length = self.class.header_value_get(headers, 'Content-Length')
342
+ case body
343
+ when Array
344
+ content_length = body.reduce(0) {|accum, e| accum += e.bytesize}
345
+ when String
346
+ content_length = body.bytesize
181
347
  else
182
- '-'
348
+ content_length = '-'
183
349
  end
184
350
  end
185
351
 
186
- params = [
352
+ request_uuid = request_uuid_from_env(env)
353
+ logger.info '[%s] Completed in %dms | %s | %s bytes' % [
354
+ request_uuid,
187
355
  duration,
188
356
  "#{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}",
189
- content_length.to_s,
190
- env['rack.request_uuid'] || ''
357
+ content_length.to_s
191
358
  ]
192
359
 
193
- logger.info 'Completed in %dms | %s | %s bytes | Request ID: %s' % params
360
+ # more detail, if appropriate.
361
+ if env[DEBUG_MODE_ENV_KEY] && logger.debug?
362
+ unless headers.nil? || headers.empty?
363
+ logger.debug("[#{request_uuid}] #{headers.inspect}")
364
+ end
365
+ unless body.nil? || body.empty?
366
+ body = Array(body) unless body.respond_to?(:each)
367
+ body.each do |b|
368
+ b = b.to_s.strip
369
+ logger.debug("[#{request_uuid}] #{b}") unless b.empty?
370
+ end
371
+ end
372
+ end
373
+ true
194
374
  end
195
375
 
196
376
  # Log exception
@@ -199,11 +379,10 @@ module RightSupport::Rack
199
379
  # @param [Exception] exception to be logged
200
380
  #
201
381
  # @return [TrueClass] always true
202
- def log_exception(logger, exception)
203
- msg = ["#{exception.class} - #{exception.message}", *exception.backtrace].join("\n")
204
- logger.error(msg)
382
+ def log_exception(logger, e)
383
+ logger.error(self.class.format_exception_message(e))
205
384
  rescue
206
- #no-op, something is seriously messed up by this point...
385
+ # no-op; something is seriously messed up by this point...
207
386
  end
208
387
  end
209
388
  end
@@ -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
@@ -21,43 +21,130 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
23
  module RightSupport::Rack
24
- # TODO docs
24
+
25
+ # middleware to detect a request ID header or generate a new ID for each
26
+ # incoming request. the purpose is to track a request lineage throughout a
27
+ # chain of internal API calls and, in some cases, out to external APIs that
28
+ # also support the X-Request-ID header as a de-facto standard (google it).
25
29
  class RequestTracker
26
- REQUEST_LINEAGE_UUID_HEADER = "HTTP_X_REQUEST_LINEAGE_UUID".freeze
27
- REQUEST_UUID_HEADER = "X-Request-Uuid".freeze
28
- REQUEST_UUID_ENV_NAME = "rack.request_uuid".freeze
29
- UUID_SEPARATOR = " ".freeze
30
+ # shorthand
31
+ Generator = ::RightSupport::Data::Token
30
32
 
31
- # Make a new Request tracker.
32
- #
33
- # Tags the requset with a new request UUID
34
- #
35
- # === Parameters
36
- # app(Rack client): application to run
33
+ # used by many public services to represent a client-generated request ID
34
+ # that can be tracked and safely logged throughout the lineage of a request.
35
+ # this is also supported by goa middleware.
36
+ REQUEST_ID_HEADER = 'X-Request-Id'.freeze
37
+
38
+ # incoming header as found in Rack env hash, if any.
39
+ HTTP_REQUEST_ID_HEADER = 'HTTP_X_REQUEST_ID'.freeze
40
+
41
+ # LEGACY: still supported but new code should use the standard (see above).
42
+ REQUEST_UUID_HEADER = "X-Request-Uuid".freeze
43
+
44
+ # LEGACY: incoming header as found in Rack env hash, if any.
45
+ HTTP_REQUEST_UUID_HEADER = 'HTTP_X_REQUEST_UUID'.freeze
46
+
47
+ # LEGACY: refers to the generated or passed-in request ID. the key has been
48
+ # hardcoded in some places so is not easy to change and/or there is not much
49
+ # value to finding and replacing with _id in all cases.
50
+ REQUEST_UUID_ENV_NAME = 'rack.request_uuid'.freeze
51
+
52
+ # @deprecated do not send the lineage header as support may go away.
53
+ REQUEST_LINEAGE_UUID_HEADER = 'HTTP_X_REQUEST_LINEAGE_UUID'.freeze
54
+
55
+ # @deprecated do not send the lineage header as support may go away.
56
+ UUID_SEPARATOR = ' '.freeze
57
+
58
+ # limit request [UU]ID to something reasonable to keep our logs from
59
+ # overflowing on bad user-provided IDs. we do not want to be too restrictive
60
+ # in case people are encoding some useful information, etc. the issue is
61
+ # that the request ID will tend to show up a lot in logs.
62
+ MAX_REQUEST_UUID_LENGTH = 128
63
+
64
+ # @param [Object] app as next middleware or the rack application
37
65
  def initialize(app)
38
66
  @app = app
39
67
  end
40
68
 
69
+ # request tracking.
70
+ #
71
+ # @param [Hash] env from preceding middleware
72
+ #
73
+ # @return [Array] tuple of [status, headers, body]
41
74
  def call(env)
42
- if env.has_key? REQUEST_LINEAGE_UUID_HEADER
43
- request_uuid = env[REQUEST_LINEAGE_UUID_HEADER] + UUID_SEPARATOR +
44
- generate_request_uuid
45
- else
46
- request_uuid = generate_request_uuid
47
- end
48
-
75
+ request_uuid, response_header_name = self.class.detect_request_uuid(env)
49
76
  env[REQUEST_UUID_ENV_NAME] = request_uuid
50
77
 
51
78
  status, headers, body = @app.call(env)
52
79
 
53
- headers[REQUEST_UUID_HEADER] = request_uuid
54
- [status, headers,body]
80
+ headers[response_header_name] = request_uuid
81
+ [status, headers, body]
55
82
  end
56
83
 
84
+ # detects whether the incoming env hash contains a request ID is some form
85
+ # and generates a new ID when missing.
86
+ #
87
+ # @return [Array] tuple of detected/generated ID and the name of the header
88
+ # to use to represent the ID in a response.
89
+ def self.detect_request_uuid(env)
90
+ request_uuid = ''
91
+ response_header_name = nil
92
+ {
93
+ HTTP_REQUEST_ID_HEADER => REQUEST_ID_HEADER,
94
+ HTTP_REQUEST_UUID_HEADER => REQUEST_UUID_HEADER,
95
+ REQUEST_LINEAGE_UUID_HEADER => REQUEST_UUID_HEADER
96
+ }.each do |in_key, out_key|
97
+ if env.has_key?(in_key)
98
+ request_uuid = env[in_key].to_s.strip
99
+ response_header_name = out_key
100
+ break
101
+ end
102
+ end
103
+
104
+ # for legacy reasons we default to the -UUID header in response for newly-
105
+ # generated IDs. we will use the -ID standard if that was passed-in. once
106
+ # all apps are updated you will mostly see -ID in response except for API
107
+ # calls initiated by browser code. the javascript can gradually be changed
108
+ # to send -ID with requests as well.
109
+ if request_uuid.empty?
110
+ request_uuid = generate_request_uuid
111
+ response_header_name = REQUEST_UUID_HEADER
112
+ else
113
+ # truncate, if necessary.
114
+ request_uuid = request_uuid[0, MAX_REQUEST_UUID_LENGTH]
115
+ end
57
116
 
58
- def generate_request_uuid
59
- ::RightSupport::Data::UUID.generate
117
+ return request_uuid, response_header_name
118
+ end
119
+
120
+ # 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.)
123
+ #
124
+ # @param [Hash] from_env as source
125
+ # @param [Hash] to_headers as target
126
+ #
127
+ # @return [Hash] updated headers
128
+ def self.copy_request_uuid(from_env, to_headers)
129
+ to_headers ||= {}
130
+ if from_env
131
+ 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
136
+ end
137
+ end
138
+ to_headers
139
+ end
140
+
141
+ # generates a token (nicer than an actual UUID for logging) but we continue
142
+ # to refer to it as the "request UUID" for legacy reasons.
143
+ #
144
+ # @return [String] a new token
145
+ def self.generate_request_uuid
146
+ Generator.generate
60
147
  end
61
- end
62
148
 
63
- end
149
+ end # RequestTracker
150
+ end # RightSupport::Rack
@@ -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.10.1 ruby lib
5
+ # stub: right_support 2.11.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "right_support"
9
- s.version = "2.10.1"
9
+ s.version = "2.11.2"
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-06-01"
14
+ s.date = "2016-07-08"
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 = [
@@ -58,6 +58,7 @@ Gem::Specification.new do |s|
58
58
  "lib/right_support/data/hash_tools.rb",
59
59
  "lib/right_support/data/mash.rb",
60
60
  "lib/right_support/data/serializer.rb",
61
+ "lib/right_support/data/token.rb",
61
62
  "lib/right_support/data/unknown_type.rb",
62
63
  "lib/right_support/data/uuid.rb",
63
64
  "lib/right_support/db.rb",
@@ -70,6 +71,7 @@ Gem::Specification.new do |s|
70
71
  "lib/right_support/log/mixin.rb",
71
72
  "lib/right_support/log/multiplexer.rb",
72
73
  "lib/right_support/log/null_logger.rb",
74
+ "lib/right_support/log/step_level_logger.rb",
73
75
  "lib/right_support/log/syslog/remote.rb",
74
76
  "lib/right_support/log/system_logger.rb",
75
77
  "lib/right_support/net.rb",
@@ -77,6 +79,7 @@ Gem::Specification.new do |s|
77
79
  "lib/right_support/net/dns.rb",
78
80
  "lib/right_support/net/http_client.rb",
79
81
  "lib/right_support/net/lb.rb",
82
+ "lib/right_support/net/lb/base.rb",
80
83
  "lib/right_support/net/lb/health_check.rb",
81
84
  "lib/right_support/net/lb/round_robin.rb",
82
85
  "lib/right_support/net/lb/sticky.rb",
@@ -107,6 +110,7 @@ Gem::Specification.new do |s|
107
110
  "spec/crypto/signed_hash_spec.rb",
108
111
  "spec/data/hash_tools_spec.rb",
109
112
  "spec/data/mash_spec.rb",
113
+ "spec/data/token_spec.rb",
110
114
  "spec/data/uuid_spec.rb",
111
115
  "spec/db/cassandra_model_part1_spec.rb",
112
116
  "spec/db/cassandra_model_part2_spec.rb",
@@ -122,13 +126,14 @@ Gem::Specification.new do |s|
122
126
  "spec/log/mixin_spec.rb",
123
127
  "spec/log/multiplexer_spec.rb",
124
128
  "spec/log/null_logger_spec.rb",
129
+ "spec/log/step_level_logger_spec.rb",
125
130
  "spec/log/system_logger_spec.rb",
126
131
  "spec/net/address_helper_spec.rb",
127
- "spec/net/balancing/health_check_spec.rb",
128
- "spec/net/balancing/round_robin_spec.rb",
129
- "spec/net/balancing/sticky_policy_spec.rb",
130
132
  "spec/net/dns_spec.rb",
131
133
  "spec/net/http_client_spec.rb",
134
+ "spec/net/lb/health_check_spec.rb",
135
+ "spec/net/lb/round_robin_spec.rb",
136
+ "spec/net/lb/sticky_spec.rb",
132
137
  "spec/net/request_balancer_spec.rb",
133
138
  "spec/net/s3_helper_spec.rb",
134
139
  "spec/net/ssl_spec.rb",
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Data::Token do
4
+
5
+ context :generate do
6
+ let(:match) { /^[0-9a-zA-Z]{13}$/ }
7
+
8
+ subject { described_class }
9
+
10
+ it 'generates UUIDs' do
11
+ seen = {}
12
+ 100.times do
13
+ actual = subject.generate
14
+ actual.should =~ match
15
+ seen[actual].should be_nil
16
+ seen[actual] = true
17
+ end
18
+ end
19
+ end
20
+
21
+ end