Class: DumbLogger

Inherits:
Object
  • Object
show all
Defined in:
lib/dumb-logger.rb,
lib/dumb-logger/version.rb

Overview

TODO:

Allow assignment of prefices to levels the way we now do labels. Will probably only work with level-based reporting, since mask-based reports may get transmitted due to a mask AND that doesn't match any named masks.

TODO:

Add a :seek_to_eof option so that text written to a sink is always possitioned after any data written by other processes. (Except on the first write to a file in truncation :append => false mode, of course.)

This is just a one-off class to allow reporting things to stderr according to the verbosity level. Very simple, not a complete logger at all.

Constant Summary collapse

NO_NL =

Message flag for "do not append a newline".

:no_nl
USE_BITMASK =

Treat loglevel numbers as bitmasks.

:loglevels_are_bitmasks
USE_LEVELS =

Treat loglevel numbers as actual levels.

:loglevels_are_numbers
SPECIAL_SINKS =

Special sink values, which will get evaluated on every transmission.

[
 :$stdout,
 :$stderr,
]
VERSION =

Frozen string representation of the module version number.

@version.to_s.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ DumbLogger

Note:

Streams are always treated as being in :append => true mode.

Constructor.

Parameters:

  • args (Hash) (defaults to: {})

Options Hash (args):

  • :append (Boolean) — default: true

    If true, any files opened will have transmitted text appended to them. See #append=.

  • :prefix (String) — default: ''

    String to insert at the beginning of each line of report text. See #prefix=.

  • :sink (IO, String) — default: :$stderr

    Where reports should be sent. See #sink=.

  • :loglevel (Integer) — default: 0

    Maximum log level for reports. See #loglevel=.

  • :logmask (Integer) — default: 0

    Alias for :loglevel.

  • :level_style (Symbol) — default: USE_LEVELS

    Whether message loglevels should be treated as integer levels or as bitmasks. See #level_style=.

Raises:

  • (ArgumentError)

    Raises an ArgumentError exception if the argument isn't a hash.


400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/dumb-logger.rb', line 400

def initialize(args={})
  unless (args.kind_of?(Hash))
    raise ArgumentError.new("#{self.class.name}.new requires a hash")
  end
  @options = {
    :labels		=> {},
  }
  #
  # Here are the default settings for a new instance with no
  # arguments.  We put 'em here so they don't show up in docco under
  # the Constants heading.
  #
  default_opts	= {
    :append		=> true,
    :level_style	=> USE_LEVELS,
    :loglevel		=> 0,
    :prefix		=> '',
    :sink		=> :$stderr,
  }
  #
  # Make a new hash merging the user arguments on top of the
  # defaults.  This avoids altering the user's hash.
  #
  temp_opts		= default_opts.merge(args)
  #
  # Throw out any option keys we don't recognise.
  #
  temp_opts.delete_if { |k,v| (! CONSTRUCTOR_OPTIONS.include?(k)) }
  #
  # Do loglevel stuff.  We're going to run this through the writer
  # method, since it has argument validation code.
  #
  # If the user wants to use bitmasks, then the :logmask argument
  # key takes precedence over the :loglevel one.
  #
  self.level_style = temp_opts[:level_style]
  temp_opts.delete(:level_style)
  if (self.log_masks?)
    temp_opts[:loglevel] = temp_opts[:logmask] if (temp_opts.key?(:logmask))
  end
  temp_opts.delete(:logmask)
  #
  # Now go through the remaining options and handle them.  If the
  # option has an associated writer method, call it -- otherwise,
  # just load it into the `@options` hash.
  #
  temp_opts.each do |opt,val|
    wmethod	= (opt.to_s + '=').to_sym
    if (self.respond_to?(wmethod))
      self.send(wmethod, val)
    else
      @options[opt] = val
    end
  end
end

Instance Attribute Details

#appendBoolean

Note:

This setting is only important when a sink is being activated, such as DumbLogger object instantiation or because of a call to #sink=, and it controls the position of the first write to the sink. Once a sink is activated (opened), writing continues sequentially from that point.

Note:

Setting this attribute only affects files opened by DumbLogger. Stream sinks are always in append-mode. As long as the sink is a stream, this setting will be ignored -- but it will become active whenever the sink becomes a file.

Controls the behaviour of sink files (but not IO streams). If true, report text will be added to the end of any existing contents; if false, files will be truncated and reports will begin at position 0.

Returns:

  • (Boolean)

    Sets or returns the file append-on-write control value.


480
481
482
# File 'lib/dumb-logger.rb', line 480

def append
  return (@options[:append] ? true : false)
end

#level_styleSymbol

Control whether loglevels are treated as ascending integers, or as bitmasks.

Returns:

Raises:

  • (ArgumentError)

    Raises an ArgumentError exception if the style isn't recognised.


97
98
99
# File 'lib/dumb-logger.rb', line 97

def level_style
  return @options[:level_style]
end

#loglevelInteger Also known as: logmask

If loglevels are being treated as integers, this is the maximum level that will reported; that is, if a message is submitted with level 7, but the loglevel is 5, the message will not be reported.

