Class: Logging::Layouts::Pattern::FormatMethodBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/logging/layouts/pattern.rb

Overview

This class is used to build the `format` method for the Pattern layout. It parses the user defined pattern and emits Ruby source code (as a string) that can be `eval`d in the context of the Pattern layout instance.

Constant Summary collapse

DIRECTIVE_RGXP =

Matches the first directive encountered and the stuff around it.

  • $1 is the stuff before directive or “” if not applicable

  • $2 is the %#.# match within directive group

  • $3 is the directive letter

  • $4 is the precision specifier for the logger name

  • $5 is the stuff after the directive or “” if not applicable

%r/([^%]*)(?:(%-?\d*(?:\.\d+)?)([a-zA-Z%])(?:\{([^\}]+)\})?)?(.*)/m
DIRECTIVE_TABLE =

Arguments to sprintf keyed to directive letters

{
  'c' => 'event.logger'.freeze,
  'd' => 'format_date(event.time)'.freeze,
  'F' => 'event.file'.freeze,
  'l' => '::Logging::LNAMES[event.level]'.freeze,
  'L' => 'event.line'.freeze,
  'm' => 'format_obj(event.data)'.freeze,
  'M' => 'event.method_name'.freeze,
  'h' => "'#{Socket.gethostname}'".freeze,
  'p' => 'Process.pid'.freeze,
  'r' => 'Integer(([email protected]_at)*1000).to_s'.freeze,
  't' => 'Thread.current.object_id.to_s'.freeze,
  'T' => 'Thread.current[:name]'.freeze,
  'X' => :placeholder,
  'x' => :placeholder,
  '%' => :placeholder
}.freeze
COLOR_ALIAS_TABLE =

Human name aliases for directives - used for colorization of tokens

{
  'c' => :logger,
  'd' => :date,
  'm' => :message,
  'h' => :hostname,
  'p' => :pid,
  'r' => :time,
  'T' => :thread,
  't' => :thread_id,
  'F' => :file,
  'L' => :line,
  'M' => :method,
  'X' => :mdc,
  'x' => :ndc
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pattern_layout) ⇒ FormatMethodBuilder

Creates the format method builder and initializes some variables from the given Patter layout instance.

pattern_layout - The Pattern Layout instance


350
351
352
353
354
355
356
357
358
# File 'lib/logging/layouts/pattern.rb', line 350

def initialize( pattern_layout )
  @layout         = pattern_layout
  @pattern        = layout.pattern.dup
  @color_scheme   = layout.color_scheme

  @sprintf_args   = []
  @format_string  = '"'
  @name_map_count = 0
end

Instance Attribute Details

#color_schemeObject (readonly)

Returns the value of attribute color_scheme.


340
341
342
# File 'lib/logging/layouts/pattern.rb', line 340

def color_scheme
  @color_scheme
end

#format_stringObject (readonly)

Returns the value of attribute format_string.


342
343
344
# File 'lib/logging/layouts/pattern.rb', line 342

def format_string
  @format_string
end

#layoutObject (readonly)

Returns the value of attribute layout.


338
339
340
# File 'lib/logging/layouts/pattern.rb', line 338

def layout
  @layout
end

#name_map_countObject

Returns the value of attribute name_map_count.


343
344
345
# File 'lib/logging/layouts/pattern.rb', line 343

def name_map_count
  @name_map_count
end

#patternObject

Returns the value of attribute pattern.


339
340
341
# File 'lib/logging/layouts/pattern.rb', line 339

def pattern
  @pattern
end

#sprintf_argsObject (readonly)

Returns the value of attribute sprintf_args.


341
342
343
# File 'lib/logging/layouts/pattern.rb', line 341

def sprintf_args
  @sprintf_args
end

Instance Method Details

#build_codeObject

This method returns a String which can be `eval`d in the context of the Pattern layout. When it is `eval`d, a `format` method is defined in the Pattern layout.

At the heart of the format method is `sprintf`. The conversion pattern specified in the Pattern layout is parsed and converted into a format string and corresponding arguments list. The format string and arguments are then processed by `sprintf` to format log events.

Returns a Ruby code as a String.


385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/logging/layouts/pattern.rb', line 385

def build_code
  build_format_string

  sprintf = "sprintf("
  sprintf << format_string
  sprintf << ', ' + sprintf_args.join(', ') unless sprintf_args.empty?
  sprintf << ")"

  if colorize_lines?
    sprintf = "color_scheme.color(#{sprintf}, ::Logging::LNAMES[event.level])"
  end

  code = "undef :format if method_defined? :format\n"
  code << "def format( event )\n#{sprintf}\nend\n"
end

#build_format_stringObject

This method builds the format string used by `sprintf` to format log events. The conversion pattern given by the user is iteratively parsed by a regular expression into separate format directives. Each directive builds up the format string and the corresponding arguments list that will be formatted.

The actual building of the format string is handled by separate directive specific methods. Those handlers also populate the arguments list passed to `sprintf`.

Returns the format String.


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
# File 'lib/logging/layouts/pattern.rb', line 412

