rage-rb 1.19.1 → 1.20.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Appraisals +19 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CODE_OF_CONDUCT.md +13 -17
  6. data/Gemfile +3 -0
  7. data/README.md +60 -63
  8. data/Rakefile +14 -0
  9. data/lib/rage/all.rb +3 -0
  10. data/lib/rage/cable/cable.rb +11 -7
  11. data/lib/rage/cable/channel.rb +6 -1
  12. data/lib/rage/cable/connection.rb +4 -0
  13. data/lib/rage/cable/router.rb +14 -9
  14. data/lib/rage/configuration.rb +235 -21
  15. data/lib/rage/controller/api.rb +50 -45
  16. data/lib/rage/cookies.rb +7 -23
  17. data/lib/rage/deferred/context.rb +30 -2
  18. data/lib/rage/deferred/deferred.rb +18 -6
  19. data/lib/rage/deferred/metadata.rb +39 -0
  20. data/lib/rage/deferred/middleware_chain.rb +67 -0
  21. data/lib/rage/deferred/task.rb +45 -17
  22. data/lib/rage/events/events.rb +3 -3
  23. data/lib/rage/events/subscriber.rb +36 -25
  24. data/lib/rage/fiber.rb +33 -31
  25. data/lib/rage/fiber_scheduler.rb +6 -2
  26. data/lib/rage/logger/logger.rb +7 -1
  27. data/lib/rage/middleware/body_finalizer.rb +14 -0
  28. data/lib/rage/middleware/cors.rb +12 -12
  29. data/lib/rage/middleware/request_id.rb +1 -1
  30. data/lib/rage/openapi/openapi.rb +2 -2
  31. data/lib/rage/response.rb +12 -7
  32. data/lib/rage/rspec.rb +17 -17
  33. data/lib/rage/setup.rb +2 -2
  34. data/lib/rage/telemetry/handler.rb +131 -0
  35. data/lib/rage/telemetry/spans/await_fiber.rb +50 -0
  36. data/lib/rage/telemetry/spans/broadcast_cable_stream.rb +50 -0
  37. data/lib/rage/telemetry/spans/create_websocket_connection.rb +50 -0
  38. data/lib/rage/telemetry/spans/dispatch_fiber.rb +48 -0
  39. data/lib/rage/telemetry/spans/enqueue_deferred_task.rb +52 -0
  40. data/lib/rage/telemetry/spans/process_cable_action.rb +56 -0
  41. data/lib/rage/telemetry/spans/process_cable_connection.rb +56 -0
  42. data/lib/rage/telemetry/spans/process_controller_action.rb +56 -0
  43. data/lib/rage/telemetry/spans/process_deferred_task.rb +54 -0
  44. data/lib/rage/telemetry/spans/process_event_subscriber.rb +54 -0
  45. data/lib/rage/telemetry/spans/publish_event.rb +54 -0
  46. data/lib/rage/telemetry/spans/spawn_fiber.rb +50 -0
  47. data/lib/rage/telemetry/telemetry.rb +121 -0
  48. data/lib/rage/telemetry/tracer.rb +97 -0
  49. data/lib/rage/version.rb +1 -1
  50. data/rage.gemspec +5 -4
  51. metadata +42 -9
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The **events.event.publish** span tracks the publishing of an event.
5
+ #
6
+ # This span is triggered whenever an event is published via {Rage::Events.publish Rage::Events.publish}.
7
+ # See {handle handle} for the list of arguments passed to handler methods.
8
+ #
9
+ # @see Rage::Telemetry::Handler Rage::Telemetry::Handler
10
+ #
11
+ class Rage::Telemetry::Spans::PublishEvent
12
+ class << self
13
+ # @private
14
+ def id
15
+ "events.event.publish"
16
+ end
17
+
18
+ # @private
19
+ def span_parameters
20
+ %w[event: context:]
21
+ end
22
+
23
+ # @private
24
+ def handler_arguments
25
+ {
26
+ name: '"Events.publish(#{event.class})"',
27
+ event: "event",
28
+ context: "context",
29
+ subscriber_classes: "Rage::Events.__get_subscribers(event.class)"
30
+ }
31
+ end
32
+
33
+ # @!parse [ruby]
34
+ # # @param id ["events.event.publish"] ID of the span
35
+ # # @param name [String] human-readable name of the operation (e.g., `Events.publish(UpdateRecommendations)`)
36
+ # # @param event [Object] the event being published
37
+ # # @param context [Object, nil] the additional context passed along with the event
38
+ # # @param subscriber_classes [Array<Rage::Events::Subscriber>] the list of subscriber classes that will receive the event
39
+ # # @yieldreturn [Rage::Telemetry::SpanResult]
40
+ # #
41
+ # # @example
42
+ # # class MyTelemetryHandler < Rage::Telemetry::Handler
43
+ # # handle "events.event.publish", with: :my_handler
44
+ # #
45
+ # # def my_handler(id:, name:, event:, context:, subscriber_classes:)
46
+ # # yield
47
+ # # end
48
+ # # end
49
+ # # @note Rage automatically detects which parameters your handler method accepts and only passes those parameters.
50
+ # # You can omit any of the parameters described here.
51
+ # def handle(id:, name:, event:, context:, subscriber_classes:)
52
+ # end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The **core.fiber.spawn** span tracks the scheduling and processing of application-level fibers created via {Fiber.schedule}.
5
+ #
6
+ # This span is started when a fiber begins processing and ends when the fiber has completed processing.
7
+ # See {handle handle} for the list of arguments passed to handler methods.
8
+ #
9
+ # @see Rage::Telemetry::Handler Rage::Telemetry::Handler
10
+ #
11
+ class Rage::Telemetry::Spans::SpawnFiber
12
+ class << self
13
+ # @private
14
+ def id
15
+ "core.fiber.spawn"
16
+ end
17
+
18
+ # @private
19
+ def span_parameters
20
+ %w[parent:]
21
+ end
22
+
23
+ # @private
24
+ def handler_arguments
25
+ {
26
+ name: '"Fiber.schedule"',
27
+ parent: "parent"
28
+ }
29
+ end
30
+
31
+ # @!parse [ruby]
32
+ # # @param id ["core.fiber.spawn"] ID of the span
33
+ # # @param name ["Fiber.schedule"] human-readable name of the operation
34
+ # # @param parent [Fiber] the parent fiber that the current fiber was scheduled from
35
+ # # @yieldreturn [Rage::Telemetry::SpanResult]
36
+ # #
37
+ # # @example
38
+ # # class MyTelemetryHandler < Rage::Telemetry::Handler
39
+ # # handle "core.fiber.spawn", with: :my_handler
40
+ # #
41
+ # # def my_handler(id:, name:, parent:)
42
+ # # yield
43
+ # # end
44
+ # # end
45
+ # # @note Rage automatically detects which parameters your handler method accepts and only passes those parameters.
46
+ # # You can omit any of the parameters described here.
47
+ # def handle(id:, name:, parent:)
48
+ # end
49
+ end
50
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The `Rage::Telemetry` component provides an interface to monitor various operations and events within the Rage framework.
5
+ #
6
+ # To start using telemetry, define and register custom handlers that will process the telemetry data.
7
+ #
8
+ # 1. **Define Handlers**: Create custom telemetry handlers by subclassing {Rage::Telemetry::Handler Rage::Telemetry::Handler} and implementing the desired logic for processing telemetry data.
9
+ #
10
+ # ```ruby
11
+ # class MyTelemetryHandler < Rage::Telemetry::Handler
12
+ # handle "controller.action.process", with: :log_action
13
+ #
14
+ # def log_action(controller:)
15
+ # puts "Processing action: #{controller.action_name}"
16
+ # yield
17
+ # end
18
+ # end
19
+ # ```
20
+ #
21
+ # 2. **Register Handlers**: Register your custom handlers in the Rage configuration.
22
+ #
23
+ # ```ruby
24
+ # Rage.configure do
25
+ # config.telemetry.use MyTelemetryHandler.new
26
+ # end
27
+ # ```
28
+ #
29
+ # @see Rage::Telemetry::Handler Rage::Telemetry::Handler
30
+ # @see Rage::Telemetry::Spans Rage::Telemetry::Spans
31
+ #
32
+ module Rage::Telemetry
33
+ # Returns the list of all available telemetry spans.
34
+ # @return [Array<String>] the list of available telemetry spans
35
+ def self.available_spans
36
+ __registry.keys
37
+ end
38
+
39
+ # @private
40
+ def self.__registry
41
+ @__registry ||= Spans.constants.each_with_object({}) do |const, memo|
42
+ span = Spans.const_get(const)
43
+ memo[span.id] = span
44
+ end
45
+ end
46
+
47
+ # @private
48
+ def self.tracer
49
+ @tracer ||= Tracer.new(__registry, Rage.config.telemetry.handlers_map)
50
+ end
51
+
52
+ # @private
53
+ def self.__setup
54
+ tracer.setup
55
+ end
56
+
57
+ ##
58
+ # The namespace contains all telemetry span definitions.
59
+ # Each span represents a specific operation or event within the framework that can be monitored and traced.
60
+ #
61
+ # Spans always pass two standard keyword arguments to their handlers:
62
+ #
63
+ # * `:id` - The unique identifier of the span.
64
+ # * `:name` - The human-readable name of the operation.
65
+ #
66
+ # Handlers can also receive additional context-specific keyword arguments as defined by each span.
67
+ #
68
+ # # Available Spans
69
+ #
70
+ # | ID | Reference | Description |
71
+ # | --- | --- |
72
+ # | `core.fiber.dispatch` | {DispatchFiber} | Wraps the scheduling and processing of system-level fibers created by the framework to process requests and deferred tasks |
73
+ # | `core.fiber.spawn` | {SpawnFiber} | Wraps the scheduling and processing of application-level fibers created via {Fiber.schedule} |
74
+ # | `core.fiber.await` | {AwaitFiber} | Wraps the processing of the {Fiber.await} calls |
75
+ # | `controller.action.process` | {ProcessControllerAction} | Wraps the processing of controller actions |
76
+ # | `cable.websocket.handshake` | {CreateWebsocketConnection} | Wraps the WebSocket connection handshake process |
77
+ # | `cable.connection.process` | {ProcessCableConnection} | Wraps the processing of connect actions in {Rage::Cable Rage::Cable} |
78
+ # | `cable.action.process` | {ProcessCableAction} | Wraps the processing of {Rage::Cable Rage::Cable} channel actions |
79
+ # | `cable.stream.broadcast` | {BroadcastCableStream} | Wraps the broadcasting of messages to {Rage::Cable Rage::Cable} streams |
80
+ # | `deferred.task.enqueue` | {EnqueueDeferredTask} | Wraps the enqueuing of deferred tasks |
81
+ # | `deferred.task.process` | {ProcessDeferredTask} | Wraps the processing of deferred tasks |
82
+ # | `events.event.publish` | {PublishEvent} | Wraps the publishing of events via {Rage::Events Rage::Events} |
83
+ # | `events.subscriber.process` | {ProcessEventSubscriber} | Wraps the processing of events by subscribers |
84
+ #
85
+ module Spans
86
+ end
87
+
88
+ # @private
89
+ HandlerRef = Data.define(:instance, :method_name)
90
+
91
+ # Contains the result of a span execution.
92
+ # @!attribute [r] exception
93
+ # @return [Exception, nil] The exception raised during the span execution, if any.
94
+ # @example
95
+ # class MyTelemetryHandler < Rage::Telemetry::Handler
96
+ # handle "controller.action.process", with: :monitor_500
97
+ #
98
+ # def monitor_500
99
+ # result = yield
100
+ #
101
+ # if result.error?
102
+ # MyObservabilitySDK.notify("500 Error Detected", result.exception)
103
+ # end
104
+ # end
105
+ # end
106
+ SpanResult = Struct.new(:exception) do
107
+ # Returns `true` if the span resulted in an error.
108
+ def error?
109
+ !!exception
110
+ end
111
+
112
+ # Returns `true` if the span executed successfully.
113
+ def success?
114
+ !error?
115
+ end
116
+ end
117
+ end
118
+
119
+ require_relative "tracer"
120
+ require_relative "handler"
121
+ Dir["#{__dir__}/spans/*.rb"].each { |span| require_relative span }
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rage::Telemetry::Tracer
4
+ DEFAULT_SPAN_RESULT = Rage::Telemetry::SpanResult.new.freeze
5
+ private_constant :DEFAULT_SPAN_RESULT
6
+
7
+ # @param spans_registry [Hash{String => Rage::Telemetry::Spans}]
8
+ # @param handlers_map [Hash{String => Array<Rage::Telemetry::HandlerRef>}]
9
+ def initialize(spans_registry, handlers_map)
10
+ @spans_registry = spans_registry
11
+ @handlers_map = handlers_map
12
+
13
+ @all_handler_refs = handlers_map.values.flatten
14
+
15
+ @spans_registry.each do |_, span|
16
+ setup_noop(span)
17
+ end
18
+ end
19
+
20
+ def setup
21
+ @handlers_map.each do |span_id, handler_refs|
22
+ setup_tracer(@spans_registry[span_id], handler_refs)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # @param span [Rage::Telemetry::Spans]
29
+ def setup_noop(span)
30
+ parameters = span.span_parameters.join(", ")
31
+
32
+ self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
33
+ def #{tracer_name(span.id)}(#{parameters})
34
+ yield
35
+ end
36
+ RUBY
37
+ end
38
+
39
+ # @param span [Rage::Telemetry::Spans]
40
+ # @param handler_refs [Array<Rage::Telemetry::HandlerRef>]
41
+ def setup_tracer(span, handler_refs)
42
+ yield_call = <<~RUBY
43
+ yield_called = true
44
+ yield_result = yield
45
+ span_result = DEFAULT_SPAN_RESULT
46
+ rescue Exception => e
47
+ yield_error = e
48
+ span_result = Rage::Telemetry::SpanResult.new(e).freeze
49
+ RUBY
50
+
51
+ calls_chain = handler_refs.reverse.inject(yield_call) do |memo, handler_ref|
52
+ handler_index = @all_handler_refs.index(handler_ref)
53
+
54
+ handler_method = handler_ref.instance.method(handler_ref.method_name)
55
+ handler_arguments = Rage::Internal.build_arguments(
56
+ handler_method,
57
+ { **span.handler_arguments, id: "\"#{span.id}\".freeze" }
58
+ )
59
+
60
+ <<~RUBY
61
+ @all_handler_refs[#{handler_index}].instance.#{handler_ref.method_name}(#{handler_arguments}) do
62
+ #{memo}
63
+ span_result
64
+ end
65
+ RUBY
66
+ end
67
+
68
+ parameters = span.span_parameters.join(", ")
69
+
70
+ self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
71
+ def #{tracer_name(span.id)}(#{parameters})
72
+ span_result = yield_called = yield_result = yield_error = nil
73
+
74
+ begin
75
+ #{calls_chain}
76
+ rescue Exception => e
77
+ Rage.logger.error("Telemetry handler failed with error \#{e}:\\n\#{e.backtrace.join("\\n")}")
78
+ end
79
+
80
+ unless yield_called
81
+ Rage.logger.warn("Telemetry handler didn't call `yield` when processing span '#{span.id}'\\n\#{caller.join("\\n")}")
82
+ yield_result = yield
83
+ end
84
+
85
+ if yield_error
86
+ raise yield_error
87
+ else
88
+ yield_result
89
+ end
90
+ end
91
+ RUBY
92
+ end
93
+
94
+ def tracer_name(span_id)
95
+ "span_#{span_id.gsub(".", "_")}"
96
+ end
97
+ end
data/lib/rage/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rage
4
- VERSION = "1.19.1"
4
+ VERSION = "1.20.0"
5
5
  end
data/rage.gemspec CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "rage-rb"
7
7
  spec.version = Rage::VERSION
8
8
  spec.authors = ["Roman Samoilov"]
9
- spec.email = ["rsamoi@icloud.com"]
9
+ spec.email = ["developers@rage-rb.dev"]
10
10
 
11
11
  spec.summary = "Fast web framework compatible with Rails."
12
- spec.homepage = "https://github.com/rage-rb/rage"
12
+ spec.homepage = "https://rage-rb.dev"
13
13
  spec.license = "MIT"
14
14
  spec.required_ruby_version = ">= 3.2.0"
15
15
 
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
21
  spec.files = Dir.chdir(__dir__) do
22
22
  `git ls-files -z`.split("\x0").reject do |f|
23
- (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor .rubocop])
23
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ gemfiles/ test/ spec/ features/ .git .circleci appveyor .rubocop])
24
24
  end
