Module: Twin::Journal

Defined in:
lib/twin/journal.rb

Overview

Append-only sync journal: one JSON line per synced job in ~/.local/state/twin/log.jsonl. Answers "did yesterday's sync actually run, and what did it do?" — and stays machine-readable (jq/grubber). Journal failures never break a sync; they degrade to a warning.

Class Method Summary collapse

Class Method Details

.log_pathObject



17
# File 'lib/twin/journal.rb', line 17

def log_path = File.join(state_dir, "log.jsonl")

.record(job, success:, transferred:, output: nil) ⇒ Object

Record one job result. Dry-runs are not journaled.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/twin/journal.rb', line 20

def record(job, success:, transferred:, output: nil)
  entry = {
    ts:      Time.now.iso8601,
    program: job.program,
    path:    job.path,
    target:  job.target,
    ok:      success,
    changed: transferred,
  }
  unless success
    entry[:error] = output.to_s.lines.map(&:strip).reject(&:empty?).last.to_s[0, 200]
  end
  FileUtils.mkdir_p(state_dir)
  File.open(log_path, "a") { |f| f.puts(JSON.generate(entry)) }
rescue SystemCallError => e
  warn "journal: #{e.message}" unless @warned
  @warned = true
end

.state_dirObject



13
14
15
# File 'lib/twin/journal.rb', line 13

def state_dir
  ENV["TWIN_STATE_DIR"] || File.join(Dir.home, ".local", "state", "twin")
end

.tail(n) ⇒ Object

Last n entries, oldest first. Unparseable lines are skipped.



40
41
42
43
44
45
46
47
# File 'lib/twin/journal.rb', line 40

def tail(n)
  return [] unless File.exist?(log_path)
  File.readlines(log_path).last(n).filter_map do |line|
    JSON.parse(line)
  rescue JSON::ParserError
    nil
  end
end