Skip to content

Commit

Permalink
Migrate rex/logging into Core from Msf namespace
Browse files Browse the repository at this point in the history
Using Rex' various gems without Msf will result in errors when the
logging subsystem is undefined (as that remained in Msf during the
great Rex excision). This manifests in rex-socket as noted by
@zeroSteiner in rapid7/rex-socket#38.

Address the dependency problem by moving rex/logging into this gem
which is already required by rex-socket and other descendants.

Testing:
 None - this is a quick-n-dirty subdirectory move. If this works,
someone with real git skill should migrate the relevant history
of the code; as losing that stuff results in people not knowing
whom to ask when the time comes to fix some deeply-bored bug.
  • Loading branch information
RageLtMan committed Dec 4, 2022
1 parent 1e43b17 commit cbd267a
Show file tree
Hide file tree
Showing 10 changed files with 553 additions and 0 deletions.
67 changes: 67 additions & 0 deletions lib/rex/logging.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- coding: binary -*-
module Rex::Logging


#
# Log severities
#
LOG_ERROR = :error
LOG_DEBUG = :debug
LOG_INFO = :info
LOG_WARN = :warn
LOG_RAW = :raw

##
#
# Log levels
#
##

#
# LEV_0 - Default
#
# This log level is the default log level if none is specified. It should be
# used when a log message should always be displayed when logging is enabled.
# Very few log messages should occur at this level aside from necessary
# information logging and error/warning logging. Debug logging at level zero
# is not advised.
#
LEV_0 = 0

#
# LEV_1 - Extra
#
# This log level should be used when extra information may be needed to
# understand the cause of an error or warning message or to get debugging
# information that might give clues as to why something is happening. This
# log level should be used only when information may be useful to understanding
# the behavior of something at a basic level. This log level should not be
# used in an exhaustively verbose fashion.
#
LEV_1 = 1

#
# LEV_2 - Verbose
#
# This log level should be used when verbose information may be needed to
# analyze the behavior of the framework. This should be the default log
# level for all detailed information not falling into LEV_0 or LEV_1.
# It is recommended that this log level be used by default if you are
# unsure.
#
LEV_2 = 2

#
# LEV_3 - Insanity
#
# This log level should contain very verbose information about the
# behavior of the framework, such as detailed information about variable
# states at certain phases including, but not limited to, loop iterations,
# function calls, and so on. This log level will rarely be displayed,
# but when it is the information provided should make it easy to analyze
# any problem.
#
LEV_3 = 3

require 'rex/logging/log_dispatcher'
end
216 changes: 216 additions & 0 deletions lib/rex/logging/log_dispatcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# -*- coding: binary -*-
require 'rex/sync'

module Rex
module Logging

###
#
# The log dispatcher associates log sources with log sinks. A log source
# is a unique identity that is associated with one and only one log sink.
# For instance, the framework-core registers the 'core'
#
###
class LogDispatcher

#
# Creates the global log dispatcher instance and initializes it for use.
#
def initialize()
self.log_sinks = {}
self.log_levels = {}
self.log_sinks_lock = Mutex.new
end

#
# Returns the sink that is associated with the supplied source.
#
def [](src)
sink = nil

log_sinks_lock.synchronize {
sink = log_sinks[src]
}

return sink
end

#
# Calls the source association routie.
#
def []=(src, sink)
store(src, sink)
end

#
# Associates the supplied source with the supplied sink. If a log level
# has already been defined for the source, the level argument is ignored.
# Use set_log_level to alter it.
#
def store(src, sink, level = 0)
log_sinks_lock.synchronize {
if (log_sinks[src] == nil)
log_sinks[src] = sink

set_log_level(src, level) if (log_levels[src] == nil)
else
raise(
RuntimeError,
"The supplied log source #{src} is already registered.",
caller)
end
}
end

#
# Removes a source association if one exists.
#
def delete(src)
sink = nil

log_sinks_lock.synchronize {
sink = log_sinks[src]

log_sinks.delete(src)
}

if (sink)
sink.cleanup

return true
end

return false
end

#
# Performs the actual log operation against the supplied source
#
def log(sev, src, level, msg)
log_sinks_lock.synchronize {
if ((sink = log_sinks[src]))
next if (log_levels[src] and level > log_levels[src])

sink.log(sev, src, level, msg)
end
}
end

#
# This method sets the log level threshold for a given source.
#
def set_level(src, level)
log_levels[src] = level.to_i
end

#
# This method returns the log level threshold of a given source.
#
def get_level(src)
log_levels.fetch(src, DEFAULT_LOG_LEVEL)
end

attr_accessor :log_sinks, :log_sinks_lock # :nodoc:
attr_accessor :log_levels # :nodoc:
end

end
end

# An instance of the log dispatcher exists in the global namespace, along
# with stubs for many of the common logging methods. Various sources can
# register themselves as a log sink such that logs can be directed at
# various targets depending on where they're sourced from. By doing it
# this way, things like sessions can use the global logging stubs and
# still be directed at the correct log file.
#
###
ExceptionCallStack = "__EXCEPTCALLSTACK__"

BACKTRACE_LOG_LEVEL = 3 # Equal to LEV_3
DEFAULT_LOG_LEVEL = 0 # Equal to LEV_3

