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
|
require 'thread'
require 'active_support/core_ext/class/attribute_accessors'
module ActiveSupport
# Inspired by the buffered logger idea by Ezra
class BufferedLogger
module Severity
DEBUG = 0
INFO = 1
WARN = 2
ERROR = 3
FATAL = 4
UNKNOWN = 5
end
include Severity
MAX_BUFFER_SIZE = 1000
##
# :singleton-method:
# Set to false to disable the silencer
cattr_accessor :silencer
self.silencer = true
# Silences the logger for the duration of the block.
def silence(temporary_level = ERROR)
if silencer
begin
old_logger_level, self.level = level, temporary_level
yield self
ensure
self.level = old_logger_level
end
else
yield self
end
end
attr_accessor :level
attr_reader :auto_flushing
def initialize(log, level = DEBUG)
@level = level
@buffer = Hash.new { |h,k| h[k] = [] }
@auto_flushing = 1
@guard = Mutex.new
if log.respond_to?(:write)
@log = log
elsif File.exist?(log)
@log = open(log, (File::WRONLY | File::APPEND))
@log.binmode
@log.sync = true
else
FileUtils.mkdir_p(File.dirname(log))
@log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
@log.binmode
@log.sync = true
end
end
def add(severity, message = nil, progname = nil, &block)
return if @level > severity
message = (message || (block && block.call) || progname).to_s
# If a newline is necessary then create a new message ending with a newline.
# Ensures that the original message is not mutated.
message = "#{message}\n" unless message[-1] == ?\n
buffer << message
auto_flush
message
end
# Dynamically add methods such as:
# def info
# def warn
# def debug
for severity in Severity.constants
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
end # end
def #{severity.downcase}? # def debug?
#{severity} >= @level # DEBUG >= @level
end # end
EOT
end
# Set the auto-flush period. Set to true to flush after every log message,
# to an integer to flush every N messages, or to false, nil, or zero to
# never auto-flush. If you turn auto-flushing off, be sure to regularly
# flush the log yourself -- it will eat up memory until you do.
def auto_flushing=(period)
@auto_flushing =
case period
when true; 1
when false, nil, 0; MAX_BUFFER_SIZE
when Integer; period
else raise ArgumentError, "Unrecognized auto_flushing period: #{period.inspect}"
end
end
def flush
@guard.synchronize do
buffer.each do |content|
@log.write(content)
end
# Important to do this even if buffer was empty or else @buffer will
# accumulate empty arrays for each request where nothing was logged.
clear_buffer
end
end
def close
flush
@log.close if @log.respond_to?(:close)
@log = nil
end
protected
def auto_flush
flush if buffer.size >= @auto_flushing
end
def buffer
@buffer[Thread.current]
end
def clear_buffer
@buffer.delete(Thread.current)
end
end
end
|