lumberjack 1.0.13 → 1.2.8

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 (58) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +129 -0
  3. data/{MIT_LICENSE → MIT_LICENSE.txt} +0 -0
  4. data/README.md +142 -23
  5. data/VERSION +1 -1
  6. data/lib/lumberjack.rb +70 -22
  7. data/lib/lumberjack/context.rb +35 -0
  8. data/lib/lumberjack/device.rb +22 -8
  9. data/lib/lumberjack/device/date_rolling_log_file.rb +9 -9
  10. data/lib/lumberjack/device/log_file.rb +14 -3
  11. data/lib/lumberjack/device/multi.rb +46 -0
  12. data/lib/lumberjack/device/null.rb +1 -3
  13. data/lib/lumberjack/device/rolling_log_file.rb +45 -21
  14. data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
  15. data/lib/lumberjack/device/writer.rb +92 -61
  16. data/lib/lumberjack/formatter.rb +97 -28
  17. data/lib/lumberjack/formatter/date_time_formatter.rb +25 -0
  18. data/lib/lumberjack/formatter/exception_formatter.rb +25 -2
  19. data/lib/lumberjack/formatter/id_formatter.rb +23 -0
  20. data/lib/lumberjack/formatter/object_formatter.rb +12 -0
  21. data/lib/lumberjack/formatter/pretty_print_formatter.rb +4 -4
  22. data/lib/lumberjack/formatter/string_formatter.rb +1 -1
  23. data/lib/lumberjack/formatter/strip_formatter.rb +12 -0
  24. data/lib/lumberjack/formatter/structured_formatter.rb +63 -0
  25. data/lib/lumberjack/log_entry.rb +44 -16
  26. data/lib/lumberjack/logger.rb +275 -69
  27. data/lib/lumberjack/rack.rb +3 -2
  28. data/lib/lumberjack/rack/context.rb +18 -0
  29. data/lib/lumberjack/rack/request_id.rb +4 -4
  30. data/lib/lumberjack/rack/unit_of_work.rb +1 -1
  31. data/lib/lumberjack/severity.rb +11 -10
  32. data/lib/lumberjack/tag_formatter.rb +96 -0
  33. data/lib/lumberjack/tagged_logger_support.rb +66 -0
  34. data/lib/lumberjack/tagged_logging.rb +29 -0
  35. data/lib/lumberjack/tags.rb +42 -0
  36. data/lib/lumberjack/template.rb +81 -33
  37. data/lumberjack.gemspec +31 -0
  38. metadata +26 -53
  39. data/Rakefile +0 -40
  40. data/spec/device/date_rolling_log_file_spec.rb +0 -73
  41. data/spec/device/log_file_spec.rb +0 -48
  42. data/spec/device/null_spec.rb +0 -12
  43. data/spec/device/rolling_log_file_spec.rb +0 -151
  44. data/spec/device/size_rolling_log_file_spec.rb +0 -58
  45. data/spec/device/writer_spec.rb +0 -118
  46. data/spec/formatter/exception_formatter_spec.rb +0 -20
  47. data/spec/formatter/inspect_formatter_spec.rb +0 -13
  48. data/spec/formatter/pretty_print_formatter_spec.rb +0 -14
  49. data/spec/formatter/string_formatter_spec.rb +0 -12
  50. data/spec/formatter_spec.rb +0 -45
  51. data/spec/log_entry_spec.rb +0 -69
  52. data/spec/logger_spec.rb +0 -411
  53. data/spec/lumberjack_spec.rb +0 -29
  54. data/spec/rack/request_id_spec.rb +0 -48
  55. data/spec/rack/unit_of_work_spec.rb +0 -26
  56. data/spec/severity_spec.rb +0 -23
  57. data/spec/spec_helper.rb +0 -32
  58. data/spec/template_spec.rb +0 -34
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ # A context is used to store tags that are then added to all log entries within a block.
5
+ class Context
6
+ attr_reader :tags
7
+
8
+ def initialize(parent_context = nil)
9
+ @tags = {}
10
+ @tags.merge!(parent_context.tags) if parent_context
11
+ end
12
+
13
+ # Set tags on the context.
14
+ def tag(tags)
15
+ tags.each do |key, value|
16
+ @tags[key.to_s] = value
17
+ end
18
+ end
19
+
20
+ # Get a context tag.
21
+ def [](key)
22
+ @tags[key.to_s]
23
+ end
24
+
25
+ # Set a context tag.
26
+ def []=(key, value)
27
+ @tags[key.to_s] = value
28
+ end
29
+
30
+ # Clear all the context data.
31
+ def reset
32
+ @tags.clear
33
+ end
34
+ end
35
+ end
@@ -4,25 +4,39 @@ module Lumberjack
4
4
  # This is an abstract class for logging devices. Subclasses must implement the +write+ method and
