tina4ruby 3.11.13 → 3.11.15
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/CHANGELOG.md +80 -80
- data/LICENSE.txt +21 -21
- data/README.md +137 -137
- data/exe/tina4ruby +5 -5
- data/lib/tina4/ai.rb +696 -696
- data/lib/tina4/api.rb +189 -189
- data/lib/tina4/auth.rb +305 -305
- data/lib/tina4/auto_crud.rb +244 -244
- data/lib/tina4/cache.rb +154 -154
- data/lib/tina4/cli.rb +1449 -1449
- data/lib/tina4/constants.rb +46 -46
- data/lib/tina4/container.rb +74 -74
- data/lib/tina4/cors.rb +74 -74
- data/lib/tina4/crud.rb +692 -692
- data/lib/tina4/database/sqlite3_adapter.rb +165 -165
- data/lib/tina4/database.rb +625 -625
- data/lib/tina4/database_result.rb +208 -208
- data/lib/tina4/debug.rb +8 -8
- data/lib/tina4/dev.rb +14 -14
- data/lib/tina4/dev_admin.rb +935 -935
- data/lib/tina4/dev_mailbox.rb +191 -191
- data/lib/tina4/drivers/firebird_driver.rb +124 -110
- data/lib/tina4/drivers/mongodb_driver.rb +561 -561
- data/lib/tina4/drivers/mssql_driver.rb +112 -112
- data/lib/tina4/drivers/mysql_driver.rb +90 -90
- data/lib/tina4/drivers/odbc_driver.rb +191 -191
- data/lib/tina4/drivers/postgres_driver.rb +116 -106
- data/lib/tina4/drivers/sqlite_driver.rb +122 -122
- data/lib/tina4/env.rb +95 -95
- data/lib/tina4/error_overlay.rb +252 -252
- data/lib/tina4/events.rb +109 -109
- data/lib/tina4/field_types.rb +154 -154
- data/lib/tina4/frond.rb +2025 -2025
- data/lib/tina4/gallery/auth/meta.json +1 -1
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
- data/lib/tina4/gallery/database/meta.json +1 -1
- data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
- data/lib/tina4/gallery/error-overlay/meta.json +1 -1
- data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
- data/lib/tina4/gallery/orm/meta.json +1 -1
- data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
- data/lib/tina4/gallery/queue/meta.json +1 -1
- data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
- data/lib/tina4/gallery/rest-api/meta.json +1 -1
- data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
- data/lib/tina4/gallery/templates/meta.json +1 -1
- data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
- data/lib/tina4/graphql.rb +966 -966
- data/lib/tina4/health.rb +39 -39
- data/lib/tina4/html_element.rb +170 -170
- data/lib/tina4/job.rb +80 -80
- data/lib/tina4/localization.rb +168 -168
- data/lib/tina4/log.rb +203 -203
- data/lib/tina4/mcp.rb +696 -696
- data/lib/tina4/messenger.rb +587 -587
- data/lib/tina4/metrics.rb +793 -793
- data/lib/tina4/middleware.rb +445 -445
- data/lib/tina4/migration.rb +451 -451
- data/lib/tina4/orm.rb +790 -790
- data/lib/tina4/public/css/tina4.css +2463 -2463
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/public/images/logo.svg +5 -5
- data/lib/tina4/public/js/frond.min.js +2 -2
- data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
- data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
- data/lib/tina4/public/js/tina4.min.js +92 -92
- data/lib/tina4/public/js/tina4js.min.js +48 -48
- data/lib/tina4/public/swagger/index.html +90 -90
- data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
- data/lib/tina4/query_builder.rb +380 -380
- data/lib/tina4/queue.rb +366 -366
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
- data/lib/tina4/queue_backends/lite_backend.rb +298 -298
- data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
- data/lib/tina4/rack_app.rb +817 -817
- data/lib/tina4/rate_limiter.rb +130 -130
- data/lib/tina4/request.rb +268 -255
- data/lib/tina4/response.rb +346 -346
- data/lib/tina4/response_cache.rb +551 -551
- data/lib/tina4/router.rb +406 -406
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
- data/lib/tina4/scss/tina4css/_badges.scss +22 -22
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
- data/lib/tina4/scss/tina4css/_cards.scss +49 -49
- data/lib/tina4/scss/tina4css/_forms.scss +156 -156
- data/lib/tina4/scss/tina4css/_grid.scss +81 -81
- data/lib/tina4/scss/tina4css/_modals.scss +84 -84
- data/lib/tina4/scss/tina4css/_nav.scss +149 -149
- data/lib/tina4/scss/tina4css/_reset.scss +94 -94
- data/lib/tina4/scss/tina4css/_tables.scss +54 -54
- data/lib/tina4/scss/tina4css/_typography.scss +55 -55
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
- data/lib/tina4/scss/tina4css/_variables.scss +117 -117
- data/lib/tina4/scss/tina4css/base.scss +1 -1
- data/lib/tina4/scss/tina4css/colors.scss +48 -48
- data/lib/tina4/scss/tina4css/tina4.scss +17 -17
- data/lib/tina4/scss_compiler.rb +178 -178
- data/lib/tina4/seeder.rb +567 -567
- data/lib/tina4/service_runner.rb +303 -303
- data/lib/tina4/session.rb +297 -297
- data/lib/tina4/session_handlers/database_handler.rb +72 -72
- data/lib/tina4/session_handlers/file_handler.rb +67 -67
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
- data/lib/tina4/session_handlers/redis_handler.rb +43 -43
- data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
- data/lib/tina4/shutdown.rb +84 -84
- data/lib/tina4/sql_translation.rb +158 -158
- data/lib/tina4/swagger.rb +124 -124
- data/lib/tina4/template.rb +894 -894
- data/lib/tina4/templates/base.twig +26 -26
- data/lib/tina4/templates/errors/302.twig +14 -14
- data/lib/tina4/templates/errors/401.twig +9 -9
- data/lib/tina4/templates/errors/403.twig +29 -29
- data/lib/tina4/templates/errors/404.twig +29 -29
- data/lib/tina4/templates/errors/500.twig +38 -38
- data/lib/tina4/templates/errors/502.twig +9 -9
- data/lib/tina4/templates/errors/503.twig +12 -12
- data/lib/tina4/templates/errors/base.twig +37 -37
- data/lib/tina4/test_client.rb +159 -159
- data/lib/tina4/testing.rb +340 -340
- data/lib/tina4/validator.rb +174 -174
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +312 -312
- data/lib/tina4/websocket.rb +343 -343
- data/lib/tina4/websocket_backplane.rb +190 -190
- data/lib/tina4/wsdl.rb +564 -564
- data/lib/tina4.rb +458 -458
- data/lib/tina4ruby.rb +4 -4
- metadata +3 -3
data/lib/tina4/log.rb
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "json"
|
|
5
|
-
|
|
6
|
-
module Tina4
|
|
7
|
-
module Log
|
|
8
|
-
LEVELS = {
|
|
9
|
-
"[TINA4_LOG_ALL]" => 0,
|
|
10
|
-
"[TINA4_LOG_DEBUG]" => 0,
|
|
11
|
-
"[TINA4_LOG_INFO]" => 1,
|
|
12
|
-
"[TINA4_LOG_WARNING]" => 2,
|
|
13
|
-
"[TINA4_LOG_ERROR]" => 3,
|
|
14
|
-
"[TINA4_LOG_NONE]" => 4
|
|
15
|
-
}.freeze
|
|
16
|
-
|
|
17
|
-
SEVERITY_MAP = {
|
|
18
|
-
debug: 0, info: 1, warn: 2, error: 3
|
|
19
|
-
}.freeze
|
|
20
|
-
|
|
21
|
-
COLORS = {
|
|
22
|
-
reset: "\e[0m", red: "\e[31m", green: "\e[32m",
|
|
23
|
-
yellow: "\e[33m", blue: "\e[34m", magenta: "\e[35m",
|
|
24
|
-
cyan: "\e[36m", gray: "\e[90m"
|
|
25
|
-
}.freeze
|
|
26
|
-
|
|
27
|
-
# ANSI escape code regex for stripping from file output
|
|
28
|
-
ANSI_RE = /\033\[[0-9;]*m/
|
|
29
|
-
|
|
30
|
-
class << self
|
|
31
|
-
attr_reader :log_dir
|
|
32
|
-
|
|
33
|
-
def configure(root_dir = Dir.pwd)
|
|
34
|
-
@log_dir = File.join(root_dir, "logs")
|
|
35
|
-
FileUtils.mkdir_p(@log_dir)
|
|
36
|
-
|
|
37
|
-
@max_size_mb = (ENV["TINA4_LOG_MAX_SIZE"] || "10").to_i
|
|
38
|
-
@max_size = @max_size_mb * 1024 * 1024
|
|
39
|
-
@keep = (ENV["TINA4_LOG_KEEP"] || "5").to_i
|
|
40
|
-
@json_mode = production?
|
|
41
|
-
@log_file = File.join(@log_dir, "tina4.log")
|
|
42
|
-
|
|
43
|
-
@console_level = resolve_level
|
|
44
|
-
@request_id = nil
|
|
45
|
-
@current_context = {}
|
|
46
|
-
@mutex = Mutex.new
|
|
47
|
-
@initialized = true
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def set_request_id(id)
|
|
51
|
-
@mutex.synchronize { @request_id = id }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def clear_request_id
|
|
55
|
-
@mutex.synchronize { @request_id = nil }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def get_request_id
|
|
59
|
-
@mutex.synchronize { @request_id }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def json_mode?
|
|
63
|
-
@json_mode
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def info(message, context = {})
|
|
67
|
-
log(:info, message, context)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def debug(message, context = {})
|
|
71
|
-
log(:debug, message, context)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def warning(message, context = {})
|
|
75
|
-
log(:warn, message, context)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def error(message, context = {})
|
|
79
|
-
log(:error, message, context)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
def production?
|
|
85
|
-
env = ENV["TINA4_ENV"] || ENV["RACK_ENV"] || ENV["RUBY_ENV"] || "development"
|
|
86
|
-
env.downcase == "production"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def log(level, message, context = {})
|
|
90
|
-
configure unless @initialized
|
|
91
|
-
@current_context = context.is_a?(Hash) ? context : {}
|
|
92
|
-
|
|
93
|
-
formatted = format_line(level, message)
|
|
94
|
-
|
|
95
|
-
# Console output respects TINA4_LOG_LEVEL
|
|
96
|
-
severity = SEVERITY_MAP[level] || 0
|
|
97
|
-
if severity >= @console_level
|
|
98
|
-
if @json_mode
|
|
99
|
-
$stdout.puts json_line(level, message)
|
|
100
|
-
else
|
|
101
|
-
$stdout.puts colorize(level, formatted)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# File always gets ALL levels, plain text (no ANSI)
|
|
106
|
-
write_to_file(strip_ansi(formatted))
|
|
107
|
-
|
|
108
|
-
@current_context = {}
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def resolve_level
|
|
112
|
-
env_level = ENV["TINA4_LOG_LEVEL"] || "[TINA4_LOG_ALL]"
|
|
113
|
-
LEVELS[env_level] || 0
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def severity_to_level(level)
|
|
117
|
-
case level
|
|
118
|
-
when :debug then "DEBUG"
|
|
119
|
-
when :info then "INFO"
|
|
120
|
-
when :warn then "WARNING"
|
|
121
|
-
when :error then "ERROR"
|
|
122
|
-
else level.to_s.upcase
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def utc_timestamp
|
|
127
|
-
now = Time.now.utc
|
|
128
|
-
now.strftime("%Y-%m-%dT%H:%M:%S.") + format("%03d", now.usec / 1000) + "Z"
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def strip_ansi(text)
|
|
132
|
-
text.gsub(ANSI_RE, "")
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def format_line(level, message)
|
|
136
|
-
level_str = severity_to_level(level)
|
|
137
|
-
ts = utc_timestamp
|
|
138
|
-
rid = get_request_id
|
|
139
|
-
rid_str = rid ? " [#{rid}]" : ""
|
|
140
|
-
ctx = @current_context && !@current_context.empty? ? " #{JSON.generate(@current_context)}" : ""
|
|
141
|
-
"#{ts} [#{level_str.ljust(7)}]#{rid_str} #{message}#{ctx}"
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def json_line(level, message)
|
|
145
|
-
level_str = severity_to_level(level)
|
|
146
|
-
entry = {
|
|
147
|
-
timestamp: utc_timestamp,
|
|
148
|
-
level: level_str,
|
|
149
|
-
message: message
|
|
150
|
-
}
|
|
151
|
-
rid = get_request_id
|
|
152
|
-
entry[:request_id] = rid if rid
|
|
153
|
-
entry[:context] = @current_context if @current_context && !@current_context.empty?
|
|
154
|
-
JSON.generate(entry)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def colorize(level, line)
|
|
158
|
-
color = case level
|
|
159
|
-
when :debug then COLORS[:cyan]
|
|
160
|
-
when :info then COLORS[:green]
|
|
161
|
-
when :warn then COLORS[:yellow]
|
|
162
|
-
when :error then COLORS[:red]
|
|
163
|
-
else COLORS[:reset]
|
|
164
|
-
end
|
|
165
|
-
"#{color}#{line}#{COLORS[:reset]}"
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def write_to_file(line)
|
|
169
|
-
rotate_if_needed
|
|
170
|
-
begin
|
|
171
|
-
File.open(@log_file, "a") { |f| f.puts(line) }
|
|
172
|
-
rescue IOError, SystemCallError
|
|
173
|
-
# Don't crash on log write failure
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Numbered rotation: tina4.log → tina4.log.1 → tina4.log.2 → ... → tina4.log.{keep}
|
|
178
|
-
def rotate_if_needed
|
|
179
|
-
return unless File.exist?(@log_file)
|
|
180
|
-
|
|
181
|
-
begin
|
|
182
|
-
return if File.size(@log_file) < @max_size
|
|
183
|
-
rescue SystemCallError
|
|
184
|
-
return
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Delete the oldest rotated file if it exists
|
|
188
|
-
oldest = "#{@log_file}.#{@keep}"
|
|
189
|
-
File.delete(oldest) if File.exist?(oldest)
|
|
190
|
-
|
|
191
|
-
# Shift existing rotated files: .{n} → .{n+1}
|
|
192
|
-
(@keep - 1).downto(1) do |n|
|
|
193
|
-
src = "#{@log_file}.#{n}"
|
|
194
|
-
dst = "#{@log_file}.#{n + 1}"
|
|
195
|
-
File.rename(src, dst) if File.exist?(src)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Rename current log to .1
|
|
199
|
-
File.rename(@log_file, "#{@log_file}.1") rescue nil
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Tina4
|
|
7
|
+
module Log
|
|
8
|
+
LEVELS = {
|
|
9
|
+
"[TINA4_LOG_ALL]" => 0,
|
|
10
|
+
"[TINA4_LOG_DEBUG]" => 0,
|
|
11
|
+
"[TINA4_LOG_INFO]" => 1,
|
|
12
|
+
"[TINA4_LOG_WARNING]" => 2,
|
|
13
|
+
"[TINA4_LOG_ERROR]" => 3,
|
|
14
|
+
"[TINA4_LOG_NONE]" => 4
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
SEVERITY_MAP = {
|
|
18
|
+
debug: 0, info: 1, warn: 2, error: 3
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
COLORS = {
|
|
22
|
+
reset: "\e[0m", red: "\e[31m", green: "\e[32m",
|
|
23
|
+
yellow: "\e[33m", blue: "\e[34m", magenta: "\e[35m",
|
|
24
|
+
cyan: "\e[36m", gray: "\e[90m"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# ANSI escape code regex for stripping from file output
|
|
28
|
+
ANSI_RE = /\033\[[0-9;]*m/
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
attr_reader :log_dir
|
|
32
|
+
|
|
33
|
+
def configure(root_dir = Dir.pwd)
|
|
34
|
+
@log_dir = File.join(root_dir, "logs")
|
|
35
|
+
FileUtils.mkdir_p(@log_dir)
|
|
36
|
+
|
|
37
|
+
@max_size_mb = (ENV["TINA4_LOG_MAX_SIZE"] || "10").to_i
|
|
38
|
+
@max_size = @max_size_mb * 1024 * 1024
|
|
39
|
+
@keep = (ENV["TINA4_LOG_KEEP"] || "5").to_i
|
|
40
|
+
@json_mode = production?
|
|
41
|
+
@log_file = File.join(@log_dir, "tina4.log")
|
|
42
|
+
|
|
43
|
+
@console_level = resolve_level
|
|
44
|
+
@request_id = nil
|
|
45
|
+
@current_context = {}
|
|
46
|
+
@mutex = Mutex.new
|
|
47
|
+
@initialized = true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_request_id(id)
|
|
51
|
+
@mutex.synchronize { @request_id = id }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clear_request_id
|
|
55
|
+
@mutex.synchronize { @request_id = nil }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def get_request_id
|
|
59
|
+
@mutex.synchronize { @request_id }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def json_mode?
|
|
63
|
+
@json_mode
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def info(message, context = {})
|
|
67
|
+
log(:info, message, context)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def debug(message, context = {})
|
|
71
|
+
log(:debug, message, context)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def warning(message, context = {})
|
|
75
|
+
log(:warn, message, context)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def error(message, context = {})
|
|
79
|
+
log(:error, message, context)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def production?
|
|
85
|
+
env = ENV["TINA4_ENV"] || ENV["RACK_ENV"] || ENV["RUBY_ENV"] || "development"
|
|
86
|
+
env.downcase == "production"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def log(level, message, context = {})
|
|
90
|
+
configure unless @initialized
|
|
91
|
+
@current_context = context.is_a?(Hash) ? context : {}
|
|
92
|
+
|
|
93
|
+
formatted = format_line(level, message)
|
|
94
|
+
|
|
95
|
+
# Console output respects TINA4_LOG_LEVEL
|
|
96
|
+
severity = SEVERITY_MAP[level] || 0
|
|
97
|
+
if severity >= @console_level
|
|
98
|
+
if @json_mode
|
|
99
|
+
$stdout.puts json_line(level, message)
|
|
100
|
+
else
|
|
101
|
+
$stdout.puts colorize(level, formatted)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# File always gets ALL levels, plain text (no ANSI)
|
|
106
|
+
write_to_file(strip_ansi(formatted))
|
|
107
|
+
|
|
108
|
+
@current_context = {}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def resolve_level
|
|
112
|
+
env_level = ENV["TINA4_LOG_LEVEL"] || "[TINA4_LOG_ALL]"
|
|
113
|
+
LEVELS[env_level] || 0
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def severity_to_level(level)
|
|
117
|
+
case level
|
|
118
|
+
when :debug then "DEBUG"
|
|
119
|
+
when :info then "INFO"
|
|
120
|
+
when :warn then "WARNING"
|
|
121
|
+
when :error then "ERROR"
|
|
122
|
+
else level.to_s.upcase
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def utc_timestamp
|
|
127
|
+
now = Time.now.utc
|
|
128
|
+
now.strftime("%Y-%m-%dT%H:%M:%S.") + format("%03d", now.usec / 1000) + "Z"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def strip_ansi(text)
|
|
132
|
+
text.gsub(ANSI_RE, "")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def format_line(level, message)
|
|
136
|
+
level_str = severity_to_level(level)
|
|
137
|
+
ts = utc_timestamp
|
|
138
|
+
rid = get_request_id
|
|
139
|
+
rid_str = rid ? " [#{rid}]" : ""
|
|
140
|
+
ctx = @current_context && !@current_context.empty? ? " #{JSON.generate(@current_context)}" : ""
|
|
141
|
+
"#{ts} [#{level_str.ljust(7)}]#{rid_str} #{message}#{ctx}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def json_line(level, message)
|
|
145
|
+
level_str = severity_to_level(level)
|
|
146
|
+
entry = {
|
|
147
|
+
timestamp: utc_timestamp,
|
|
148
|
+
level: level_str,
|
|
149
|
+
message: message
|
|
150
|
+
}
|
|
151
|
+
rid = get_request_id
|
|
152
|
+
entry[:request_id] = rid if rid
|
|
153
|
+
entry[:context] = @current_context if @current_context && !@current_context.empty?
|
|
154
|
+
JSON.generate(entry)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def colorize(level, line)
|
|
158
|
+
color = case level
|
|
159
|
+
when :debug then COLORS[:cyan]
|
|
160
|
+
when :info then COLORS[:green]
|
|
161
|
+
when :warn then COLORS[:yellow]
|
|
162
|
+
when :error then COLORS[:red]
|
|
163
|
+
else COLORS[:reset]
|
|
164
|
+
end
|
|
165
|
+
"#{color}#{line}#{COLORS[:reset]}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def write_to_file(line)
|
|
169
|
+
rotate_if_needed
|
|
170
|
+
begin
|
|
171
|
+
File.open(@log_file, "a") { |f| f.puts(line) }
|
|
172
|
+
rescue IOError, SystemCallError
|
|
173
|
+
# Don't crash on log write failure
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Numbered rotation: tina4.log → tina4.log.1 → tina4.log.2 → ... → tina4.log.{keep}
|
|
178
|
+
def rotate_if_needed
|
|
179
|
+
return unless File.exist?(@log_file)
|
|
180
|
+
|
|
181
|
+
begin
|
|
182
|
+
return if File.size(@log_file) < @max_size
|
|
183
|
+
rescue SystemCallError
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Delete the oldest rotated file if it exists
|
|
188
|
+
oldest = "#{@log_file}.#{@keep}"
|
|
189
|
+
File.delete(oldest) if File.exist?(oldest)
|
|
190
|
+
|
|
191
|
+
# Shift existing rotated files: .{n} → .{n+1}
|
|
192
|
+
(@keep - 1).downto(1) do |n|
|
|
193
|
+
src = "#{@log_file}.#{n}"
|
|
194
|
+
dst = "#{@log_file}.#{n + 1}"
|
|
195
|
+
File.rename(src, dst) if File.exist?(src)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Rename current log to .1
|
|
199
|
+
File.rename(@log_file, "#{@log_file}.1") rescue nil
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|