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_NONE]" => 4
}.freeze
SEVERITY_MAP =
{
  debug: 0, info: 1, warn: 2, error: 3
}.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

Class Method Summary collapse

Class Attribute Details

.log_dirObject (readonly)

Returns the value of attribute log_dir.



36
37
38
# File 'lib/tina4/log.rb', line 36

def log_dir
  @log_dir
end

.log_file_pathObject (readonly)

Returns the value of attribute log_file_path.



36
37
38
# File 'lib/tina4/log.rb', line 36

def log_file_path
  @log_file_path
end

Class Method Details

.clear_request_idObject



113
114
115
# File 'lib/tina4/log.rb', line 113

def clear_request_id
  @mutex.synchronize { @request_id = nil }
end

.close_file_loggerObject

Test/teardown helper — closes the underlying Logger so the file handle is released (Windows / tmpdir cleanup).



143
144
145
146
# File 'lib/tina4/log.rb', line 143

def close_file_logger
  @file_logger&.close rescue nil
  @file_logger = nil
end

.configure(root_dir = Dir.pwd) ⇒ Object



38
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
# File 'lib/tina4/log.rb', line 38

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". Defaults to "both".
  output_env = ENV["TINA4_LOG_OUTPUT"]
  @output = output_env && !output_env.empty? ? output_env.downcase : "both"
  unless %w[stdout file both].include?(@output)
    @output = "both"
  end

  # TINA4_LOG_CRITICAL — when true, raise on log write failures instead of swallowing.
  @critical = truthy?(ENV["TINA4_LOG_CRITICAL"])

  @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

.debug(message, context = {}) ⇒ Object



129
130
131
# File 'lib/tina4/log.rb', line 129

def debug(message, context = {})
  log(:debug, message, context)
end

.error(message, context = {}) ⇒ Object



137
138
139
# File 'lib/tina4/log.rb', line 137

def error(message, context = {})
  log(:error, message, context)
end

.get_request_idObject



117
118
119
# File 'lib/tina4/log.rb', line 117

def get_request_id
  @mutex.synchronize { @request_id }
end

.info(message, context = {}) ⇒ Object



125
126
127
# File 'lib/tina4/log.rb', line 125

def info(message, context = {})
  log(:info, message, context)
end

.json_mode?Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/tina4/log.rb', line 121

def json_mode?
  @json_mode
end

.set_request_id(id) ⇒ Object



109
110
111
# File 'lib/tina4/log.rb', line 109

def set_request_id(id)
  @mutex.synchronize { @request_id = id }
end

.warning(message, context = {}) ⇒ Object



133
134
135
# File 'lib/tina4/log.rb', line 133

def warning(message, context = {})
  log(:warn, message, context)
end