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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +129 -0
- data/{MIT_LICENSE → MIT_LICENSE.txt} +0 -0
- data/README.md +142 -23
- data/VERSION +1 -1
- data/lib/lumberjack.rb +70 -22
- data/lib/lumberjack/context.rb +35 -0
- data/lib/lumberjack/device.rb +22 -8
- data/lib/lumberjack/device/date_rolling_log_file.rb +9 -9
- data/lib/lumberjack/device/log_file.rb +14 -3
- data/lib/lumberjack/device/multi.rb +46 -0
- data/lib/lumberjack/device/null.rb +1 -3
- data/lib/lumberjack/device/rolling_log_file.rb +45 -21
- data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
- data/lib/lumberjack/device/writer.rb +92 -61
- data/lib/lumberjack/formatter.rb +97 -28
- data/lib/lumberjack/formatter/date_time_formatter.rb +25 -0
- data/lib/lumberjack/formatter/exception_formatter.rb +25 -2
- data/lib/lumberjack/formatter/id_formatter.rb +23 -0
- data/lib/lumberjack/formatter/object_formatter.rb +12 -0
- data/lib/lumberjack/formatter/pretty_print_formatter.rb +4 -4
- data/lib/lumberjack/formatter/string_formatter.rb +1 -1
- data/lib/lumberjack/formatter/strip_formatter.rb +12 -0
- data/lib/lumberjack/formatter/structured_formatter.rb +63 -0
- data/lib/lumberjack/log_entry.rb +44 -16
- data/lib/lumberjack/logger.rb +275 -69
- data/lib/lumberjack/rack.rb +3 -2
- data/lib/lumberjack/rack/context.rb +18 -0
- data/lib/lumberjack/rack/request_id.rb +4 -4
- data/lib/lumberjack/rack/unit_of_work.rb +1 -1
- data/lib/lumberjack/severity.rb +11 -10
- data/lib/lumberjack/tag_formatter.rb +96 -0
- data/lib/lumberjack/tagged_logger_support.rb +66 -0
- data/lib/lumberjack/tagged_logging.rb +29 -0
- data/lib/lumberjack/tags.rb +42 -0
- data/lib/lumberjack/template.rb +81 -33
- data/lumberjack.gemspec +31 -0
- metadata +26 -53
- data/Rakefile +0 -40
- data/spec/device/date_rolling_log_file_spec.rb +0 -73
- data/spec/device/log_file_spec.rb +0 -48
- data/spec/device/null_spec.rb +0 -12
- data/spec/device/rolling_log_file_spec.rb +0 -151
- data/spec/device/size_rolling_log_file_spec.rb +0 -58
- data/spec/device/writer_spec.rb +0 -118
- data/spec/formatter/exception_formatter_spec.rb +0 -20
- data/spec/formatter/inspect_formatter_spec.rb +0 -13
- data/spec/formatter/pretty_print_formatter_spec.rb +0 -14
- data/spec/formatter/string_formatter_spec.rb +0 -12
- data/spec/formatter_spec.rb +0 -45
- data/spec/log_entry_spec.rb +0 -69
- data/spec/logger_spec.rb +0 -411
- data/spec/lumberjack_spec.rb +0 -29
- data/spec/rack/request_id_spec.rb +0 -48
- data/spec/rack/unit_of_work_spec.rb +0 -26
- data/spec/severity_spec.rb +0 -23
- data/spec/spec_helper.rb +0 -32
- 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
|
data/lib/lumberjack/device.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
15
|
-
# or
|
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]
|
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
|
-
|
31
|
+
@file_date.strftime("week-of-%Y-%m-%d").to_s
|
32
32
|
when :monthly
|
33
|
-
|
33
|
+
@file_date.strftime("%Y-%m").to_s
|
34
34
|
else
|
35
|
-
|
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
|
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(
|
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
|
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
|
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 =
|
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 =
|
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 =
|
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
|
-
|
77
|
-
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,
|
98
|
+
new_stream = File.open(path, "a", encoding: EXTERNAL_ENCODING)
|
87
99
|
new_stream.sync = true if buffer_size > 0
|
88
|
-
@file_inode =
|
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 =
|
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
|
-
|
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
|
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
|
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
|
25
|
+
@max_size *= 1024**2
|
26
26
|
when "G"
|
27
|
-
@max_size *= 1024
|
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
|
55
|
-
suffix = filename.split(
|
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"
|
9
|
-
DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> [#:unit_of_work_id] :message"
|
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
|
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
|
-
#
|
55
|
+
# :additional_lines and :time_format options will be passed through to the
|
52
56
|
# Template constuctor.
|
53
57
|
#
|
54
|
-
# The default template is
|
55
|
-
# with additional lines formatted as
|
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
|
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, :
|
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
|
-
|
85
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
142
|
-
|
143
|
-
end
|
144
|
-
|
136
|
+
attr_writer :stream
|
137
|
+
|
145
138
|
# Get the underlying stream.
|
146
|
-
|
147
|
-
|
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
|