5
5
  # may implement the +close+ and +flush+ methods if applicable.
6
6
  class Device
7
- require File.expand_path("../device/writer.rb", __FILE__)
8
- require File.expand_path("../device/log_file.rb", __FILE__)
9
- require File.expand_path("../device/rolling_log_file.rb", __FILE__)
10
- require File.expand_path("../device/date_rolling_log_file.rb", __FILE__)
11
- require File.expand_path("../device/size_rolling_log_file.rb", __FILE__)
12
- require File.expand_path("../device/null.rb", __FILE__)
7
+ require_relative "device/writer"
8
+ require_relative "device/log_file"
9
+ require_relative "device/rolling_log_file"
10
+ require_relative "device/date_rolling_log_file"
11
+ require_relative "device/size_rolling_log_file"
12
+ require_relative "device/multi"
13
+ require_relative "device/null"
13
14
 
14
15
  # Subclasses must implement this method to write a LogEntry.
15
16
  def write(entry)
16
17
  raise NotImplementedError
17
18
  end
18
-
19
+
19
20
  # Subclasses may implement this method to close the device.
20
21
  def close
21
22
  flush
22
23
  end
23
-
24
+
25
+ # Subclasses may implement this method to reopen the device.
26
+ def reopen(logdev = nil)
27
+ flush
28
+ end
29
+
24
30
  # Subclasses may implement this method to flush any buffers used by the device.
25
31
  def flush
26
32
  end
33
+
34
+ # Subclasses may implement this method to get the format for log timestamps.
35
+ def datetime_format
36
+ end
37
+
38
+ # Subclasses may implement this method to set a format for log timestamps.
39
+ def datetime_format=(format)
40
+ end
27
41
  end
28
42
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literals: true
2
2
 
3
- require 'date'
3
+ require "date"
4
4
 
5
5
  module Lumberjack
6
6
  class Device
@@ -11,12 +11,12 @@ module Lumberjack
11
11
  # roll period as buffered entries will always be written to the same file.
12
12
  class DateRollingLogFile < RollingLogFile
13
13
  # Create a new logging device to the specified file. The period to roll the file is specified
14
- # with the <tt>:roll</tt> option which may contain a value of <tt>:daily</tt>, <tt>:weekly</tt>,
15
- # or <tt>:monthly</tt>.
14
+ # with the :roll option which may contain a value of :daily, :weekly,
15
+ # or :monthly.
16
16
  def initialize(path, options = {})
17
17
  @manual = options[:manual]
18
18
  @file_date = Date.today
19
- if options[:roll] && options[:roll].to_s.match(/(daily)|(weekly)|(monthly)/i)
19
+ if options[:roll]&.to_s&.match(/(daily)|(weekly)|(monthly)/i)
20
20
  @roll_period = $~[0].downcase.to_sym
21
21
  options.delete(:roll)
22
22
  else
@@ -28,11 +28,11 @@ module Lumberjack
28
28
  def archive_file_suffix
29
29
  case @roll_period
