Class: Textus::Application::Reads::Audit

Inherits:
Object
  • Object
show all
Defined in:
lib/textus/application/reads/audit.rb

Overview

Queries .textus/audit.log. Filters: key, zone, role, verb, since, correlation_id, limit. Reads the log file as JSON-Lines (legacy TSV rows produce nil and are skipped).

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(manifest:, root:, audit_log: nil) ⇒ Audit

Returns a new instance of Audit.



11
12
13
14
15
16
# File 'lib/textus/application/reads/audit.rb', line 11

def initialize(manifest:, root:, audit_log: nil)
  @manifest  = manifest
  @root      = root
  @log_path  = File.join(root, "audit.log")
  @audit_log = audit_log
end

Class Method Details

.parse_since(str, now: Time.now.utc) ⇒ Object

Accepts ISO8601 (“2026-01-15”, “2026-01-15T10:00:00Z”) or a relative offset matching /A(d+)()z/. Returns nil for unparseable input.



49
50
51
52
53
54
55
56
# File 'lib/textus/application/reads/audit.rb', line 49

def self.parse_since(str, now: Time.now.utc)
  return nil if str.nil? || str.empty?
  return Time.parse(str) if str =~ /\A\d{4}-\d{2}-\d{2}/

  m = str.match(/\A(\d+)([smhd])\z/) or return nil
  mult = { "s" => 1, "m" => 60, "h" => 3600, "d" => 86_400 }[m[2]]
  now - (m[1].to_i * mult)
end

Instance Method Details

#call(key: nil, zone: nil, role: nil, verb: nil, since: nil, seq_since: nil, correlation_id: nil, limit: nil) ⇒ Object

rubocop:disable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/textus/application/reads/audit.rb', line 19

def call(key: nil, zone: nil, role: nil, verb: nil, since: nil, seq_since: nil, correlation_id: nil, limit: nil)
  check_cursor_expiry!(seq_since)

  files = all_log_files
  return [] if files.empty?

  rows = []
  files.each do |file|
    File.foreach(file) do |line|
      parsed = parse_row(line.chomp)
      next unless parsed
      next if key && parsed["key"] != key
      next if role && parsed["role"] != role
      next if verb && parsed["verb"] != verb
      next if zone && !key_in_zone?(parsed["key"], zone)
      next if since && (parsed["ts"].nil? || Time.parse(parsed["ts"]) < since)
      next if seq_since && (parsed["seq"].nil? || parsed["seq"] <= seq_since)
      next if correlation_id && parsed.dig("extras", "correlation_id") != correlation_id

      rows << parsed
      break if limit && rows.length >= limit
    end
    break if limit && rows.length >= limit
  end
  rows
end