wycats / merb-core

Merb Core: All you need. None you don't.

This URL has Read+Write access

merb-core / lib / merb-core / logger.rb
100755 230 lines (205 sloc) 6.649 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
require "time" # httpdate
# ==== Public Merb Logger API
#
# To replace an existing logger with a new one:
# Merb::Logger.set_log(log{String, IO},level{Symbol, String})
#
# Available logging levels are
# Merb::Logger::{ Fatal, Error, Warn, Info, Debug }
#
# Logging via:
# Merb.logger.fatal(message<String>,&block)
# Merb.logger.error(message<String>,&block)
# Merb.logger.warn(message<String>,&block)
# Merb.logger.info(message<String>,&block)
# Merb.logger.debug(message<String>,&block)
#
# Logging with autoflush:
# Merb.logger.fatal!(message<String>,&block)
# Merb.logger.error!(message<String>,&block)
# Merb.logger.warn!(message<String>,&block)
# Merb.logger.info!(message<String>,&block)
# Merb.logger.debug!(message<String>,&block)
#
# Flush the buffer to
# Merb.logger.flush
#
# Remove the current log object
# Merb.logger.close
#
# ==== Private Merb Logger API
#
# To initialize the logger you create a new object, proxies to set_log.
# Merb::Logger.new(log{String, IO},level{Symbol, String})
module Merb
 
  class << self #:nodoc:
    attr_accessor :logger
  end
 
  class Logger
 
    attr_accessor :aio
    attr_accessor :level
    attr_accessor :delimiter
    attr_accessor :auto_flush
    attr_reader :buffer
    attr_reader :log
    attr_reader :init_args
 
    # ==== Notes
    # Ruby (standard) logger levels:
    # :fatal:: An unhandleable error that results in a program crash
    # :error:: A handleable error condition
    # :warn:: A warning
    # :info:: generic (useful) information about system operation
    # :debug:: low-level information for developers
    Levels =
    {
      :fatal => 7,
      :error => 6,
      :warn => 4,
      :info => 3,
      :debug => 0
    }
 
    private
 
    # Define the write method based on if asynchronous IO an be used.
    #
    # ==== Notes
    # The idea here is that instead of performing an 'if' conditional check on
    # each logging we do it once when the log object is setup.
    def set_write_method
      @log.instance_eval do
 
        # ==== Returns
        # Boolean:: True if asynchronous IO can be used.
        def aio?
          @aio = !Merb.environment.to_s.match(/development|test/) &&
          !RUBY_PLATFORM.match(/java|mswin/) &&
          !(@log == STDOUT) &&
          @log.respond_to?(:write_nonblock)
        end
 
        undef write_method if defined? write_method #:nodoc:
        if aio?
          alias :write_method :write_nonblock
        else
          alias :write_method :write
        end
      end
    end
 
    # Readies a log for writing.
    #
    # ==== Parameters
    # log<IO, String>:: Either an IO object or a name of a logfile.
    def initialize_log(log)
      close if @log # be sure that we don't leave open files laying around.
 
      if log.respond_to?(:write)
        @log = log
      elsif File.exist?(log)
        @log = open(log, (File::WRONLY | File::APPEND))
        @log.sync = true
      else
        FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
        @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
        @log.sync = true
        @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
      end
      set_write_method
    end
 
    public
 
    # To initialize the logger you create a new object, proxies to set_log.
    #
    # ==== Parameters
    # *args:: Arguments to create the log from. See set_logs for specifics.
    def initialize(*args)
      @init_args = args
      set_log(*args)
    end
 
    # Replaces an existing logger with a new one.
    #
    # ==== Parameters
    # log<IO, String>:: Either an IO object or a name of a logfile.
    # log_level<~to_sym>::
    # The log level from, e.g. :fatal or :info. Defaults to :error in the
    # production environment and :debug otherwise.
    # delimiter<String>::
    # Delimiter to use between message sections. Defaults to " ~ ".
    # auto_flush<Boolean>::
    # Whether the log should automatically flush after new messages are
    # added. Defaults to false.
    def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
      if log_level && Levels[log_level.to_sym]
        @level = Levels[log_level.to_sym]
      elsif Merb.environment == "production"
        @level = Levels[:warn]
      else
        @level = Levels[:debug]
      end
      @buffer = []
      @delimiter = delimiter
      @auto_flush = auto_flush
 
      initialize_log(log)
 
      Merb.logger = self
    end
 
    # Flush the entire buffer to the log object.
    def flush
      return unless @buffer.size > 0
      @log.write_method(@buffer.slice!(0..-1).to_s)
    end
 
    # Close and remove the current log object.
    def close
      flush
      @log.close if @log.respond_to?(:close)
      @log = nil
    end
 
    # Appends a message to the log. The methods yield to an optional block and
    # the output of this block will be appended to the message.
    #
    # ==== Parameters
    # string<String>:: The message to be logged. Defaults to nil.
    #
    # ==== Returns
    # String:: The resulting message added to the log file.
    def <<(string = nil)
      message = ""
      message << delimiter
      message << string if string
      message << "\n" unless message[-1] == ?\n
      @buffer << message
      flush if @auto_flush
 
      message
    end
    alias :push :<<
 
    # Generate the logging methods for Merb.logger for each log level.
    Levels.each_pair do |name, number|
      class_eval <<-LEVELMETHODS, __FILE__, __LINE__
 
# Appends a message to the log if the log level is at least as high as
# the log level of the logger.
#
# ==== Parameters
# string<String>:: The message to be logged. Defaults to nil.
#
# ==== Returns
# self:: The logger object for chaining.
def #{name}(message = nil)
self << message if #{number} >= level
self
end
 
# Appends a message to the log if the log level is at least as high as
# the log level of the logger. The bang! version of the method also auto
# flushes the log buffer to disk.
#
# ==== Parameters
# string<String>:: The message to be logged. Defaults to nil.
#
# ==== Returns
# self:: The logger object for chaining.
def #{name}!(message = nil)
self << message if #{number} >= level
flush if #{number} >= level
self
end
 
# ==== Returns
# Boolean:: True if this level will be logged by this logger.
def #{name}?
#{number} >= level
end
LEVELMETHODS
    end
 
  end
  
end