Module: Tina4::Log
- Defined in:
- lib/tina4/log.rb
Constant Summary collapse
- LEVELS =
{ "[TINA4_LOG_ALL]" => 0, "[TINA4_LOG_DEBUG]" => 0, "[TINA4_LOG_INFO]" => 1, "[TINA4_LOG_WARNING]" => 2, "[TINA4_LOG_ERROR]" => 3, "[TINA4_LOG_CRITICAL]" => 4, "[TINA4_LOG_NONE]" => 5 }.freeze
- SEVERITY_MAP =
{ debug: 0, info: 1, warn: 2, error: 3, critical: 4 }.freeze
- COLORS =
{ reset: "\e[0m", red: "\e[31m", green: "\e[32m", yellow: "\e[33m", blue: "\e[34m", magenta: "\e[35m", cyan: "\e[36m", gray: "\e[90m" }.freeze
- ANSI_RE =
ANSI escape code regex for stripping from file output
/\033\[[0-9;]*m/- DEFAULT_ROTATE_SIZE =
Defaults used when env vars are unset.
10 * 1024 * 1024
- DEFAULT_ROTATE_KEEP =
10MB
5
Class Attribute Summary collapse
-
.log_dir ⇒ Object
readonly
Returns the value of attribute log_dir.
-
.log_file_path ⇒ Object
readonly
Returns the value of attribute log_file_path.
Class Method Summary collapse
- .clear_request_id ⇒ Object
-
.close_file_logger ⇒ Object
Test/teardown helper — closes the underlying Logger so the file handle is released (Windows / tmpdir cleanup).
- .configure(root_dir = Dir.pwd) ⇒ Object
-
.critical(message, context = {}) ⇒ Object
critical is the HIGHEST severity (4, above error).
- .debug(message, context = {}) ⇒ Object
-
.enabled?(level) ⇒ Boolean
Would a message at ‘level` pass the configured MINIMUM CONSOLE LEVEL (TINA4_LOG_LEVEL)? Returns true iff `log` would print it to stdout — it reflects CONSOLE visibility only.
- .error(message, context = {}) ⇒ Object
- .get_request_id ⇒ Object
- .info(message, context = {}) ⇒ Object
- .json_mode? ⇒ Boolean
- .set_request_id(id) ⇒ Object
- .warning(message, context = {}) ⇒ Object
Class Attribute Details
.log_dir ⇒ Object (readonly)
Returns the value of attribute log_dir.
37 38 39 |
# File 'lib/tina4/log.rb', line 37 def log_dir @log_dir end |
.log_file_path ⇒ Object (readonly)
Returns the value of attribute log_file_path.
37 38 39 |
# File 'lib/tina4/log.rb', line 37 def log_file_path @log_file_path end |
Class Method Details
.clear_request_id ⇒ Object
135 136 137 |
# File 'lib/tina4/log.rb', line 135 def clear_request_id @mutex.synchronize { @request_id = nil } end |
.close_file_logger ⇒ Object
Test/teardown helper — closes the underlying Logger so the file handle is released (Windows / tmpdir cleanup).
195 196 197 198 |
# File 'lib/tina4/log.rb', line 195 def close_file_logger @file_logger&.close rescue nil @file_logger = nil end |
.configure(root_dir = Dir.pwd) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/tina4/log.rb', line 39 def configure(root_dir = Dir.pwd) # TINA4_LOG_DIR — relative or absolute. Default "logs". log_dir_env = ENV["TINA4_LOG_DIR"] log_dir_env = "logs" if log_dir_env.nil? || log_dir_env.empty? @log_dir = if File.absolute_path?(log_dir_env) log_dir_env else File.join(root_dir, log_dir_env) end FileUtils.mkdir_p(@log_dir) # TINA4_LOG_FILE — explicit log file path (absolute or relative to log_dir). # Default: <log_dir>/tina4.log. log_file_env = ENV["TINA4_LOG_FILE"] @log_file_path = if log_file_env && !log_file_env.empty? File.absolute_path?(log_file_env) ? log_file_env : File.join(@log_dir, log_file_env) else File.join(@log_dir, "tina4.log") end # TINA4_LOG_ROTATE_SIZE — bytes per file before rotation. 0 = no rotation. @rotate_size = (ENV["TINA4_LOG_ROTATE_SIZE"] || DEFAULT_ROTATE_SIZE).to_i # TINA4_LOG_ROTATE_KEEP — number of rotated backups to keep. @rotate_keep = (ENV["TINA4_LOG_ROTATE_KEEP"] || DEFAULT_ROTATE_KEEP).to_i # TINA4_LOG_FORMAT — "text" or "json". Defaults to "json" in production, else "text". format_env = ENV["TINA4_LOG_FORMAT"] @format = format_env && !format_env.empty? ? format_env.downcase : (production? ? "json" : "text") @json_mode = @format == "json" # TINA4_LOG_OUTPUT — "stdout", "file", or "both". # # Default (UNSET): stdout is ALWAYS on. The log FILE (tina4.log + any # error log) is written ONLY in development — i.e. when TINA4_DEBUG is # truthy. In production / containers (TINA4_DEBUG falsy) the logger is # stdout-only: writing a log file inside a container just bloats the # writable layer + disk, and 12-factor wants logs on stdout for the # platform to capture. An explicit TINA4_LOG_OUTPUT=file/both (or an # explicit TINA4_LOG_FILE path) overrides this and STILL writes a file. # Mirrors the Python master (debug/__init__.py configure()). # An explicit TINA4_LOG_FILE always wins: a path the operator named must # be written even in production (parity with the Python master, where an # explicit log_file builds a writer unconditionally), so the dev-gated # default below resolves to "both" (stdout + file) rather than "stdout". explicit_file = !(log_file_env.nil? || log_file_env.empty?) default_output = if explicit_file || truthy?(ENV["TINA4_DEBUG"]) "both" else "stdout" end output_env = ENV["TINA4_LOG_OUTPUT"] @output = if output_env && !output_env.empty? output_env.downcase else default_output end @output = default_output unless %w[stdout file both].include?(@output) # TINA4_LOG_STRICT — when true, raise on log write failures instead of swallowing. @strict = truthy?(ENV["TINA4_LOG_STRICT"]) @console_level = resolve_level @request_id = nil @current_context = {} @mutex = Mutex.new # v3.13.14: unbuffer stdout so logs reach `docker logs` / k8s # immediately. A non-TTY $stdout (every container) is block-buffered # by default — logs sat in the buffer until it filled or the process # exited, so operators "weren't getting logs". No-op when output is # file-only. $stdout.sync = true if @output != "file" # Build the file logger via stdlib Logger which handles rotation natively. # Logger.new(path, shift_age, shift_size): # shift_age = number of files to keep # shift_size = bytes before rotation # When @rotate_size is 0, omit rotation args. close_file_logger if @output != "stdout" @file_logger = if @rotate_size > 0 ::Logger.new(@log_file_path, @rotate_keep, @rotate_size) else ::Logger.new(@log_file_path) end # We do our own formatting — strip Logger's default formatter. @file_logger.formatter = proc { |_sev, _t, _p, msg| msg.to_s.end_with?("\n") ? msg : "#{msg}\n" } end @initialized = true end |
.critical(message, context = {}) ⇒ Object
critical is the HIGHEST severity (4, above error). Like every other level it ALWAYS emits, subject only to the TINA4_LOG_LEVEL threshold (which critical passes at every level except none). A critical log is never a silent no-op. Mirrors the Python master.
189 190 191 |
# File 'lib/tina4/log.rb', line 189 def critical(, context = {}) log(:critical, , context) end |
.debug(message, context = {}) ⇒ Object
173 174 175 |
# File 'lib/tina4/log.rb', line 173 def debug(, context = {}) log(:debug, , context) end |
.enabled?(level) ⇒ Boolean
Would a message at ‘level` pass the configured MINIMUM CONSOLE LEVEL (TINA4_LOG_LEVEL)? Returns true iff `log` would print it to stdout —it reflects CONSOLE visibility only. The log FILE records every level regardless of this threshold, so this never gates file output.
‘level` accepts a String or Symbol and is case-insensitive (“INFO”, :info, “Warning”, :warning all work). Mirrors Python’s Log.is_enabled. It REUSES the exact severity >= @console_level comparison the console branch in ‘log` uses (line ~167) via SEVERITY_MAP / resolve_level — it never re-implements level comparison, so it can never disagree with what the logger prints.
“critical” is a FIRST-CLASS top-level severity (4 — above error 3), not a parity alias for error. It is evaluated with ordinary threshold logic (critical 4 >= @console_level), so it passes at every level except none (5) — matching the Python master.
163 164 165 166 167 |
# File 'lib/tina4/log.rb', line 163 def enabled?(level) sym = normalize_level(level) severity = SEVERITY_MAP[sym] || 0 severity >= console_level end |
.error(message, context = {}) ⇒ Object
181 182 183 |
# File 'lib/tina4/log.rb', line 181 def error(, context = {}) log(:error, , context) end |
.get_request_id ⇒ Object
139 140 141 |
# File 'lib/tina4/log.rb', line 139 def get_request_id @mutex.synchronize { @request_id } end |
.info(message, context = {}) ⇒ Object
169 170 171 |
# File 'lib/tina4/log.rb', line 169 def info(, context = {}) log(:info, , context) end |
.json_mode? ⇒ Boolean
143 144 145 |
# File 'lib/tina4/log.rb', line 143 def json_mode? @json_mode end |
.set_request_id(id) ⇒ Object
131 132 133 |
# File 'lib/tina4/log.rb', line 131 def set_request_id(id) @mutex.synchronize { @request_id = id } end |
.warning(message, context = {}) ⇒ Object
177 178 179 |
# File 'lib/tina4/log.rb', line 177 def warning(, context = {}) log(:warn, , context) end |