require 'soar_auditing_provider_api' require 'soar_auditing_format' require 'soar_configured_factory' require 'soar_flow' require 'time' require 'securerandom' module SoarAuditingProvider class AuditingProvider < SoarAuditingProviderAPI::AuditingProviderAPI private #Aliases for bypassing overridden methods when accessing underlying super class api alias :super_debug :debug alias :super_info :info alias :super_warn :warn alias :super_error :error alias :super_fatal :fatal public DEFAULT_FLOW_ID_GENERATOR = -> { SoarFlow::ID::generate_flow_id } unless defined?(DEFAULT_FLOW_ID_GENERATOR) attr_accessor :startup_flow_id attr_accessor :service_identifier attr_reader :configuration def initialize(configuration) validate_provider_configuration(configuration) @configuration = configuration @flow_id_generator = @configuration["flow_id_generator"] || DEFAULT_FLOW_ID_GENERATOR super(create_auditors(configuration)) select_auditor(configuration['default_nfrs']) create_auditing_worker @buffer_overflow_count = 0 install_at_exit_handler initialize_metrics end def select_auditor(nfrs) select(nfrs) set_audit_level(@configuration['level'].to_sym) end def set_audit_level(level) @auditor.set_audit_level(level) rescue ArgumentError $stderr.puts 'Invalid auditing level' raise end def debug(data, flow_identifier = nil) audit(:debug, data, flow_identifier) end def info(data, flow_identifier = nil) audit(:info, data, flow_identifier) end alias_method :<<, :info def warn(data, flow_identifier = nil) audit(:warn, data, flow_identifier) end def error(data, flow_identifier = nil) audit(:error, data, flow_identifier) end def fatal(data, flow_identifier = nil) audit(:fatal, data, flow_identifier) end def detailed_status detail = basic_status_detail detail = detail.merge(verbose_status_detail) if @configuration['verbose_detail'] detail end def flush(timeout: 1) @worker.flush(timeout: timeout) end def audit_exception(exception:, level: :error, flow_id: nil, message: nil) exception_message = "#{exception.class}: #{exception.message}" exception_message = "#{message} - #{exception_message}" if message exception_message = exception_message + ":\n\t" + exception.backtrace.join("\n\t") if ENV['RACK_ENV'] == 'development' level = :error if not is_valid_audit_level?(level) send(level,exception_message,flow_id) end private def prepend_caller_information(data) if 'true' == @configuration['add_caller_source_location'] caller_key_value_pair = SoarAuditingFormatter::Formatter.optional_field_format("caller_source_location","#{caller_locations(2,1)[0]}") data = "#{caller_key_value_pair} #{data}" end data end def install_at_exit_handler if 'true' == @configuration['install_exit_handler'] Kernel.at_exit do exit_cleanup end end end def exit_cleanup(exception = nil) audit_exception(exception: exception, level: :fatal, flow_id: @startup_flow_id) if exception info("Application exit",@startup_flow_id) flush end def audit(level, data, flow_identifier = nil) flow_identifier ||= @flow_id_generator.call formatted_data = format(level, prepend_caller_information(data), flow_identifier) audit_formatted(level, formatted_data) end def audit_formatted(level, data) if 'true' == @configuration['direct_auditor_call'] super_class_caller(level, data) else enqueue(level, data) end end def enqueue(level, data) @worker.enqueue(level, data) @enqueued_audit_events += 1 rescue AuditingOverflowError increase_buffer_overflow_count $stderr.puts "Audit buffer full, unable to audit event : #{level} : #{data}" end def increase_buffer_overflow_count @buffer_overflow_count += 1 end def validate_provider_configuration(configuration) raise 'Missing auditing level' if configuration['level'].nil? end def format(level, data, flow_identifier) SoarAuditingFormatter::Formatter.format(level,@service_identifier,flow_identifier,Time.now.utc.iso8601(3),data) end def create_auditing_worker @worker = AuditingWorker.instance @worker.configure(queue_worker_configuration: @configuration['queue_worker'], auditor_audit_method: method(:super_class_caller)) @worker.start end def super_class_caller(level, data) send("super_#{level}",data) end def create_auditors(configuration) auditor_factory = SoarConfiguredFactory::ConfiguredFactory.new(configuration['auditors']) auditors = {} configuration['auditors'].each do |auditor_name, auditor_configuration| raise 'Missing auditor configuration' if auditor_configuration.nil? auditor = create_auditor(auditor_factory,auditor_name) auditors[auditor] = { 'name' => auditor_name, 'nfrs' => auditor_configuration['nfrs'] } end auditors rescue $stderr.puts 'Failure initializing auditor' raise end def create_auditor(auditor_factory,auditor_name) auditor_factory.create(auditor_name) rescue $stderr.puts 'Invalid auditor configuration' raise end def initialize_metrics @startup_timestamp = Time.now.utc.iso8601(3) @enqueued_audit_events = 0 end def verbose_status_detail { 'worker' => @worker.status_detail } end def basic_status_detail { 'audit_buffer_overflows' => @buffer_overflow_count, 'enqueued_audit_events' => @enqueued_audit_events, 'startup_flow_id' => @startup_flow_id, 'startup_timestamp' => @startup_timestamp } end def is_valid_audit_level?(level) [:debug, :info, :warn, :error, :fatal].include?(level) end end end