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.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +47 -0
- data/CHANGELOG.md +23 -0
- data/lib/rage/all.rb +1 -0
- data/lib/rage/application.rb +3 -25
- data/lib/rage/cable/cable.rb +2 -3
- data/lib/rage/cli.rb +101 -18
- data/lib/rage/code_loader.rb +8 -0
- data/lib/rage/configuration.rb +525 -195
- data/lib/rage/controller/api.rb +15 -3
- data/lib/rage/deferred/context.rb +48 -0
- data/lib/rage/deferred/deferred.rb +5 -1
- data/lib/rage/deferred/queue.rb +8 -8
- data/lib/rage/deferred/task.rb +29 -23
- data/lib/rage/env.rb +15 -0
- data/lib/rage/events/events.rb +140 -0
- data/lib/rage/events/subscriber.rb +174 -0
- data/lib/rage/fiber.rb +11 -2
- data/lib/rage/fiber_scheduler.rb +2 -2
- data/lib/rage/hooks.rb +1 -0
- data/lib/rage/internal.rb +53 -0
- data/lib/rage/log_processor.rb +117 -0
- data/lib/rage/logger/json_formatter.rb +37 -18
- data/lib/rage/logger/logger.rb +136 -30
- data/lib/rage/logger/text_formatter.rb +21 -2
- data/lib/rage/middleware/fiber_wrapper.rb +8 -0
- data/lib/rage/middleware/reloader.rb +6 -11
- data/lib/rage/request.rb +18 -1
- data/lib/rage/response.rb +1 -1
- data/lib/rage/router/util.rb +8 -0
- data/lib/rage/setup.rb +2 -0
- data/lib/rage/templates/config-environments-production.rb +1 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +51 -0
- data/rage.gemspec +1 -0
- metadata +22 -4
- data/OVERVIEW.md +0 -83
- data/lib/rage/deferred/metadata.rb +0 -43
|
@@ -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
|
|
19
|
-
|
|
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
|
-
|
|
40
|
+
"{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\"],"
|
|
32
41
|
elsif tags.length == 0
|
|
33
|
-
|
|
42
|
+
"{"
|
|
34
43
|
else
|
|
35
|
-
|
|
44
|
+
msg = "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\""
|
|
36
45
|
i = 2
|
|
37
46
|
while i < tags.length
|
|
38
|
-
|
|
47
|
+
msg << ",\"#{tags[i]}\""
|
|
39
48
|
i += 1
|
|
40
49
|
end
|
|
41
|
-
|
|
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
|
data/lib/rage/logger/logger.rb
CHANGED
|
@@ -51,18 +51,30 @@ require "logger"
|
|
|
51
51
|
# end
|
|
52
52
|
# ```
|
|
53
53
|
class Rage::Logger
|
|
54
|
+
# @private
|
|
54
55
|
METHODS_MAP = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
128
|
-
(Thread.current[:rage_logger] ||= { tags: [], context: {} })[:tags]
|
|
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]
|
|
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
|
|
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 @
|
|
154
|
-
#
|
|
155
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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 "
|
|
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 "
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
data/lib/rage/router/util.rb
CHANGED
|
@@ -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
data/lib/rage/version.rb
CHANGED