# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'logger' require 'ougai' require 'singleton' require 'contrast/extension/module' require 'contrast/logger/application' require 'contrast/logger/format' require 'contrast/logger/request' require 'contrast/logger/time' require 'contrast/components/config' require 'contrast/utils/log_utils' module Contrast # Used as a wrapper around our logging. The module option specifically adds in a new method for error that raises the # logged exception, used in testing so that we can see if anything unexpected happens without it being swallowed # while still providing safe options for customers. module Logger # For development set following env var to raise logged exceptions instead of just logging. if ENV['CONTRAST__AGENT__RUBY_MORE_COWBELL'] Ougai::Logger.class_eval do alias_method :cs__error, :error alias_method :cs__warn, :warn def error *args, **kwargs if kwargs.empty? cs__error(*args) else cs__error(*args, **kwargs) end args.each { |arg| raise(arg) if arg && arg.cs__class < Exception } end end end # 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 Log include Singleton include Contrast::Utils::LogUtils attr_reader :previous_path, :previous_level def initialize update end # Given new settings from TeamServer, update our logging to use the new file and level, assuming they weren't # set by local configuration. # # @param log_file [String] the file to which to log, as provided by TeamServer settings # @param log_level [String] the level at which to log, as provided by TeamServer settings def update log_file = nil, log_level = nil current_path = find_valid_path(log_file) current_level_const = find_valid_level(log_level) path_change = current_path != previous_path level_change = current_level_const != previous_level # don't needlessly recreate logger return if @_logger && !(path_change || level_change) @previous_path = current_path @previous_level = current_level_const progname = Contrast::CONFIG.root.agent.logger.progname @_logger = build(path: current_path, level_const: current_level_const, progname: progname) # If we're logging to a new path, then let's start it w/ our helpful # data gathering messages log_update if path_change rescue StandardError => e if logger logger.error('Unable to process update to LoggerManager.', e) else puts('Unable to process update to LoggerManager.') raise(e) if ENV['CONTRAST__AGENT__RUBY_MORE_COWBELL'] puts(e.message) puts(e.backtrace.join("\n")) end end def logger @_logger end # StringIO is a valid path because it logs directly to a string buffer def write_permission? path return false if path.nil? return true if path.is_a?(StringIO) return File.writable?(path) if File.exist?(path) dir_name = File.dirname(File.absolute_path(path)) File.writable?(dir_name) end end end end