gruf 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -19
  3. data/README.md +71 -21
  4. data/lib/gruf.rb +3 -0
  5. data/lib/gruf/authentication.rb +9 -1
  6. data/lib/gruf/authentication/base.rb +16 -8
  7. data/lib/gruf/authentication/basic.rb +6 -6
  8. data/lib/gruf/authentication/none.rb +2 -2
  9. data/lib/gruf/authentication/strategies.rb +20 -7
  10. data/lib/gruf/client.rb +73 -11
  11. data/lib/gruf/configuration.rb +9 -6
  12. data/lib/gruf/error.rb +49 -15
  13. data/lib/gruf/errors/debug_info.rb +10 -5
  14. data/lib/gruf/errors/field.rb +12 -5
  15. data/lib/gruf/hooks/active_record/connection_reset.rb +15 -1
  16. data/lib/gruf/hooks/base.rb +26 -4
  17. data/lib/gruf/hooks/registry.rb +15 -9
  18. data/lib/gruf/instrumentation/base.rb +66 -11
  19. data/lib/gruf/instrumentation/output_metadata_timer.rb +6 -3
  20. data/lib/gruf/instrumentation/registry.rb +22 -13
  21. data/lib/gruf/instrumentation/request_context.rb +66 -0
  22. data/lib/gruf/instrumentation/request_logging/formatters/base.rb +38 -0
  23. data/lib/gruf/instrumentation/request_logging/formatters/logstash.rb +40 -0
  24. data/lib/gruf/instrumentation/request_logging/formatters/plain.rb +45 -0
  25. data/lib/gruf/instrumentation/request_logging/hook.rb +145 -0
  26. data/lib/gruf/instrumentation/statsd.rb +19 -13
  27. data/lib/gruf/loggable.rb +4 -1
  28. data/lib/gruf/logging.rb +19 -0
  29. data/lib/gruf/response.rb +19 -4
  30. data/lib/gruf/serializers/errors/base.rb +10 -3
  31. data/lib/gruf/serializers/errors/json.rb +5 -2
  32. data/lib/gruf/server.rb +10 -2
  33. data/lib/gruf/service.rb +32 -22
  34. data/lib/gruf/timer.rb +26 -5
  35. data/lib/gruf/version.rb +1 -1
  36. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56cf6dec601362d024f7af23944ab121a4f2aff1
4
- data.tar.gz: 7e765e811c3c98b53faf823b168bbeca03f34cfc
3
+ metadata.gz: 0bf92528e6ff160d8d3a6997b8b00a44d86c46ac
4
+ data.tar.gz: 52bc1ac08fbf89488719d85c00bab0e741359c87
5
5
  SHA512:
6
- metadata.gz: 3b2bcfb022a963fbf10cb645e92a40691dc0cb4586b4579a4362a452123312a3341c8e72dd79f37f5d3615390acb1db718fc0cb5cf4d69ce0a6b479edd327584
7
- data.tar.gz: 534ef51f35af8c749c12455f8c50e63b9967c9f9b9cf9b012795913b51b2080a068cae7acb85235d6ce9ffaddf37172a82473cc559cf864b17b876ca96682d58
6
+ metadata.gz: 9caf811ac34255710d42a13a1a7f9066279d6c747a0e9b86fb3fff100aea31dc9889deb161acdfaa4b80907810f689c367851497ecd5399cde16a417df2bcf61
7
+ data.tar.gz: 86151ce87a7c7598e9a2a132b2d21fee05c10e621456766b78c3d11b94df942fb5d7d3e478333a0e84c8d6d7ebf0a223f1272be819dafbf2fd530bb007fd7983
@@ -1,47 +1,65 @@
1
1
  Changelog for the gruf gem. This includes internal history before the gem was made.
2
2
 
3
- h3. 1.1.0
3
+ ### Pending release
4
+
5
+
6
+
7
+ ### 1.2.0
8
+
9
+ - Instrumentation hooks now execute similarly to outer_around hooks; they can
10
+ now instrument failures
11
+ - Instrumentation hooks now pass a `RequestContext` object that contains information
12
+ about the incoming request, instead of relying on instance variables
13
+ - StatsD hook now sends success/failure metrics for endpoints
14
+ - Add ability to turn off sending exception message on uncaught exception.
15
+ - Add configuration to set the error message when an uncaught exception is
16
+ handled by gruf.
17
+ - Add a request logging hook for Rails-style request logging, with optional
18
+ parameter logging, blacklists, and formatter support
19
+ - Optimizations around Symbol casting within service calls
20
+
21
+ ### 1.1.0
4
22
 
