Class: Ukiryu::Logger

Inherits:
Object
  • Object
show all
Defined in:
lib/ukiryu/logger.rb

Overview

Ukiryu Logger with level-based message classification

Provides structured logging with support for:

  • Debug mode controlled by UKIRYU_DEBUG environment variable

  • Colored output via Paint gem (when available)

  • Message classification (debug, info, warn, error)

  • Structured output for tool resolution process

  • Class-level convenience methods for quick debug output

Examples:

Enable debug mode

ENV['UKIRYU_DEBUG'] = '1'
logger = Ukiryu::Logger.new
logger.debug("Tool resolution started")

Class-level debug output (recommended)

Ukiryu::Logger.debug("Message", category: :executable)
Ukiryu::Logger.debug("Found executable", context: { path: '/usr/bin/tool' })

Standard logging

logger = Ukiryu::Logger.new
logger.info("Command completed successfully")
logger.warn("Tool not found: #{name}")
logger.error("Execution failed: #{message}")

Constant Summary collapse

LEVELS =

Log levels

%i[debug info warn error].freeze
DEBUG_ENV_VAR =

Environment variable names for debug control

'UKIRYU_DEBUG'
DEBUG_EXECUTABLE_ENV_VAR =
'UKIRYU_DEBUG_EXECUTABLE'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output: nil, level: nil) ⇒ Logger

Initialize a new Logger

Parameters:

  • output (IO) (defaults to: nil)

    the output stream (default: $stderr for debug, $stdout for info/warn/error)

  • level (Symbol) (defaults to: nil)

    the log level (default: :warn)



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ukiryu/logger.rb', line 85

def initialize(output: nil, level: nil)
  @output = output || $stderr
  @logger = ::Logger.new(@output)
  @logger.level = ::Logger::WARN
  @paint_available = false

  # Check if debug mode is enabled via Config system
  @debug_mode = Config.debug

  # Try to load Paint for colored output
  if @debug_mode
    begin
      require 'paint'
      @paint_available = true
    rescue LoadError
      # Paint not available, fall back to plain text
    end
  end

  # Set log level based on debug mode or explicit level
  set_level(level) if level
end

Instance Attribute Details

#levelObject (readonly)

Returns the value of attribute level.



79
80
81
# File 'lib/ukiryu/logger.rb', line 79

def level
  @level
end

#loggerObject (readonly)

Returns the value of attribute logger.



79
80
81
# File 'lib/ukiryu/logger.rb', line 79

def logger
  @logger
end

#outputObject (readonly)

Returns the value of attribute output.



79
80
81
# File 'lib/ukiryu/logger.rb', line 79

def output
  @output
end

#paint_availableObject (readonly)

Returns the value of attribute paint_available.



79
80
81
# File 'lib/ukiryu/logger.rb', line 79

def paint_available
  @paint_available
end

Class Method Details

.debug(message, category: nil, context: {}) ⇒ Object

Class-level debug output (quick access without instance)

Parameters:

  • message (String)

    the debug message

  • category (Symbol, nil) (defaults to: nil)

    optional category (:executable for UKIRYU_DEBUG_EXECUTABLE)

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

    optional context data to include



43
44
45
46
47
48
49
# File 'lib/ukiryu/logger.rb', line 43

def debug(message, category: nil, context: {})
  return unless debug_enabled?(category)

  prefix = "[UKIRYU DEBUG#{category ? " #{category.to_s.upcase}" : ''}]"
  details = context.empty? ? '' : " (#{context.map { |k, v| "#{k}=#{v.inspect}" }.join(', ')})"
  warn "#{prefix} #{message}#{details}"
end

.debug_enabled?(category = nil) ⇒ Boolean

Check if debug mode is enabled for a given category

Parameters:

  • category (Symbol, nil) (defaults to: nil)

    the category to check

Returns:

  • (Boolean)

    true if debug is enabled



55
56
57
58
59
60
61
62
# File 'lib/ukiryu/logger.rb', line 55

def debug_enabled?(category = nil)
  case category
  when :executable
    ENV[DEBUG_EXECUTABLE_ENV_VAR]
  else
    ENV[DEBUG_ENV_VAR]
  end
end

.instanceLogger

Get the singleton instance

Returns:

  • (Logger)

    the logger instance



67
68
69
# File 'lib/ukiryu/logger.rb', line 67

def instance
  @instance ||= new
end

Instance Method Details

#debug(message, context = {}) ⇒ Object

Log a debug message (only when UKIRYU_DEBUG is enabled)

Parameters:

  • message (String)

    the message

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

    optional context data



128
129
130
131
132
# File 'lib/ukiryu/logger.rb', line 128

def debug(message, context = {})
  return unless @debug_mode

  @output.puts(format_message('DEBUG', message, :cyan, context))
end

#debug_enabled?Boolean

Check if debug mode is enabled

Returns:

  • (Boolean)

    true if debug mode is enabled



161
162
163
# File 'lib/ukiryu/logger.rb', line 161

def debug_enabled?
  @debug_mode
end

#debug_resolution(identifier, step, data = {}) ⇒ Object

Log structured tool resolution debug information

