lumberjack 1.0.13 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|