30
30
  when :weekly
31
- "#{@file_date.strftime('week-of-%Y-%m-%d')}"
31
+ @file_date.strftime("week-of-%Y-%m-%d").to_s
32
32
  when :monthly
33
- "#{@file_date.strftime('%Y-%m')}"
33
+ @file_date.strftime("%Y-%m").to_s
34
34
  else
35
- "#{@file_date.strftime('%Y-%m-%d')}"
35
+ @file_date.strftime("%Y-%m-%d").to_s
36
36
  end
37
37
  end
38
38
 
@@ -54,9 +54,9 @@ module Lumberjack
54
54
  end
55
55
  end
56
56
  end
57
-
57
+
58
58
  protected
59
-
59
+
60
60
  def after_roll
61
61
  @file_date = Date.today
62
62
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literals: true
2
2
 
3
- require 'fileutils'
3
+ require "fileutils"
4
4
 
5
5
  module Lumberjack
6
6
  class Device
@@ -10,12 +10,23 @@ module Lumberjack
10
10
 
11
11
  # The absolute path of the file being logged to.
12
12
  attr_reader :path
13
-
13
+
14
14
  # Create a logger to the file at +path+. Options are passed through to the Writer constructor.
15
15
  def initialize(path, options = {})
16
16
  @path = File.expand_path(path)
17
17
  FileUtils.mkdir_p(File.dirname(@path))
18
- super(File.new(@path, 'a', :encoding => EXTERNAL_ENCODING), options)
18
+ super(file_stream, options)
19
+ end
20
+
21
+ def reopen(logdev = nil)
22
+ close
23
+ @stream = file_stream
24
+ end
25
+
26
+ private
27
+
28
+ def file_stream
29
+ File.new(@path, "a", encoding: EXTERNAL_ENCODING)
19
30
  end
20
31
  end
21
32
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literals: true
2
+
3
+ module Lumberjack
4
+ class Device
5
+ # This is a logging device that forward log entries to multiple other devices.
6
+ class Multi < Device
7
+ def initialize(*devices)
8
+ @devices = devices.flatten
9
+ end
10
+
11
+ def write(entry)
12
+ @devices.each do |device|
13
+ device.write(entry)
14
+ end
15
+ end
16
+
17
+ def flush
18
+ @devices.each do |device|
19
+ device.flush
20
+ end
21
+ end
22
+
23
+ def close
24
+ @devices.each do |device|
25
+ device.close
26
+ end
27
+ end
28
+
29
+ def reopen(logdev = nil)
30
+ @devices.each do |device|
31
+ device.reopen(logdev = nil)
32
+ end
33
+ end
34
+
35
+ def datetime_format
36
+ @devices.detect(&:datetime_format).datetime_format
37
+ end
38
+
39
+ def datetime_format=(format)
40
+ @devices.each do |device|
41
+ device.datetime_format = format
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literals: true
2
2
 
3
- require 'date'
4
-
5
3
  module Lumberjack
6
4
  class Device
7
5
  # This is a logging device that produces no output. It can be useful in
@@ -9,7 +7,7 @@ module Lumberjack
9
7
  class Null < Device
10
8
  def initialize(*args)
11
9
  end
12
-
10
+
13
11
  def write(entry)
14
12
  end
15
13
  end
@@ -6,43 +6,51 @@ module Lumberjack
6
6
  # the existing file and starts a one. Subclasses must implement the roll_file? and archive_file_suffix
7
7
  # methods.
8
8
  #
9
- # The <tt>:keep</tt> option can be used to specify a maximum number of rolled log files to keep.
9
+ # The :keep option can be used to specify a maximum number of rolled log files to keep.
10
10
  # Older files will be deleted based on the time they were created. The default is to keep all files.
11
11
  #
12
- # The <tt>:min_roll_check</tt> option can be used to specify the number of seconds between checking
12
+ # The :min_roll_check option can be used to specify the number of seconds between checking
13
13
  # the file to determine if it needs to be rolled. The default is to check at most once per second.