Parameters:

  • identifier (String)

    the tool identifier being resolved

  • step (Symbol)

    the resolution step (:header, :context, :step, :result, :not_found)

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

    the step-specific data



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ukiryu/logger.rb', line 170

def debug_resolution(identifier, step, data = {})
  return unless @debug_mode

  case step
  when :header
    debug_header(identifier)
  when :context
    debug_context(data[:platform], data[:shell], data[:all_tools])
  when :step
    debug_step(data[:tool_name], data[:tool_def], data[:interface_match], data[:cached])
  when :result
    debug_result(identifier, data[:tool_name], data[:executable])
  when :not_found
    debug_not_found(identifier)
  end
end

#debug_section_execution_report(execution_report) ⇒ Object

Debug section: Execution Report (metrics) Shows detailed metrics for each execution stage

Parameters:

  • execution_report (ExecutionReport)

    the execution report



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/ukiryu/logger.rb', line 384

def debug_section_execution_report(execution_report)
  return unless @debug_mode

  debug_section_header('Execution Report')

  @output.puts '  Run Environment:'
  format_env_field(@output, 'Hostname', execution_report.run_environment.hostname)
  format_env_field(@output, 'Platform', execution_report.run_environment.platform)
  format_env_field(@output, 'OS Version', execution_report.run_environment.os_version)
  format_env_field(@output, 'Shell', execution_report.run_environment.shell)
  format_env_field(@output, 'Ruby', execution_report.run_environment.ruby_version)
  format_env_field(@output, 'Ukiryu', execution_report.run_environment.ukiryu_version)
  format_env_field(@output, 'CPUs', execution_report.run_environment.cpu_count.to_s)
  format_env_field(@output, 'Memory', "#{execution_report.run_environment.total_memory}GB")

  @output.puts ''

  @output.puts '  Stage Timings:'
  execution_report.all_stages.each do |stage|
    @output.puts "    #{stage.name.ljust(20)}: #{stage.formatted_duration.ljust(10)} " \
                 "(#{stage.memory_delta}KB)" \
                 "#{stage.success ? '' : ' - FAILED'}"
    @output.puts "      #{stage.error}" unless stage.success
  end

  @output.puts ''
  @output.puts "  Total: #{execution_report.formatted_total_duration}"

  
end

#debug_section_raw_response(stdout:, stderr:, exit_code:) ⇒ Object

Debug section: Raw Response Shows the raw output from the command

Parameters:

  • stdout (String)

    the stdout from the command

  • stderr (String)

    the stderr from the command

  • exit_code (Integer)

    the exit code



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/ukiryu/logger.rb', line 331

def debug_section_raw_response(stdout:, stderr:, exit_code:)
  return unless @debug_mode

  debug_section_header('Raw Command Response')

  debug_field('Exit Code', exit_code.to_s, boxed: false)

  unless stdout.empty?
    @output.puts ''
    @output.puts '  STDOUT:'
    stdout.each_line do |line|
      @output.puts "    #{line}"
    end
  end

  unless stderr.empty?
    @output.puts ''
    @output.puts '  STDERR:'
    stderr.each_line do |line|
      @output.puts "    #{line}"
    end
  end

  
end

#debug_section_shell_command(executable:, full_command:, env_vars: {}) ⇒ Object

Debug section: Shell Command Shows the actual shell command that will be executed

Parameters:

  • executable (String)

    the executable path

  • full_command (String)

    the full command string

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

    optional environment variables



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/ukiryu/logger.rb', line 306

def debug_section_shell_command(executable:, full_command:, env_vars: {})
  return unless @debug_mode

  debug_section_header('Shell Command')

  debug_field('Executable', executable, boxed: false)
  debug_field('Full Command', full_command, boxed: false)

  unless env_vars.empty?
    @output.puts ''
    @output.puts '  Environment Variables:'
    env_vars.each do |key, value|
      @output.puts "    #{key}=#{value}"
    end
  end

  
end

#debug_section_structured_options(tool_name, command_name, options_object) ⇒ Object

Debug section: Structured Options (Tool Command Options) Shows the structured options object that will be passed to the executable

Parameters:

  • tool_name (String)

    the tool name

  • command_name (String)

    the command name

  • options_object (Object)

    the structured options object



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/ukiryu/logger.rb', line 275

def debug_section_structured_options(tool_name, command_name, options_object)
  return unless @debug_mode

  debug_section_header("Structured Options (#{tool_name} #{command_name})")

  # Show the options object's attributes
  if options_object.respond_to?(:to_h)
    options_object.to_h.each do |key, value|
      debug_field(key.to_s, format_value(value), boxed: false)
    end
  elsif options_object.is_a?(Hash)
    options_object.each do |key, value|
      debug_field(key.to_s, format_value(value), boxed: false)
    end
  else
    # Try to get instance variables
    options_object.instance_variables.each do |var|
      value = options_object.instance_variable_get(var)
      debug_field(var.to_s.sub('@', ''), format_value(value), boxed: false)
    end
  end

  
end

#debug_section_structured_response(response) ⇒ Object

Debug section: Structured Response Shows the final structured response object

