Class: OpenC3::LogWriter
Overview
Creates a log. Can automatically cycle the log based on an elasped time period or when the log file reaches a predefined size.
Direct Known Subclasses
Constant Summary collapse
- CYCLE_TIME_INTERVAL =
The cycle time interval. Cycle times are only checked at this level of granularity.
10
- @@mutex =
Mutex protecting class variables
Mutex.new
- @@instances =
Array of instances used to keep track of cycling logs
[]
- @@cycle_thread =
Thread used to cycle logs across all log writers
nil
- @@cycle_sleeper =
Sleeper used to delay cycle thread
nil
Instance Attribute Summary collapse
-
#cycle_hour ⇒ Object
readonly
Returns the value of attribute cycle_hour.
-
#cycle_minute ⇒ Object
readonly
Returns the value of attribute cycle_minute.
-
#cycle_time ⇒ Object
readonly
Returns the value of attribute cycle_time.
-
#filename ⇒ String
readonly
The filename of the packet log.
-
#logging_enabled ⇒ true/false
readonly
Whether logging is enabled.
-
#mutex ⇒ Mutex
readonly
Instance mutex protecting file.
-
#start_time ⇒ Time
readonly
Time that the current log file started.
Instance Method Summary collapse
-
#close_file(take_mutex = true) ⇒ Object
Closing a log file isn’t critical so we just log an error.
-
#create_unique_filename(ext = extension) ⇒ Object
implementation details.
- #cycle_thread_body ⇒ Object
- #extension ⇒ Object
- #first_time ⇒ Object
- #first_timestamp ⇒ Object
- #graceful_kill ⇒ Object
-
#initialize(remote_log_directory, logging_enabled = true, cycle_time = nil, cycle_size = 1000000000, cycle_hour = nil, cycle_minute = nil, redis_topic: nil) ⇒ LogWriter
constructor
A new instance of LogWriter.
- #last_time ⇒ Object
- #last_timestamp ⇒ Object
- #prepare_write(time_nsec_since_epoch, data_length, redis_offset) ⇒ Object
- #s3_filename ⇒ Object
-
#shutdown ⇒ Object
Stop all logging, close the current log file, and kill the logging threads.
-
#start ⇒ Object
Starts a new log file by closing the existing log file.
-
#start_new_file ⇒ Object
Starting a new log file is a critical operation so the entire method is wrapped with a rescue and handled with handle_critical_exception Assumes mutex has already been taken.
-
#stop ⇒ Object
Stops all logging and closes the current log file.
Constructor Details
#initialize(remote_log_directory, logging_enabled = true, cycle_time = nil, cycle_size = 1000000000, cycle_hour = nil, cycle_minute = nil, redis_topic: nil) ⇒ LogWriter
Returns a new instance of LogWriter.
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 130 131 132 133 134 135 |
# File 'lib/openc3/logs/log_writer.rb', line 86 def initialize( remote_log_directory, logging_enabled = true, cycle_time = nil, cycle_size = 1000000000, cycle_hour = nil, cycle_minute = nil, redis_topic: nil ) @remote_log_directory = remote_log_directory @logging_enabled = ConfigParser.handle_true_false(logging_enabled) @cycle_time = ConfigParser.handle_nil(cycle_time) if @cycle_time @cycle_time = Integer(@cycle_time) raise "cycle_time must be >= #{CYCLE_TIME_INTERVAL}" if @cycle_time < CYCLE_TIME_INTERVAL end @cycle_size = ConfigParser.handle_nil(cycle_size) @cycle_size = Integer(@cycle_size) if @cycle_size @cycle_hour = ConfigParser.handle_nil(cycle_hour) @cycle_hour = Integer(@cycle_hour) if @cycle_hour @cycle_minute = ConfigParser.handle_nil(cycle_minute) @cycle_minute = Integer(@cycle_minute) if @cycle_minute @mutex = Mutex.new @file = nil @file_size = 0 @filename = nil @start_time = Time.now.utc @first_time = nil @last_time = nil @cancel_threads = false @last_offset = nil @previous_file_redis_offset = nil @redis_topic = redis_topic # This is an optimization to avoid creating a new entry object # each time we create an entry which we do a LOT! @entry = String.new if @cycle_time or @cycle_hour or @cycle_minute @@mutex.synchronize do @@instances << self unless @@cycle_thread @@cycle_thread = OpenC3.safe_thread("Log cycle") do cycle_thread_body() end end end end end |
Instance Attribute Details
#cycle_hour ⇒ Object (readonly)
Returns the value of attribute cycle_hour.
43 44 45 |
# File 'lib/openc3/logs/log_writer.rb', line 43 def cycle_hour @cycle_hour end |
#cycle_minute ⇒ Object (readonly)
Returns the value of attribute cycle_minute.
47 48 49 |
# File 'lib/openc3/logs/log_writer.rb', line 47 def cycle_minute @cycle_minute end |
#cycle_time ⇒ Object (readonly)
Returns the value of attribute cycle_time.
38 39 40 |
# File 'lib/openc3/logs/log_writer.rb', line 38 def cycle_time @cycle_time end |
#filename ⇒ String (readonly)
Returns The filename of the packet log.
30 31 32 |
# File 'lib/openc3/logs/log_writer.rb', line 30 def filename @filename end |
#logging_enabled ⇒ true/false (readonly)
Returns Whether logging is enabled.
33 34 35 |
# File 'lib/openc3/logs/log_writer.rb', line 33 def logging_enabled @logging_enabled end |
#mutex ⇒ Mutex (readonly)
Returns Instance mutex protecting file.
53 54 55 |
# File 'lib/openc3/logs/log_writer.rb', line 53 def mutex @mutex end |
#start_time ⇒ Time (readonly)
Returns Time that the current log file started.
50 51 52 |
# File 'lib/openc3/logs/log_writer.rb', line 50 def start_time @start_time end |
Instance Method Details
#close_file(take_mutex = true) ⇒ Object
Closing a log file isn’t critical so we just log an error. NOTE: This also trims the Redis stream to keep a full file’s worth of data in the stream. This is what prevents continuous stream growth.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/openc3/logs/log_writer.rb', line 251 def close_file(take_mutex = true) @mutex.lock if take_mutex begin if @file begin @file.close unless @file.closed? Logger.debug "Log File Closed : #{@filename}" date = [0..7] # YYYYMMDD s3_key = File.join(@remote_log_directory, date, s3_filename) S3Utilities.move_log_file_to_s3(@filename, s3_key) # Now that the file is in S3, trim the Redis stream up until the previous file. # This keeps one file worth of data in Redis as a safety buffer Topic.trim_topic(@redis_topic, @previous_file_redis_offset) if @redis_topic and @previous_file_redis_offset @previous_file_redis_offset = @last_offset rescue Exception => err Logger.instance.error "Error closing #{@filename} : #{err.formatted}" end @file = nil @file_size = 0 @filename = nil end ensure @mutex.unlock if take_mutex end end |
#create_unique_filename(ext = extension) ⇒ Object
implementation details
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/openc3/logs/log_writer.rb', line 168 def create_unique_filename(ext = extension) # Create a filename that doesn't exist attempt = nil while true filename_parts = [attempt] filename_parts.unshift @label if @label filename = File.join(Dir.tmpdir, File.([@label, attempt], ext)) if File.exist?(filename) attempt ||= 0 attempt += 1 else return filename end end end |
#cycle_thread_body ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/openc3/logs/log_writer.rb', line 184 def cycle_thread_body @@cycle_sleeper = Sleeper.new while true start_time = Time.now @@mutex.synchronize do @@instances.each do |instance| # The check against start_time needs to be mutex protected to prevent a packet coming in between the check # and closing the file instance.mutex.synchronize do utc_now = Time.now.utc # Logger.debug("start:#{@start_time.to_f} now:#{utc_now.to_f} cycle:#{@cycle_time} new:#{(utc_now - @start_time) > @cycle_time}") if instance.logging_enabled and ( # Cycle based on total time logging (instance.cycle_time and (utc_now - instance.start_time) > instance.cycle_time) or # Cycle daily at a specific time (instance.cycle_hour and instance.cycle_minute and utc_now.hour == instance.cycle_hour and utc_now.min == instance.cycle_minute and instance.start_time.yday != utc_now.yday) or # Cycle hourly at a specific time (instance.cycle_minute and not instance.cycle_hour and utc_now.min == instance.cycle_minute and instance.start_time.hour != utc_now.hour) ) instance.close_file(false) end end end end # Only check whether to cycle at a set interval run_time = Time.now - start_time sleep_time = CYCLE_TIME_INTERVAL - run_time sleep_time = 0 if sleep_time < 0 break if @@cycle_sleeper.sleep(sleep_time) end end |
#extension ⇒ Object
282 283 284 |
# File 'lib/openc3/logs/log_writer.rb', line 282 def extension '.log'.freeze end |
#first_time ⇒ Object
286 287 288 |
# File 'lib/openc3/logs/log_writer.rb', line 286 def first_time Time.from_nsec_from_epoch(@first_time) end |
#first_timestamp ⇒ Object
294 295 296 |
# File 'lib/openc3/logs/log_writer.rb', line 294 def first_time(). # "YYYYMMDDHHmmSSNNNNNNNNN" end |
#graceful_kill ⇒ Object
162 163 164 |
# File 'lib/openc3/logs/log_writer.rb', line 162 def graceful_kill @cancel_threads = true end |
#last_time ⇒ Object
290 291 292 |
# File 'lib/openc3/logs/log_writer.rb', line 290 def last_time Time.from_nsec_from_epoch(@last_time) end |
#last_timestamp ⇒ Object
298 299 300 |
# File 'lib/openc3/logs/log_writer.rb', line 298 def last_time(). # "YYYYMMDDHHmmSSNNNNNNNNN" end |
#prepare_write(time_nsec_since_epoch, data_length, redis_offset) ⇒ Object
241 242 243 244 245 246 247 |
# File 'lib/openc3/logs/log_writer.rb', line 241 def prepare_write(time_nsec_since_epoch, data_length, redis_offset) # This check includes logging_enabled again because it might have changed since we acquired the mutex if @logging_enabled and (!@file or (@cycle_size and (@file_size + data_length) > @cycle_size)) start_new_file() end @last_offset = redis_offset # This is needed for the redis offset marker entry at the end of the log file end |
#s3_filename ⇒ Object
278 279 280 |
# File 'lib/openc3/logs/log_writer.rb', line 278 def s3_filename "#{}__#{}" + extension end |
#shutdown ⇒ Object
Stop all logging, close the current log file, and kill the logging threads.
150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/openc3/logs/log_writer.rb', line 150 def shutdown stop() @@mutex.synchronize do @@instances.delete(self) if @@instances.length <= 0 @@cycle_sleeper.cancel if @@cycle_sleeper OpenC3.kill_thread(self, @@cycle_thread) if @@cycle_thread @@cycle_thread = nil end end end |
#start ⇒ Object
Starts a new log file by closing the existing log file. New log files are not created until packets are written by #write so this does not immediately create a log file on the filesystem.
140 141 142 |
# File 'lib/openc3/logs/log_writer.rb', line 140 def start @mutex.synchronize { close_file(false); @logging_enabled = true } end |
#start_new_file ⇒ Object
Starting a new log file is a critical operation so the entire method is wrapped with a rescue and handled with handle_critical_exception Assumes mutex has already been taken
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/openc3/logs/log_writer.rb', line 223 def start_new_file close_file(false) # Start log file @filename = create_unique_filename() @file = File.new(@filename, 'wb') @file_size = 0 @start_time = Time.now.utc @first_time = nil @last_time = nil Logger.debug "Log File Opened : #{@filename}" rescue => err Logger.error "Error starting new log file: #{err.formatted}" @logging_enabled = false OpenC3.handle_critical_exception(err) end |
#stop ⇒ Object
Stops all logging and closes the current log file.
145 146 147 |
# File 'lib/openc3/logs/log_writer.rb', line 145 def stop @mutex.synchronize { @logging_enabled = false; close_file(false) } end |