Class: Furnish::Logger

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

Overview

Furnish::Logger is a thread safe, auto-flushing, IO-delegating logger with numeric level control.

See Furnish::Logger::Mixins for functionality you can add to your provisioners to deal with loggers easily.

Example:

# debug level is 0
logger = Furnish::Logger.new($stderr, 0)
# IO methods are sent straight to the IO object, synchronized by a
# mutex:
logger.puts "foo"
logger.print "foo"

# if_debug is a way to scope log writes:

# this will never run because debug level is 0
logger.if_debug(1) do
  # self is the IO object here
  puts "foo"
end

logger.if_debug(0) do # this will run
  puts "foo"
end

logger.debug_level = 2

# if_debug's parameter merely must equal or be less than the debug
# level to process.
logger.if_debug(1) do # will run
  puts "bar"
end

Defined Under Namespace

Modules: Mixins

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(logger_io = $stderr, debug_level = 0) ⇒ Logger

Create a new Furnish::Logger. Takes an IO object and an Integer debug level. See Furnish::Logger class documentation for more information.



86
87
88
89
90
91
# File 'lib/furnish/logger.rb', line 86

def initialize(logger_io=$stderr, debug_level=0)
  @write_mutex = Mutex.new
  @io = logger_io
  @io.sync = true
  @debug_level = debug_level
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args) ⇒ Object

Delegates to the Furnish::Logger#io if possible. If not possible, raises a NoMethodError. All calls are synchronized over the logger’s mutex.



219
220
221
222
223
224
225
# File 'lib/furnish/logger.rb', line 219

def method_missing(sym, *args)
  raise NoMethodError, "#{io.inspect} has no method #{sym}" unless io.respond_to?(sym)
  run = lambda { io.__send__(sym, *args) }
  @write_mutex.synchronize { run.call }
rescue ThreadError
  run.call
end

Instance Attribute Details

#debug_levelObject

Set the debug level - adjustable after creation.



69
70
71
# File 'lib/furnish/logger.rb', line 69

def debug_level
  @debug_level
end

#ioObject (readonly)

The IO object. Probably best to not mess with this attribute directly, most methods will be proxied to it.



75
76
77
# File 'lib/furnish/logger.rb', line 75

def io
  @io
end

#tagObject (readonly)

Optional tag. See #with_tag for an example.



80
81
82
# File 'lib/furnish/logger.rb', line 80

def tag
  @tag
end

Instance Method Details

#closeObject

Hacky close method that keeps us from closing stdio.



209
210
211
212
213
# File 'lib/furnish/logger.rb', line 209

def close
  # XXX StringIO apparently overrides respond_to_missing for #to_i but
  #     doesn't implement #to_i
  io.close unless io == $stdout or io == $stderr
end

#hijack_stdioObject

Makes stdio Furnish::Logger objects for the duration of the block. Used by #if_debug for the purposes of not breaking expectations.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/furnish/logger.rb', line 128

def hijack_stdio
  orig_stdout, orig_stderr = nil, nil

  if io != $stdout
    orig_stdout = $stdout
    $stdout = self
  end

  if io != $stderr
    orig_stderr = $stderr
    $stderr = self
  end

  yield
ensure
  $stdout = orig_stdout if orig_stdout
  $stderr = orig_stderr if orig_stderr
end

#if_debug(level = 1, else_block = nil, &block) ⇒ Object

Runs the block if the level is equal to or lesser than the Furnish::Logger#debug_level. The default debug level is 1.

The block runs in the context of the Furnish::Logger#io object, that is, ‘self` is the IO object.

If an additional proc is applied, will run that if the debug block would not fire, effectively creating an else. Generally an anti-pattern, but is useful in a few situations.

if_debug is synchronized over the logger’s mutex.



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/furnish/logger.rb', line 106

def if_debug(level=1, else_block=nil, &block)
  hijack_stdio do
    begin
      run = lambda do
        if debug_level >= level and block
          instance_eval(&block)
        elsif else_block
          instance_eval(&else_block)
        end
      end

      @write_mutex.synchronize { run.call }
    rescue ThreadError
      run.call
    end
  end
end

#puts(*args) ⇒ Object

Hacky puts method to handle tags.



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

def puts(*args)
  if tag
    io.print("[#{tag}] ")
  end

  method_missing(:puts, *args)
end

#redirect(new_io, &block) ⇒ Object

Temporarily redirects IO to a new IO object for the duration of the block. Example below:

Furnish.logger.redirect($stdout) do
  Furnish.logger.if_debug do
    puts "herp" # prints to stdout
  end
end

# prints to original logger IO
Furnish.logger.puts "hi"


160
161
162
163
164
165
# File 'lib/furnish/logger.rb', line 160

def redirect(new_io, &block)
  tmp_io = @io
  @io = new_io
  yield
  @io = tmp_io
end

#with_tag(new_tag, &block) ⇒ Object

Prefixes all ‘puts’ calls with a string of text (a “tag”) provided for the duration of the block.

Example:

@logger.with_tag("hi") do
  @logger.if_debug do
    puts "hello" # "[hi] hello"
  end
end

@logger.puts "hello" # "hello"


182
183
184
185
186
# File 'lib/furnish/logger.rb', line 182

def with_tag(new_tag, &block)
  @tag = new_tag
  yield
  @tag = nil
end

#write(*args) ⇒ Object

Makes it possible to assign this to $stdout and $stderr.



202
203
204
# File 'lib/furnish/logger.rb', line 202

def write(*args)
  method_missing(:write, *args)
end