Parameters:

  • response (Object)

    the response object



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/ukiryu/logger.rb', line 361

def debug_section_structured_response(response)
  return unless @debug_mode

  debug_section_header('Structured Response')

  # Show response as YAML for readability
  response_yaml = if response.respond_to?(:to_yaml)
                    response.to_yaml
                  else
                    response.inspect
                  end

  response_yaml.each_line do |line|
    @output.puts "  #{line}"
  end

  
end

#debug_section_tool_not_found(identifier:, platform:, shell:, all_tools:) ⇒ Object

Debug section: Tool Not Found Shows the tool not found error with bordered style

Parameters:

  • identifier (String)

    the tool identifier being resolved

  • platform (Symbol)

    the detected platform

  • shell (Symbol)

    the detected shell

  • all_tools (Array<String>)

    list of all available tools



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/ukiryu/logger.rb', line 246

def debug_section_tool_not_found(identifier:, platform:, shell:, all_tools:)
  return unless @debug_mode

  debug_section_header("Tool Resolution: #{identifier}")

  debug_field('Platform', platform.to_s, boxed: false)
  debug_field('Shell', shell.to_s, boxed: false)
  debug_field('Available Tools', all_tools.count.to_s, boxed: false)

  @output.puts ''
  @output.puts "  #{all_tools.sort.join('')}"
  @output.puts ''

  if @paint_available
    paint = Paint.method(:[])
    @output.puts "#{paint['', :red]} #{paint['Tool not found', :red, :bright]}"
  else
    @output.puts '  ✗ Tool not found'
  end

  
end

#debug_section_tool_resolution(identifier:, platform:, shell:, all_tools:, selected_tool:, executable:) ⇒ Object

Debug section: Tool Resolution Shows the tool resolution process with bordered style

Parameters:

  • identifier (String)

    the tool identifier being resolved

  • platform (Symbol)

    the detected platform

  • shell (Symbol)

    the detected shell

  • all_tools (Array<String>)

    list of all available tools

  • selected_tool (String)

    the selected tool name

  • executable (String)

    the path to the executable



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/ukiryu/logger.rb', line 210

def debug_section_tool_resolution(identifier:, platform:, shell:, all_tools:, selected_tool:, executable:)
  return unless @debug_mode

  debug_section_header("Tool Resolution: #{identifier}")

  debug_field('Platform', platform.to_s, boxed: false)
  debug_field('Shell', shell.to_s, boxed: false)
  debug_field('Available Tools', all_tools.count.to_s, boxed: false)

  @output.puts ''
  @output.puts "  #{all_tools.sort.join('')}"

  if @paint_available
    paint = Paint.method(:[])
    @output.puts ''
    @output.puts "#{paint['',
                          :green]} #{paint[selected_tool, :cyan,
                                           :bright]}#{paint[' implements: ', :white]}#{paint[identifier, :yellow]}"
  else
    @output.puts ''
    @output.puts "#{selected_tool} implements: #{identifier}"
  end
  @output.puts ''
  debug_field('Selected', selected_tool, boxed: false)
  debug_field('Executable', executable, boxed: false)

  
end

#debug_section_ukiryu_options(options) ⇒ Object

Debug section: Ukiryu CLI Options Shows the options passed to the Ukiryu CLI itself (not the tool options)

Parameters:

  • options (Hash)

    the Ukiryu CLI options



191
192
193
194
195
196
197
198
199
# File 'lib/ukiryu/logger.rb', line 191

def debug_section_ukiryu_options(options)
  return unless @debug_mode

  debug_section_header('Ukiryu CLI Options')
  options.each do |key, value|
    debug_field(key.to_s, value.inspect, boxed: false)
  end
  
end

#error(message, context = {}) ⇒ Object

Log an error message

Parameters:

  • message (String)

    the message

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

    optional context data



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

def error(message, context = {})
  @output.puts(format_message('ERROR', message, :red, context))
end

#info(message, context = {}) ⇒ Object

Log an info message

Parameters:

  • message (String)

    the message

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

    optional context data



138
139
140
# File 'lib/ukiryu/logger.rb', line 138

def info(message, context = {})
  @output.puts(format_message('INFO', message, :green, context))
end

#set_level(level) ⇒ Object

Set the log level

Parameters:

  • level (Symbol)

    the log level (:debug, :info, :warn, :error)

Raises:

  • (ArgumentError)


111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ukiryu/logger.rb', line 111

def set_level(level)
  level_sym = level.to_sym
  raise ArgumentError, "Invalid log level: #{level}" unless LEVELS.include?(level_sym)

  @level = level_sym
  @logger.level = case level_sym
                  when :debug then ::Logger::DEBUG
                  when :info then ::Logger::INFO
                  when :warn then ::Logger::WARN
                  when :error then ::Logger::ERROR
                  end
end

#warn(message, context = {}) ⇒ Object

Log a warning message

Parameters:

  • message (String)

    the message

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

    optional context data



146
147
148
# File 'lib/ukiryu/logger.rb', line 146

def warn(message, context = {})
  @output.puts(format_message('WARN', message, :yellow, context))
end