Class: Doing::Logger

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

Overview

Log adapter

Constant Summary collapse

TOPIC_WIDTH =
12
LOG_LEVELS =
{
  debug: ::Logger::DEBUG,
  info: ::Logger::INFO,
  warn: ::Logger::WARN,
  error: ::Logger::ERROR
}.freeze
COUNT_KEYS =
%i[
  added
  added_tags
  archived
  autotag
  completed
  completed_archived
  deleted
  moved
  removed_tags
  rotated
  skipped
  updated
  exported
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(level = :info) ⇒ Logger

Create a new instance of a log writer

Parameters:

  • level (optional, symbol) (defaults to: :info)

    the log level



49
50
51
52
53
54
55
56
57
58
# File 'lib/doing/logger.rb', line 49

def initialize(level = :info)
  @messages = []
  @counters = {}
  COUNT_KEYS.each { |key| @counters[key] = { tag: [], count: 0 } }
  @results = []
  @logdev = $stderr
  @max_length = TTY::Screen.columns - 5 || 85
  self.log_level = level
  @prev_level = level
end

Instance Attribute Details

#levelObject (readonly)

Returns the current log level (debug, info, warn, error)



15
16
17
# File 'lib/doing/logger.rb', line 15

def level
  @level
end

#logdev=(value) ⇒ Object (writeonly)

Sets the log device



9
10
11
# File 'lib/doing/logger.rb', line 9

def logdev=(value)
  @logdev = value
end

#max_length=(value) ⇒ Object (writeonly)

Max length of log messages (truncate in middle)



12
13
14
# File 'lib/doing/logger.rb', line 12

def max_length=(value)
  @max_length = value
end

#messagesObject (readonly)

Returns the value of attribute messages.



17
18
19
# File 'lib/doing/logger.rb', line 17

def messages
  @messages
end

#resultsObject (readonly)

Returns the value of attribute results.



17
18
19
# File 'lib/doing/logger.rb', line 17

def results
  @results
end

Instance Method Details

#abort_with(topic, message = nil, &block) ⇒ Object

Print an error message and immediately abort the process

Parameters:

  • topic

    the topic of the message, e.g. "Configuration file", "Deprecation", etc.

  • message (defaults to: nil)

    the message detail (can be omitted)

Returns:

  • nothing



187
188
189
190
# File 'lib/doing/logger.rb', line 187

def abort_with(topic, message = nil, &block)
  error(topic, message, &block)
  abort
end

#adjust_verbosity(options = {}) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
# File 'lib/doing/logger.rb', line 100

def adjust_verbosity(options = {})
  if options[:log_level]
    self.log_level = options[:log_level].to_sym
  elsif options[:quiet]
    self.log_level = :error
  elsif options[:verbose] || options[:debug]
    self.log_level = :debug
  end
  log_now :debug, 'Logging at level:', @level.to_s
  # log_now :debug, 'Doing Version:', Doing::VERSION
end

#benchmark(key, state) ⇒ Object



270
271
272
273
274
275
276
# File 'lib/doing/logger.rb', line 270

def benchmark(key, state)
  return unless ENV['DOING_BENCHMARK']

  @benchmarks ||= {}
  @benchmarks[key] ||= { start: nil, finish: nil }
  @benchmarks[key][state] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

#count(key, level: :info, count: 1, tag: nil, message: nil) ⇒ Object

Raises:

  • (ArgumentError)


112
113
114
115
116
117
118
119
# File 'lib/doing/logger.rb', line 112

def count(key, level: :info, count: 1, tag: nil, message: nil)
  raise ArgumentError, 'invalid counter key' unless COUNT_KEYS.include?(key)

  @counters[key][:count] += count
  @counters[key][:tag].concat(tag).sort.uniq unless tag.nil?
  @counters[key][:level] ||= level
  @counters[key][:message] ||= message
end

#debug(topic, message = nil, &block) ⇒ Object

Print a debug message

Parameters:

  • topic

    the topic of the message

  • message (defaults to: nil)

    the message detail

Returns:

  • nothing



129
130
131
# File 'lib/doing/logger.rb', line 129

def debug(topic, message = nil, &block)
  write(:debug, topic, message, &block)
end

#error(topic, message = nil, &block) ⇒ Object

Print an error message

Parameters:

  • topic

    the topic of the message, e.g. "Configuration file", "Deprecation", etc.

  • message (defaults to: nil)

    the message detail

Returns:

  • nothing



171
172
173
# File 'lib/doing/logger.rb', line 171

def error(topic, message = nil, &block)
  write(:error, topic, message, &block)
end

#formatted_topic(topic, colon: false) ⇒ Object

Format the topic

Parameters:

  • topic

    the topic of the message, e.g. "Configuration file", "Deprecation", etc.

  • colon (defaults to: false)

    Separate with a colon?

Returns:

  • the formatted topic statement



202
203
204
205
206
207
208
209
210
# File 'lib/doing/logger.rb', line 202

def formatted_topic(topic, colon: false)
  if colon
    "#{topic}: ".rjust(TOPIC_WIDTH)
  elsif topic =~ /:$/
    "#{topic} ".rjust(TOPIC_WIDTH)
  else
    "#{topic} "
  end
end

#info(topic, message = nil, &block) ⇒ Object

Print a message

Parameters:

  • topic

    the topic of the message, e.g. "Configuration file", "Deprecation", etc.

  • message (defaults to: nil)

    the message detail

Returns:

  • nothing



143
144
145
# File 'lib/doing/logger.rb', line 143

def info(topic, message = nil, &block)
  write(:info, topic, message, &block)
end

#log_benchmarksObject



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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/doing/logger.rb', line 278

def log_benchmarks
  if ENV['DOING_BENCHMARK']

    output = []
    beginning = @benchmarks[:total][:start]
    ending = @benchmarks[:total][:finish]
    total = ending - beginning
    factor = TTY::Screen.columns / total

    cols = Array.new(TTY::Screen.columns)

    colors = %w[bgred bggreen bgyellow bgblue bgmagenta bgcyan bgwhite boldbgred boldbggreen boldbgyellow boldbgblue boldbgwhite]
    idx = 0
    # @benchmarks.delete(:total)

    @benchmarks.sort_by { |_, timers| [timers[:start], timers[:finish]] }.each do |k, timers|
      if timers[:finish] && timers[:start]
        color = colors[idx % colors.count]
        fg = if idx < 7
               Color.boldblack
             else
               Color.boldwhite
             end
        color = Color.send(color) + fg

        start = ((timers[:start] - beginning) * factor).floor
        finish = ((timers[:finish] - beginning) * factor).ceil

        cols.fill("#{color}-", start..finish)
        cols[start] = "#{color}|"
        cols[finish] = "#{color}|"
        output << "#{color}#{k}#{Color.default}: #{timers[:finish] - timers[:start]}"
      else
        output << "#{k}: error"
      end

      idx += 1
    end

    output.each do |msg|
      $stdout.puts color_message(:debug, 'Benchmark:', msg)
    end

    $stdout.puts color_message(:debug, 'Benchmark:', "Total: #{total}")

    $stdout.puts cols[0..TTY::Screen.columns-1].join + Color.reset
  end
end

#log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false) ⇒ Object



327
328
329
330
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
356
357
# File 'lib/doing/logger.rb', line 327

def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
  if tags_added.empty? && tags_removed.empty?
    count(:skipped, level: :debug, message: '%count %items with no change', count: count)
  else
    if tags_added.empty?
      count(:skipped, level: :debug, message: 'no tags added to %count %items')
    elsif single && item
      elapsed = if item && tags_added.include?('done')
                  item.interval ? " (#{item.interval&.time_string(format: :dhm)})" : ''
                else
                  ''
                end

      added = tags_added.log_tags
      info('Tagged:',
           %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{added}#{elapsed} to #{item.title}))
    else
      count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
    end

    if tags_removed.empty?
      count(:skipped, level: :debug, message: 'no tags removed from %count %items')
    elsif single && item
      removed = tags_removed.log_tags
      info('Untagged:',
           %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{removed} from #{item.title}))
    else
      count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
    end
  end
end

#log_level=(level = 'info') ⇒ Object

Set the log level on the writer

Parameters:

  • level (symbol) (defaults to: 'info')

    the log level

Returns:

  • nothing



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/doing/logger.rb', line 67

def log_level=(level = 'info')
  level = level.to_s

  level = case level
          when /^[e0]/i
            :error
          when /^[w1]/i
            :warn
          when /^[d3]/i
            :debug
          else
            :info
          end

  @level = level
end

#log_now(level, topic, message = nil, &block) ⇒ Object

Log to console immediately instead of writing messages on exit

Parameters:

  • level (Symbol)

    The level

  • topic (String)

    The topic or full message

  • message (String) (defaults to: nil)

    The message (optional)

  • block

    a block containing the message (optional)



241
242
243
244
245
246
247
248
249
# File 'lib/doing/logger.rb', line 241

def log_now(level, topic, message = nil, &block)
  return false unless write_message?(level)

  if @logdev == $stdout
    @logdev.puts message(topic, message, &block)
  else
    @logdev.puts color_message(level, topic, message, &block)
  end
end

#output_resultsObject

Output registers based on log level

Returns:

  • nothing



256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/doing/logger.rb', line 256

def output_results
  total_counters
  results = @results.select { |msg| write_message?(msg[:level]) }.uniq

  if @logdev == $stdout
    $stdout.print results.map { |res| res[:message].uncolor }.join("\n")
    $stdout.puts
  else
    results.each do |msg|
      @logdev.puts color_message(msg[:level], msg[:message])
    end
  end
end

#restore_levelObject

Restore temporary level



93
94
95
96
97
98
# File 'lib/doing/logger.rb', line 93

def restore_level
  return if @prev_level.nil? || @prev_level == @log_level

  self.log_level = @prev_level
  @prev_level = nil
end

#temp_level(level) ⇒ Object

Set log level temporarily



85
86
87
88
89
90
# File 'lib/doing/logger.rb', line 85

def temp_level(level)
  return if level.nil? || level.to_sym == @log_level

  @prev_level = log_level.dup
  @log_level = level.to_sym
end

#warn(topic, message = nil, &block) ⇒ Object

Print a message

Parameters:

  • topic

    the topic of the message, e.g. "Configuration file", "Deprecation", etc.

  • message (defaults to: nil)

    the message detail

Returns:

  • nothing



157
158
159
# File 'lib/doing/logger.rb', line 157

def warn(topic, message = nil, &block)
  write(:warn, topic, message, &block)
end

#write(level_of_message, topic, message = nil, &block) ⇒ Boolean

Log a message.

Parameters:

  • level_of_message (Symbol)

    the Symbol level of message, one of :debug, :info, :warn, :error

  • topic (String)

    the String topic or full message

  • message (String) (defaults to: nil)

    the String message (optional)

  • block

    a block containing the message (optional)

Returns:

  • (Boolean)

    false if the message was not written



228
229
230
231
# File 'lib/doing/logger.rb', line 228

def write(level_of_message, topic, message = nil, &block)
  @results << { level: level_of_message, message: message(topic, message, &block) }
  true
end