# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'logger' cs__scoped_require 'contrast/core_extensions/module' cs__scoped_require 'contrast/components/interface' module Contrast module Agent # This class functions to serve as a wrapper around our logging, as we need # to be able to dynamically update level based on updates to TeamServer. class LoggerManager include Contrast::Components::Interface access_component :app_context, :logging, :config DEFAULT_NAME = 'contrast.log' DEFAULT_LEVEL = ::Logger::INFO VALID_LEVELS = %w[INFO WARN DEBUG FATAL UNKNOWN ERROR].cs__freeze STDOUT_STR = 'STDOUT' STDERR_STR = 'STDERR' attr_reader :current, :previous_path, :previous_level def initialize update rescue StandardError => e Contrast::Agent::FeatureState::Logging::FALLBACK.puts 'Unable to initialize regular logger in LoggerManager.' Contrast::Agent::FeatureState::Logging::FALLBACK.puts e.backtrace.join(Contrast::Utils::ObjectShare::NEW_LINE) end def update log_file = nil, log_level = nil config = CONFIG.root.agent.logger config_path = config.path&.length.to_i.positive? ? config.path : nil config_level = config.level&.length&.positive? ? config.level : nil # config > settings > default path = valid_path(config_path || log_file) level_const = valid_level(config_level || log_level) # don't needlessly recreate logger return if @current && (path == previous_path) && (level_const == previous_level) @previous_path = path @previous_level = level_const @current = build(path: path, level_const: level_const) @current.debug { 'Initialized new contrast agent logger' } self rescue StandardError => e Contrast::Agent::FeatureState::Logging::FALLBACK.puts 'Unable to process update to LoggerManager.' Contrast::Agent::FeatureState::Logging::FALLBACK.puts e.backtrace.join(Contrast::Utils::ObjectShare::NEW_LINE) end private def default_progname "Contrast #{ APP_CONTEXT.name }" end def build path: STDOUT_STR, progname: default_progname, level_const: DEFAULT_LEVEL current = case path when STDOUT_STR, STDERR_STR ::Logger.new(Object.cs__const_get(path)) else ::Logger.new(path) end current.progname = progname current.level = level_const current.formatter = proc do |severity_f, datetime_f, progname_f, msg_f| "#{ datetime_f.strftime('%Y-%m-%d %H:%M:%S,%L') } [#{ progname_f }] #{ severity_f } - #{ msg_f }\n" end current end def valid_path path path = path.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : path return path if path == STDOUT_STR return path if path == STDERR_STR path = DEFAULT_NAME if path.empty? if Contrast::Utils::IOUtil.write_permission?(path) path else if File.directory?(path) Contrast::Agent::FeatureState::Logging::FALLBACK.puts "Unable to create log in directory: #{ path }. \n Writing to standard out instead" else Contrast::Agent::FeatureState::Logging::FALLBACK.puts "Unable to write to log file: #{ path } \n Writing to standard out instead" end STDOUT_STR end end def valid_level level level = level.nil? ? DEFAULT_LEVEL : level level = 'DEBUG' if level == 'TRACE' if VALID_LEVELS.include?(level) Object.cs__const_get("::Logger::#{ level }") else DEFAULT_LEVEL end rescue StandardError DEFAULT_LEVEL end end end end