right_support 2.10.1 → 2.11.2

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