Class: Loginator

Inherits:
Object show all
Defined in:
lib/ceedling/loginator.rb

Overview

Loginator handles console and file output of logging statements

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#decorators=(value) ⇒ Object (writeonly)

Sets the attribute decorators

Parameters:

  • value

    the value to set the attribute decorators to.



15
16
17
# File 'lib/ceedling/loginator.rb', line 15

def decorators=(value)
  @decorators = value
end

#project_loggingObject (readonly)

Returns the value of attribute project_logging.



14
15
16
# File 'lib/ceedling/loginator.rb', line 14

def project_logging
  @project_logging
end

Instance Method Details

#decorate(str, label = LogLabels::NONE) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/ceedling/loginator.rb', line 174

def decorate(str, label=LogLabels::NONE)
  return str if !@decorators

  prepend = ''

  case label
  when LogLabels::NOTICE
    prepend = 'ℹ️ '
  when LogLabels::WARNING
    prepend = '⚠️ '
  when LogLabels::ERROR
    prepend = '🪲 '
  when LogLabels::EXCEPTION
    prepend = '🧨 '
  when LogLabels::CONSTRUCT
    prepend = '🚧 '
  when LogLabels::CRASH
    prepend = '☠️ '
  when LogLabels::RUN
    prepend = '👟 '
  when LogLabels::PASS
    prepend = ''
  when LogLabels::FAIL
    prepend = ''
  when LogLabels::TITLE
    prepend = '🌱 '
  end

  return prepend + str
end

#log(message = "\n", verbosity = Verbosity::NORMAL, label = LogLabels::AUTO, stream = nil) ⇒ Object

log()


Write the given string to an optional log file and to the console

- Logging statements to a file are always at the highest verbosity
- Console logging is controlled by the verbosity level

For default label of LogLabels::AUTO

- If verbosity ERRORS, add ERROR: heading
- If verbosity COMPLAIN, added WARNING: heading
- All other verbosity levels default to no heading

By setting a label:

- A heading begins a message regardless of verbosity level, except NONE
- NONE forcibly presents headings and emoji decorators

If decoration is enabled:

- Add fun emojis before all headings, except TITLE
- TITLE log level adds seedling emoji alone

If decoration is disabled:

- No emojis are added to label
- Any problematic console characters in a message are replaced with
  simpler variants


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/ceedling/loginator.rb', line 143

def log(message="\n", verbosity=Verbosity::NORMAL, label=LogLabels::AUTO, stream=nil)
  # Choose appropriate console stream
  stream = get_stream( verbosity, stream )

  # Flatten if needed
  message = message.flatten.join("\n") if (message.class == Array)

  # Message contatenated with "\n" (unless it aready ends with a newline)
  message += "\n" unless message.end_with?( "\n" )

  # Add item to the queue
  item = {
    :message => message,
    :verbosity => verbosity,
    :label => label,
    :stream => stream
  }
  @queue << item
end

#log_debug_backtrace(exception) ⇒ Object



164
165
166
167
168
169
170
171
# File 'lib/ceedling/loginator.rb', line 164

def log_debug_backtrace(exception)
    log( "\nDebug Backtrace ==>", Verbosity::DEBUG )
    
    # Send backtrace to debug logging, formatted almost identically to how Ruby does it.
    # Don't log the exception message itself in the first `log()` call as it will already be logged elsewhere
    log( "#{exception.backtrace.first}: (#{exception.class})", Verbosity::DEBUG )
    log( exception.backtrace.drop(1).map{|s| "\t#{s}"}.join("\n"),                  Verbosity::DEBUG )
end

#sanitize(string, decorate = nil) ⇒ Object



206
207
208
209
210
211
212
213
214
# File 'lib/ceedling/loginator.rb', line 206

def sanitize(string, decorate=nil)
  # Redefine `decorate` to @decorators value by default,
  # otherwise use the argument as an override value
  decorate = @decorators if decorate.nil?

  # Remove problematic console characters in-place if decoration disabled
  @replace.each_pair {|k,v| string.gsub!( k, v) } if (decorate == false)
  return string
end

#set_logfile(log_filepath) ⇒ Object



110
111
112
113
114
115
# File 'lib/ceedling/loginator.rb', line 110

def set_logfile( log_filepath )
  if !log_filepath.empty?
    @project_logging = true
    @log_filepath = log_filepath
  end
end

#setupObject



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ceedling/loginator.rb', line 19

def setup()
  $loginator = self
  @decorators = false

  # Friendly robustification for certain testing scenarios
  unless @system_wrapper.nil?
    # Automatic setting of console printing decorations based on platform
    @decorators = !@system_wrapper.windows?()

    # Environment variable takes precedence over automatic setting
    _env = @system_wrapper.env_get('CEEDLING_DECORATORS') if !@system_wrapper.nil?
    if !_env.nil?
      if (_env == '1' or _env.strip().downcase() == 'true')
        @decorators = true
      elsif (_env == '0' or _env.strip().downcase() == 'false')
        @decorators = false
      end
    end
  end

  @replace = {
    # Problematic characters pattern => Simple characters
    // => '>>', # Config sub-entry notation
    // => '*',  # Bulleted lists
  }

  @project_logging = false
  @log_filepath = nil

  @queue = Queue.new
  @worker = Thread.new do
    # Run tasks until there are no more enqueued
    @done = false
    while !@done do
      Thread.handle_interrupt(Exception => :never) do
        begin
          Thread.handle_interrupt(Exception => :immediate) do
            # pop(false) is blocking and should just hang here and wait for next message
            item = @queue.pop(false)
            if (item.nil?)
              @done = true
              next
            end

            # pick out the details
            message   = item[:message]
            label     = item[:label]
            verbosity = item[:verbosity]
            stream    = item[:stream]
            
            # Write to log as though Verbosity::DEBUG (no filtering at all) but without fun characters
            if @project_logging
              file_msg = message.dup() # Copy for safe inline modifications
      
              # Add labels
              file_msg = format( file_msg, verbosity, label, false )
      
              # Note: In practice, file-based logging only works with trailing newlines (i.e. `log()` calls)
              #       `out()` calls will be a little ugly in the log file, but these are typically only
              #       used for console logging anyhow.
              logfile( sanitize( file_msg, false ), extract_stream_name( stream ) )
            end
      
            # Only output to console when message reaches current verbosity level
            if !stream.nil? && (@verbosinator.should_output?( verbosity ))
              # Add labels and fun characters
              console_msg = format( message, verbosity, label, @decorators )
      
              # Write to output stream after optionally removing any problematic characters
              stream.print( sanitize( console_msg, @decorators ) )
            end
          end
        rescue ThreadError
          @done = true
        rescue Exception => e
          puts e.inspect
        end
      end
    end
  end
end

#wrapupObject



101
102
103
104
105
106
107
108
# File 'lib/ceedling/loginator.rb', line 101

def wrapup
  begin
    @queue.close
    @worker.join
  rescue
    #If we failed at this point, just give up on it
  end
end