14
14
  class RollingLogFile < LogFile
15
15
  attr_reader :path
16
16
  attr_accessor :keep
17
-
17
+
18
18
  def initialize(path, options = {})
19
19
  @path = File.expand_path(path)
20
20
  @keep = options[:keep]
21
21
  super(path, options)
22
- @file_inode = stream.lstat.ino rescue nil
22
+ @file_inode = begin
23
+ stream.lstat.ino
24
+ rescue
25
+ nil
26
+ end
23
27
  @@rolls = []
24
28
  @next_stat_check = Time.now.to_f
25
29
  @min_roll_check = (options[:min_roll_check] || 1.0).to_f
26
30
  end
27
-
31
+
28
32
  # Returns a suffix that will be appended to the file name when it is archived.. The suffix should
29
33
  # change after it is time to roll the file. The log file will be renamed when it is rolled.
30
34
  def archive_file_suffix
31
35
  raise NotImplementedError
32
36
  end
33
-
37
+
34
38
  # Return +true+ if the file should be rolled.
35
39
  def roll_file?
36
40
  raise NotImplementedError
37
41
  end
38
-
42
+
39
43
  # Roll the log file by renaming it to the archive file name and then re-opening a stream to the log
40
44
  # file path. Rolling a file is safe in multi-threaded or multi-process environments.
41
45
  def roll_file! #:nodoc:
42
46
  do_once(stream) do
43
47
  archive_file = "#{path}.#{archive_file_suffix}"
44
48
  stream.flush
45
- current_inode = File.stat(path).ino rescue nil
49
+ current_inode = begin
50
+ File.stat(path).ino
51
+ rescue
52
+ nil
53
+ end
46
54
  if @file_inode && current_inode == @file_inode && !File.exist?(archive_file) && File.exist?(path)
47
55
  begin
48
56
  File.rename(path, archive_file)
@@ -59,41 +67,49 @@ module Lumberjack
59
67
  end
60
68
 
61
69
  protected
62
-
70
+
63
71
  # This method will be called after a file has been rolled. Subclasses can
64
72
  # implement code to reset the state of the device. This method is thread safe.
65
73
  def after_roll
66
74
  end
67
-
75
+
68
76
  # Handle rolling the file before flushing.
69
77
  def before_flush # :nodoc:
70
78
  if @min_roll_check <= 0.0 || Time.now.to_f >= @next_stat_check
71
79
  @next_stat_check += @min_roll_check
72
- path_inode = File.lstat(path).ino rescue nil
80
+ path_inode = begin
81
+ File.lstat(path).ino
82
+ rescue
83
+ nil
84
+ end
73
85
  if path_inode != @file_inode
74
86
  @file_inode = path_inode
75
87
  reopen_file
76
- else
77
- roll_file! if roll_file?
88
+ elsif roll_file?
89
+ roll_file!
78
90
  end
79
91
  end
80
92
  end
81
-
93
+
82
94
  private
83
95
 
84
96
  def reopen_file
85
97
  old_stream = stream
86
- new_stream = File.open(path, 'a', encoding: EXTERNAL_ENCODING)
98
+ new_stream = File.open(path, "a", encoding: EXTERNAL_ENCODING)
87
99
  new_stream.sync = true if buffer_size > 0
88
- @file_inode = new_stream.lstat.ino rescue nil
100
+ @file_inode = begin
101
+ new_stream.lstat.ino
102
+ rescue
103
+ nil
104
+ end
89
105
  self.stream = new_stream
90
106
  old_stream.close
91
107
  end
92
108
  end
93
-
109
+
94
110
  def cleanup_files!
95
111
  if keep
