lumberjack 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.rdoc +86 -0
  3. data/Rakefile +56 -0
  4. data/VERSION +1 -0
  5. data/lib/lumberjack.rb +39 -0
  6. data/lib/lumberjack/device.rb +26 -0
  7. data/lib/lumberjack/device/date_rolling_log_file.rb +58 -0
  8. data/lib/lumberjack/device/log_file.rb +18 -0
  9. data/lib/lumberjack/device/null.rb +15 -0
  10. data/lib/lumberjack/device/rolling_log_file.rb +109 -0
  11. data/lib/lumberjack/device/size_rolling_log_file.rb +58 -0
  12. data/lib/lumberjack/device/writer.rb +119 -0
  13. data/lib/lumberjack/formatter.rb +76 -0
  14. data/lib/lumberjack/formatter/exception_formatter.rb +12 -0
  15. data/lib/lumberjack/formatter/inspect_formatter.rb +10 -0
  16. data/lib/lumberjack/formatter/pretty_print_formatter.rb +23 -0
  17. data/lib/lumberjack/formatter/string_formatter.rb +10 -0
  18. data/lib/lumberjack/log_entry.rb +36 -0
  19. data/lib/lumberjack/logger.rb +302 -0
  20. data/lib/lumberjack/rack.rb +5 -0
  21. data/lib/lumberjack/rack/unit_of_work.rb +15 -0
  22. data/lib/lumberjack/severity.rb +23 -0
  23. data/lib/lumberjack/template.rb +71 -0
  24. data/spec/device/date_rolling_log_file_spec.rb +66 -0
  25. data/spec/device/date_rolling_log_file_spec.rbc +2118 -0
  26. data/spec/device/log_file_spec.rb +26 -0
  27. data/spec/device/log_file_spec.rbc +727 -0
  28. data/spec/device/null_spec.rb +12 -0
  29. data/spec/device/null_spec.rbc +362 -0
  30. data/spec/device/rolling_log_file_spec.rb +117 -0
  31. data/spec/device/rolling_log_file_spec.rbc +2894 -0
  32. data/spec/device/size_rolling_log_file_spec.rb +54 -0
  33. data/spec/device/size_rolling_log_file_spec.rbc +1961 -0
  34. data/spec/device/stream_spec.rbc +3310 -0
  35. data/spec/device/writer_spec.rb +118 -0
  36. data/spec/entry_spec.rbc +2333 -0
  37. data/spec/formatter/exception_formatter_spec.rb +20 -0
  38. data/spec/formatter/exception_formatter_spec.rbc +620 -0
  39. data/spec/formatter/inspect_formatter_spec.rb +13 -0
  40. data/spec/formatter/inspect_formatter_spec.rbc +360 -0
  41. data/spec/formatter/pretty_print_formatter_spec.rb +14 -0
  42. data/spec/formatter/pretty_print_formatter_spec.rbc +380 -0
  43. data/spec/formatter/string_formatter_spec.rb +12 -0
  44. data/spec/formatter/string_formatter_spec.rbc +314 -0
  45. data/spec/formatter_spec.rb +45 -0
  46. data/spec/formatter_spec.rbc +1431 -0
  47. data/spec/log_entry_spec.rb +69 -0
  48. data/spec/logger_spec.rb +390 -0
  49. data/spec/logger_spec.rbc +10043 -0
  50. data/spec/lumberjack_spec.rb +22 -0
  51. data/spec/lumberjack_spec.rbc +523 -0
  52. data/spec/rack/unit_of_work_spec.rb +26 -0
  53. data/spec/rack/unit_of_work_spec.rbc +697 -0
  54. data/spec/severity_spec.rb +23 -0
  55. data/spec/spec_helper.rb +16 -0
  56. data/spec/spec_helper.rbc +391 -0
  57. data/spec/template_spec.rb +34 -0
  58. data/spec/template_spec.rbc +1563 -0
  59. data/spec/unique_identifier_spec.rbc +329 -0
  60. metadata +128 -0