5
23
  - Add the ability for call options to the client, which enables deadline setting
6
24
 
7
- h3. 1.0.0
25
+ ### 1.0.0
8
26
 
9
27
  - Bump gRPC to 1.4
10
28
 
11
- h3. 0.14.2
29
+ ### 0.14.2
12
30
 
13
31
  - Added rubocop style-guide checks
14
32
 
15
- h3. 0.14.1
33
+ ### 0.14.1
16
34
 
17
35
  - Updated license to MIT
18
36
 
19
- h3. 0.14.0
37
+ ### 0.14.0
20
38
 
21
39
  - Send gRPC status 16 (Unauthenticated) instead of 7 (PermissionDenied) when authentication fails
22
40
 
23
- h3. 0.13.0
41
+ ### 0.13.0
24
42
 
25
43
  - Move to gRPC 1.3.4
26
44
 
27
- h4. 0.12.2
45
+ ### 0.12.2
28
46
 
29
47
  - Add outer_around hook for wrapping the entire call chain
30
48
 
31
- h4. 0.12.1
49
+ ### 0.12.1
32
50
 
33
51
  - Add ability to specify a separate gRPC logger from the Gruf logger
34
52
 
35
- h3. 0.12.0
53
+ ### 0.12.0
36
54
 
37
55
  - Add ability to run multiple around hooks
38
56
  - Fix bug with error handling that caused error messages to repeat across streams
39
57
 
40
- h3. 0.11.5
58
+ ### 0.11.5
41
59
 
42
60
  - Fix issue with around hook
43
61
 
44
- h3. 0.11.4
62
+ ### 0.11.4
45
63
 
46
64
  - Add catchall rescue handler to capture uncaught exceptions and
47
65
  raise a GRPC::Internal error.
@@ -49,37 +67,37 @@ h3. 0.11.4
49
67
  will call Service.set_debug_info with the exception backtrace
50
68
  if an uncaught exception occurs.
51
69
 
52
- h3. 0.11.3
70
+ ### 0.11.3
53
71
 
54
72
  - Pass the service instance into hooks for reference
55
73
 
56
- h3. 0.11.2
74
+ ### 0.11.2
57
75
 
58
76
  - Ensure timer is measuring in milliseconds
59
77
 
60
- h3. 0.11.1
78
+ ### 0.11.1
61
79
 
62
80
  - Fix issue with interceptor and call signature
63
81
 
64
- h3. 0.11.0
82
+ ### 0.11.0
65
83
 
66
84
  - Add instrumentation layer and ability to register new instrumentors
67
85
  - Add out-of-the-box statsd instrumentation support
68
86
 
69
- h3. 0.10.0
87
+ ### 0.10.0
70
88
 
71
89
  - Rename Gruf::Endpoint to Gruf::Service
72
90
  - Make services auto-mount to server upon declaration
73
91
 
74
- h3. 0.9.2
92
+ ### 0.9.2
75
93
 
76
94
  - Support mount command on services to allow automatic setup on the server
77
95
  - Cleanup and consolidate binstub to prevent need for custom binstub per-app
78
96
 
79
- h3. 0.9.1
97
+ ### 0.9.1
80
98
 
81
99
  - Relax licensing to a clean BSD license
82
100
 
83
- h3. 0.9.0
101
+ ### 0.9.0
84
102
 
85
103
  - Initial public release
data/README.md CHANGED
@@ -32,6 +32,9 @@ Then in an initializer or before use:
32
32
  require 'gruf'
33
33
  ```
34
34
 
35
+ Make sure to review [UPGRADING.md](https://github.com/bigcommerce/gruf/blob/master/UPGRADING.md)
36
+ if you are upgrading gruf between minor or major versions.
37
+
35
38
  ### Client
36
39
 
37
40
  From there, you can instantiate a client given a stub service (say on an SslCertificates proto with a GetSslCertificate call):
@@ -42,7 +45,7 @@ require 'gruf'
42
45
  id = args[:id].to_i.presence || 1
43
46
 
44
47
  begin
45
- client = ::Gruf::Client.new(service: MyPackage::MyService)
48
+ client = ::Gruf::Client.new(service: ::Demo::ThingService)
46
49
  response = client.call(:GetMyThing, id: id)
47
50
  puts response.message.inspect
48
51
  rescue Gruf::Client::Error => e
@@ -72,40 +75,40 @@ syntax = "proto3";
72
75
 
73
76
  package demo;
74
77
 
75
- service Thing {
76
- rpc GetThing(GetThingReq) returns (GetSslCertificateResp) { }
78
+ service Jobs {
79
+ rpc GetJob(GetJobReq) returns (GetJobResp) { }
77
80
  }
78
81
 
79
- message ThingReq {
82
+ message GetJobReq {
80
83
  uint64 id = 1;
81
84
  }
82
85
 
83
- message ThingResp {
86
+ message GetJobResp {
84
87
  uint64 id = 1;
85
88
  string name = 2;
86
89
  }
87
90
  ```