def dlog(msg, src = 'core', level = 0)
$dispatcher.log(LOG_DEBUG, src, level, msg)
end

# Logs errors in a standard format for each Log Level.
#
# @param msg [String] Contains message from the developer explaining why an error was encountered.
# Can also be an +Exception+, in which case a log is built from the +Exception+ with no accompanying message.
#
# @param src [String] Used to indicate where the error is originating from. Most commonly set to 'core'.
#
# @param log_level [Integer] Indicates the level of logging the message should be recorded at. If log_level is greater than
# the global log level set for +src+, then the log is not recorded.
#
# @param error [Exception] Exception of an error that needs to be logged. For all log messages, the class and message of
# an exception is added to a log message. If the global log level set for +src+ is greater than +BACKTRACE_LOG_LEVEL+,
# then the stack trace for an error is also added to the log message.
#
# (Eg Loop Iterations, Variables, Function Calls).
#
# @return [NilClass].
def elog(msg, src = 'core', log_level = 0, error: nil)
error = msg.is_a?(Exception) ? msg : error

if error.nil? || !error.is_a?(Exception)
$dispatcher.log(LOG_ERROR, src, log_level, msg)
else
error_details = "#{error.class} #{error.message}"
if get_log_level(src) >= BACKTRACE_LOG_LEVEL
if error.backtrace
error_details << "\nCall stack:\n#{error.backtrace.join("\n")}"
else
error_details << "\nCall stack:\nNone"
end
end

if msg.is_a?(Exception)
$dispatcher.log(LOG_ERROR, src, log_level,"#{error_details}")
else
$dispatcher.log(LOG_ERROR, src, log_level,"#{msg} - #{error_details}")
end
end
end

def wlog(msg, src = 'core', level = 0)
$dispatcher.log(LOG_WARN, src, level, msg)
end

def ilog(msg, src = 'core', level = 0)
$dispatcher.log(LOG_INFO, src, level, msg)
end

def rlog(msg, src = 'core', level = 0)
if (msg == ExceptionCallStack)
msg = "\nCall stack:\n" + $@.join("\n") + "\n"
end

$dispatcher.log(LOG_RAW, src, level, msg)
end

def log_source_registered?(src)
($dispatcher[src] != nil)
end

def register_log_source(src, sink, level = nil)
$dispatcher[src] = sink

set_log_level(src, level) if (level)
end

def deregister_log_source(src)
$dispatcher.delete(src)
end

def set_log_level(src, level)
$dispatcher.set_level(src, level)
end

def get_log_level(src)
$dispatcher.get_level(src)
end

# Creates the global log dispatcher
$dispatcher = Rex::Logging::LogDispatcher.new
39 changes: 39 additions & 0 deletions lib/rex/logging/log_sink.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: binary -*-

module Rex
module Logging

###
#
# This abstract interface is what must be implemented by any class
# that would like to register as a log sink on a given LogDispatcher
# instance, such as the Framework object.
#
###
module LogSink

def cleanup
end

#
# This method must be implemented by any derived log sink classes and is
# intended to take the supplied parameters and persist them to an arbitrary
# medium.
#
def log(sev, src, level, msg)
raise NotImplementedError
end

protected

#
# This method returns the current timestamp in MM/DD/YYYY HH:Mi:SS format.
#
def get_current_timestamp
return ::Time.now.strftime("%m/%d/%Y %H:%M:%S")
end

end

end
end
37 changes: 37 additions & 0 deletions lib/rex/logging/log_sink_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: binary -*-


module Rex
module Logging

###
#
# LogSinkFactory can instantiate a LogSink based on the given name.
#
###
module LogSinkFactory
# Creates a new log sink of the given name. If no name is provided, a default
# Flatfile log sink is chosen
#
# @param [String] name The name of the required log sink within Rex::Logging::Sinks
# @param [Array] attrs The attributes to use with the given log sink
# @return [Rex::Logging::LogSink] The newly created log sink
def self.new(name = nil, *attrs)
name ||= Rex::Logging::Sinks::Flatfile.name.demodulize
raise NameError unless available_sinks.include?(name.to_sym)

log_sink = Rex::Logging::Sinks.const_get(name)
log_sink.new(*attrs)
rescue NameError
raise Rex::ArgumentError, "Could not find logger #{name}, expected one of #{available_sinks.join(', ')}"
end

# Returns a list of the available sinks that can be created by this factory
#
# @return [Array<Sym>] The available sinks that can be created by this factory
def self.available_sinks
Rex::Logging::Sinks.constants - [Rex::Logging::Sinks::Stream.name.demodulize.to_sym]
end
end
end
end
24 changes: 24 additions & 0 deletions lib/rex/logging/sinks/flatfile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: binary -*-
module Rex
module Logging
module Sinks

###
#
# This class implements the LogSink interface and backs it against a
# file on disk.
#
###
class Flatfile < Rex::Logging::Sinks::Stream

#
# Creates a flatfile log sink instance that will be configured to log to
# the supplied file path.
#
def initialize(file)
super(File.new(file, 'a'))
end

end

end end end
Loading

0 comments on commit cbd267a

Please sign in to comment.