rage-rb 1.17.1 → 1.19.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.
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rage::LogProcessor
4
+ DEFAULT_LOG_CONTEXT = {}.freeze
5
+ private_constant :DEFAULT_LOG_CONTEXT
6
+
7
+ attr_reader :dynamic_tags, :dynamic_context
8
+
9
+ def initialize
10
+ rebuild!
11
+ end
12
+
13
+ def add_custom_context(context_objects)
14
+ @custom_context = context_objects
15
+ rebuild!
16
+ end
17
+
18
+ def add_custom_tags(tag_objects)
19
+ @custom_tags = tag_objects
20
+ rebuild!
21
+ end
22
+
23
+ def finalize_request_logger(env, response, params)
24
+ logger = Thread.current[:rage_logger]
25
+
26
+ duration = (
27
+ (Process.clock_gettime(Process::CLOCK_MONOTONIC) - logger[:request_start]) * 1000
28
+ ).round(2)
29
+
30
+ logger[:final] = { env:, params:, response:, duration: }
31
+ Rage.logger.info(nil)
32
+ logger[:final] = nil
33
+ end
34
+
35
+ private
36
+
37
+ def build_static_tags
38
+ calls = @custom_tags&.filter_map&.with_index do |tag_object, i|
39
+ if tag_object.is_a?(String)
40
+ "@custom_tags[#{i}]"
41
+ elsif tag_object.respond_to?(:to_str)
42
+ "@custom_tags[#{i}].to_str"
43
+ end
44
+ end
45
+
46
+ unless calls&.any?
47
+ return "[env[\"rage.request_id\"]]"
48
+ end
49
+
50
+ "[env[\"rage.request_id\"], #{calls.join(", ")}]"
51
+ end
52
+
53
+ def build_static_context
54
+ calls = @custom_context&.filter_map&.with_index do |context_object, i|
55
+ "@custom_context[#{i}]" if context_object.is_a?(Hash)
56
+ end
57
+
58
+ unless calls&.any?
59
+ return "DEFAULT_LOG_CONTEXT"
60
+ end
61
+
62
+ if calls.one?
63
+ calls[0]
64
+ else
65
+ "{}.merge!(#{calls.join(", ")})"
66
+ end
67
+ end
68
+
69
+ def build_dynamic_tags_proc
70
+ calls = @custom_tags&.filter_map&.with_index do |tag_object, i|
71
+ if tag_object.respond_to?(:call)
72
+ "*@custom_tags[#{i}].call"
73
+ end
74
+ end
75
+
76
+ return unless calls&.any?
77
+
78
+ eval <<~RUBY
79
+ ->() do
80
+ [#{calls.join(", ")}]
81
+ end
82
+ RUBY
83
+ end
84
+
85
+ def build_dynamic_context_proc
86
+ calls = @custom_context&.filter_map&.with_index do |context_object, i|
87
+ if context_object.respond_to?(:call)
88
+ "@custom_context[#{i}].call || DEFAULT_LOG_CONTEXT"
89
+ end
90
+ end
91
+
92
+ return unless calls&.any?
93
+
94
+ eval <<~RUBY
95
+ ->() do
96
+ {}.merge!(#{calls.join(", ")})
97
+ end
98
+ RUBY
99
+ end
100
+
101
+ def rebuild!
102
+ @dynamic_tags = build_dynamic_tags_proc
103
+ @dynamic_context = build_dynamic_context_proc
104
+
105
+ self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
106
+ def init_request_logger(env)
107
+ env["rage.request_id"] ||= Iodine::Rack::Utils.gen_request_tag
108
+
109
+ Thread.current[:rage_logger] = {
110
+ tags: #{build_static_tags},
111
+ context: #{build_static_context},
112
+ request_start: Process.clock_gettime(Process::CLOCK_MONOTONIC)
113
+ }
114
+ end
115
+ RUBY
116
+ end
117
+ end
@@ -1,3 +1,22 @@
1
+ ##
2
+ # JSON formatter for Rage logger.
3
+ #
4
+ # Produces log lines in JSON format, including tags, context, and request details if available.
5
+ #
6
+ # Example log line:
7
+ #
8
+ # ```json
9
+ # {"tags":["fecbba0735355738"],"timestamp":"2025-10-19T11:12:56+00:00","pid":"1825","level":"info","method":"GET","path":"/api/v1/resource","controller":"Api::V1::ResourceController","action":"index","status":200,"duration":0.15}
10
+ # ```
11
+ #
12
+ # Use {Rage.configure Rage.configure} to set the formatter:
13
+ #
14
+ # ```ruby
15
+ # Rage.configure do |config|
16
+ # config.log_formatter = Rage::JSONFormatter.new
17
+ # end
18
+ # ```
19
+ #
1
20
  class Rage::JSONFormatter
2
21
  def initialize
3
22
  @pid = Process.pid.to_s
@@ -15,32 +34,32 @@ class Rage::JSONFormatter
15
34
  context.each { |k, v| context_msg << "\"#{k}\":#{v.to_json}," }
16
35
  end
17
36
 
18
- if (final = logger[:final])
19
- params, env = final[:params], final[:env]
20
- if params && params[:controller]
21
- return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",\"controller\":\"#{Rage::Router::Util.path_to_name(params[:controller])}\",\"action\":\"#{params[:action]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
22
- else
23
- # no controller/action keys are written if there are no params
24
- return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
25
- end
26
- end
27
-
28
- if tags.length == 1
29
- tags_msg = "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
37
+ tags_msg = if tags.length == 1
38
+ "{\"tags\":[\"#{tags[0]}\"],"
30
39
  elsif tags.length == 2
31
- tags_msg = "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
40
+ "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\"],"
32
41
  elsif tags.length == 0
33
- tags_msg = "{\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
42
+ "{"
34
43
  else
35
- tags_msg = "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\""
44
+ msg = "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\""
36
45
  i = 2
37
46
  while i < tags.length
38
- tags_msg << ",\"#{tags[i]}\""
47
+ msg << ",\"#{tags[i]}\""
39
48
  i += 1
40
49
  end
41
- tags_msg << "],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
50
+ msg << "],"
51
+ end
52
+
53
+ if (final = logger[:final])
54
+ params, env = final[:params], final[:env]
55
+ if params && params[:controller]
56
+ return "#{tags_msg}\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",\"controller\":\"#{Rage::Router::Util.path_to_name(params[:controller])}\",\"action\":\"#{params[:action]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
57
+ else
58
+ # no controller/action keys are written if there are no params
59
+ return "#{tags_msg}\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
60
+ end
42
61
  end
43
62
 
44
- "#{tags_msg},#{context_msg}\"message\":\"#{message}\"}\n"
63
+ "#{tags_msg}\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\",#{context_msg}\"message\":\"#{message}\"}\n"
45
64
  end
46
65
  end
@@ -51,18 +51,30 @@ require "logger"
51
51
  # end
52
52
  # ```
53
53
  class Rage::Logger
54
+ # @private
54
55
  METHODS_MAP = {
55
- "debug" => Logger::DEBUG,
56
- "info" => Logger::INFO,
57
- "warn" => Logger::WARN,
58
- "error" => Logger::ERROR,
59
- "fatal" => Logger::FATAL,
60
- "unknown" => Logger::UNKNOWN
56
+ debug: Logger::DEBUG,
57
+ info: Logger::INFO,
58
+ warn: Logger::WARN,
59
+ error: Logger::ERROR,
60
+ fatal: Logger::FATAL,
61
+ unknown: Logger::UNKNOWN
61
62
  }
62
- private_constant :METHODS_MAP
63
63
 
64
64
  attr_reader :level, :formatter
65
65
 
66
+ # @private
67
+ attr_reader :dynamic_tags, :dynamic_context
68
+
69
+ # @private
70
+ attr_reader :external_logger
71
+
72
+ # @private
73
+ module External
74
+ Static = Data.define(:wrapped)
75
+ Dynamic = Data.define(:wrapped)
76
+ end
77
+
66
78
  # Create a new logger.
67
79
  #
68
80
  # @param log [Object] a filename (`String`), IO object (typically `STDOUT`, `STDERR`, or an open file), `nil` (it writes nothing) or `File::NULL` (same as `nil`)
@@ -73,7 +85,10 @@ class Rage::Logger
73
85
  # @param shift_period_suffix [String] the log file suffix format for daily, weekly or monthly rotation
74
86
  # @param binmode sets whether the logger writes in binary mode
75
87
  def initialize(log, level: Logger::DEBUG, formatter: Rage::TextFormatter.new, shift_age: 0, shift_size: 104857600, shift_period_suffix: "%Y%m%d", binmode: false)
76
- @logdev = if log && log != File::NULL
88
+ @logdev = if log.class.name.start_with?("Rage::Logger::External::")
89
+ @external_logger = log
90
+ Logger::LogDevice.new(STDERR)
91
+ elsif log && log != File::NULL
77
92
  Logger::LogDevice.new(log, shift_age:, shift_size:, shift_period_suffix:, binmode:)
78
93
  end
79
94
 
@@ -83,17 +98,38 @@ class Rage::Logger
83
98
 
84
99
  @formatter = formatter
85
100
  @level = @logdev ? level : Logger::UNKNOWN
86
- define_log_methods
101
+ rebuild!
87
102
  end
88
103
 
104
+ # Set the logging severity threshold.
105
+ # @param level [Integer] logging severity threshold
89
106
  def level=(level)
90
107
  @level = level
91
- define_log_methods
108
+ rebuild!
92
109
  end
93
110
 
111
+ # Set the logging formatter.
112
+ # @param formatter [#call] logging formatter
94
113
  def formatter=(formatter)
95
114
  @formatter = formatter
96
- define_log_methods
115
+ rebuild!
116
+ end
117
+
118
+ # Write the given `msg` to the log with no formatting.
119
+ def <<(msg)
120
+ @logdev&.write(msg)
121
+ end
122
+
123
+ # @private
124
+ def dynamic_tags=(dynamic_tags)
125
+ @dynamic_tags = dynamic_tags
126
+ rebuild!
127
+ end
128
+
129
+ # @private
130
+ def dynamic_context=(dynamic_context)
131
+ @dynamic_context = dynamic_context
132
+ rebuild!
97
133
  end
98
134
 
99
135
  # Add custom keys to an entry.
@@ -119,29 +155,37 @@ class Rage::Logger
119
155
 
120
156
  # Add a custom tag to an entry.
121
157
  #
122
- # @param tag [String] the tag to add to an entry
158
+ # @param tags [String] the tag to add to an entry
123
159
  # @example
124
160
  # Rage.logger.tagged("ApiCall") do
125
161
  # Rage.logger.info "success"
126
162
  # end
127
- def tagged(tag)
128
- (Thread.current[:rage_logger] ||= { tags: [], context: {} })[:tags] << tag
163
+ def tagged(*tags)
164
+ old_tags = (Thread.current[:rage_logger] ||= { tags: [], context: {} })[:tags]
165
+ Thread.current[:rage_logger][:tags] = old_tags + tags
129
166
  yield(self)
130
167
  ensure
131
- Thread.current[:rage_logger][:tags].pop
168
+ Thread.current[:rage_logger][:tags] = old_tags
132
169
  end
133
170
 
134
171
  alias_method :with_tag, :tagged
135
172
 
173
+ # Check if the debug level is enabled.
136
174
  def debug? = @level <= Logger::DEBUG
175
+ # Check if the error level is enabled.
137
176
  def error? = @level <= Logger::ERROR
177
+ # Check if the fatal level is enabled.
138
178
  def fatal? = @level <= Logger::FATAL
179
+ # Check if the info level is enabled.
139
180
  def info? = @level <= Logger::INFO
181
+ # Check if the warn level is enabled.
140
182
  def warn? = @level <= Logger::WARN
183
+ # Check if the unknown level is enabled.
184
+ def unknown? = @level <= Logger::UNKNOWN
141
185
 
142
186
  private
143
187
 
144
- def define_log_methods
188
+ def rebuild!
145
189
  methods = METHODS_MAP.map do |level_name, level_val|
146
190
  if @logdev.nil? || level_val < @level
147
191
  # logging is disabled or the log level is higher than the current one
@@ -150,25 +194,55 @@ class Rage::Logger
150
194
  false
151
195
  end
152
196
  RUBY
153
- elsif @formatter.class.name.start_with?("Rage::")
154
- # the call was made from within the application and a built-in formatter is used;
155
- # in such case we use the `gen_timestamp` method which is much faster than `Time.now.strftime`;
156
- # it's not a standard approach however, so it's used with built-in formatters only
157
- <<-RUBY
197
+ elsif @external_logger.is_a?(External::Static)
198
+ # an object that implements Ruby's Logger interface is used as a logger
199
+ <<~RUBY
158
200
  def #{level_name}(msg = nil)
159
- @logdev.write(
160
- @formatter.call("#{level_name}".freeze, Iodine::Rack::Utils.gen_timestamp, nil, msg || yield)
161
- )
201
+ #{with_dynamic_tags_and_context do
202
+ <<~RUBY
203
+ @external_logger.wrapped.#{level_name}(
204
+ #{build_formatter_call(level_name, level_val)}
205
+ )
206
+ RUBY
207
+ end}
208
+ end
209
+ RUBY
210
+ elsif @external_logger.is_a?(External::Dynamic)
211
+ # a callable object is used as a logger
212
+ call_method = if @external_logger.wrapped.is_a?(Proc)
213
+ @external_logger.wrapped
214
+ else
215
+ @external_logger.wrapped.method(:call)
216
+ end
217
+
218
+ parameters = Rage::Internal.build_arguments(call_method, {
219
+ severity: ":#{level_name}",
220
+ tags: "logger[:tags].freeze",
221
+ context: "logger[:context].freeze",
222
+ message: "block_given? ? yield : msg",
223
+ request_info: "logger[:final].freeze"
224
+ })
225
+
226
+ <<~RUBY
227
+ def #{level_name}(msg = nil)
228
+ #{with_dynamic_tags_and_context do
229
+ <<~RUBY
230
+ logger = Thread.current[:rage_logger] || { tags: [], context: {} }
231
+ @external_logger.wrapped.call(#{parameters})
232
+ RUBY
233
+ end}
162
234
  end
