Module: Locd::Newsyslog

Includes:
NRSER::Log::Mixin
Defined in:
lib/locd/newsyslog.rb

Overview

Use newsyslog to rotate Agent log files.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

DEFAULT_WORKDIR =

Default place to work out of.

Pathname.new( '~/.locd/tmp/newsyslog' ).expand_path

Class Method Summary collapse

Class Method Details

.log_dirObject



333
334
335
# File 'lib/locd/newsyslog.rb', line 333

def self.log_dir
  @log_dir ||= Locd.config.log_dir / Locd::ROTATE_LOGS_LABEL
end

.log_to_file(&block) ⇒ Object



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/locd/newsyslog.rb', line 338

def self.log_to_file &block
  time = Time.now.iso8601
  date = time.split( 'T', 2 )[0]
  
  # Like
  # 
  #   ~/.locd/log/com.nrser.locd.rotate-logs/2018-02-14/2018-02-14T03:46:57+08:00.log
  # 
  path = self.log_dir / date / "#{ time }.log"
  
  FileUtils.mkdir_p( path.dirname ) unless path.dirname.exist?

  appender = SemanticLogger.add_appender \
    file_name: path.to_s
  
  begin
    result = block.call
  ensure
    SemanticLogger.remove_appender appender
  end
end

.run(agent, workdir: DEFAULT_WORKDIR) ⇒ Cmds::Result?

Run newsyslog for an agent to rotate it's log files (if present and needed).



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/locd/newsyslog.rb', line 215

def self.run agent, workdir: DEFAULT_WORKDIR
  logger.debug "Calling {.run_for} agent #{ agent.label }...",
    agent: agent,
    workdir: workdir
  
  # Make sure `workdir` is a {Pathname}
  workdir = workdir.to_pn
  
  # Collect the unique log paths
  log_paths = agent.log_paths
  
  if log_paths.empty?
    logger.info "Agent #{ agent.label } has no log files."
    return nil
  end
  
  logger.info "Setting up to run `newsyslog` for agent `#{ agent.label }`",
    log_paths: log_paths.map( &:to_s )
  
  # NOTE  Total race condition since agent may be started after this and
  #       before we rotate... f-it for now.
  pid_path = nil
  if pid = agent.pid( refresh: true )
    logger.debug "Agent is running", pid: pid
    
    pid_path = workdir / 'pids' / "#{ agent.label }.pid"
    FileUtils.mkdir_p( pid_path.dirname ) unless pid_path.dirname.exist?
    pid_path.write pid
    
    logger.debug "Wrote PID #{ pid } to file", pid_path: pid_path
  end
  
  entries = log_paths.map { |log_path|
    Entry.new log_path: log_path, pid_path: pid_path
  }
  conf_contents = entries.map( &:render ).join( "\n" ) + "\n"
  
  logger.debug "Generated conf entries",
    entries.map { |entry|
      [
        entry.log_path.to_s,
        entry.instance_variables.map_values { |name|
          entry.instance_variable_get name
        }
      ]
    }.to_h
  
  conf_path = workdir / 'confs' / "#{ agent.label }.conf"
  FileUtils.mkdir_p( conf_path.dirname ) unless conf_path.dirname.exist?
  conf_path.write conf_contents
  
  logger.debug "Wrote entries to conf file",
    conf_path: conf_path.to_s,
    conf_contents: conf_contents
  
  cmd = Cmds.new "newsyslog <%= opts %>", kwds: {
    opts: {
      # Turn on verbose output
      v: true,
      # Point to the conf file
      f: conf_path,
      # Don't run as root
      r: true,
    }
  }
  
  logger.info "Executing `#{ cmd.prepare }`"
  
  result = cmd.capture
  
  if result.ok?
    logger.info \
      "`newsyslog` command succeeded for agent `#{ agent.label }`" +
      ( result.out.empty? ?
        nil :
        ", output:\n" + result.out.indent(1, indent_string: '> ') )
    
    FileUtils.rm( pid_path ) if pid_path
    FileUtils.rm conf_path
    
    logger.debug "Files cleaned up."
    
  else
    logger.error "`newsyslog` command failed for agent #{ agent.label }",
      result: result.to_h
  end
  
  logger.debug "Returning",
    result: result.to_h
  
  result
end

.run_all(workdir: DEFAULT_WORKDIR, trim_logs: true) ⇒ Hash<Locd::Agent, Cmds::Result?>

Call run for each agent.



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/locd/newsyslog.rb', line 316

def self.run_all workdir: DEFAULT_WORKDIR, trim_logs: true
  log_to_file do
    Locd::Agent.all.values.
      reject { |agent|
        agent.label == Locd::ROTATE_LOGS_LABEL
      }.
      map { |agent|
        [agent, run( agent, workdir: workdir )]
      }.
      to_h.
      tap { |_|
        self.trim_logs if trim_logs
      }
  end
end

.trim_logs(keep_days: 7) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/locd/newsyslog.rb', line 361

def self.trim_logs keep_days: 7
  logger.info "Removing old self run log directories...",
    log_dir: self.log_dir.to_s,
    keep_days: keep_days
  
  unless self.log_dir.directory?
    logger.warn "{Locd::Newsyslog.log_dir} does not exist!",
      log_dir: self.log_dir
    return nil
  end
  
  day_dirs = self.log_dir.entries.select { |dir_name|
    dir_name.to_s =~ /\d{4}\-\d{2}\-\d{2}/ &&
      (self.log_dir / dir_name).directory?
  }
  
  to_remove = day_dirs.sort[0...(-1 * keep_days)]
  
  if to_remove.empty?
    logger.info "No old self run log directories to remove."
  else
    to_remove.each { |dir_name|
      path = self.log_dir / dir_name
      
      logger.info "Removing old day directory",
        path: path
      
      FileUtils.rm_rf path
    }
    
    logger.info "Done.",
      log_dir: self.log_dir.to_s,
      keep_days: keep_days
  end
  
  to_remove
end