@@ -0,0 +1,119 @@
1
+ module Lumberjack
2
+ class Device
3
+ # This logging device writes log entries as strings to an IO stream.
4
+ class Writer < Device
5
+ DEFAULT_FIRST_LINE_TEMPLATE = "[:time :severity :progname(:pid) #:unit_of_work_id] :message".freeze
6
+ DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> [#:unit_of_work_id] :message".freeze
7
+
8
+ # The size of the internal buffer. Defaults to 32K.
9
+ attr_accessor :buffer_size
10
+
11
+ # Internal buffer to batch writes to the stream.
12
+ class Buffer # :nodoc:
13
+ attr_reader :size
14
+
15
+ def initialize
16
+ @values = []
17
+ @size = 0
18
+ end
19
+
20
+ def <<(string)
21
+ @values << string
22
+ @size += string.size
23
+ end
24
+
25
+ def empty?
26
+ @values.empty?
27
+ end
28
+
29
+ def join(delimiter)
30
+ @values.join(delimiter)
31
+ end
32
+
33
+ def clear
34
+ @values = []
35
+ @size = 0
36
+ end
37
+ end
38
+
39
+ # Create a new device to write log entries to a stream. Entries are converted to strings
40
+ # using a Template. The template can be specified using the <tt>:template</tt> option. This can
41
+ # either be a Proc or a string that will compile into a Template object.
42
+ #
43
+ # If the template is a Proc, it should accept an LogEntry as its only argument and output a string.
44
+ #
45
+ # If the template is a template string, it will be used to create a Template. The
46
+ # <tt>:additional_lines</tt> and <tt>:time_format</tt> options will be passed through to the
47
+ # Template constuctor.
48
+ #
49
+ # The default template is <tt>"[:time :severity :progname(:pid) #:unit_of_work_id] :message"</tt>
50
+ # with additional lines formatted as <tt>"\n [#:unit_of_work_id] :message"</tt>. The unit of
51
+ # work id will only appear if it is present.
52
+ def initialize(stream, options = {})
53
+ @lock = Mutex.new
54
+ @stream = stream
55
+ @stream.sync = true if @stream.respond_to?(:sync=)
56
+ @buffer = Buffer.new
57
+ @buffer_size = (options[:buffer_size] || 32 * 1024)
58
+ template = (options[:template] || DEFAULT_FIRST_LINE_TEMPLATE)
59
+ if template.respond_to?(:call)
60
+ @template = template
61
+ else
62
+ additional_lines = (options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE)
63
+ @template = Template.new(template, :additional_lines => additional_lines, :time_format => options[:time_format])
64
+ end
65
+ end
66
+
67
+ # Write an entry to the stream. The entry will be converted into a string using the defined template.
68
+ def write(entry)
69
+ string = @template.call(entry)
70
+ @lock.synchronize do
71
+ @buffer << string
72
+ end
73
+ flush if @buffer.size >= buffer_size
74
+ end
75
+
76
+ # Close the underlying stream.
77
+ def close
78
+ flush
79
+ stream.close
80
+ end
81
+
82
+ # Flush the underlying stream.
83
+ def flush
84
+ @lock.synchronize do
85
+ before_flush
86
+ unless @buffer.empty?
87
+ out = @buffer.join(Lumberjack::LINE_SEPARATOR) << Lumberjack::LINE_SEPARATOR
88
+ begin
89
+ stream.write(out)
90
+ stream.flush
91
+ rescue => e
92
+ $stderr.write("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}")
93
+ $stderr.write(out)
94
+ $stderr.flush
95
+ end
96
+ @buffer.clear
97
+ end
98
+ end
99
+ end
100
+
101
+ protected
102
+
103
+ # Callback method that will be executed before data is written to the stream. Subclasses
104
+ # can override this method if needed.
105
+ def before_flush
106
+ end
107
+
108
+ # Set the underlying stream.
109
+ def stream=(stream)
110
+ @stream = stream
111
+ end
112
+
113
+ # Get the underlying stream.
114
+ def stream
115
+ @stream
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,76 @@
1
+ module Lumberjack
2
+ # This class controls the conversion of log entry messages into strings. This allows you
3
+ # to log any object you want and have the logging system worry about converting it into a string.
4
+ #
5
+ # Formats are added to a Formatter by associating them with a class using the +add+ method. Formats
6
+ # are any object that responds to the +call+ method.
7
+ #
8
+ # By default, all object will be converted to strings using their inspect method except for Strings
9
+ # and Exceptions. Strings are not converted and Exceptions are converted using the ExceptionFormatter.
10
+ class Formatter
11
+ autoload :ExceptionFormatter, File.expand_path("../formatter/exception_formatter.rb", __FILE__)
12
+ autoload :InspectFormatter, File.expand_path("../formatter/inspect_formatter.rb", __FILE__)
13
+ autoload :PrettyPrintFormatter, File.expand_path("../formatter/pretty_print_formatter.rb", __FILE__)
14
+ autoload :StringFormatter, File.expand_path("../formatter/string_formatter.rb", __FILE__)
15
+
16
+ def initialize
17
+ @class_formatters = {}
18
+ @default_formatter = InspectFormatter.new
19
+ add(Object, @default_formatter)
20
+ add(String, :string)
21
+ add(Exception, :exception)
22
+ end
23
+
24
+ # Add a formatter for a class. The formatter can be specified as either an object
25
+ # that responds to the +call+ method or as a symbol representing one of the predefined
26
+ # formatters, or as a block to the method call.
27
+ #
28
+ # The predefined formatters are: <tt>:inspect</tt>, <tt>:string</tt>, <tt>:exception</tt>, and <tt>:pretty_print</tt>.
29
+ #
30
+ # === Examples
31
+ #
32
+ # # Use a predefined formatter
33
+ # formatter.add(MyClass, :pretty_print)
34
+ #
35
+ # # Pass in a formatter object
36
+ # formatter.add(MyClass, Lumberjack::Formatter::PrettyPrintFormatter.new)
37
+ #
38
+ # # Use a block
39
+ # formatter.add(MyClass){|obj| obj.humanize}
40
+ #
41
+ # # Add statements can be chained together
42
+ # formatter.add(MyClass, :pretty_print).add(YourClass){|obj| obj.humanize}
43
+ def add(klass, formatter = nil, &block)
44
+ formatter ||= block
45
+ if formatter.is_a?(Symbol)
46
+ formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter"
47
+ formatter = Formatter.const_get(formatter_class_name).new
48
+ end
49
+ @class_formatters[klass] = formatter
50
+ self
51
+ end
52
+
53
+ # Remove the formatter associated with a class. Remove statements can be chained together.
54
+ def remove(klass)
55
+ @class_formatters.delete(klass)
56
+ self
57
+ end
58
+
59
+ # Format a message object as a string.
60
+ def format(message)
61
+ formatter_for(message.class).call(message)
62
+ end
63
+
64
+ private
65
+
66
+ # Find the formatter for a class by looking it up using the class hierarchy.
67
+ def formatter_for(klass) #:nodoc:
68
+ while klass != nil do
69
+ formatter = @class_formatters[klass]
70
+ return formatter if formatter
71
+ klass = klass.superclass
72
+ end
73
+ @default_formatter
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,12 @@
1
+ module Lumberjack
2
+ class Formatter
3
+ # Format an exception including the backtrace.
4
+ class ExceptionFormatter
5
+ def call(exception)
6
+ message = "#{exception.class.name}: #{exception.message}"
7
+ message << "#{Lumberjack::LINE_SEPARATOR} #{exception.backtrace.join("#{Lumberjack::LINE_SEPARATOR} ")}" if exception.backtrace
8
+ message
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Lumberjack
2
+ class Formatter
3
+ # Format an object by calling +inspect+ on it.
4
+ class InspectFormatter
5
+ def call(obj)
6
+ obj.inspect
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ require 'pp'
2
+ require 'stringio'
3
+
4
+ module Lumberjack
5
+ class Formatter
6
+ # Format an object with it's pretty print method.
7
+ class PrettyPrintFormatter
8
+ attr_accessor :width
9
+
10
+ # Create a new formatter. The maximum width of the message can be specified with the width
11
+ # parameter (defaults to 79 characters).
12
+ def initialize(width = 79)
13
+ @width = width
14
+ end
15
+
16
+ def call(obj)
17
+ s = StringIO.new
18
+ PP.pp(obj, s)
19
+ s.string.chomp
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ module Lumberjack
2
+ class Formatter
3
+ # Format an object by calling +to_s+ on it.
4
+ class StringFormatter
5
+ def call(obj)
6
+ obj.to_s
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ module Lumberjack
2
+ # An entry in a log is a data structure that captures the log message as well as
3
+ # information about the system that logged the message.
4
+ class LogEntry
5
+ attr_accessor :time, :message, :severity, :progname, :pid, :unit_of_work_id
6
+
7
+ TIME_FORMAT = "%Y-%m-%dT%H:%M:%S".freeze
8
+
9
+ def initialize(time, severity, message, progname, pid, unit_of_work_id)
10
+ @time = time
11
+ @severity = (severity.is_a?(Fixnum) ? severity : Severity.label_to_level(severity))
12
+ @message = message
13
+ @progname = progname
14
+ @pid = pid
15
+ @unit_of_work_id = unit_of_work_id
16
+ end
17
+
18
+ def severity_label
19
+ Severity.level_to_label(severity)
20
+ end
21
+
22
+ def to_s
23
+ buf = "[#{time.strftime(TIME_FORMAT)}.#{(time.usec / 1000.0).round.to_s.rjust(3, '0')} #{severity_label} #{progname}(#{pid})"
24
+ if unit_of_work_id
25
+ buf << " #"
26
+ buf << unit_of_work_id
27
+ end
28
+ buf << "] "
29
+ buf << message
30
+ end
31
+
32
+ def inspect
33
+ to_s
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,302 @@
1
+ module Lumberjack
2
+ # Logger is a thread safe logging object. It has a compatible API with the Ruby
3
+ # standard library Logger class, the Log4r gem, and ActiveSupport::BufferedLogger.
4
+ #
5
+ # === Example
6
+ #
7
+ # logger = Lumberjack::Logger.new
8
+ # logger.info("Starting processing")
9
+ # logger.debug("Processing options #{options.inspect}")
10
+ # logger.fatal("OMG the application is on fire!")
11
+ #
12
+ # Log entries are written to a logging Device if their severity meets or exceeds the log level.
13
+ #
14
+ # Devices may use buffers internally and the log entries are not guaranteed to be written until you call
15
+ # the +flush+ method. Sometimes this can result in problems when trying to track down extraordinarily
16
+ # long running sections of code since it is likely that none of the messages logged before the long
17
+ # running code will appear in the log until the entire process finishes. You can set the +:flush_seconds+
18
+ # option on the constructor to force the device to be flushed periodically. This will create a new
19
+ # monitoring thread, but its use is highly recommended.
20
+ #
21
+ # Each log entry records the log message and severity along with the time it was logged, the
22
+ # program name, process id, and unit of work id. The message will be converted to a string, but
23
+ # otherwise, it is up to the device how these values are recorded. Messages are converted to strings
24
+ # using a Formatter associated with the logger.
25
+ class Logger
26
+ include Severity
27
+
28
+ # The Formatter object used to convert messages into strings.
29
+ attr_reader :formatter
30
+
31
+ # The time that the device was last flushed.
32
+ attr_reader :last_flushed_at
33
+
34
+ # The name of the program associated with log messages.
35
+ attr_writer :progname
36
+
37
+ # The device being written to.
38
+ attr_reader :device
39
+
40
+ # Set +silencer+ to false to disable silencing the log.
41
+ attr_accessor :silencer
42
+
43
+ # Create a new logger to log to a Device.
44
+ #
45
+ # The +device+ argument can be in any one of several formats.
46
+ #
47
+ # If it is a Device object, that object will be used.
48
+ # If it has a +write+ method, it will be wrapped in a Device::Writer class.
49
+ # If it is <tt>:null</tt>, it will be a Null device that won't record any output.
50
+ # Otherwise, it will be assumed to be file path and wrapped in a Device::LogFile class.
51
+ #
52
+ # This method can take the following options:
53
+ #
54
+ # * <tt>:level</tt> - The logging level below which messages will be ignored.
55
+ # * <tt>:progname</tt> - The name of the program that will be recorded with each log entry.
56
+ # * <tt>:flush_seconds</tt> - The maximum number of seconds between flush calls.
57
+ # * <tt>:roll</tt> - If the log device is a file path, it will be a Device::DateRollingLogFile if this is set.
58
+ # * <tt>:max_size</tt> - If the log device is a file path, it will be a Device::SizeRollingLogFile if this is set.
59
+ #
60
+ # All other options are passed to the device constuctor.
61
+ def initialize(device = STDOUT, options = {})
62
+ @thread_settings = {}
63
+
64
+ options = options.dup
65
+ self.level = options.delete(:level) || INFO
66
+ self.progname = options.delete(:progname)
67
+ max_flush_seconds = options.delete(:flush_seconds).to_f
68
+
69
+ @device = open_device(device, options)
70
+ @formatter = Formatter.new
71
+ @lock = Mutex.new
72
+ @last_flushed_at = Time.now
73
+ @silencer = true
74
+
75
+ create_flusher_thread(max_flush_seconds) if max_flush_seconds > 0
76
+ end
77
+
78
+ # Get the level of severity of entries that are logged. Entries with a lower
79
+ # severity level will be ignored.
80
+ def level
81
+ thread_local_value(:lumberjack_logger_level) || @level
82
+ end
83
+
84
+ # Add a message to the log with a given severity. The message can be either
85
+ # passed in the +message+ argument or supplied with a block. This method
86
+ # is not normally called. Instead call one of the helper functions
87
+ # +fatal+, +error+, +warn+, +info+, or +debug+.
88
+ #
89
+ # The severity can be passed in either as one of the Severity constants,
90
+ # or as a Severity label.
91
+ #
92
+ # === Example
93
+ #
94
+ # logger.add(Lumberjack::Severity::ERROR, exception)
95
+ # logger.add(Lumberjack::Severity::INFO, "Request completed")
96
+ # logger.add(:warn, "Request took a long time")
97
+ # logger.add(Lumberjack::Severity::DEBUG){"Start processing with options #{options.inspect}"}
98
+ def add(severity, message = nil, progname = nil)
99
+ severity = Severity.label_to_level(severity) if severity.is_a?(String) || severity.is_a?(Symbol)
100
+ if severity && severity >= level
101
+ time = Time.now
102
+ message = yield if message.nil? && block_given?
103
+ message = @formatter.format(message)
104
+ entry = LogEntry.new(time, severity, message, progname || self.progname, $$, Lumberjack.unit_of_work_id)
105
+ begin
106
+ device.write(entry)
107
+ rescue => e
108
+ $stderr.puts("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}")
109
+ $stderr.puts(entry.to_s)
110
+ end
111
+ end
112
+ nil
113
+ end
114
+
115
+ alias_method :log, :add
116
+
117
+ # Flush the logging device.
118
+ def flush
119
+ device.flush
120
+ @last_flushed_at = Time.now
121
+ nil
122
+ end
123
+
124
+ # Close the logging device.
125
+ def close
126
+ flush
127
+ @device.close if @device.respond_to?(:close)
128
+ end
129
+
130
+ # Log a +FATAL+ message. The message can be passed in either the +message+ argument or in a block.
131
+ def fatal(message = nil, progname = nil, &block)
132
+ add(FATAL, message, progname, &block)
133
+ end
134
+
135
+ # Return +true+ if +FATAL+ messages are being logged.
136
+ def fatal?
137
+ level <= FATAL
138
+ end
139
+
140
+ # Log an +ERROR+ message. The message can be passed in either the +message+ argument or in a block.
141
+ def error(message = nil, progname = nil, &block)
142
+ add(ERROR, message, progname, &block)
143
+ end
144
+
145
+ # Return +true+ if +ERROR+ messages are being logged.
146
+ def error?
147
+ level <= ERROR
148
+ end
149
+
150
+ # Log a +WARN+ message. The message can be passed in either the +message+ argument or in a block.
151
+ def warn(message = nil, progname = nil, &block)
152
+ add(WARN, message, progname, &block)
153
+ end
154
+
155
+ # Return +true+ if +WARN+ messages are being logged.
156
+ def warn?
157
+ level <= WARN
158
+ end
159
+
160
+ # Log an +INFO+ message. The message can be passed in either the +message+ argument or in a block.
161
+ def info(message = nil, progname = nil, &block)
162
+ add(INFO, message, progname, &block)
163
+ end
164
+
165
+ # Return +true+ if +INFO+ messages are being logged.
166
+ def info?
167
+ level <= INFO
168
+ end
169
+
170
+ # Log a +DEBUG+ message. The message can be passed in either the +message+ argument or in a block.
171
+ def debug(message = nil, progname = nil, &block)
172
+ add(DEBUG, message, progname, &block)
173
+ end
174
+
175
+ # Return +true+ if +DEBUG+ messages are being logged.
176
+ def debug?
177
+ level <= DEBUG
178
+ end
179
+
180
+ # Log a message when the severity is not known. Unknown messages will always appear in the log.
181
+ # The message can be passed in either the +message+ argument or in a block.
182
+ def unknown(message = nil, progname = nil, &block)
183
+ add(UNKNOWN, message, progname, &block)
184
+ end
185
+
186
+ alias_method :<<, :unknown
187
+
188
+ # Set the minimum level of severity of messages to log.
189
+ def level=(severity)
190
+ if severity.is_a?(Fixnum)
191
+ @level = severity
192
+ else
193
+ @level = Severity.label_to_level(severity)
194
+ end
195
+ end
196
+
197
+ # Silence the logger by setting a new log level inside a block. By default, only +ERROR+ or +FATAL+
198
+ # messages will be logged.
199
+ #
200
+ # === Example
201
+ #
202
+ # logger.level = Lumberjack::Severity::INFO
203
+ # logger.silence do
204
+ # do_something # Log level inside the block is +ERROR+
205
+ # end
206
+ def silence(temporary_level = ERROR, &block)
207
+ if silencer
208
+ push_thread_local_value(:lumberjack_logger_level, temporary_level, &block)
209
+ else
210
+ yield
211
+ end
212
+ end
213
+
214
+ # Set the program name that is associated with log messages. If a block
215
+ # is given, the program name will be valid only within the block.
216
+ def set_progname(value, &block)
217
+ if block
218
+ push_thread_local_value(:lumberjack_logger_progname, value, &block)
219
+ else
220
+ self.progname = value
221
+ end
222
+ end
223
+
224
+ # Get the program name associated with log messages.
225
+ def progname
226
+ thread_local_value(:lumberjack_logger_progname) || @progname
227
+ end
228
+
229
+ private
230
+
231
+ # Set a local value for a thread tied to this object.
232
+ def set_thread_local_value(name, value) #:nodoc:
233
+ values = Thread.current[name]
234
+ unless values
235
+ values = {}
236
+ Thread.current[name] = values
237
+ end
238
+ if value.nil?
239
+ values.delete(self)
240
+ Thread.current[name] = nil if values.empty?
241
+ else
242
+ values[self] = value
243
+ end
244
+ end
245
+
246
+ # Get a local value for a thread tied to this object.
247
+ def thread_local_value(name) #:nodoc:
248
+ values = Thread.current[name]
249
+ values[self] if values
250
+ end
251
+
252
+ # Set a local value for a thread tied to this object within a block.
253
+ def push_thread_local_value(name, value) #:nodoc:
254
+ save_val = thread_local_value(name)
255
+ set_thread_local_value(name, value)
256
+ begin
257
+ yield
258
+ ensure
259
+ set_thread_local_value(name, save_val)
260
+ end
261
+ end
262
+
263
+ # Open a logging device.
264
+ def open_device(device, options) #:nodoc:
265
+ if device.is_a?(Device)
266
+ device
267
+ elsif device.respond_to?(:write)
268
+ Device::Writer.new(device, options)
269
+ elsif device == :null
270
+ Device::Null.new
271
+ else
272
+ device = device.to_s
273
+ if options[:roll]
274
+ Device::DateRollingLogFile.new(device, options)
275
+ elsif options[:max_size]
276
+ Device::SizeRollingLogFile.new(device, options)
277
+ else
278
+ Device::LogFile.new(device, options)
279
+ end
280
+ end
281
+ end
282
+
283
+ # Create a thread that will periodically call flush.
284
+ def create_flusher_thread(flush_seconds) #:nodoc:
285
+ if flush_seconds > 0
286
+ begin
287
+ logger = self
288
+ Thread.new do
289
+ loop do
290
+ begin
291
+ sleep(flush_seconds)
292
+ logger.flush if Time.now - logger.last_flushed_at >= flush_seconds
293
+ rescue => e
294
+ STDERR.puts("Error flushing log: #{e.inspect}")
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end