If loglevels are being treated as bitmasks, messages will be reported only if submitted with a loglevel which has at least one bit set that is also set in the instance loglevel.

When used as an attribute writer (e.g., obj.loglevel = val), the argument will be treated as an integer.

Returns:

  • (Integer)

    Returns the maximum loglevel/logging mask in effect henceforth.

Raises:

  • (ArgumentError)

    Raise an ArgumentError exception if the new value cannot be converted to an integer.


129
130
131
132
133
134
135
# File 'lib/dumb-logger.rb', line 129

def loglevel=(arg)
  unless (arg.respond_to?(:to_i))
    raise ArgumentError.new('loglevels are integers')
  end
  @options[:loglevel] = arg.to_i
  return @options[:loglevel]
end

#optionsHash (readonly)

Options controlling various aspects of DumbLogger's operation.

Returns:

  • (Hash)

    Returns current set of DumbLogger options for the instance.


259
260
261
# File 'lib/dumb-logger.rb', line 259

def options
  return @options.dup.freeze
end

#prefixString

Note:

This can be overridden at runtime via the :prefix option hash element to the #message method (q.v.).

Prefix string to be inserted at the beginning of each line of output we emit.

Returns:

  • (String)

    Sets or returns the prefix string to be used henceforth.


276
277
278
# File 'lib/dumb-logger.rb', line 276

def prefix
  return @options[:prefix]
end

#sinkIO, ...

Note:

File sink contents may appear unpredictable under the following conditions:

  • Messages are being sinked to a file, and
  • the file is being accessed by one or more other processes, and
  • changes to the file are interleaved between those made by the DumbLogger #message method and activity by the other process(es).

Sets or returns the sink to which we send our messages.

