istox_gruf 2.7.1
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 +7 -0
- data/CHANGELOG.md +246 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/README.md +544 -0
- data/bin/gruf +29 -0
- data/lib/gruf.rb +50 -0
- data/lib/gruf/cli/executor.rb +99 -0
- data/lib/gruf/client.rb +217 -0
- data/lib/gruf/client/error.rb +66 -0
- data/lib/gruf/client/error_factory.rb +105 -0
- data/lib/gruf/configuration.rb +137 -0
- data/lib/gruf/controllers/base.rb +102 -0
- data/lib/gruf/controllers/request.rb +121 -0
- data/lib/gruf/controllers/service_binder.rb +117 -0
- data/lib/gruf/error.rb +230 -0
- data/lib/gruf/errors/debug_info.rb +56 -0
- data/lib/gruf/errors/field.rb +56 -0
- data/lib/gruf/errors/helpers.rb +44 -0
- data/lib/gruf/hooks/base.rb +34 -0
- data/lib/gruf/hooks/executor.rb +47 -0
- data/lib/gruf/hooks/registry.rb +159 -0
- data/lib/gruf/instrumentable_grpc_server.rb +64 -0
- data/lib/gruf/integrations/rails/railtie.rb +10 -0
- data/lib/gruf/interceptors/active_record/connection_reset.rb +48 -0
- data/lib/gruf/interceptors/authentication/basic.rb +87 -0
- data/lib/gruf/interceptors/base.rb +53 -0
- data/lib/gruf/interceptors/client_interceptor.rb +125 -0
- data/lib/gruf/interceptors/context.rb +56 -0
- data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +61 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +41 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +43 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +48 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +225 -0
- data/lib/gruf/interceptors/instrumentation/statsd.rb +82 -0
- data/lib/gruf/interceptors/registry.rb +161 -0
- data/lib/gruf/interceptors/server_interceptor.rb +34 -0
- data/lib/gruf/interceptors/timer.rb +85 -0
- data/lib/gruf/loggable.rb +30 -0
- data/lib/gruf/logging.rb +53 -0
- data/lib/gruf/outbound/request_context.rb +71 -0
- data/lib/gruf/response.rb +71 -0
- data/lib/gruf/serializers/errors/base.rb +57 -0
- data/lib/gruf/serializers/errors/json.rb +43 -0
- data/lib/gruf/server.rb +294 -0
- data/lib/gruf/synchronized_client.rb +97 -0
- data/lib/gruf/timer.rb +78 -0
- data/lib/gruf/version.rb +20 -0
- metadata +203 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
##
|
21
|
+
# Base class for interception requests
|
22
|
+
#
|
23
|
+
class Base
|
24
|
+
# @var [Gruf::Controllers::Request] request
|
25
|
+
attr_reader :request
|
26
|
+
# @var [Gruf::Error] error
|
27
|
+
attr_reader :error
|
28
|
+
# @var [Hash] options
|
29
|
+
attr_reader :options
|
30
|
+
|
31
|
+
##
|
32
|
+
# @param [Gruf::Controllers::Request] request
|
33
|
+
# @param [Gruf::Error] error
|
34
|
+
# @param [Hash] options
|
35
|
+
#
|
36
|
+
def initialize(request, error, options = {})
|
37
|
+
@request = request
|
38
|
+
@error = error
|
39
|
+
@options = options || {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
require_relative 'client_interceptor'
|
46
|
+
require_relative 'server_interceptor'
|
47
|
+
require_relative 'context'
|
48
|
+
require_relative 'timer'
|
49
|
+
require_relative 'active_record/connection_reset'
|
50
|
+
require_relative 'authentication/basic'
|
51
|
+
require_relative 'instrumentation/statsd'
|
52
|
+
require_relative 'instrumentation/output_metadata_timer'
|
53
|
+
require_relative 'instrumentation/request_logging/interceptor'
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
##
|
21
|
+
# Intercepts outbound client requests to provide a unified interface and request context
|
22
|
+
#
|
23
|
+
class ClientInterceptor < GRPC::ClientInterceptor
|
24
|
+
include Gruf::Loggable
|
25
|
+
|
26
|
+
##
|
27
|
+
# Handles interception of outbound calls. Implement this in your derivative interceptor implementation.
|
28
|
+
#
|
29
|
+
# @param [Gruf::Outbound::RequestContext] request_context The context of the outbound request
|
30
|
+
# @return [Object] This method must return the response from the yielded block
|
31
|
+
#
|
32
|
+
def call(request_context:)
|
33
|
+
logger.debug "Logging client interceptor for request: #{request_context.method}"
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Call the interceptor from the request_response call
|
39
|
+
#
|
40
|
+
# @param [Object] request The request being sent
|
41
|
+
# @param [GRPC::ActiveCall] call The GRPC ActiveCall object
|
42
|
+
# @param [Method] method The method being called
|
43
|
+
# @param [Hash] metadata A hash of outgoing metadata
|
44
|
+
# @return [Object] The response message
|
45
|
+
#
|
46
|
+
def request_response(request: nil, call: nil, method: nil, metadata: nil)
|
47
|
+
rc = Gruf::Outbound::RequestContext.new(
|
48
|
+
type: :request_response,
|
49
|
+
requests: [request],
|
50
|
+
call: call,
|
51
|
+
method: method,
|
52
|
+
metadata: metadata
|
53
|
+
)
|
54
|
+
call(request_context: rc) do
|
55
|
+
yield
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Call the interceptor from the client_streamer call
|
61
|
+
#
|
62
|
+
# @param [Enumerable] requests An enumerable of requests being sent
|
63
|
+
# @param [GRPC::ActiveCall] call The GRPC ActiveCall object
|
64
|
+
# @param [Method] method The method being called
|
65
|
+
# @param [Hash] metadata A hash of outgoing metadata
|
66
|
+
# @return [Object] The response message
|
67
|
+
#
|
68
|
+
def client_streamer(requests: nil, call: nil, method: nil, metadata: nil)
|
69
|
+
rc = Gruf::Outbound::RequestContext.new(
|
70
|
+
type: :client_streamer,
|
71
|
+
requests: requests,
|
72
|
+
call: call,
|
73
|
+
method: method,
|
74
|
+
metadata: metadata
|
75
|
+
)
|
76
|
+
call(request_context: rc) do
|
77
|
+
yield
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Call the interceptor from the server_streamer call
|
83
|
+
#
|
84
|
+
# @param [Object] request The request being sent
|
85
|
+
# @param [GRPC::ActiveCall] call The GRPC ActiveCall object
|
86
|
+
# @param [Method] method The method being called
|
87
|
+
# @param [Hash] metadata A hash of outgoing metadata
|
88
|
+
# @return [Object] The response message
|
89
|
+
#
|
90
|
+
def server_streamer(request: nil, call: nil, method: nil, metadata: nil)
|
91
|
+
rc = Gruf::Outbound::RequestContext.new(
|
92
|
+
type: :server_streamer,
|
93
|
+
requests: [request],
|
94
|
+
call: call,
|
95
|
+
method: method,
|
96
|
+
metadata: metadata
|
97
|
+
)
|
98
|
+
call(request_context: rc) do
|
99
|
+
yield
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Call the interceptor from the bidi_streamer call
|
105
|
+
#
|
106
|
+
# @param [Enumerable] requests An enumerable of requests being sent
|
107
|
+
# @param [GRPC::ActiveCall] call The GRPC ActiveCall object
|
108
|
+
# @param [Method] method The method being called
|
109
|
+
# @param [Hash] metadata A hash of outgoing metadata
|
110
|
+
#
|
111
|
+
def bidi_streamer(requests: nil, call: nil, method: nil, metadata: nil)
|
112
|
+
rc = Gruf::Outbound::RequestContext.new(
|
113
|
+
type: :bidi_streamer,
|
114
|
+
requests: requests,
|
115
|
+
call: call,
|
116
|
+
method: method,
|
117
|
+
metadata: metadata
|
118
|
+
)
|
119
|
+
call(request_context: rc) do
|
120
|
+
yield
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
##
|
21
|
+
# Runs interceptors in a given request context
|
22
|
+
#
|
23
|
+
class Context
|
24
|
+
include Gruf::Loggable
|
25
|
+
|
26
|
+
##
|
27
|
+
# Initialize the interception context
|
28
|
+
#
|
29
|
+
# @param [Array<Gruf::Interceptors::ServerInterceptor>] interceptors
|
30
|
+
#
|
31
|
+
def initialize(interceptors = [])
|
32
|
+
@interceptors = interceptors
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Intercept the given request and run interceptors in a FIFO execution order
|
37
|
+
#
|
38
|
+
def intercept!
|
39
|
+
return yield if @interceptors.none?
|
40
|
+
|
41
|
+
i = @interceptors.pop
|
42
|
+
return yield unless i
|
43
|
+
|
44
|
+
logger.debug "Intercepting request with interceptor: #{i.class}"
|
45
|
+
|
46
|
+
i.call do
|
47
|
+
if @interceptors.any?
|
48
|
+
intercept! { yield }
|
49
|
+
else
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
module Instrumentation
|
21
|
+
##
|
22
|
+
# Appends the timer metadata to the active call output metadata
|
23
|
+
#
|
24
|
+
class OutputMetadataTimer < ::Gruf::Interceptors::ServerInterceptor
|
25
|
+
delegate :active_call, to: :request
|
26
|
+
|
27
|
+
##
|
28
|
+
# Handle the instrumented response. Note: this will only instrument timings of _successful_ responses.
|
29
|
+
#
|
30
|
+
def call
|
31
|
+
return unless active_call.respond_to?(:output_metadata)
|
32
|
+
|
33
|
+
result = Gruf::Interceptors::Timer.time do
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
output_metadata.update(metadata_key => result.elapsed.to_s)
|
37
|
+
|
38
|
+
raise result.message unless result.successful?
|
39
|
+
|
40
|
+
result.message
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
##
|
46
|
+
# @return [Symbol] The metadata key that the time result should be set to
|
47
|
+
#
|
48
|
+
def metadata_key
|
49
|
+
options.fetch(:metadata_key, :timer).to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# @return [Hash]
|
54
|
+
#
|
55
|
+
def output_metadata
|
56
|
+
active_call.output_metadata
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
module Instrumentation
|
21
|
+
module RequestLogging
|
22
|
+
module Formatters
|
23
|
+
##
|
24
|
+
# Base class for request log formatting
|
25
|
+
#
|
26
|
+
class Base
|
27
|
+
##
|
28
|
+
# Format the parameters into a loggable string. Must be implemented in every derivative class
|
29
|
+
#
|
30
|
+
# @param [Hash] _payload The incoming request payload
|
31
|
+
# @return [String] The formatted string
|
32
|
+
#
|
33
|
+
def format(_payload)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
require 'json'
|
19
|
+
|
20
|
+
module Gruf
|
21
|
+
module Interceptors
|
22
|
+
module Instrumentation
|
23
|
+
module RequestLogging
|
24
|
+
module Formatters
|
25
|
+
##
|
26
|
+
# Formats logging for gruf services into a Logstash-friendly JSON format
|
27
|
+
#
|
28
|
+
class Logstash < Base
|
29
|
+
##
|
30
|
+
# Format the request into a JSON-friendly payload
|
31
|
+
#
|
32
|
+
# @param [Hash] payload The incoming request payload
|
33
|
+
# @return [String] The JSON representation of the payload
|
34
|
+
#
|
35
|
+
def format(payload)
|
36
|
+
payload.merge(format: 'json').to_json
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
module Instrumentation
|
21
|
+
module RequestLogging
|
22
|
+
module Formatters
|
23
|
+
##
|
24
|
+
# Formats the request into plaintext logging
|
25
|
+
#
|
26
|
+
class Plain < Base
|
27
|
+
##
|
28
|
+
# Format the request by only outputting the message body and params (if set to log params)
|
29
|
+
#
|
30
|
+
# @param [Hash] payload The incoming request payload
|
31
|
+
# @return [String] The formatted string
|
32
|
+
#
|
33
|
+
def format(payload)
|
34
|
+
time = payload.fetch(:duration, 0)
|
35
|
+
grpc_status = payload.fetch(:grpc_status, 'GRPC::Ok')
|
36
|
+
route_key = payload.fetch(:method, 'unknown')
|
37
|
+
body = payload.fetch(:message, '')
|
38
|
+
|
39
|
+
msg = "[#{grpc_status}] (#{route_key}) [#{time}ms] #{body}".strip
|
40
|
+
msg += " Parameters: #{payload[:params].to_h}" if payload.key?(:params)
|
41
|
+
msg.strip
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
require 'socket'
|
19
|
+
require_relative 'formatters/base'
|
20
|
+
require_relative 'formatters/logstash'
|
21
|
+
require_relative 'formatters/plain'
|
22
|
+
|
23
|
+
module Gruf
|
24
|
+
module Interceptors
|
25
|
+
module Instrumentation
|
26
|
+
module RequestLogging
|
27
|
+
##
|
28
|
+
# Represents an error if the formatter does not extend the base formatter
|
29
|
+
#
|
30
|
+
class InvalidFormatterError < StandardError; end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Handles Rails-style request logging for gruf services.
|
34
|
+
#
|
35
|
+
# This is added by default to gruf servers; if you have `Gruf.use_default_hooks = false`, you can add it back
|
36
|
+
# manually by doing:
|
37
|
+
#
|
38
|
+
# Gruf::Instrumentation::Registry.add(:request_logging, Gruf::Instrumentation::RequestLogging::Hook)
|
39
|
+
#
|
40
|
+
class Interceptor < ::Gruf::Interceptors::ServerInterceptor
|
41
|
+
# Default mappings of codes to log levels...
|
42
|
+
LOG_LEVEL_MAP = {
|
43
|
+
'GRPC::Ok' => :debug,
|
44
|
+
'GRPC::InvalidArgument' => :debug,
|
45
|
+
'GRPC::NotFound' => :debug,
|
46
|
+
'GRPC::AlreadyExists' => :debug,
|
47
|
+
'GRPC::OutOfRange' => :debug,
|
48
|
+
'GRPC::Unauthenticated' => :warn,
|
49
|
+
'GRPC::PermissionDenied' => :warn,
|
50
|
+
'GRPC::Unknown' => :error,
|
51
|
+
'GRPC::Internal' => :error,
|
52
|
+
'GRPC::DataLoss' => :error,
|
53
|
+
'GRPC::FailedPrecondition' => :error,
|
54
|
+
'GRPC::Unavailable' => :error,
|
55
|
+
'GRPC::DeadlineExceeded' => :error,
|
56
|
+
'GRPC::Cancelled' => :error
|
57
|
+
}.freeze
|
58
|
+
|
59
|
+
###
|
60
|
+
# Log the request, sending it to the appropriate formatter
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
#
|
64
|
+
def call
|
65
|
+
return yield if options.fetch(:ignore_methods, []).include?(request.method_name)
|
66
|
+
|
67
|
+
result = Gruf::Interceptors::Timer.time do
|
68
|
+
yield
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fetch log level options and merge with default...
|
72
|
+
log_level_map = LOG_LEVEL_MAP.merge(options.fetch(:log_levels, {}))
|
73
|
+
|
74
|
+
# A result is either successful, or, some level of feedback handled in the else block...
|
75
|
+
if result.successful?
|
76
|
+
type = log_level_map['GRPC::Ok'] || :debug
|
77
|
+
status_name = 'GRPC::Ok'
|
78
|
+
else
|
79
|
+
type = log_level_map[result.message_class_name] || :error
|
80
|
+
status_name = result.message_class_name
|
81
|
+
end
|
82
|
+
|
83
|
+
payload = {}
|
84
|
+
if !request.client_streamer? && !request.bidi_streamer?
|
85
|
+
payload[:params] = sanitize(request.message.to_h) if options.fetch(:log_parameters, false)
|
86
|
+
payload[:message] = message(request, result)
|
87
|
+
payload[:status] = status(result.message, result.successful?)
|
88
|
+
else
|
89
|
+
payload[:params] = {}
|
90
|
+
payload[:message] = ''
|
91
|
+
payload[:status] = GRPC::Core::StatusCodes::OK
|
92
|
+
end
|
93
|
+
|
94
|
+
payload[:service] = request.service_key
|
95
|
+
payload[:method] = request.method_key
|
96
|
+
payload[:action] = request.method_key
|
97
|
+
payload[:grpc_status] = status_name
|
98
|
+
payload[:duration] = result.elapsed_rounded
|
99
|
+
payload[:thread_id] = Thread.current.object_id
|
100
|
+
payload[:time] = Time.now.to_s
|
101
|
+
payload[:host] = Socket.gethostname
|
102
|
+
|
103
|
+
logger.send(type, formatter.format(payload))
|
104
|
+
|
105
|
+
raise result.message unless result.successful?
|
106
|
+
|
107
|
+
result.message
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
##
|
113
|
+
# @return [::Gruf::Logger]
|
114
|
+
#
|
115
|
+
def logger
|
116
|
+
@logger ||= options.fetch(:logger, ::Gruf.logger)
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Return an appropriate log message dependent on the status
|
121
|
+
#
|
122
|
+
# @param [Gruf::Controllers::Request] request
|
123
|
+
# @param [Gruf::Interceptors::Timer::Result] result
|
124
|
+
# @return [String] The appropriate message body
|
125
|
+
#
|
126
|
+
def message(request, result)
|
127
|
+
return "[GRPC::Ok] (#{request.method_name})" if result.successful?
|
128
|
+
|
129
|
+
"[#{result.message_class_name}] (#{request.method_name}) #{result.message.message}"
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Return the proper status code for the response
|
134
|
+
#
|
135
|
+
# @param [Object] response The response object
|
136
|
+
# @param [Boolean] successful If the response was successful
|
137
|
+
# @return [Boolean] The proper status code
|
138
|
+
#
|
139
|
+
def status(response, successful)
|
140
|
+
successful ? GRPC::Core::StatusCodes::OK : response.code
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Determine the appropriate formatter for the request logging
|
145
|
+
#
|
146
|
+
# @return [Gruf::Instrumentation::RequestLogging::Formatters::Base]
|
147
|
+
#
|
148
|
+
def formatter
|
149
|
+
unless @formatter
|
150
|
+
fmt = options.fetch(:formatter, :plain)
|
151
|
+
@formatter = case fmt
|
152
|
+
when Symbol
|
153
|
+
prefix = 'Gruf::Interceptors::Instrumentation::RequestLogging::Formatters::'
|
154
|
+
klass = "#{prefix}#{fmt.to_s.capitalize}"
|
155
|
+
fmt = klass.constantize.new
|
156
|
+
when Class
|
157
|
+
fmt = fmt.new
|
158
|
+
else
|
159
|
+
fmt
|
160
|
+
end
|
161
|
+
raise InvalidFormatterError unless fmt.is_a?(Formatters::Base)
|
162
|
+
end
|
163
|
+
@formatter
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Redact any blacklisted params and return an updated hash
|
168
|
+
#
|
169
|
+
# @param [Hash] params The hash of parameters to sanitize
|
170
|
+
# @return [Hash] The sanitized params in hash form
|
171
|
+
#
|
172
|
+
def sanitize(params = {})
|
173
|
+
blacklists = options.fetch(:blacklist, []).map(&:to_s)
|
174
|
+
redacted_string = options.fetch(:redacted_string, 'REDACTED')
|
175
|
+
blacklists.each do |blacklist|
|
176
|
+
parts = blacklist.split('.').map(&:to_sym)
|
177
|
+
redact!(parts, 0, params, redacted_string)
|
178
|
+
end
|
179
|
+
params
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Helper method to recursively redact based on the black list
|
184
|
+
#
|
185
|
+
# @param [Array] parts The blacklist. ex. 'data.schema' -> [:data, :schema]
|
186
|
+
# @param [Integer] idx The current index of the blacklist
|
187
|
+
# @param [Hash] params The hash of parameters to sanitize
|
188
|
+
# @param [String] redacted_string The custom redact string
|
189
|
+
#
|
190
|
+
def redact!(parts = [], idx = 0, params = {}, redacted_string = 'REDACTED')
|
191
|
+
return unless parts.is_a?(Array) && params.is_a?(Hash)
|
192
|
+
|
193
|
+
return if idx >= parts.size || !params.key?(parts[idx])
|
194
|
+
|
195
|
+
if idx == parts.size - 1
|
196
|
+
if params[parts[idx]].is_a? Hash
|
197
|
+
hash_deep_redact!(params[parts[idx]], redacted_string)
|
198
|
+
else
|
199
|
+
params[parts[idx]] = redacted_string
|
200
|
+
end
|
201
|
+
return
|
202
|
+
end
|
203
|
+
redact!(parts, idx + 1, params[parts[idx]], redacted_string)
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Helper method to recursively redact the value of all hash keys
|
208
|
+
#
|
209
|
+
# @param [Hash] hash Part of the hash of parameters to sanitize
|
210
|
+
# @param [String] redacted_string The custom redact string
|
211
|
+
#
|
212
|
+
def hash_deep_redact!(hash, redacted_string)
|
213
|
+
hash.each_key do |key|
|
214
|
+
if hash[key].is_a? Hash
|
215
|
+
hash_deep_redact!(hash[key], redacted_string)
|
216
|
+
else
|
217
|
+
hash[key] = redacted_string
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|