File: logger.rb

package info (click to toggle)
ruby-dataobjects 0.10.8-4
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 416 kB
  • sloc: ruby: 2,917; makefile: 4
file content (255 lines) | stat: -rw-r--r-- 7,201 bytes parent folder | download | duplicates (5)
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
require "time" # httpdate

module DataObjects

  module Logging

    def log(message)
      logger = driver_namespace.logger
      if logger.level <= DataObjects::Logger::LEVELS[:debug]
        message = "(%.6f) %s" % [message.duration / 1000000.0, message.query]
        logger.debug message
      end
    end

  end

  class << self
    # The global logger for DataObjects
    attr_accessor :logger
  end

  # ==== Public DataObjects Logger API
  #
  # Logger taken from Merb :)
  #
  # To replace an existing logger with a new one:
  #  DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
  #
  # Available logging levels are
  #   DataObjects::Logger::{ Fatal, Error, Warn, Info, Debug }
  #
  # Logging via:
  #   DataObjects.logger.fatal(message<String>)
  #   DataObjects.logger.error(message<String>)
  #   DataObjects.logger.warn(message<String>)
  #   DataObjects.logger.info(message<String>)
  #   DataObjects.logger.debug(message<String>)
  #
  # Flush the buffer to
  #   DataObjects.logger.flush
  #
  # Remove the current log object
  #   DataObjects.logger.close
  #
  # ==== Private DataObjects Logger API
  #
  # To initialize the logger you create a new object, proxies to set_log.
  #   DataObjects::Logger.new(log{String, IO},level{Symbol, String})
  #
  # Logger will not create the file until something is actually logged
  # This avoids file creation on DataObjects init when it creates the
  # default logger.
  class Logger

    # Use asynchronous I/O?
    attr_accessor :aio
    # delimiter to use between message sections
    attr_accessor :delimiter
    # a symbol representing the log level from {:off, :fatal, :error, :warn, :info, :debug}
    attr_reader   :level
    # Direct access to the buffer
    attr_reader   :buffer
    # The name of the log file
    attr_reader   :log

    Message = Struct.new(:query, :start, :duration)

    #
    # Ruby (standard) logger levels:
    #   off:   absolutely nothing
    #   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
    #
    # DataObjects::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
    LEVELS =
    {
      :off   => 99999,
      :fatal => 7,
      :error => 6,
      :warn  => 4,
      :info  => 3,
      :debug => 0
    }

    # Set the log level (use the level symbols as documented)
    def level=(new_level)
      @level = LEVELS[new_level.to_sym]
      reset_methods(:close)
    end

    private

    # 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

        # Determine if asynchronous IO can be used
        def aio?
          @aio = !RUBY_PLATFORM.match(/java|mswin/) &&
          !(@log == STDOUT) &&
          @log.respond_to?(:write_nonblock)
        end

        # Define the write method based on if aio an be used
        undef write_method if defined? write_method
        if aio?
          alias :write_method :write_nonblock
        else
          alias :write_method :write
        end
      end
    end

    def initialize_log(log)
      close if @log # be sure that we don't leave open files laying around.
      @log = log || "log/dm.log"
    end

    def reset_methods(o_or_c)
      if o_or_c == :open
        alias internal_push push_opened
      elsif o_or_c == :close
        alias internal_push push_closed
      end
    end

    def push_opened(string)
      message = Time.now.httpdate
      message << delimiter
      message << string
      message << "\n" unless message[-1] == ?\n
      @buffer << message
      flush # Force a flush for now until we figure out where we want to use the buffering.
    end

    def push_closed(string)
      unless @log.respond_to?(:write)
        log = Pathname(@log)
        log.dirname.mkpath
        @log = log.open('a')
        @log.sync = true
      end
      set_write_method
      reset_methods(:open)
      push(string)
    end

    alias internal_push push_closed

    def prep_msg(message, level)
      level << delimiter << message
    end

    public

    # To initialize the logger you create a new object, proxies to set_log.
    #   DataObjects::Logger.new(log{String, IO},level{Symbol, String})
    #
    # @param log<IO,String>        either an IO object or a name of a logfile.
    # @param log_level<String>     the message string to be logged
    # @param delimiter<String>     delimiter to use between message sections
    # @param log_creation<Boolean> log that the file is being created
    def initialize(*args)
      set_log(*args)
    end

    # To replace an existing logger with a new one:
    #  DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
    #
    #
    # @param log<IO,String>        either an IO object or a name of a logfile.
    # @param log_level<Symbol>     a symbol representing the log level from
    #   {:off, :fatal, :error, :warn, :info, :debug}
    # @param delimiter<String>     delimiter to use between message sections
    # @param log_creation<Boolean> log that the file is being created
    def set_log(log, log_level = :off, delimiter = " ~ ", log_creation = false)
      delimiter    ||= " ~ "

      if log_level && LEVELS[log_level.to_sym]
        self.level = log_level.to_sym
      else
        self.level = :debug
      end

      @buffer    = []
      @delimiter = delimiter

      initialize_log(log)

      DataObjects.logger = self

      self.info("Logfile created") if log_creation
    end

    # Flush the entire buffer to the log object.
    #   DataObjects.logger.flush
    #
    def flush
      return unless @buffer.size > 0
      @log.write_method(@buffer.slice!(0..-1).join)
    end

    # Close and remove the current log object.
    #   DataObjects.logger.close
    #
    def close
      flush
      @log.close if @log.respond_to?(:close)
      @log = nil
    end

    # Appends a string and log level to logger's buffer.

    #
    # Note that the string is discarded if the string's log level less than the
    # logger's log level.
    #
    # Note that if the logger is aio capable then the logger will use
    # non-blocking asynchronous writes.
    #
    # @param level<Fixnum>  the logging level as an integer
    # @param string<String> the message string to be logged
    def push(string)
      internal_push(string)
    end
    alias << push

    # Generate the following logging methods for DataObjects.logger as described
    # in the API:
    #  :fatal, :error, :warn, :info, :debug
    #  :off only gets an off? method
    LEVELS.each_pair do |name, number|
      unless name.to_sym == :off
        class_eval <<-EOS, __FILE__, __LINE__
          # DOC
          def #{name}(message)
            self.<<( prep_msg(message, "#{name}") ) if #{name}?
          end
        EOS
      end

      class_eval <<-EOS, __FILE__, __LINE__
        # DOC
        def #{name}?
          #{number} >= level
        end
      EOS
    end

  end # class Logger
end # module DataObjects