Class: NRSER::Log::Logger

Inherits:
SemanticLogger::Logger
  • Object
show all
Defined in:
lib/nrser/log/logger.rb

Overview

Extension of SemanticLogger::Logger to add and customize behavior.

Defined Under Namespace

Classes: Catcher

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(subject, level: nil, filter: nil) ⇒ Logger

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.

Parameters:

  • subject (Module | String | INSTANCE)

    Name of the class, module, or other identifier for which the log messages are being logged

  • level (nil | Symbol) (defaults to: nil)

    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.

  • filter (nil | Regexp | Proc<(SemanticLogger::Log) => Boolean>) (defaults to: nil)
    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`.

Raises:



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# 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

Instance Attribute Details

#awesome_nameString (readonly)

TODO document ‘awesome_name` attribute.

Returns:



236
237
238
# File 'lib/nrser/log/logger.rb', line 236

def awesome_name
  @awesome_name
end

Class Method Details

.args_to_kwds(*args) ⇒ Hash<Symbol, Object>

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 Plugin before passing the resulting keywords to Plugin#call because the hash this method returns is much easier to work with than the standard logging methods’ multiple valid argument formats.

Parameters:

  • args (Array)

    Valid arguments for SemanticLogger::Logger#debug, etc. - see notes above.

Returns:

  • (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?` -



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# 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

.is_exception?(value) ⇒ Boolean

Under JRuby a java exception is not a Ruby Exception

Java::JavaLang::ClassCastException.new.is_a?(Exception) => false

Returns:

  • (Boolean)


76
77
78
79
# 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

.plugin(plugin_class) ⇒ nil

Install a plugin, dynamically adding it’s Plugin.method_name instance method.

Parameters:

Returns:

  • (nil)

Raises:

  • (NRSER::ConflictError)

    If this class already has an instance method defined with the name returned from ‘plugin_class.method_name`.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# 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

Instance Method Details

#build_log(level, index, message = nil, payload = nil, exception = nil, &block) ⇒ Object

Log message at the specified level



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# 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

#catch(**options) ⇒ Catcher

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.

Examples:

Log any error as a warning

logger.catch.warn do
  something_that_may_raise
end

# We should "always" get to here

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

Return a custom value on error

result = logger.catch( on_fail: :blue ).debug do
  what_is_your_favorite_color?
end

Parameters:

Returns:



390
391
392
# File 'lib/nrser/log/logger.rb', line 390

def catch **options
  Catcher.new self, **options
end

#deprecated(*message, method:, alternative: nil, max_stack_lines: 16) ⇒ nil

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.

Parameters:

  • message (Array)

    Message segments, formatted with NRSER.fmt_msg.

  • method (String)

    Something about what method it is.

  • alternative (String?) (defaults to: nil)

    Optionally (hopefully) something about what I should be using.

  • max_stack_lines (Integer) (defaults to: 16)

    How many lines of the stack to spew out (it can get real big real easy).

Returns:

  • (nil)


445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# 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

#with_level(level, &block) ⇒ RESULT

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.

Parameters:

  • level (Symbol?)

    One of SemanticLogger::LEVELS or ‘nil` to use the global default level.

  • block (Proc<() => RESULT>)

    Block to execute with the ‘level`.

Returns:

  • (RESULT)

    Whatever ‘&block` returns.



414
415
416
417
418
419
420
421
422
423
# 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