88
91
 
89
- You'd have this handler in `/app/rpc/demo/thing_server.rb`
92
+ You'd have this handler in `/app/rpc/demo/job_server.rb`
90
93
 
91
94
  ```ruby
92
95
  module Demo
93
- class ThingServer < ::Demo::ThingService::Service
96
+ class JobServer < ::Demo::Jobs::Service
94
97
  include Gruf::Service
95
98
 
96
99
  ##
97
- # @param [Demo::GetThingReq] req
98
- # @param [GRPC::ActiveCall] call
99
- # @return [Demo::GetThingResp]
100
+ # @param [Demo::GetJobReq] req The incoming gRPC request object
101
+ # @param [GRPC::ActiveCall] call The gRPC active call instance
102
+ # @return [Demo::GetJobResp] The job response
100
103
  #
101
- def get_thing(req, call)
102
- ssl = Thing.find(req.id)
104
+ def get_job(req, call)
105
+ thing = Job.find(req.id)
103
106
 
104
- Demo::Things::GetThingResp.new(
105
- id: ssl.id
107
+ Demo::GetJobResp.new(
108
+ id: thing.id
106
109
  )
107
110
  rescue
108
- fail!(req, call, :not_found, :thing_not_found, "Failed to find Thing with ID: #{req.id}")
111
+ fail!(req, call, :not_found, :job_not_found, "Failed to find Job with ID: #{req.id}")
109
112
  end
110
113
  end
111
114
  end
@@ -277,23 +280,70 @@ The StatsD support is not enabled by default. To enable it, you'll want to do:
277
280
 
278
281
  ```ruby
279
282
  Gruf.configure do |c|
