lumberjack 1.0.13 → 1.1.0

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