lumberjack 1.0.13 → 1.1.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +74 -0
- data/README.md +92 -20
- data/VERSION +1 -1
- data/lib/lumberjack.rb +51 -16
- data/lib/lumberjack/context.rb +35 -0
- data/lib/lumberjack/device.rb +20 -6
- data/lib/lumberjack/device/date_rolling_log_file.rb +2 -2
- data/lib/lumberjack/device/log_file.rb +12 -1
- data/lib/lumberjack/device/multi.rb +46 -0
- data/lib/lumberjack/device/null.rb +0 -2
- data/lib/lumberjack/device/rolling_log_file.rb +2 -2
- data/lib/lumberjack/device/size_rolling_log_file.rb +1 -1
- data/lib/lumberjack/device/writer.rb +86 -55
- data/lib/lumberjack/formatter.rb +54 -18
- data/lib/lumberjack/formatter/date_time_formatter.rb +26 -0
- data/lib/lumberjack/formatter/id_formatter.rb +23 -0
- data/lib/lumberjack/formatter/object_formatter.rb +12 -0
- data/lib/lumberjack/formatter/structured_formatter.rb +31 -0
- data/lib/lumberjack/log_entry.rb +41 -16
- data/lib/lumberjack/logger.rb +168 -63
- 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/severity.rb +11 -9
- data/lib/lumberjack/tags.rb +24 -0
- data/lib/lumberjack/template.rb +74 -32
- data/lumberjack.gemspec +35 -0
- metadata +48 -37
- 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
@@ -11,8 +11,8 @@ 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
|
@@ -15,7 +15,18 @@ module Lumberjack
|
|
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
|
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
|
@@ -6,10 +6,10 @@ 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
|
@@ -10,7 +10,7 @@ module Lumberjack
|
|
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]
|
@@ -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
|
@@ -70,82 +74,109 @@ module Lumberjack
|
|
70
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
136
|
def stream=(stream)
|
142
137
|
@stream = stream
|
143
138
|
end
|
144
|
-
|
139
|
+
|
145
140
|
# Get the underlying stream.
|
146
141
|
def stream
|
147
142
|
@stream
|
148
143
|
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def write_to_stream(lines)
|
148
|
+
return if lines.empty?
|
149
|
+
lines = lines.first if lines.is_a?(Array) && lines.size == 1
|
150
|
+
|
151
|
+
out = nil
|
152
|
+
if lines.is_a?(Array)
|
153
|
+
out = "#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}"
|
154
|
+
else
|
155
|
+
out = "#{lines}#{Lumberjack::LINE_SEPARATOR}"
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
begin
|
160
|
+
stream.write(out)
|
161
|
+
rescue IOError => e
|
162
|
+
# This condition can happen if another thread closed the stream in the `before_flush` call.
|
163
|
+
# Synchronizing will handle the race condition, but since it's an exceptional case we don't
|
164
|
+
# want to lock the thread on every stream write call.
|
165
|
+
@lock.synchronize do
|
166
|
+
if stream.closed?
|
167
|
+
raise e
|
168
|
+
else
|
169
|
+
stream.write(out)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
stream.flush rescue nil
|
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
|
179
|
+
end
|
149
180
|
end
|
150
181
|
end
|
151
182
|
end
|
data/lib/lumberjack/formatter.rb
CHANGED
@@ -1,33 +1,45 @@
|
|
1
1
|
# frozen_string_literals: true
|
2
2
|
|
3
3
|
module Lumberjack
|
4
|
-
# This class controls the conversion of log entry messages into
|
5
|
-
# to log any object you want and have the logging system
|
4
|
+
# This class controls the conversion of log entry messages into a loggable format. This allows you
|
5
|
+
# to log any object you want and have the logging system deal with converting it into a string.
|
6
6
|
#
|
7
7
|
# Formats are added to a Formatter by associating them with a class using the +add+ method. Formats
|
8
8
|
# are any object that responds to the +call+ method.
|
9
9
|
#
|
10
10
|
# By default, all object will be converted to strings using their inspect method except for Strings
|
11
11
|
# and Exceptions. Strings are not converted and Exceptions are converted using the ExceptionFormatter.
|
12
|
+
#
|
13
|
+
# Enumerable objects (including Hash and Array) will call the formatter recursively for each element.
|
12
14
|
class Formatter
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
require_relative "formatter/date_time_formatter.rb"
|
16
|
+
require_relative "formatter/exception_formatter.rb"
|
17
|
+
require_relative "formatter/id_formatter.rb"
|
18
|
+
require_relative "formatter/inspect_formatter.rb"
|
19
|
+
require_relative "formatter/object_formatter.rb"
|
20
|
+
require_relative "formatter/pretty_print_formatter.rb"
|
21
|
+
require_relative "formatter/string_formatter.rb"
|
22
|
+
require_relative "formatter/structured_formatter.rb"
|
23
|
+
|
18
24
|
def initialize
|
19
25
|
@class_formatters = {}
|
26
|
+
@module_formatters = {}
|
20
27
|
@_default_formatter = InspectFormatter.new
|
28
|
+
structured_formatter = StructuredFormatter.new(self)
|
29
|
+
add(String, :object)
|
30
|
+
add(Numeric, :object)
|
31
|
+
add(TrueClass, :object)
|
32
|
+
add(FalseClass, :object)
|
21
33
|
add(Object, @_default_formatter)
|
22
|
-
add(String, :string)
|
23
34
|
add(Exception, :exception)
|
35
|
+
add(Enumerable, structured_formatter)
|
24
36
|
end
|
25
|
-
|
37
|
+
|
26
38
|
# Add a formatter for a class. The formatter can be specified as either an object
|
27
39
|
# that responds to the +call+ method or as a symbol representing one of the predefined
|
28
40
|
# formatters, or as a block to the method call.
|
29
41
|
#
|
30
|
-
# The predefined formatters are:
|
42
|
+
# The predefined formatters are: :inspect, :string, :exception, and :pretty_print.
|
31
43
|
#
|
32
44
|
# === Examples
|
33
45
|
#
|
@@ -48,33 +60,57 @@ module Lumberjack
|
|
48
60
|
formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter"
|
49
61
|
formatter = Formatter.const_get(formatter_class_name).new
|
50
62
|
end
|
51
|
-
|
63
|
+
if klass.is_a?(Class)
|
64
|
+
@class_formatters[klass] = formatter
|
65
|
+
else
|
66
|
+
@module_formatters[klass] = formatter
|
67
|
+
end
|
52
68
|
self
|
53
69
|
end
|
54
|
-
|
70
|
+
|
55
71
|
# Remove the formatter associated with a class. Remove statements can be chained together.
|
56
72
|
def remove(klass)
|
57
|
-
|
73
|
+
if klass.is_a?(Class)
|
74
|
+
@class_formatters.delete(klass)
|
75
|
+
else
|
76
|
+
@module_formatters.delete(klass)
|
77
|
+
end
|
58
78
|
self
|
59
79
|
end
|
60
80
|
|
81
|
+
# Remove all formatters including the default formatter. Can be chained to add method calls.
|
82
|
+
def clear
|
83
|
+
@class_formatters.clear
|
84
|
+
@module_formatters.clear
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
61
88
|
# Format a message object as a string.
|
62
89
|
def format(message)
|
63
90
|
formatter_for(message.class).call(message)
|
64
91
|
end
|
65
|
-
|
66
|
-
#
|
92
|
+
|
93
|
+
# Compatibility with the Logger::Formatter signature. This method will just convert the message
|
94
|
+
# object to a string and ignores the other parameters.
|
67
95
|
def call(severity, timestamp, progname, msg)
|
68
|
-
"#{format(msg)}
|
69
|
-
end
|
96
|
+
"#{format(msg)}#{Lumberjack::LINE_SEPARATOR}"
|
97
|
+
end
|
70
98
|
|
71
99
|
private
|
72
|
-
|
100
|
+
|
73
101
|
# Find the formatter for a class by looking it up using the class hierarchy.
|
74
102
|
def formatter_for(klass) #:nodoc:
|
103
|
+
check_modules = true
|
75
104
|
while klass != nil do
|
76
105
|
formatter = @class_formatters[klass]
|
77
106
|
return formatter if formatter
|
107
|
+
|
108
|
+
if check_modules
|
109
|
+
_, formatter = @module_formatters.detect { |mod, f| klass.include?(mod) }
|
110
|
+
check_modules = false
|
111
|
+
return formatter if formatter
|
112
|
+
end
|
113
|
+
|
78
114
|
klass = klass.superclass
|
79
115
|
end
|
80
116
|
@_default_formatter
|