class Startback::Audit::Trailer

Log & Audit trail abstraction, that can be registered as an around hook on OperationRunner and as an actual logger on Context instances.

The trail is outputted as JSON lines, using a Logger on the “device” passed at construction. The following JSON entries are dumped:

Dumping of operation data follows the following duck typing conventions:

By contributing to the Context's `h` IC, users can easily dump information that makes sense (such as the operation execution requester).

The class implements a sanitization process when dumping the context and operation data. Blacklisted words taken in construction options are used to prevent dumping hash keys that match them (insentively). Default stop words are equivalent to:

Trailer.new("/var/log/trail.log", {
  blacklist: "token password secret credential"
})

Please note that the sanitization process does not apply recursively if the operation data is hierarchic. It only applies to the top object of Hash and [Hash]. Use `Operation#to_trail` to fine-tune your audit trail.

Given that this Trailer is intended to be used as around hook on an `OperationRunner`, operations that fail at construction time will not be trailed at all, since they can't be ran in the first place. This may lead to trails not containing important errors cases if operations check their input at construction time.

Constants

DEFAULT_OPTIONS

Attributes

logger[R]
options[R]

Public Class Methods

new(device, options = {}) click to toggle source
# File lib/startback/audit/trailer.rb, line 58
def initialize(device, options = {})
  @options = DEFAULT_OPTIONS.merge(options)
  @logger = ::Logger.new(device, 'daily')
  @logger.formatter = Support::LogFormatter.new
end

Public Instance Methods

call(runner, op) { || ... } click to toggle source
# File lib/startback/audit/trailer.rb, line 65
def call(runner, op)
  result = nil
  time = Benchmark.realtime{ result = yield }
  logger.info(op_to_trail(op, time))
  result
rescue => ex
  logger.error(op_to_trail(op, time, ex))
  raise
end

Protected Instance Methods

blacklist_rx() click to toggle source
# File lib/startback/audit/trailer.rb, line 122
def blacklist_rx
  @blacklist_rx ||= Regexp.new(
    options[:blacklist].split(/\s+/).join("|"),
    Regexp::IGNORECASE
  )
end
op_context(op) click to toggle source
# File lib/startback/audit/trailer.rb, line 96
def op_context(op)
  sanitize(op.respond_to?(:context, false) ? op.context.to_h : {})
end
op_data(op) click to toggle source
# File lib/startback/audit/trailer.rb, line 100
def op_data(op)
  data = if op.respond_to?(:to_trail, false)
    op.to_trail
  elsif op.respond_to?(:input, false)
    op.input
  elsif op.respond_to?(:request, false)
    op.request
  end
  sanitize(data)
end
op_name(op) click to toggle source
# File lib/startback/audit/trailer.rb, line 88
def op_name(op)
  case op
  when String then op
  when Class  then op.name
  else op.class.name
  end
end
op_to_trail(op, time, ex = nil) click to toggle source
# File lib/startback/audit/trailer.rb, line 77
def op_to_trail(op, time, ex = nil)
  log_msg = {
    op_took: time ? time.round(8) : nil,
    op: op_name(op),
    context: op_context(op),
    op_data: op_data(op)
  }
  log_msg[:error] = ex if ex
  log_msg
end
sanitize(data) click to toggle source
# File lib/startback/audit/trailer.rb, line 111
def sanitize(data)
  case data
  when Hash, OpenStruct
    data.dup.delete_if{|k| k.to_s =~ blacklist_rx }
  when Enumerable
    data.map{|elm| sanitize(elm) }.compact
  else
    data
  end
end