25
25
  end
26
26
  spec.bindir = "exe"
@@ -28,10 +28,11 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ["lib"]
29
29
 
30
30
  spec.add_dependency "thor", "~> 1.0"
31
- spec.add_dependency "rack", "~> 2.0"
31
+ spec.add_dependency "rack", "< 4"
32
32
  spec.add_dependency "rage-iodine", "~> 4.3"
33
33
  spec.add_dependency "zeitwerk", "~> 2.6"
34
34
  spec.add_dependency "rack-test", "~> 2.1"
35
35
  spec.add_dependency "rake", ">= 12.0"
36
36
  spec.add_dependency "logger"
37
+ spec.add_dependency "irb"
37
38
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rage-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.19.1
4
+ version: 1.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Samoilov
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-12-26 00:00:00.000000000 Z
10
+ date: 2026-01-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -27,16 +27,16 @@ dependencies:
27
27
  name: rack
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '2.0'
32
+ version: '4'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - "<"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.0'
39
+ version: '4'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rage-iodine
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -107,8 +107,22 @@ dependencies:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: irb
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
110
124
  email:
111
- - rsamoi@icloud.com
125
+ - developers@rage-rb.dev
112
126
  executables:
113
127
  - rage
114
128
  extensions: []
@@ -117,6 +131,7 @@ files:
117
131
  - ".rspec"
