Class: Kotoshu::Integrity::AuditLog
- Inherits:
-
Object
- Object
- Kotoshu::Integrity::AuditLog
- Defined in:
- lib/kotoshu/integrity/audit_log.rb
Overview
Append-only JSON audit log of every resource download.
Each entry is one JSON object per line, written to audit.log
under the configured Kotoshu home directory. The log is consulted
by users when investigating "what did Kotoshu fetch?" and by CI
for reproducibility audits.
Statuses:
"verified" — content matched manifest entry's SHA-256
"unverified" — no manifest entry available; bytes trusted as-is
"mismatch" — SHA-256 mismatch (also raises IntegrityError)
"missing" — attempted download failed (network, 404, etc.)
The log is opened, appended, and closed per entry — no long-lived file handle. Writes are line-buffered and fsync'd so the record survives a crash mid-batch.
Rotation
When the current file exceeds rotation_policy.max_bytes, the
policy produces a rename plan (see RotationPolicy#plan_for) and
the log shifts the existing rotations up by one slot before
writing the new entry to a fresh current file. The total on-disk
footprint is bounded at +max_bytes * (rotations + 1)+.
Rotation happens under an exclusive flock on a sibling lockfile (+audit.log.lock+) — not on the log itself — because the log path moves during rotation and the lock would otherwise travel with it.
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
-
#rotation_policy ⇒ Object
readonly
Returns the value of attribute rotation_policy.
Class Method Summary collapse
-
.default_path ⇒ Object
Default location: $XDG_DATA_HOME/kotoshu/audit.log (~/.local/share/kotoshu/audit.log), or $KOTOSHU_AUDIT_LOG.
Instance Method Summary collapse
- #clear! ⇒ Object
-
#each(&block) ⇒ Object
Iterate every recorded entry (parsed Hashes) across the current log and all rotations, newest-first.
- #entries ⇒ Object
-
#initialize(path: self.class.default_path, rotation_policy: nil) ⇒ AuditLog
constructor
A new instance of AuditLog.
-
#record(url:, status:, size: nil, sha256: nil, manifest_sha256: nil, resource_id: nil) ⇒ Object
Record one download attempt.
Constructor Details
#initialize(path: self.class.default_path, rotation_policy: nil) ⇒ AuditLog
Returns a new instance of AuditLog.
50 51 52 53 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 50 def initialize(path: self.class.default_path, rotation_policy: nil) @path = path @rotation_policy = rotation_policy end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
44 45 46 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 44 def path @path end |
#rotation_policy ⇒ Object (readonly)
Returns the value of attribute rotation_policy.
44 45 46 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 44 def rotation_policy @rotation_policy end |
Class Method Details
.default_path ⇒ Object
Default location: $XDG_DATA_HOME/kotoshu/audit.log (~/.local/share/kotoshu/audit.log), or $KOTOSHU_AUDIT_LOG.
40 41 42 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 40 def self.default_path Kotoshu::Paths.audit_log_path end |
Instance Method Details
#clear! ⇒ Object
109 110 111 112 113 114 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 109 def clear! with_exclusive_lock do FileUtils.rm_f(@path) clear_rotations end end |
#each(&block) ⇒ Object
Iterate every recorded entry (parsed Hashes) across the current log and all rotations, newest-first.
Newest-first means: within each file, lines are yielded in reverse write order (the last appended entry first); files are visited in order current, .1, .2, ... (newest file first). This guarantees a strictly decreasing write-time ordering across the whole log, which is what an auditor scanning "what did kotoshu fetch most recently?" expects.
94 95 96 97 98 99 100 101 102 103 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 94 def each(&block) return enum_for(:each) unless block each_file_reversed(@path, &block) return unless rotation_policy&.rotations&.positive? 1.upto(rotation_policy.rotations) do |n| each_file_reversed("#{@path}.#{n}", &block) end end |
#entries ⇒ Object
105 106 107 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 105 def entries each.to_a end |
#record(url:, status:, size: nil, sha256: nil, manifest_sha256: nil, resource_id: nil) ⇒ Object
Record one download attempt. Returns the written entry hash.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/kotoshu/integrity/audit_log.rb', line 63 def record(url:, status:, size: nil, sha256: nil, manifest_sha256: nil, resource_id: nil) entry = { timestamp: Time.now.utc.iso8601, url: url, resource_id: resource_id, size: size, sha256: sha256, manifest_sha256: manifest_sha256, status: status } FileUtils.mkdir_p(File.dirname(@path)) with_exclusive_lock do rotate_if_needed! File.open(@path, "a", encoding: "UTF-8") do |f| f.write("#{entry.to_json}\n") f.fsync end end entry end |