def build_format_string
  while true
    match = DIRECTIVE_RGXP.match(pattern)
    _, pre, format, directive, precision, post = *match

    format_string << pre unless pre.empty?

    case directive
    when '%'; format_string << '%%'
    when 'c'; handle_logger( format, directive, precision )
    when 'l'; handle_level(  format, directive, precision )
    when 'X'; handle_mdc(    format, directive, precision )
    when 'x'; handle_ndc(    format, directive, precision )

    when *DIRECTIVE_TABLE.keys
      handle_directives(format, directive, precision)

    when nil; break
    else
      raise ArgumentError, "illegal format character - '#{directive}'"
    end

    break if post.empty?
    self.pattern = post
  end

  format_string << '"'
end

#colorize?Boolean

Returns `true` if the log messages should be colorized.

Returns:

  • (Boolean)

361
362
363
# File 'lib/logging/layouts/pattern.rb', line 361

def colorize?
  color_scheme && !color_scheme.lines?
end

#colorize_levels?Boolean

Returns `true` if the log levels have special colorization defined.

Returns:

  • (Boolean)

371
372
373
# File 'lib/logging/layouts/pattern.rb', line 371

def colorize_levels?
  color_scheme && color_scheme.levels?
end

#colorize_lines?Boolean

Returns `true` if the log messages should be colorized by line.

Returns:

  • (Boolean)

366
367
368
# File 'lib/logging/layouts/pattern.rb', line 366

def colorize_lines?
  color_scheme && color_scheme.lines?
end

#handle_directives(format, directive, precision) ⇒ Object

Handles the rest of the directives; none of these need any special handling.

format - format String directive - the directive character precision - added back to the format string

Returns nil


552
553
554
555
556
557
558
559
560
561
# File 'lib/logging/layouts/pattern.rb', line 552

def handle_directives( format, directive, precision )
  fmt = format + 's'
  fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?

  format_string << fmt
  format_string << "{#{precision}}" if precision
  sprintf_args << DIRECTIVE_TABLE[directive]

  nil
end

#handle_level(format, directive, precision) ⇒ Object

Add the log event level to the `format_string` and the `sprintf_args`. The color scheme is taken into account when formatting the log event level.

format - format String directive - the directive character ('l') precision - added back to the format string

Returns nil


483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/logging/layouts/pattern.rb', line 483

def handle_level( format, directive, precision )
  if colorize_levels?
    name_map = ::Logging::LNAMES.map { |name| color_scheme.color(("#{format}s" % name), name) }
    var = "@name_map_#{name_map_count}"
    layout.instance_variable_set(var.to_sym, name_map)
    self.name_map_count += 1

    format_string << '%s'
    format_string << "{#{precision}}" if precision
    sprintf_args << "#{var}[event.level]"
  else
    format_string << format + 's'
    format_string << "{#{precision}}" if precision
    sprintf_args << DIRECTIVE_TABLE[directive]
  end

  nil
end

#handle_logger(format, directive, slice) ⇒ Object

Add the logger name to the `format_string` and the `sprintf_args`. The `slice` argument is a little interesting - this is the number of logger name segments to keep. If we have a logger named “Foo::Bar::Baz” and our `slice` is 2, then “Bar::Baz” will appear in the generated log message. So the `slice` selects the last two parts of the logger name.

format - format String directive - the directive character ('c') slice - the number of name segments to keep

Returns nil


452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/logging/layouts/pattern.rb', line 452

def handle_logger( format, directive, slice )
  fmt = format + 's'
  fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?

  format_string << fmt
  sprintf_args << DIRECTIVE_TABLE[directive].dup

  if slice
    numeric = Integer(slice) rescue nil
    if numeric
      raise ArgumentError, "logger name slice must be an integer greater than zero: #{numeric}" unless numeric > 0
      sprintf_args.last <<
          ".split(::Logging::Repository::PATH_DELIMITER)" \
          ".last(#{slice}).join(::Logging::Repository::PATH_DELIMITER)"
    else
      format_string << "{#{slice}}"
    end
  end

  nil
end

#handle_mdc(format, directive, key) ⇒ Object

Add a Mapped Diagnostic Context to the `format_string` and the `sprintf_args`. Only one MDC value is added at a time, so this directive can appear multiple times using various keys.

format - format String directive - the directive character ('X') key - which MDC value to add to the log message

Returns nil

Raises:

  • (ArgumentError)

511
512
513
514
515
516
517
518
519
520
# File 'lib/logging/layouts/pattern.rb', line 511

def handle_mdc( format, directive, key )
  raise ArgumentError, "MDC must have a key reference" unless key
  fmt = format + 's'
  fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?

  format_string << fmt
  sprintf_args << "::Logging.mdc['#{key}']"

  nil
end

#handle_ndc(format, directive, separator) ⇒ Object

Add a Nested Diagnostic Context to the `format_string` and the `sprintf_args`. Since the NDC is an Array of values, the directive will appear only once in the conversion pattern. A `separator` is inserted between the values in generated log message.

format - format String directive - the directive character ('x') separator - used to separate the values in the NDC array

Returns nil


532
533
534
535
536
537
538
539
540
541
542
# File 'lib/logging/layouts/pattern.rb', line 532

def handle_ndc( format, directive, separator )
  fmt = format + 's'
  fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?

  format_string << fmt
  separator = separator.to_s
  separator = ' ' if separator.empty?
  sprintf_args << "::Logging.ndc.context.join('#{separator}')"

  nil
end