118
132
  - ".yardopts"
119
133
  - ARCHITECTURE.md
134
+ - Appraisals
120
135
  - CHANGELOG.md
121
136
  - CODE_OF_CONDUCT.md
122
137
  - Gemfile
@@ -146,6 +161,8 @@ files:
146
161
  - lib/rage/deferred/backends/nil.rb
147
162
  - lib/rage/deferred/context.rb
148
163
  - lib/rage/deferred/deferred.rb
164
+ - lib/rage/deferred/metadata.rb
165
+ - lib/rage/deferred/middleware_chain.rb
149
166
  - lib/rage/deferred/proxy.rb
150
167
  - lib/rage/deferred/queue.rb
151
168
  - lib/rage/deferred/task.rb
@@ -163,6 +180,7 @@ files:
163
180
  - lib/rage/logger/json_formatter.rb
164
181
  - lib/rage/logger/logger.rb
165
182
  - lib/rage/logger/text_formatter.rb
183
+ - lib/rage/middleware/body_finalizer.rb
166
184
  - lib/rage/middleware/cors.rb
167
185
  - lib/rage/middleware/fiber_wrapper.rb
168
186
  - lib/rage/middleware/origin_validator.rb
@@ -204,6 +222,21 @@ files:
204
222
  - lib/rage/setup.rb