96
- files = Dir.glob("#{path}.*").collect{|f| [f, File.ctime(f)]}.sort{|a,b| b.last <=> a.last}.collect{|a| a.first}
112
+ files = Dir.glob("#{path}.*").collect { |f| [f, File.ctime(f)] }.sort { |a, b| b.last <=> a.last }.collect { |a| a.first }
97
113
  if files.size > keep
98
114
  files[keep, files.length].each do |f|
99
115
  File.delete(f)
@@ -101,7 +117,7 @@ module Lumberjack
101
117
  end
102
118
  end
103
119
  end
104
-
120
+
105
121
  def do_once(file)
106
122
  begin
107
123
  file.flock(File::LOCK_EX)
@@ -110,11 +126,19 @@ module Lumberjack
110
126
  return
111
127
  end
112
128
  begin
113
- verify = file.lstat rescue nil
129
+ verify = begin
130
+ file.lstat
131
+ rescue
132
+ nil
133
+ end
114
134
  # Execute only if the file we locked is still the same one that needed to be rolled
115
135
  yield if verify && verify.ino == @file_inode && verify.size > 0
116
136
  ensure
117
- file.flock(File::LOCK_UN) rescue nil
137
+ begin
138
+ file.flock(File::LOCK_UN)
139
+ rescue
140
+ nil
141
+ end
118
142
  end
119
143
  end
120
144
  end
@@ -8,30 +8,30 @@ module Lumberjack
8
8
  # production.log.1, then production.log.2, etc.
9
9
  class SizeRollingLogFile < RollingLogFile
10
10
  attr_reader :max_size
11
-
11
+
12
12
  # Create an new log device to the specified file. The maximum size of the log file is specified with
13
- # the <tt>:max_size</tt> option. The unit can also be specified: "32K", "100M", "2G" are all valid.
13
+ # the :max_size option. The unit can also be specified: "32K", "100M", "2G" are all valid.
14
14
  def initialize(path, options = {})
15
15
  @manual = options[:manual]
16
16
  @max_size = options[:max_size]
17
17
  if @max_size.is_a?(String)
18
- if @max_size.match(/^(\d+(\.\d+)?)([KMG])?$/i)
18
+ if @max_size =~ /^(\d+(\.\d+)?)([KMG])?$/i
19
19
  @max_size = $~[1].to_f
20
20
  units = $~[3].to_s.upcase
21
21
  case units
22
22
  when "K"
23
23
  @max_size *= 1024
24
24
  when "M"
25
- @max_size *= 1024 ** 2
25
+ @max_size *= 1024**2
26
26
  when "G"
27
- @max_size *= 1024 ** 3
27
+ @max_size *= 1024**3
28
28
  end
29
29
  @max_size = @max_size.round
30
30
  else
31
31
  raise ArgumentError.new("illegal value for :max_size (#{@max_size})")
32
32
  end
33
33
  end
34
-
34
+
35
35
  super
36
36
  end
37
37
 
@@ -44,15 +44,15 @@ module Lumberjack
44
44
  rescue SystemCallError
45
45
  false
46
46
  end
47
-
47
+
48
48
  protected
49
-
49
+
50
50
  # Calculate the next archive file name extension.
51
51
  def next_archive_number # :nodoc:
52
52
  max = 0
53
53
  Dir.glob("#{path}.*").each do |filename|
54
- if filename.match(/\.\d+$/)
55
- suffix = filename.split('.').last.to_i
54
+ if /\.\d+\z/ =~ filename
55
+ suffix = filename.split(".").last.to_i
56
56
  max = suffix if suffix > max
57
57
  end
58
58
  end
@@ -4,58 +4,62 @@ module Lumberjack
4
4
  class Device
5
5
  # This logging device writes log entries as strings to an IO stream. By default, messages will be buffered
6
6
  # and written to the stream in a batch when the buffer is full or when +flush+ is called.
7
+ #
8
+ # Subclasses can implement a +before_flush+ method if they have logic to execute before flushing the log.
9
+ # If it is implemented, it will be called before every flush inside a mutex lock.
7
10
  class Writer < Device
