class NRSER::Log::Logger
Extension of {SemanticLogger::Logger} to add and customize behavior.
Attributes
TODO document `awesome_name` attribute.
@return [String]
Public Class Methods
Normalizes the valid argument formats to {SemanticLogger::Logger}'s “standard logging methods” into a keywords hash.
By “standard logging methods”, we mean {SemanticLogger::Logger#debug}, {SemanticLogger::Logger#info}, etc., as detailed in the
- Semantic
Logger
API][ -
documentation.
[Semantic Logger
API]: rocketjob.github.io/semantic_logger/api.html
Semantic Logger
documents the standard logging methods API as
log.info(message, payload_or_exception = nil, exception = nil, &block)
or
logger.info( message: String?, payload: Hash?, exception: Object?, **metric_keywords )
Which means that the hash returned by this method can be used as the argument to the standard logging methods:
def proxy_info_call *args, &block logger.info **NRSER::Log::Logger.args_to_kwds( *args ), &block end
This makes this method very useful when pre-processing log call arguments, as is done in {NRSER::Log::Plugin} before passing the resulting keywords to {NRSER::Log::Plugin#call} because the hash this method returns is much easier to work with than the standard logging methods' multiple valid argument formats.
@param [Array] args
Valid arguments for {SemanticLogger::Logger#debug}, etc. - see notes above.
@return [Hash<Symbol, Object>]
Always has the following keys (**all** of which can have `nil` value): - `message: String?` - we don't actually check that it's a {String}, but that seems to be pretty much what Semantic Logger expects. - `payload: Hash?` - A hash of names to values to log. By convention names are {Symbol}, though I'm not sure what happens if they are not. - `exception: Object?` - An error to log. This will be an {Exception} in MRI. In JRuby, and perhaps elsewhere, they won't be, but they should always respond to `#message` and `#backtrace`. - `metric: Object?` -
# File lib/nrser/log/logger.rb, line 136 def self.args_to_kwds *args log_kwds = { message: nil, payload: nil, exception: nil, metric: nil, } case args.length when 0 # pass - they all stay `nil` (which it seems SemLog allows, so we'll # stick to it) when 1 case args[0] when Hash if [:message, :payload, :exception, :metric]. any? { |key| args[0].key? key } log_kwds = args[0] else # It's the payload log_kwds[:payload] = args[0] end else if is_exception? args[0] # It's the exception log_kwds[:exception] = args[0] else # It's got to be the message log_kwds[:message] = args[0] end end when 2 if args[0].is_a? Hash log_kwds[:payload] = args[0] log_kwds[:exception] = args[1] else log_kwds[:message] = args[0] if args[1].is_a? Hash log_kwds[:payload] = args[1] else log_kwds[:exception] = args[1] end end when 3 log_kwds[:message] = args[0] log_kwds[:payload] = args[1] log_kwds[:exception] = args[2] else raise NRSER::ArgumentError.new \ "Too many args dude - max 3", args: args end log_kwds end
Under JRuby a java exception is not a Ruby Exception
Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
# File lib/nrser/log/logger.rb, line 76 def self.is_exception? value value.is_a?( Exception) || [:backtrace, :message].all? { |name| value.respond_to? name } end
Overrides {SemanticLogger::Logger#initialize}¹ to extend “subject” support to instances (as well as modules/classes and strings).
> ¹ {SemanticLogger::Logger#initialize} is just a proxy to > {SemanticLogger::Base#initialize}, which is what this implementation > is based off.
@param [Module | String
| INSTANCE] subject
Name of the class, module, or other identifier for which the log messages are being logged
@param [nil | Symbol] level
Only allow log entries of this level or higher to be written to this appender For example if set to :warn, this appender would only log `:warn` and `:fatal` log messages when other appenders could be logging `:info` and lower.
@param [nil | Regexp | Proc<(SemanticLogger::Log) => Boolean>] filter
1. {RegExp}: Only include log messages where the class name matches the supplied regular expression. All other messages will be ignored. 2. {Proc}: Only include log messages where the supplied Proc returns `true`.
@raise [NRSER::TypeError]
If `filter:` is not an acceptable type.
# File lib/nrser/log/logger.rb, line 270 def initialize subject, level: nil, filter: nil # Support filtering all messages to this logger using a Regular Expression # or Proc unless filter.nil? || filter.is_a?( Regexp ) || filter.is_a?( Proc ) raise NRSER::TypeError, ":filter must be a Regexp or Proc", filter: filter, subject: subject, level: level end @filter = filter.is_a?(Regexp) ? filter.freeze : filter # @name = klass.is_a?(String) ? klass : klass.name case subject when String @name = subject @awesome_name = subject @type = :string when Module @name = subject.safe_name @awesome_name = subject.ai multiline: true, raw: true @type = subject.is_a?( Class ) ? :class : :module else @name = subject.to_s @awesome_name = subject.ai multiline: true, raw: true @type = :instance end if level.nil? # Allow the global default level to determine this loggers log level @level_index = nil @level = nil else self.level = level end end
Install a plugin, dynamically adding it's {NRSER::Log::Plugin.method_name} instance method.
@param [Class<NRSER::Log::Plugin>] plugin_class
The plugin class to add.
@return [nil]
@raise [NRSER::ConflictError]
If this class already has an instance method defined with the name returned from `plugin_class.method_name`.
# File lib/nrser/log/logger.rb, line 212 def self.plugin plugin_class method_name = plugin_class.method_name if instance_methods.include? method_name.to_sym raise NRSER::ConflictError.new \ "Can not install", plugin_class.safe_name, "Logger plugin: instance method", method_name, "already defined" end define_method method_name do |*args, &block| plugin_class.new self, *args, &block end nil end
Public Instance Methods
Log message at the specified level
# File lib/nrser/log/logger.rb, line 316 def build_log level, index, message = nil, payload = nil, exception = nil, &block log = SemanticLogger::Log.new name, level, index should_log = if payload.nil? && exception.nil? && message.is_a?( Hash ) # Check if someone just logged a hash payload instead of meaning to call semantic logger if message.key?( :message ) || message.key?( :payload ) || message.key?( :exception ) || message.key?( :metric ) log.assign message else log.assign_positional nil, message, nil, &block end else log.assign_positional message, payload, exception, &block end # Log level may change during assign due to :on_exception_level [ log, should_log && should_log?( log ) ] end
A sweet way to try something and just log any {Exception}.
Useful for situations where the operation is question is not necessary or can not be allowed to propagate errors, but you would like to record and/or let the user know that it failed.
Create a new {Catcher} for this logger that defines the log methods (`#error`, `warn`, … `:trace`) to also accept blocks that will be executed in a `begin`/`rescue`.
If the block raises, the catcher will call the log method, adding the caught {Exception}.
@example Log any error as a warning
logger.catch.warn do something_that_may_raise end # We should "always" get to here
@example Log any error as warning with message and payload
logger.catch.warn( "This thing failed!", some_detail: some_value, ) do something_that_may_raise end # We should "always" get to here
@example Return a custom value on error
result = logger.catch( on_fail: :blue ).debug do what_is_your_favorite_color? end
@param [Hash] options
Passed to {Catcher#initialize}.
@return [Catcher]
# File lib/nrser/log/logger.rb, line 390 def catch **options Catcher.new self, **options end
Let 'em know they done wrong.
Which is funny, because only I use the library, and probably only I will ever use this library. So I'm basically nagging my-fucking-self. Sad.
@param [Array] message
Message segments, formatted with {NRSER.fmt_msg}.
@param [String] method
Something about what method it is.
@param [String?] alternative
Optionally (hopefully) something about what I should be using.
@param [Integer] max_stack_lines
How many lines of the stack to spew out (it can get real big real easy).
@return [nil]
# File lib/nrser/log/logger.rb, line 445 def deprecated *message, method:, alternative: nil, max_stack_lines: 16 kwds = { method: method } kwds[:alternative] = alternative if alternative if max_stack_lines > 0 kwds[:stack] = NRSER::ellipsis \ caller, max_stack_lines, omission: '[ ...omitted... ]' end if message.empty? message = [ "Method", method, "has been DEPRECIATED" ] else message = [ 'DEPRECIATED:', *message ] end warn NRSER.fmt_msg( *message ), **kwds nil end
Set the level for the execution of `&block`, restoring it to it's previous level afterwards.
Like what {SemanticLogger::Logger#silence} does (which just forwards to {SemanticLogger.silence}), but applies only to this logger (where as {SemanticLogger::Logger#silence} applies on to the global default level).
Useful for quickly turning down the log level to see trace/debug output from a specific section.
@param [Symbol?] level
One of {SemanticLogger::LEVELS} or `nil` to use the global default level.
@param [Proc<() => RESULT>] block
Block to execute with the `level`.
@return [RESULT]
Whatever `&block` returns.
# File lib/nrser/log/logger.rb, line 414 def with_level level, &block prior_level = @level self.level = level begin block.call ensure self.level = prior_level end end