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.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +74 -0
  3. data/README.md +92 -20
  4. data/VERSION +1 -1
  5. data/lib/lumberjack.rb +51 -16
  6. data/lib/lumberjack/context.rb +35 -0
  7. data/lib/lumberjack/device.rb +20 -6
  8. data/lib/lumberjack/device/date_rolling_log_file.rb +2 -2
  9. data/lib/lumberjack/device/log_file.rb +12 -1
  10. data/lib/lumberjack/device/multi.rb +46 -0
  11. data/lib/lumberjack/device/null.rb +0 -2
  12. data/lib/lumberjack/device/rolling_log_file.rb +2 -2
  13. data/lib/lumberjack/device/size_rolling_log_file.rb +1 -1
  14. data/lib/lumberjack/device/writer.rb +86 -55
  15. data/lib/lumberjack/formatter.rb +54 -18
  16. data/lib/lumberjack/formatter/date_time_formatter.rb +26 -0
  17. data/lib/lumberjack/formatter/id_formatter.rb +23 -0
  18. data/lib/lumberjack/formatter/object_formatter.rb +12 -0
  19. data/lib/lumberjack/formatter/structured_formatter.rb +31 -0
  20. data/lib/lumberjack/log_entry.rb +41 -16
  21. data/lib/lumberjack/logger.rb +168 -63
  22. data/lib/lumberjack/rack.rb +3 -2
  23. data/lib/lumberjack/rack/context.rb +18 -0
  24. data/lib/lumberjack/rack/request_id.rb +4 -4
  25. data/lib/lumberjack/severity.rb +11 -9
  26. data/lib/lumberjack/tags.rb +24 -0
  27. data/lib/lumberjack/template.rb +74 -32
  28. data/lumberjack.gemspec +35 -0
  29. metadata +48 -37
  30. data/Rakefile +0 -40
  31. data/spec/device/date_rolling_log_file_spec.rb +0 -73
  32. data/spec/device/log_file_spec.rb +0 -48
  33. data/spec/device/null_spec.rb +0 -12
  34. data/spec/device/rolling_log_file_spec.rb +0 -151
  35. data/spec/device/size_rolling_log_file_spec.rb +0 -58
  36. data/spec/device/writer_spec.rb +0 -118
  37. data/spec/formatter/exception_formatter_spec.rb +0 -20
  38. data/spec/formatter/inspect_formatter_spec.rb +0 -13
  39. data/spec/formatter/pretty_print_formatter_spec.rb +0 -14
  40. data/spec/formatter/string_formatter_spec.rb +0 -12
  41. data/spec/formatter_spec.rb +0 -45
  42. data/spec/log_entry_spec.rb +0 -69
  43. data/spec/logger_spec.rb +0 -411
  44. data/spec/lumberjack_spec.rb +0 -29
  45. data/spec/rack/request_id_spec.rb +0 -48
  46. data/spec/rack/unit_of_work_spec.rb +0 -26
  47. data/spec/severity_spec.rb +0 -23
  48. data/spec/spec_helper.rb +0 -32
  49. 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 <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
@@ -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(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
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
@@ -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 <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
@@ -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 <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]
@@ -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
@@ -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
- 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
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
@@ -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 strings. This allows you
5
- # to log any object you want and have the logging system worry about converting it into a string.
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
- require File.expand_path("../formatter/exception_formatter.rb", __FILE__)
14
- require File.expand_path("../formatter/inspect_formatter.rb", __FILE__)
15
- require File.expand_path("../formatter/pretty_print_formatter.rb", __FILE__)
16
- require File.expand_path("../formatter/string_formatter.rb", __FILE__)
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: <tt>:inspect</tt>, <tt>:string</tt>, <tt>:exception</tt>, and <tt>:pretty_print</tt>.
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
- @class_formatters[klass] = formatter
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
- @class_formatters.delete(klass)
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
- # Hack for compatibility with Logger::Formatter
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)}\n"
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