280
- c.instrumentation_options = {
281
- statsd: {
282
- client: ::Statsd.new('my.statsd.host', 8125),
283
- prefix: 'my_application_prefix.rpc'
284
- }
283
+ c.instrumentation_options[:statsd] = {
284
+ client: ::Statsd.new('my.statsd.host', 8125),
285
+ prefix: 'my_application_prefix.rpc'
285
286
  }
286
287
  end
287
288
  Gruf::Instrumentation::Registry.add(:statsd, Gruf::Instrumentation::Statsd)
288
289
  ```
289
290
 
290
- This will measure counts and timings for each endpoint.
291
+ This will measure counts and timings for each endpoint. Note: instrumentation hooks happen in LIFO order; they also
292
+ run similarly to an outer_around hook, executing _before_ authorization happens. Note: It's important that in your
293
+ instrumentors, you pass-through exceptions (such as `GRPC::BadStatus`); catching them in instrumentors will cause errors
294
+ upstream.
295
+
296
+ ### Request Logging
297
+
298
+ Gruf 1.2+ comes built with request logging out of the box; you'll get Rails-style logs with your gRPC calls:
299
+
300
+ ```
301
+ # plain
302
+ I, [2017-07-14T09:50:54.200506 #70571] INFO -- : [GRPC::Ok] (thing_service.get_thing) [0.348ms]
303
+ # logstash
304
+ I, [2017-07-14T09:51:03.299050 #70595] INFO -- : {"message":"[GRPC::Ok] (thing_service.get_thing) [0.372ms]","service":"thing_service","method":"thing_service.get_thing","grpc_status":"GRPC::Ok"}
305
+ ```
306
+
307
+ It supports formatters (including custom ones) that you can use to specify the formatting of the logging:
308
+
309
+ ```ruby
310
+ Gruf.configure do |c|
311
+ c.instrumentation_options[:request_logging] = {
312
+ formatter: :logstash
313
+ }
314
+ end
315
+ ```
316
+
317
+ It comes with a few more options as well:
318
+
319
+ | Option | Description | Default |
320
+ | ------ | ----------- | ------- |
321
+ | formatter | The formatter to use. By default `:plain` and `:logstash` are supported. | `:plain` |
322
+ | log_parameters | If set to true, will log parameters in the response | `false` |
323
+ | blacklist | An array of parameter key names to redact from logging | `[]` |
324
+ | redacted_string | The string to use for redacted parameters. | `REDACTED` |
325
+
326
+ It's important to maintain a safe blacklist should you decide to log parameters; gruf does no
327
+ parameter sanitization on its own. We also recommend blacklisting parameters that may contain
328
+ very large values (such as binary or json data).
291
329
 
292
330
  ### Custom Instrumentors
293
331
 
294
332
  Similar to hooks, simply extend the `Gruf::Instrumentation::Base` class, and implement the `call` method. See the StatsD
295
333
  instrumentor for an example.
296
334
 
335
+ ## Plugins
336
+
337
+ You can build your own hooks and middleware for gruf; here's a list of known open source gems for
338
+ gruf that you can use today:
339
+
340
+ * [gruf-zipkin](https://github.com/bigcommerce/gruf-zipkin) - Provides a [Zipkin](https://zipkin.io)
341
+ integration for gruf
342
+ * [gruf-circuit-breaker](https://github.com/bigcommerce/gruf-circuit-breaker) - Provides circuit breaker
343
+ support for gruf services
344
+ * [gruf-profiler](https://github.com/bigcommerce/gruf-profiler) - Profiles and provides memory usage
345
+ reports for gruf services
346
+
297
347
  ## License
298
348
 
299
349
  Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
@@ -33,6 +33,9 @@ require_relative 'gruf/error'
33
33
  require_relative 'gruf/client'
34
34
  require_relative 'gruf/server'
35
35
 
36
+ ##
37
+ # Initializes configuration of gruf core module
38
+ #
36
39
  module Gruf
37
40
  extend Configuration
38
41
  end
@@ -20,11 +20,19 @@ require_relative 'authentication/basic'
20
20
  require_relative 'authentication/none'
21
21
 
22
22
  module Gruf
23
+ ##
24
+ # Handles authentication for gruf services
25
+ #
23
26
  module Authentication
27
+ ##
28
+ # Custom error class for handling unauthorized requests
29
+ #
24
30
  class UnauthorizedError < StandardError; end
25
31
 
26
32
  ##
27
- # @param [GRPC::ActiveCall] call
33
+ # Verify a given gruf request
34
+ #
35
+ # @param [GRPC::ActiveCall] call The gRPC active call with marshalled data that is being executed
28
36
  # @param [Symbol] strategy The authentication strategy to use
29
37
  # @return [Boolean]
30
38
  #
@@ -17,16 +17,21 @@
17
17
  module Gruf
18
18
  module Authentication
19
19
  ##
20
- # Base interface for Authentication strategies
20
+ # Base interface for Authentication strategies. All derived strategies must define the `valid?` method.
21
21
  #
22
22
  class Base
23
23
  include Gruf::Loggable
24
24
 
25
- attr_reader :credentials, :options
25
+ # @return [String] The credentials sent in the request
26
+ attr_reader :credentials
27
+ # @return [Hash] A hash of authentication options
28
+ attr_reader :options
26
29
 
27
30
  ##
31
+ # Initialize the authentication middleware
28
32
  #
29
- # @param [String] credentials
33
+ # @param [String] credentials The credentials sent in the request
34
+ # @param [Hash] options A hash of authentication options
30
35
  #
31
36
  def initialize(credentials, options = {})
32
37
  opts = Gruf.authentication_options || {}
@@ -37,17 +42,20 @@ module Gruf
37
42
  ##
38
43
  # Verify the credentials. Helper class method.
39
44
  #
40
- # @param [GRPC::ActiveCall] call
41
- # @param [String] credentials
42
- # @param [Hash] options
45
+ # @param [GRPC::ActiveCall] call The gRPC active call for the given operation
46
+ # @param [String] credentials The credentials sent in the request
47
+ # @param [Hash] options A hash of authentication options
43
48
  #
44
49
  def self.verify(call, credentials = '', options = {})
45
50
  new(credentials, options).valid?(call)
46
51
  end
47
52
 
48
53
  ##
49
- # @param [GRPC::ActiveCall] _call
50
- # @return [Boolean]
54
+ # Abstract method that is required to be implemented in every derivative class.
55
+ # Return true if the call is authenticated, false if a permission denied error is to be sent.
56
+ #
57
+ # @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
58
+ # @return [Boolean] True if the call was authenticated
51
59
  #
52
60
  def valid?(_call)
53
61
  raise NotImplementedError
@@ -23,8 +23,8 @@ module Gruf
23
23
  #
24
24
  class Basic < Base
25
25
  ##
26
- # @param [GRPC::ActiveCall] _call
27
- # @return [Boolean]
26
+ # @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
27
+ # @return [Boolean] True if the basic authentication was valid
28
28
  #
29
29
  def valid?(_call)
30
30
  server_credentials.any? do |cred|
@@ -41,21 +41,21 @@ module Gruf
41
41
  private
42
42
 
43
43
  ##
44
- # @return [Array<Hash>]
44
+ # @return [Array<Hash>] An array of valid server credentials for this service
45
45
  #
46
46
  def server_credentials
47
47
  options.fetch(:credentials, [])
48
48
  end
49
49
 
50
50
  ##
51
- # @return [String]
51
+ # @return [String] The decoded request credentials
52
52
  #
53
53
  def request_credentials
54
54
  Base64.decode64(credentials.to_s.gsub('Basic ', '').strip)
55
55
  end
56
56
 
57
57
  ##
58
- # @return [String]
58
+ # @return [String] The decoded request username
59
59
  # @deprecated
60
60
  # :nocov:
61
61
  def request_username
@@ -64,7 +64,7 @@ module Gruf
64
64
  # :nocov:
65
65
 
66
66
  ##
67
- # @return [String]
67
+ # @return [String] The decoded request password
68
68
  #
69
69
  def request_password
70
70
  @request_password ||= request_credentials.split(':').last
@@ -21,8 +21,8 @@ module Gruf
21
21
  #
22
22
  class None < Base
23
23
  ##
24
- # @param [GRPC::ActiveCall] _call
25
- # @return [TrueClass]
24
+ # @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
25
+ # @return [TrueClass] Always true for this passthrough strategy.
26
26
  #
27
27
  def valid?(_call)
28
28
  true
@@ -20,15 +20,19 @@ module Gruf
20
20
  # Provides a modifiable repository of strategies for authentication
21
21
  #
22
22
  class Strategies
23
+ ##
24
+ # Error class that represents when a strategy does not extend the base class
25
+ #
23
26
  class StrategyDescendantError < StandardError; end
24
27
 
25
28
  class << self
26
29
  ##
27
30
  # Add an authentication strategy, either through a class or a block
28
31
  #
29
- # @param [String] name
30
- # @param [Class|NilClass] strategy
31
- # @return [Class]
32
+ # @param [String] name The vanity name of the strategy being loaded
33
+ # @param [Class|NilClass] strategy (Optional) The class that represents the strategy
34
+ # @param [Proc] &block If given, will attempt to build a strategy class from the base class with this block
35
+ # @return [Class] The loaded strategy
32
36
  #
33
37
  def add(name, strategy = nil, &block)
34
38
  base = Gruf::Authentication::Base
@@ -46,12 +50,15 @@ module Gruf
46
50
  ##
47
51
  # Return a strategy via a hash accessor syntax
48
52
  #
53
+ # @param [Symbol] label The name of the strategy
54
+ # @return [Gruf::Authentication::Base] The requested strategy
55
+ #
49
56
  def [](label)
50
57
  _strategies[label.to_sym]
51
58
  end
52
59
 
53
60
  ##
54
- # Iterate over each
61
+ # Iterate over each strategy and yield it to the caller
55
62
  #
56
63
  def each
57
64
  _strategies.each do |s|
@@ -60,21 +67,27 @@ module Gruf
60
67
  end
61
68
 
62
69
  ##
63
- # @return [Hash<Class>]
70
+ # Return the loaded strategies as a hash
71
+ #
72
+ # @return [Hash<Class>] A name/strategy pair of loaded strategies
64
73
  #
65
74
  def to_h
66
75
  _strategies
67
76
  end
68
77
 
69
78
  ##
70
- # @return [Boolean]
79
+ # Return if there are any loaded strategies
80
+ #
81
+ # @return [Boolean] True if there are loaded strategies
71
82
  #
72
83
  def any?
73
84
  to_h.keys.count > 0
74
85
  end
75
86
 
76
87
  ##
77
- # @return [Hash]
88
+ # Clear all given strategies
89
+ #
90
+ # @return [Hash] The newly empty hash
78
91
  #
79
92
  def clear
80
93
  @strategies = {}