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:
-
severity : INFO or ERROR
-
time : ISO8601 Datetime of operation execution
-
op : class name of the operation executed
-
op_took : Execution duration of the operation
-
op_data
: Dump of operation input data -
context : Execution context, through its `h` information contract (IC)
Dumping of operation data follows the following duck typing conventions:
-
If the operation instance responds to `to_trail`, this data is taken
-
If the operation instance responds to `input`, this data is taken
-
If the operation instance responds to `request`, this data is taken
-
Otherwise
op_data
is a JSON null
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
Public Class Methods
# 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
# 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
# File lib/startback/audit/trailer.rb, line 122 def blacklist_rx @blacklist_rx ||= Regexp.new( options[:blacklist].split(/\s+/).join("|"), Regexp::IGNORECASE ) end
# File lib/startback/audit/trailer.rb, line 96 def op_context(op) sanitize(op.respond_to?(:context, false) ? op.context.to_h : {}) end
# 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
# 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
# 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
# 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