205
223
  - lib/rage/sidekiq_session.rb
206
224
  - lib/rage/tasks.rb
225
+ - lib/rage/telemetry/handler.rb
226
+ - lib/rage/telemetry/spans/await_fiber.rb
227
+ - lib/rage/telemetry/spans/broadcast_cable_stream.rb
228
+ - lib/rage/telemetry/spans/create_websocket_connection.rb
229
+ - lib/rage/telemetry/spans/dispatch_fiber.rb
230
+ - lib/rage/telemetry/spans/enqueue_deferred_task.rb
231
+ - lib/rage/telemetry/spans/process_cable_action.rb
232
+ - lib/rage/telemetry/spans/process_cable_connection.rb
233
+ - lib/rage/telemetry/spans/process_controller_action.rb
234
+ - lib/rage/telemetry/spans/process_deferred_task.rb
235
+ - lib/rage/telemetry/spans/process_event_subscriber.rb
236
+ - lib/rage/telemetry/spans/publish_event.rb
237
+ - lib/rage/telemetry/spans/spawn_fiber.rb
238
+ - lib/rage/telemetry/telemetry.rb
239
+ - lib/rage/telemetry/tracer.rb
207
240
  - lib/rage/templates/Gemfile
208
241
  - lib/rage/templates/Rakefile
209
242
  - lib/rage/templates/app-controllers-application_controller.rb
@@ -229,11 +262,11 @@ files:
229
262
  - lib/rage/uploaded_file.rb
230
263
  - lib/rage/version.rb
231
264
  - rage.gemspec
232
- homepage: https://github.com/rage-rb/rage
265
+ homepage: https://rage-rb.dev
233
266
  licenses:
234
267
  - MIT
235
268
  metadata:
236
- homepage_uri: https://github.com/rage-rb/rage
269
+ homepage_uri: https://rage-rb.dev
237
270
  source_code_uri: https://github.com/rage-rb/rage
238
271
  rdoc_options: []
239
272
  require_paths: