Class: Tina4::LogFileSink
- Inherits:
-
Object
- Object
- Tina4::LogFileSink
- Defined in:
- lib/tina4/log.rb
Overview
One owned log file: bounded, PREDICTIVE rotation guarded by a single in-process (thread) exclusive lock over the size check, rotation and append.
Decision 20 (2026-08-10 owner override): SINGLE FILE + IN-PROCESS LOCK ONLY. Cross-process exclusive locking is deliberately not implemented; concurrent PROCESSES writing the same file may interleave. Run one file per process, or route through a log shipper, for that case.
Constant Summary collapse
- LOCK_TIMEOUT_SECONDS =
2.0
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Instance Method Summary collapse
- #close ⇒ Object
-
#initialize(path, rotate_size, rotate_keep) ⇒ LogFileSink
constructor
A new instance of LogFileSink.
-
#open ⇒ Object
Create the directory and prove the file is writable.
- #rotate_if_needed(next_record_bytes) ⇒ Object
-
#write(encoded_line) ⇒ Object
Append one complete encoded record, rotating first if it would cross the threshold.
Constructor Details
#initialize(path, rotate_size, rotate_keep) ⇒ LogFileSink
Returns a new instance of LogFileSink.
57 58 59 60 61 62 |
# File 'lib/tina4/log.rb', line 57 def initialize(path, rotate_size, rotate_keep) @path = path @rotate_size = rotate_size @rotate_keep = rotate_keep @mutex = Mutex.new end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
55 56 57 |
# File 'lib/tina4/log.rb', line 55 def path @path end |
Instance Method Details
#close ⇒ Object
115 |
# File 'lib/tina4/log.rb', line 115 def close; end |
#open ⇒ Object
Create the directory and prove the file is writable.
65 66 67 68 69 70 71 72 73 |
# File 'lib/tina4/log.rb', line 65 def open dir = File.dirname(@path) FileUtils.mkdir_p(dir) File.open(@path, "a") {} rescue SystemCallError, IOError => e raise LogConfigurationError.new( "cannot open log sink #{@path}: #{e.}", sink: @path, operation: "open" ) end |
#rotate_if_needed(next_record_bytes) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/tina4/log.rb', line 75 def rotate_if_needed(next_record_bytes) current_size = File.exist?(@path) ? File.size(@path) : 0 return if current_size.zero? return if current_size + next_record_bytes <= @rotate_size if @rotate_keep <= 0 File.delete(@path) return end oldest = "#{@path}.#{@rotate_keep}" File.delete(oldest) if File.exist?(oldest) (@rotate_keep - 1).downto(1) do |n| src = "#{@path}.#{n}" dst = "#{@path}.#{n + 1}" File.rename(src, dst) if File.exist?(src) end File.rename(@path, "#{@path}.1") end |
#write(encoded_line) ⇒ Object
Append one complete encoded record, rotating first if it would cross the threshold. Raises LogWriteError (timeout/write) to the caller, which applies the sink failure policy.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/tina4/log.rb', line 98 def write(encoded_line) clean = encoded_line.gsub(/\e\[[0-9;]*m/, "") payload = clean.b acquired = try_lock_with_timeout raise LogWriteError.new("timed out acquiring the log sink lock for #{@path}", sink: @path, operation: "lock") unless acquired begin rotate_if_needed(payload.bytesize) File.open(@path, "ab") { |f| f.write(payload) } rescue SystemCallError, IOError => e raise LogWriteError.new("cannot write log sink #{@path}: #{e.}", sink: @path, operation: "write") ensure @mutex.unlock if @mutex.owned? end end |