servus 1.0.2 → 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.
- checksums.yaml +4 -4
- data/lib/servus/base.rb +75 -1
- data/lib/servus/config.rb +29 -0
- data/lib/servus/support/logger.rb +29 -14
- data/lib/servus/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 10e4e061222d303e5d694d816afd1d0557eb6c4e7804dd5b454e48cddcdf0797
|
|
4
|
+
data.tar.gz: b6cdb1fcabe827988e6e8daf44d20d2da2649cf4b252d36ac68dd1f8f14db584
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61ab96b3ed1da09eb768ced10cbacc5cb6f4e8a8f4aead7ed57aa6f49dbdd016513dee1f1f2402a7a21504913b9ae5c0ef23298301fc398c3dcb6f224d2dcedc
|
|
7
|
+
data.tar.gz: 011eaaabc88c46604d60c9f82727e43f6152521a7f1cb64b483a1b912eb0a047eaf8607eab31b51f31a92df90333c4130af845f10a8d7263aa3bcfd3fda85f60
|
data/lib/servus/base.rb
CHANGED
|
@@ -115,6 +115,40 @@ module Servus
|
|
|
115
115
|
Response = Servus::Support::Response
|
|
116
116
|
Validator = Servus::Support::Validator
|
|
117
117
|
|
|
118
|
+
class << self
|
|
119
|
+
# Assigns the logger Servus writes this service's call, outcome, and
|
|
120
|
+
# error lines through. It covers this class and every service below it.
|
|
121
|
+
#
|
|
122
|
+
# Build the logger from {Servus.logger} so it keeps the appenders and
|
|
123
|
+
# formatting the app configured, rather than replacing them.
|
|
124
|
+
#
|
|
125
|
+
# Assigning +nil+ returns the class to the logger it inherits.
|
|
126
|
+
#
|
|
127
|
+
# @param logger [::Logger, nil]
|
|
128
|
+
#
|
|
129
|
+
# @example Giving an engine's services their own logger
|
|
130
|
+
# class Treasury::ApplicationService < Servus::Base
|
|
131
|
+
# self.logger = SemanticLogger[Treasury]
|
|
132
|
+
# end
|
|
133
|
+
attr_writer :logger
|
|
134
|
+
|
|
135
|
+
# The logger for this service class: its own, else the one it inherits,
|
|
136
|
+
# else {Servus.logger}.
|
|
137
|
+
#
|
|
138
|
+
# @return [::Logger]
|
|
139
|
+
def logger
|
|
140
|
+
return @logger if @logger
|
|
141
|
+
|
|
142
|
+
superclass <= Servus::Base ? superclass.logger : Servus.logger
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @return [::Logger] the logger of this service's class
|
|
147
|
+
# @see .logger
|
|
148
|
+
def logger
|
|
149
|
+
self.class.logger
|
|
150
|
+
end
|
|
151
|
+
|
|
118
152
|
# Creates a successful response with the provided data.
|
|
119
153
|
#
|
|
120
154
|
# Use this method to return successful results from your service's call method.
|
|
@@ -182,6 +216,46 @@ module Servus
|
|
|
182
216
|
Response.new(false, data, error)
|
|
183
217
|
end
|
|
184
218
|
|
|
219
|
+
# Ends the service with a failure response, from anywhere inside +call+.
|
|
220
|
+
#
|
|
221
|
+
# Builds the same response as {#failure} and returns it from the service as
|
|
222
|
+
# if +call+ had returned it, so it is logged, validated against the failure
|
|
223
|
+
# schema, and emits events exactly as a returned failure does. Unlike
|
|
224
|
+
# {#failure}, it does not need to be the value of +call+: a private check
|
|
225
|
+
# method can end the service itself, which keeps +call+ a list of steps.
|
|
226
|
+
#
|
|
227
|
+
# It is not an exception: +rescue+ does not see it, and +ensure+ still runs.
|
|
228
|
+
# A failure ends only the service that raised it — a nested service called
|
|
229
|
+
# from +call+ hands its failure back as a response, as it always has.
|
|
230
|
+
#
|
|
231
|
+
# @param message [String, nil] custom error message (uses error type's default if nil)
|
|
232
|
+
# @param data [Object, nil] optional structured data to attach to the failure response
|
|
233
|
+
# @param type [Class] error class to instantiate (must inherit from ServiceError)
|
|
234
|
+
# @return [void] never returns
|
|
235
|
+
#
|
|
236
|
+
# @example Ending the service from a check
|
|
237
|
+
# def call
|
|
238
|
+
# account_open!
|
|
239
|
+
# success(account.withdraw!(amount))
|
|
240
|
+
# end
|
|
241
|
+
#
|
|
242
|
+
# private
|
|
243
|
+
#
|
|
244
|
+
# def account_open!
|
|
245
|
+
# return if account.open?
|
|
246
|
+
#
|
|
247
|
+
# failure!("Account is closed", type: Servus::Support::Errors::UnprocessableEntityError)
|
|
248
|
+
# end
|
|
249
|
+
#
|
|
250
|
+
# @note Leaving an ActiveRecord +transaction+ (or +with_lock+) block this way
|
|
251
|
+
# commits it, as any non-exception exit does. Run checks before writing, or
|
|
252
|
+
# raise to roll back.
|
|
253
|
+
# @see #failure
|
|
254
|
+
# @see #error!
|
|
255
|
+
def failure!(message = nil, data: nil, type: Servus::Support::Errors::ServiceError)
|
|
256
|
+
throw :service_failure, failure(message, data:, type:)
|
|
257
|
+
end
|
|
258
|
+
|
|
185
259
|
# Logs an error and raises an exception, halting service execution.
|
|
186
260
|
#
|
|
187
261
|
# Use this method when you need to immediately halt execution with an exception
|
|
@@ -254,7 +328,7 @@ module Servus
|
|
|
254
328
|
|
|
255
329
|
# Wrap execution in catch block to handle guard failures
|
|
256
330
|
result = catch(:guard_failure) do
|
|
257
|
-
benchmark(**args) { instance.send(:call) }
|
|
331
|
+
benchmark(**args) { catch(:service_failure) { instance.send(:call) } }
|
|
258
332
|
end
|
|
259
333
|
|
|
260
334
|
if result.is_a?(Servus::Support::Errors::GuardError)
|
data/lib/servus/config.rb
CHANGED
|
@@ -76,6 +76,20 @@ module Servus
|
|
|
76
76
|
# @return [Boolean] true to require payload schemas, false to allow schema-less events
|
|
77
77
|
attr_accessor :require_event_payload_schema
|
|
78
78
|
|
|
79
|
+
# The logger Servus writes through.
|
|
80
|
+
#
|
|
81
|
+
# Left +nil+ (the default), Servus resolves one itself: `Rails.logger`
|
|
82
|
+
# when Rails is loaded, otherwise a `$stdout` logger. Read the resolved
|
|
83
|
+
# logger through {Servus.logger}.
|
|
84
|
+
#
|
|
85
|
+
# @return [::Logger, nil] the configured logger, or nil when none is set
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# Servus.configure do |config|
|
|
89
|
+
# config.logger = SemanticLogger[Servus]
|
|
90
|
+
# end
|
|
91
|
+
attr_accessor :logger
|
|
92
|
+
|
|
79
93
|
# The ordered list of routers that resolve invocations for events.
|
|
80
94
|
#
|
|
81
95
|
# The Bus iterates routers in order, collects invocations, deduplicates
|
|
@@ -181,6 +195,21 @@ module Servus
|
|
|
181
195
|
@config ||= Config.new
|
|
182
196
|
end
|
|
183
197
|
|
|
198
|
+
# The logger Servus writes through.
|
|
199
|
+
#
|
|
200
|
+
# Build a service's own logger from this one rather than replacing it, so
|
|
201
|
+
# the app's appenders and formatting are kept.
|
|
202
|
+
#
|
|
203
|
+
# @return [::Logger]
|
|
204
|
+
#
|
|
205
|
+
# @example A per-service logger that adds a tag (SemanticLogger)
|
|
206
|
+
# class Treasury::ApplicationService < Servus::Base
|
|
207
|
+
# self.logger = Servus.logger.tagged(engine: 'treasury')
|
|
208
|
+
# end
|
|
209
|
+
def self.logger
|
|
210
|
+
Servus::Support::Logger.logger
|
|
211
|
+
end
|
|
212
|
+
|
|
184
213
|
# Yields the configuration for modification.
|
|
185
214
|
#
|
|
186
215
|
# @yieldparam config [Servus::Config] the configuration object to modify
|
|
@@ -6,15 +6,16 @@ module Servus
|
|
|
6
6
|
module Support
|
|
7
7
|
# Logger class for logging service calls and results
|
|
8
8
|
class Logger
|
|
9
|
-
#
|
|
9
|
+
# The logger Servus writes through: the one set in {Servus::Config#logger},
|
|
10
|
+
# else `Rails.logger`, else a `$stdout` logger. Resolved on every read, so
|
|
11
|
+
# Servus follows Rails if the application swaps its logger after boot.
|
|
10
12
|
#
|
|
11
|
-
#
|
|
13
|
+
# Lines about a service go to that service's {Servus::Base.logger} instead.
|
|
14
|
+
#
|
|
15
|
+
# @return [::Logger] The logger instance
|
|
16
|
+
# @see Servus.logger
|
|
12
17
|
def self.logger
|
|
13
|
-
|
|
14
|
-
Rails.logger
|
|
15
|
-
else
|
|
16
|
-
@logger ||= ::Logger.new($stdout)
|
|
17
|
-
end
|
|
18
|
+
Servus.config.logger || rails_logger || stdout_logger
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
# Logs a call to a service.
|
|
@@ -27,7 +28,7 @@ module Servus
|
|
|
27
28
|
# @param args [Hash] The arguments passed to the service
|
|
28
29
|
def self.log_call(service_class, args)
|
|
29
30
|
rendered = log_parameters(args)
|
|
30
|
-
logger.info("Calling #{service_class.name} with args: #{rendered.inspect}")
|
|
31
|
+
service_class.logger.info("Calling #{service_class.name} with args: #{rendered.inspect}")
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
# Logs a result from a service
|
|
@@ -48,7 +49,7 @@ module Servus
|
|
|
48
49
|
# @param service_class [Class] The service class
|
|
49
50
|
# @param duration [Float] The duration of the service call
|
|
50
51
|
def self.log_success(service_class, duration)
|
|
51
|
-
logger.info("#{service_class.name} succeeded in #{duration.round(3)}s")
|
|
52
|
+
service_class.logger.info("#{service_class.name} succeeded in #{duration.round(3)}s")
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
# Logs a failed result from a service
|
|
@@ -57,7 +58,7 @@ module Servus
|
|
|
57
58
|
# @param error [Servus::Support::Errors::ServiceError] The error from the service
|
|
58
59
|
# @param duration [Float] The duration of the service call
|
|
59
60
|
def self.log_failure(service_class, error, duration)
|
|
60
|
-
logger.warn("#{service_class.name} failed in #{duration.round(3)}s with error: #{error}")
|
|
61
|
+
service_class.logger.warn("#{service_class.name} failed in #{duration.round(3)}s with error: #{error}")
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
# Logs a guard failure from a service
|
|
@@ -65,7 +66,7 @@ module Servus
|
|
|
65
66
|
# @param service_class [Class] The service class
|
|
66
67
|
# @param error [Servus::Support::Errors::GuardError] The guard error
|
|
67
68
|
def self.log_guard_failure(service_class, error)
|
|
68
|
-
logger.warn("#{service_class.name} guard failed: #{error.message}")
|
|
69
|
+
service_class.logger.warn("#{service_class.name} guard failed: #{error.message}")
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
# Logs an event emission with correlation ID and duration.
|
|
@@ -83,7 +84,7 @@ module Servus
|
|
|
83
84
|
# @param service_class [Class] The service class
|
|
84
85
|
# @param error [Servus::Support::Errors::ValidationError] The validation error
|
|
85
86
|
def self.log_validation_error(service_class, error)
|
|
86
|
-
logger.error("#{service_class.name} validation error: #{error.message}")
|
|
87
|
+
service_class.logger.error("#{service_class.name} validation error: #{error.message}")
|
|
87
88
|
end
|
|
88
89
|
|
|
89
90
|
# Logs an uncaught exception from a service
|
|
@@ -91,7 +92,9 @@ module Servus
|
|
|
91
92
|
# @param service_class [Class] The service class
|
|
92
93
|
# @param exception [Exception] The uncaught exception
|
|
93
94
|
def self.log_exception(service_class, exception)
|
|
94
|
-
logger.error(
|
|
95
|
+
service_class.logger.error(
|
|
96
|
+
"#{service_class.name} uncaught exception: #{exception.class} - #{exception.message}"
|
|
97
|
+
)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
100
|
# Logs that a registered schema fragment was replaced with a different value.
|
|
@@ -113,7 +116,7 @@ module Servus
|
|
|
113
116
|
# @param service_class [Class<Servus::Base>] The service whose job was not published
|
|
114
117
|
# @param const_name [String] The constant the application already owns
|
|
115
118
|
def self.log_job_class_conflict(service_class, const_name)
|
|
116
|
-
logger.warn(
|
|
119
|
+
service_class.logger.warn(
|
|
117
120
|
"#{service_class.name} did not publish its generated job as #{const_name} — that constant " \
|
|
118
121
|
'is already defined by the application. The application class is unchanged; ' \
|
|
119
122
|
"#{service_class.name}.call_async cannot be enqueued until the service or the job is renamed."
|
|
@@ -131,6 +134,18 @@ module Servus
|
|
|
131
134
|
Servus.config.parameter_filter.filter(params)
|
|
132
135
|
end
|
|
133
136
|
end
|
|
137
|
+
|
|
138
|
+
# @return [::Logger, nil] Rails' logger, when Rails is loaded and has one
|
|
139
|
+
def self.rails_logger
|
|
140
|
+
Rails.logger if defined?(Rails) && Rails.respond_to?(:logger)
|
|
141
|
+
end
|
|
142
|
+
private_class_method :rails_logger
|
|
143
|
+
|
|
144
|
+
# @return [::Logger] the fallback logger, built once
|
|
145
|
+
def self.stdout_logger
|
|
146
|
+
@stdout_logger ||= ::Logger.new($stdout)
|
|
147
|
+
end
|
|
148
|
+
private_class_method :stdout_logger
|
|
134
149
|
end
|
|
135
150
|
end
|
|
136
151
|
end
|
data/lib/servus/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: servus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Scholl
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|