163
235
  RUBY
164
236
  else
165
- # the call was made from within the application and a custom formatter is used;
166
- # stick to the standard approach of using one of the Log Level constants as sevetiry and `Time.now` as time
167
- <<-RUBY
237
+ <<~RUBY
168
238
  def #{level_name}(msg = nil)
169
- @logdev.write(
170
- @formatter.call(#{level_val}, Time.now, nil, msg || yield)
171
- )
239
+ #{with_dynamic_tags_and_context do
240
+ <<~RUBY
241
+ @logdev.write(
242
+ #{build_formatter_call(level_name, level_val)}
243
+ )
244
+ RUBY
245
+ end}
172
246
  end
173
247
  RUBY
174
248
  end
@@ -176,4 +250,36 @@ class Rage::Logger
176
250
 
177
251
  self.class.class_eval(methods.join("\n"))
178
252
  end
253
+
254
+ def build_formatter_call(level_name, level_val)
255
+ if @formatter.class.name.start_with?("Rage::")
256
+ # a built-in formatter is used - use the `gen_timestamp` method which is much faster than `Time.now.strftime`;
257
+ # it's not a standard approach however, so it's used with built-in formatters only
258
+ <<~RUBY
259
+ @formatter.call("#{level_name}".freeze, Iodine::Rack::Utils.gen_timestamp, nil, block_given? ? yield : msg)
260
+ RUBY
261
+ else
262
+ # a custom formatter is used - stick to the standard approach of using one of the
263
+ # Log Level constants as severity and `Time.now` as time
264
+ <<~RUBY
265
+ @formatter.call(#{level_val}, Time.now, nil, block_given? ? yield : msg)
266
+ RUBY
267
+ end
268
+ end
269
+
270
+ def with_dynamic_tags_and_context
271
+ do_calls, end_calls = [], []
272
+
273
+ if @dynamic_tags
274
+ do_calls << "tagged(*@dynamic_tags.call) do"
275
+ end_calls << "end"
276
+ end
277
+
278
+ if @dynamic_context
279
+ do_calls << "with_context(@dynamic_context.call) do"
280
+ end_calls << "end"
281
+ end
282
+
283
+ "#{do_calls.join("\n")}\n#{yield}\n#{end_calls.join("\n")}"
284
+ end
179
285
  end
@@ -1,3 +1,20 @@
1
+ ##
2
+ # Text formatter for Rage logger.
3
+ #
4
+ # Example log line:
5
+ #
6
+ # ```
7
+ # [fecbba0735355738] timestamp=2025-10-19T11:12:56+00:00 pid=1825 level=info method=GET path=/api/v1/resource controller=Api::V1::ResourceController action=index status=200 duration=0.15
8
+ # ```
9
+ #
10
+ # Use {Rage.configure Rage.configure} to set the formatter:
11
+ #
12
+ # ```ruby
13
+ # Rage.configure do |config|
14
+ # config.log_formatter = Rage::TextFormatter.new
15
+ # end
16
+ # ```
17
+ #
1
18
  class Rage::TextFormatter
2
19
  def initialize
3
20
  @pid = Process.pid
@@ -17,11 +34,13 @@ class Rage::TextFormatter
17
34
 
18
35
  if (final = logger[:final])
19
36
  params, env = final[:params], final[:env]
37
+ tags = tags.map { |tag| "[#{tag}]" }.join
38
+
20
39
  if params && params[:controller]
21
- return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{Rage::Router::Util.path_to_name(params[:controller])} action=#{params[:action]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
40
+ return "#{tags} timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{Rage::Router::Util.path_to_name(params[:controller])} action=#{params[:action]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
22
41
  else
23
42
  # no controller/action keys are written if there are no params
24
- return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
43
+ return "#{tags} timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
25
44
  end
26
45
  end
27
46
 
@@ -17,6 +17,14 @@ class Rage::FiberWrapper
17
17
  def call(env)
18
18
  fiber = Fiber.schedule do
19
19
  @app.call(env)
20
+ rescue Exception => e
21
+ exception_str = "#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}"
22
+ Rage.logger << exception_str
23
+ if Rage.env.development?
24
+ [500, {}, [exception_str]]
25
+ else
26
+ [500, {}, []]
27
+ end
20
28
  ensure
21
29
  # notify Iodine the request can now be resumed
22
30
  Iodine.publish(Fiber.current.__get_id, "", Iodine::PubSub::PROCESS)
@@ -5,29 +5,24 @@ class Rage::Reloader
5
5
  Iodine.on_state(:on_start) do
6
6
  Rage.code_loader.check_updated!
7
7
  end
8
+
8
9
  @app = app
10
+ @mutex = Mutex.new
9
11
  end
10
12
 
11
13
  def call(env)
12
14
  with_reload do
13
15
  @app.call(env)
14
16
  end
15
- rescue Exception => e
16
- exception_str = "#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}"
17
- puts(exception_str)
18
- [500, {}, [exception_str]]
19
17
  end
20
18
 
21
19
  private
22
20
 
23
21
  def with_reload
24
- if Rage.code_loader.check_updated!
25
- Fiber.new(blocking: true) {
26
- Rage.code_loader.reload
27
- yield
28
- }.resume
29
- else
30
- yield
22
+ @mutex.synchronize do
23
+ Rage.code_loader.reload if Rage.code_loader.check_updated!
31
24
  end
25
+
26
+ yield
32
27
  end
33
28
  end
data/lib/rage/request.rb CHANGED
@@ -36,8 +36,11 @@ class Rage::Request
36
36
  KNOWN_HTTP_METHODS = (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).to_set
37
37
 
38
38
  # @private
39
- def initialize(env)
39
+ # @param env [Hash] Rack env
40
+ # @param controller [RageController::API]
41
+ def initialize(env, controller: nil)
40
42
  @env = env
43
+ @controller = controller
41
44
  end
42
45
 
43
46
  # Check if the request was made using TLS/SSL which is if http or https protocol is used inside the URL.
@@ -203,6 +206,20 @@ class Rage::Request
203
206
 
204
207
  alias_method :uuid, :request_id
205
208
 
209
+ # Get the route URI pattern matched for this request.
210
+ # @return [String] the route URI pattern
211
+ # @example
212
+ # # For a route defined as:
213
+ # # get "/users/:id", to: "users#show"
214
+ # request.route_uri_pattern # => "/users/:id"
215
+ def route_uri_pattern
216
+ if @controller
217
+ Rage::Router::Util.route_uri_pattern(@controller.class, @controller.action_name)
218
+ else
219
+ path
220
+ end
221
+ end
222
+
206
223
  private
207
224
 
208
225
  def rack_request
data/lib/rage/response.rb CHANGED
@@ -16,7 +16,7 @@ class Rage::Response
16
16
  # Returns the content of the response as a string. This contains the contents of any calls to `render`.
17
17
  # @return [String]
18
18
  def body
19
- @body[0]
19
+ @body[0] || ""
20
20
  end
21
21
 
22
22
  # Returns the headers for the response.
@@ -31,6 +31,14 @@ class Rage::Router::Util
31
31
  @@names_map[str] = path_to_class(str).name
32
32
  end
33
33
  end
34
+
35
+ @@uri_patterns_map = Hash.new { |h, k| h[k] = {} }
36
+
37
+ def route_uri_pattern(controller_class, action_name)
38
+ @@uri_patterns_map[controller_class][action_name] ||= Rage.__router.routes.find { |route|
39
+ route[:meta][:controller_class] == controller_class && route[:meta][:action] == action_name
40
+ }[:path]
41
+ end
34
42
  end
35
43
 
36
44
  # @private
data/lib/rage/setup.rb CHANGED
@@ -18,3 +18,5 @@ Rage.code_loader.setup
18
18
  Rage.config.run_after_initialize!
19
19
 
20
20
  require_relative "#{Rage.root}/config/routes"
21
+
22
+ Rage.config.internal.initialized!
@@ -8,4 +8,5 @@ Rage.configure do
8
8
  # Specify the logger
9
9
  config.logger = Rage::Logger.new(STDOUT)
10
10
  config.log_level = Logger::INFO
11
+ config.log_formatter = Rage::JSONFormatter.new
11
12
  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.17.1"
4
+ VERSION = "1.19.0"
5
5
  end