Class: Musa::Logger::Logger

Inherits:
Logger
  • Object
show all
Defined in:
lib/musa-dsl/logger/logger.rb

Overview

Note:

The logger inherits all standard Ruby Logger methods (debug, info, warn, error, fatal).

Note:

Position values are formatted as floating point for readability, even though the sequencer internally uses Rational numbers.

Custom logger that displays sequencer position with log messages.

This logger extends Ruby's standard Logger class to prepend the current sequencer position to each log message, making it easy to track events in musical time during composition and playback.

Features

  • Automatic sequencer position formatting in log output
  • Configurable position precision (integer and decimal digits)
  • Conditional formatting (position only shown when sequencer is provided)
  • Uses InspectNice refinements for better Rational display
  • Defaults to STDERR output with WARN level

Log Format

The formatted log output follows this pattern:

[position]: [LEVEL] [progname] message

Where:

  • position is the sequencer position (only if sequencer provided)
  • LEVEL is the severity level (omitted for DEBUG)
  • progname is the program/module name (optional)
  • message is the actual log message

Examples:

Basic usage without sequencer

require 'musa-dsl'

logger = Musa::Logger::Logger.new
logger.warn "Something happened"
# Output: [WARN] Something happened

With sequencer integration

require 'musa-dsl'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)
logger = Musa::Logger::Logger.new(sequencer: sequencer)

sequencer.at 4.5r do
  logger.info "Note played"
end

sequencer.run

# Output:  4.500: [INFO] Note played

With custom position format

require 'musa-dsl'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)

# 5 integer digits, 2 decimal places
logger = Musa::Logger::Logger.new(
  sequencer: sequencer,
  position_format: 5.2
)
logger.level = Logger::DEBUG

# At position 123.456:
sequencer.at 123.456r do
  logger.debug "Debugging info"
end

sequencer.run

# Output:  123.46: Debugging info

With program name

require 'musa-dsl'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)
logger = Musa::Logger::Logger.new(sequencer: sequencer)
logger.level = Logger::INFO

sequencer.at 4.5r do
  logger.info('MIDIVoice') { "Playing note 60" }
end

sequencer.run

# Output:  4.500: [INFO] [MIDIVoice] Playing note 60

Real-world scenario with multiple components

require 'musa-dsl'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)
logger = Musa::Logger::Logger.new(sequencer: sequencer)
logger.level = Logger::DEBUG

# Different components log at different times
sequencer.at 0 do
  logger.info('Transport') { "Starting playback" }
end

sequencer.at 1.5r do
  logger.debug('Series') { "Evaluating next value" }
end

sequencer.at 2.25r do
  logger.warn('MIDIVoice') { "Note overflow detected" }
end

sequencer.run

# Output:
#  0.000: [INFO] [Transport] Starting playback
#  1.500: [Series] Evaluating next value
#  2.250: [WARN] [MIDIVoice] Note overflow detected

Changing log level dynamically

require 'musa-dsl'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)
logger = Musa::Logger::Logger.new(sequencer: sequencer)
logger.level = Logger::DEBUG  # Show all messages

# Later, reduce verbosity
logger.level = Logger::WARN  # Only warnings and errors

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(sequencer: nil, position_format: nil) ⇒ Logger

Note:

The logger outputs to STDERR by default with level set to WARN.

Note:

Uses InspectNice refinements for better formatting of Rationals and Hashes.

Note:

The sequencer's position is read at log time, not at event scheduling time. This means the position reflects when the log message is actually generated.

Creates a new logger with optional sequencer integration.

Examples:

Position format examples

require 'musa-dsl'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)

# Different formats for position display:
# 3.3 => "  4.500" (3 digits, 3 decimals) - default
# 5.2 => "  123.46" (5 digits, 2 decimals)
# 2.0 => "  4" (2 digits, no decimals)
# 4.4 => "   4.5000" (4 digits, 4 decimals)

logger_compact = Musa::Logger::Logger.new(sequencer: sequencer, position_format: 2.0)
logger_precise = Musa::Logger::Logger.new(sequencer: sequencer, position_format: 4.4)

Parameters:

  • sequencer (Musa::Sequencer::Sequencer, nil) (defaults to: nil)

    sequencer whose position will be displayed in log messages. When nil, position is not shown.

  • position_format (Numeric, nil) (defaults to: nil)

    format specification for position display. The integer part specifies the number of digits before the decimal point, and the decimal part (×10) specifies digits after the decimal point. Defaults to 3.3 (3 integer digits, 3 decimal places).



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/musa-dsl/logger/logger.rb', line 215

def initialize(sequencer: nil, position_format: nil)
  super STDERR, level: WARN

  # Store sequencer reference for position queries in formatter
  @sequencer = sequencer

  # Store position format specification
  @position_format = position_format || 3.3

  # Custom formatter that integrates sequencer position with log messages.
  #
  # This proc is called by Ruby's Logger for each log entry. It captures
  # @sequencer and @position_format from the enclosing scope to format
  # messages with musical timing information.
  #
  # The formatter constructs messages in the format:
  #   [position]: [LEVEL] [progname] message
  #
  # Position calculation:
  #
  # - Splits position_format into integer and decimal parts
  # - Example: 3.3 => 3 integer digits + 3 decimal digits
  # - Formats sequencer position with calculated precision
  # - Right-aligns position in the allocated width
  #
  # Severity handling:
  #
  # - DEBUG level: severity not shown in output
  # - Other levels: shown as [WARN], [INFO], [ERROR], [FATAL]
  #
  # Spacing:
  #
  # - Adds separator space only if position, level, or progname are present
  # - Empty messages output just a newline
  #
  # @param severity [String] log level (DEBUG, INFO, WARN, ERROR, FATAL)
  # @param time [Time] timestamp of the log event (not used in current implementation)
  # @param progname [String, nil] program/component name
  # @param msg [String, nil] the actual log message
  # @return [String] formatted log line with newline
  self.formatter = proc do |severity, time, progname, msg|
    # Omit severity label for DEBUG level
    level = "[#{severity}] " unless severity == 'DEBUG'

    if msg
      # Calculate and format sequencer position if available
      position = if @sequencer
                   # Extract integer and decimal digit counts from position_format
                   # e.g., 3.3 => integer_digits=3, decimal_digits=3
                   integer_digits = @position_format.to_i
                   decimal_digits = ((@position_format - integer_digits) * 10).round

                   # Format position: total width includes digits + decimal point + ': '
                   # Right-aligned to keep positions visually aligned in logs
                   "%#{integer_digits + decimal_digits + 1}s: " % ("%.#{decimal_digits}f" % @sequencer.position.to_f)
                 end

      # Wrap progname in brackets if provided
      progname = "[#{progname}]" if progname

      # Construct final message with conditional spacing
      "#{position}#{level}#{progname}#{' ' if position || level || progname}#{msg}\n"
    else
      # Empty message case
      "\n"
    end
  end
end

Instance Method Details

#levelInteger

Override level getter to handle encoding compatibility issues.

Ruby's Logger (>= 1.7.0) has an encoding bug in level_override that causes Encoding::CompatibilityError when mixing UTF-8 strings from Musa with Logger's BINARY (ASCII-8BIT) internal strings.

This override catches the encoding error and returns the level directly from the instance variable, bypassing the buggy level_override method.

Returns:

  • (Integer)

    current log level (DEBUG=0, INFO=1, WARN=2, ERROR=3, FATAL=4)



294
295
296
297
298
299
# File 'lib/musa-dsl/logger/logger.rb', line 294

def level
  super
rescue Encoding::CompatibilityError
  # Bypass level_override and return raw level
  @level
end