8
- DEFAULT_FIRST_LINE_TEMPLATE = "[:time :severity :progname(:pid) #:unit_of_work_id] :message".freeze
9
- DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> [#:unit_of_work_id] :message".freeze
11
+ DEFAULT_FIRST_LINE_TEMPLATE = "[:time :severity :progname(:pid) #:unit_of_work_id] :message :tags"
12
+ DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> [#:unit_of_work_id] :message"
10
13
 
11
14
  # The size of the internal buffer. Defaults to 32K.
12
15
  attr_reader :buffer_size
13
-
16
+
14
17
  # Internal buffer to batch writes to the stream.
15
18
  class Buffer # :nodoc:
16
19
  attr_reader :size
17
-
20
+
18
21
  def initialize
19
22
  @values = []
20
23
  @size = 0
21
24
  end
22
-
25
+
23
26
  def <<(string)
24
27
  @values << string
25
28
  @size += string.size
26
29
  end
27
-
30
+
28
31
  def empty?
29
32
  @values.empty?
30
33
  end
31
-
34
+
32
35
  def pop!
36
+ return nil if @values.empty?
33
37
  popped = @values
34
38
  clear
35
39
  popped
36
40
  end
37
-
41
+
38
42
  def clear
39
43
  @values = []
40
44
  @size = 0
41
45
  end
42
46
  end
43
-
47
+
44
48
  # Create a new device to write log entries to a stream. Entries are converted to strings
45
- # using a Template. The template can be specified using the <tt>:template</tt> option. This can
49
+ # using a Template. The template can be specified using the :template option. This can
46
50
  # either be a Proc or a string that will compile into a Template object.
47
51
  #
48
52
  # If the template is a Proc, it should accept an LogEntry as its only argument and output a string.
49
53
  #
50
54
  # If the template is a template string, it will be used to create a Template. The
51
- # <tt>:additional_lines</tt> and <tt>:time_format</tt> options will be passed through to the
55
+ # :additional_lines and :time_format options will be passed through to the
52
56
  # Template constuctor.
53
57
  #
54
- # The default template is <tt>"[:time :severity :progname(:pid) #:unit_of_work_id] :message"</tt>
55
- # with additional lines formatted as <tt>"\n [#:unit_of_work_id] :message"</tt>. The unit of
58
+ # The default template is "[:time :severity :progname(:pid) #:unit_of_work_id] :message"
59
+ # with additional lines formatted as "\n [#:unit_of_work_id] :message". The unit of
56
60
  # work id will only appear if it is present.
57
61
  #
58
- # The size of the internal buffer in bytes can be set by providing <tt>:buffer_size</tt> (defaults to 32K).
62
+ # The size of the internal buffer in bytes can be set by providing :buffer_size (defaults to 32K).
59
63
  def initialize(stream, options = {})
60
64
  @lock = Mutex.new
61
65
  @stream = stream
@@ -67,84 +71,111 @@ module Lumberjack
67
71
  @template = template
68
72
  else
69
73
  additional_lines = (options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE)
70
- @template = Template.new(template, :additional_lines => additional_lines, :time_format => options[:time_format])
74
+ @template = Template.new(template, additional_lines: additional_lines, time_format: options[:time_format])
71
75
  end
72
76
  end
73
-
77
+
74
78
  # Set the buffer size in bytes. The device will only be physically written to when the buffer size
75
79
  # is exceeded.
76
80
  def buffer_size=(value)
77
81
  @buffer_size = value
78
82
  flush
79
83
  end
80
-
84
+
81
85
  # Write an entry to the stream. The entry will be converted into a string using the defined template.
82
86
  def write(entry)
83
- string = @template.call(entry)
84
- unless string.nil?
85
- string = string.encode("UTF-8".freeze, invalid: :replace, undef: :replace)
87
+ string = (entry.is_a?(LogEntry) ? @template.call(entry) : entry)
88
+ return if string.nil?
89
+ string = string.strip
90
+ return if string.length == 0
91
+
92
+ unless string.encoding == Encoding::UTF_8
93
+ string = string.encode("UTF-8", invalid: :replace, undef: :replace)
94
+ end
95
+
96
+ if buffer_size > 1
86
97
  @lock.synchronize do
87
98
  @buffer << string
88
99
  end
100
+ flush if @buffer.size >= buffer_size
101
+ else
102
+ flush if respond_to?(:before_flush, true)
103
+ write_to_stream(string)
89
104
  end
90
- flush if @buffer.size >= buffer_size
91
105
  end
92
-
106
+
93
107
  # Close the underlying stream.
94
108
  def close
95
109
  flush
96
110
  stream.close
97
111
  end
98
-
112
+
99
113
  # Flush the underlying stream.
100
114
  def flush
101
115
  lines = nil
102
116
  @lock.synchronize do
103
- before_flush
117
+ before_flush if respond_to?(:before_flush, true)
104
118
  lines = @buffer.pop!
105
119
  end
106
-
107
- unless lines.empty?
108
- out = "#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}"
109
- begin
110
- begin
111
- stream.write(out)
112
- rescue IOError => e
113
- # This condition can happen if another thread closed the stream in the `before_flush` call.
114
- # Synchronizing will handle the race condition, but since it's an exceptional case we don't
115
- # to lock the thread on every stream write call.
116
- @lock.synchronize do
117
- if stream.closed?
118
- raise e
119
- else
120
- stream.write(out)
121
- end
122
- end
123
- end
124
- stream.flush rescue nil
125
- rescue => e
126
- $stderr.write("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}")
127
- $stderr.write(out)
128
- $stderr.flush
129
- end
120
+ write_to_stream(lines) if lines
121
+ end
122
+
123
+ def datetime_format
124
+ @template.datetime_format if @template.respond_to?(:datetime_format)
125
+ end
126
+
127
+ def datetime_format=(format)
128
+ if @template.respond_to?(:datetime_format=)
129
+ @template.datetime_format = format
130
130
  end
131
131
  end
132
-
132
+
133
133
  protected
134
-
135
- # Callback method that will be executed before data is written to the stream. Subclasses
136
- # can override this method if needed. This method will be called in a mutex lock.
137
- def before_flush
138
- end
139
-
134
+
140
135
  # Set the underlying stream.
141
- def stream=(stream)
142
- @stream = stream
143
- end
144
-
136
+ attr_writer :stream
137
+
145
138
  # Get the underlying stream.
146
- def stream
147
- @stream
139
+ attr_reader :stream
140
+
141
+ private
142
+
143
+ def write_to_stream(lines)
144
+ return if lines.empty?
145
+ lines = lines.first if lines.is_a?(Array) && lines.size == 1
146
+
147
+ out = nil
148
+ out = if lines.is_a?(Array)
149
+ "#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}"
150
+ else
151
+ "#{lines}#{Lumberjack::LINE_SEPARATOR}"
152
+ end
153
+
154
+ begin
155
+ begin
156
+ stream.write(out)
157
+ rescue IOError => e
158
+ # This condition can happen if another thread closed the stream in the `before_flush` call.
159
+ # Synchronizing will handle the race condition, but since it's an exceptional case we don't
160
+ # want to lock the thread on every stream write call.
161
+ @lock.synchronize do
162
+ if stream.closed?
163
+ raise e
164
+ else
165
+ stream.write(out)
166
+ end
167
+ end
168
+ end
169
+ begin
170
+ stream.flush
171
+ rescue
172
+ nil
173
+ end
174
+ rescue => e
175
+ $stderr.write("#{e.class.name}: #{e.message}#{" at " + e.backtrace.first if e.backtrace}")
176
+ $stderr.write(out)
177
+ $stderr.flush
178
+ end
148
179
  end
149
180
  end
150
181
  end