When setting the sink, the value can be an IO instance, a special symbol, or a string. If a string, the :append flag from the instance options (see #append= and #append?) is used to determine whether the file will be rewritten from the beginning, or just have text appended to it.

Sinking to one of the special symbols (:$stderr or :$stdout; see SPECIAL_SINKS) results in the sink being re-evaluated at each call to #message. This is useful if these streams might be altered after the logger has been instantiated.

Returns:

  • (IO, String, Symbol)

    Returns the sink path, special name, or IO object.


334
335
336
# File 'lib/dumb-logger.rb', line 334

def sink
  return @options[:sink]
end

Class Method Details

.finalize(obj) ⇒ Object

If we have a currently open output stream that needs to be closed (usually because we opened it ourself), close it as part of the DumbLogger object teardown.

Parameters:

  • obj (DumbLogger)

    Instance being torn down ('destructed').


54
55
56
57
58
# File 'lib/dumb-logger.rb', line 54

def finalize(obj)
  if (obj.instance_variable_get(:@options)[:needs_close])
    obj.sink.close
  end
end

.versionVersionomy

Returns the Versionomy representation of the package version number.

Returns:

  • (Versionomy)

52
53
54
# File 'lib/dumb-logger/version.rb', line 52

def self.version
  return @version
end

.VERSIONString

Returns the package version number as a string.

Returns:

  • (String)

    Package version number.


62
63
64
# File 'lib/dumb-logger/version.rb', line 62

def self.VERSION
  return self.const_get('VERSION')
end

Instance Method Details

#append?Boolean

Returns true if new sink files opened by the instance will have report text appended to them.

Returns:

  • (Boolean)

    Returns true if new sink files opened by the instance will have report text appended to them.


494
495
496
# File 'lib/dumb-logger.rb', line 494

def append?
  return self.append
end

#label_levels(labelhash) ⇒ Hash<Symbol,Integer>

Allow the user to assign labels to different log levels or mask combinations. All labels will be downcased and converted to symbols.

In addition, the labels are added to the instance as methods that will log messages with the specified level.

Parameters:

  • labelhash (Hash{String,Symbol=>Integer})

    Hash of names or symbols and the integer log levels/masks they're labelling.

Returns:

  • (Hash<Symbol,Integer>)

    Returns a hash of the labels (as symbols) and levels/masks that have been assigned.

Raises:

  • (ArgumentError)

    Raises an ArgumentError exception if the argument isn't a hash with integer values.

See Also:


200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/dumb-logger.rb', line 200

def label_levels(labelhash)
  unless (labelhash.kind_of?(Hash))
    raise ArgumentError.new('level labels must be supplied as a hash')
  end
  unless (labelhash.values.all? { |o| o.kind_of?(Integer) })
    raise ArgumentError.new('labeled levels must be integers')
  end
  newhash = labelhash.inject({}) { |memo,(label,level)|
    label_sym = label.to_s.downcase.to_sym
    memo[label_sym] = level
    memo
  }
  @options[:labels].merge!(newhash)
  newhash.each do |label,level|
    self.define_singleton_method(label) do |*args|
      (scratch, newargs) = args.partition { |o| o.kind_of?(Integer) }
      return self.message(level, *newargs)
    end
  end
  return newhash
end

#labeled_levelsHash<Symbol,Integer>

Return a list of all the levels or bitmasks that have been labeled. The return value is suitable for use as input to the #label_levels method of this or another instance of this class.

Returns:

  • (Hash<Symbol,Integer>)

    Returns a hash of labels (as symbols) and the log levels they identify.

See Also:


233
234
235
# File 'lib/dumb-logger.rb', line 233

def labeled_levels
  return Hash[@options[:labels].sort].freeze
end

#log_levels?Boolean

Returns true if loglevel numbers are interpreted as integers rather than bitmasks. (See #level_style for more information.)

Returns:

  • (Boolean)

    Returns true if loglevels are regarded as integers rather than bitmasks, or false otherwise.

See Also:


154
155
156
# File 'lib/dumb-logger.rb', line 154

def log_levels?
  return (@options[:level_style] == USE_LEVELS) ? true : false
end

#log_masks?Boolean

Returns true if loglevel numbers are interpreted as bitmasks rather than integers. (See #level_style for more information.)

Determine how loglevel numbers are interpreted. (See #level_style for more information.)

Returns true if they're treated as bitmasks rather than integers.

Returns:

  • (Boolean)

    Returns true if loglevels are regarded as bitmasks rather than integers, or false otherwise.

See Also:


174
175
176
# File 'lib/dumb-logger.rb', line 174

def log_masks?
  return (@options[:level_style] == USE_BITMASK) ? true : false
end

#message(*args) ⇒ nil, Integer

Submit a message for possible transmission to the current sink. The argument is an array of arrays, strings, integers, and/or symbols. Reports with a loglevel of zero (the default) are always transmitted.

Parameters:

  • args (Array<Array,String,Symbol,Integer,Hash>)
    • The last integer in the array will be treated as the report's loglevel; default is 0.
    • Any Array elements in the arguments will be merged and the values interpreted as level labels (see #label_levels). If loglevels are bitmasks, the labeled levels are ORed together; otherwise the lowest labeled level will be used for the message.
    • Any Hash elements in the array will be merged and will temporarily override instance-wide options -- e.g., { :prefix => 'alt' } .
    • If the DumbLogger::NO_NL value (a Symbol) appears in the array, or a hash element of :return => false, the report will not include a terminating newline (useful for "progress:..done" reports).
    • Any strings in the array are treated as text to be reported, one per line. Each line will begin with the value of #prefix, and only the final line is subject to the DumbLogger::NO_NL special-casing.

Returns:

  • (nil, Integer)

    Returns either nil if the message's loglevel is higher than the reporting level, or the level of the report.

    If integer levels are being used, a non-nil return value is that of the message. If bitmask levels are being used, the return value is a mask of the active level bits that applied to the message -- i.e., msg_bits & logging_mask .


532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/dumb-logger.rb', line 532

def message(*args)
  #
  # Extract any symbols, hashes, and integers from the argument
  # list.  This makes the calling format very flexible.
  #
  (symopts, args)	= args.partition { |elt| elt.kind_of?(Symbol) }
  symlevels		= (symopts & self.labeled_levels.keys).map { |o|
    self.labeled_levels[o]
  }.compact
  (hashopts, args)	= args.partition { |elt| elt.kind_of?(Hash) }
  hashopts		= hashopts.reduce(:merge) || {}
  (level, args)	= args.partition { |elt| elt.kind_of?(Integer) }
  level		= level.last || hashopts[:level] || hashopts[:mask] || 0
  cur_loglevel	= self.loglevel
  cur_style		= self.level_style
  unless (level.zero?)
    if (self.log_levels?)
      return nil if ([ cur_loglevel, *symlevels ].min < level)
    elsif (self.log_masks?)
      level = [ level, *symlevels ].reduce(:|) & cur_loglevel
      return nil if (level.zero?)
    end
  end
  prefix_text		= hashopts[:prefix] || self.prefix
  text		= prefix_text + args.join("\n#{prefix_text}")
  unless (hashopts.key?(:newline))
    hashopts[:newline]= (! symopts.include?(NO_NL))
  end
  text << "\n" if (hashopts[:newline])
  stream = @options[:volatile] ? eval(self.sink.to_s) : @sink_io
  stream.write(text)
  stream.flush if (@options[:volatile])
  return level
end

#reopenBoolean

Re-open the current sink (unless it's a stream). This may be useful if you want to stop and truncate in the middle of logging (by changing the #append= option), or something.

Returns:

  • (Boolean)

    Returns true if the sink was successfully re-opened, or false otherwise (such as if it's a stream).

Raises:

  • (IOError)

    Raises an IOError exception if the sink stream is already closed.


298
299
300
301
302
303
304
# File 'lib/dumb-logger.rb', line 298

def reopen
  return false unless (@options[:needs_close] && self.sink.kind_of?(String))
  raise IOError.new('sink stream is already closed') if (@sink_io.closed?)
  @sink_io.reopen(self.sink, (self.append? ? 'a' : 'w'))
  @sink_io.sync = true if (@sink_io.respond_to?(:sync=))
  return true
end