gruf 1.1.0 → 1.2.0

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.
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 = {}