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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +129 -0
  3. data/{MIT_LICENSE → MIT_LICENSE.txt} +0 -0
  4. data/README.md +142 -23
  5. data/VERSION +1 -1
  6. data/lib/lumberjack.rb +70 -22
  7. data/lib/lumberjack/context.rb +35 -0
  8. data/lib/lumberjack/device.rb +22 -8
  9. data/lib/lumberjack/device/date_rolling_log_file.rb +9 -9
  10. data/lib/lumberjack/device/log_file.rb +14 -3
  11. data/lib/lumberjack/device/multi.rb +46 -0
  12. data/lib/lumberjack/device/null.rb +1 -3
  13. data/lib/lumberjack/device/rolling_log_file.rb +45 -21
  14. data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
  15. data/lib/lumberjack/device/writer.rb +92 -61
  16. data/lib/lumberjack/formatter.rb +97 -28
  17. data/lib/lumberjack/formatter/date_time_formatter.rb +25 -0
  18. data/lib/lumberjack/formatter/exception_formatter.rb +25 -2
  19. data/lib/lumberjack/formatter/id_formatter.rb +23 -0
  20. data/lib/lumberjack/formatter/object_formatter.rb +12 -0
  21. data/lib/lumberjack/formatter/pretty_print_formatter.rb +4 -4
  22. data/lib/lumberjack/formatter/string_formatter.rb +1 -1
  23. data/lib/lumberjack/formatter/strip_formatter.rb +12 -0
  24. data/lib/lumberjack/formatter/structured_formatter.rb +63 -0
  25. data/lib/lumberjack/log_entry.rb +44 -16
  26. data/lib/lumberjack/logger.rb +275 -69
  27. data/lib/lumberjack/rack.rb +3 -2
  28. data/lib/lumberjack/rack/context.rb +18 -0
  29. data/lib/lumberjack/rack/request_id.rb +4 -4
  30. data/lib/lumberjack/rack/unit_of_work.rb +1 -1
  31. data/lib/lumberjack/severity.rb +11 -10
  32. data/lib/lumberjack/tag_formatter.rb +96 -0
  33. data/lib/lumberjack/tagged_logger_support.rb +66 -0
  34. data/lib/lumberjack/tagged_logging.rb +29 -0
  35. data/lib/lumberjack/tags.rb +42 -0
  36. data/lib/lumberjack/template.rb +81 -33
  37. data/lumberjack.gemspec +31 -0
  38. metadata +26 -53
  39. data/Rakefile +0 -40
  40. data/spec/device/date_rolling_log_file_spec.rb +0 -73
  41. data/spec/device/log_file_spec.rb +0 -48
  42. data/spec/device/null_spec.rb +0 -12
  43. data/spec/device/rolling_log_file_spec.rb +0 -151
  44. data/spec/device/size_rolling_log_file_spec.rb +0 -58
  45. data/spec/device/writer_spec.rb +0 -118
  46. data/spec/formatter/exception_formatter_spec.rb +0 -20
  47. data/spec/formatter/inspect_formatter_spec.rb +0 -13
  48. data/spec/formatter/pretty_print_formatter_spec.rb +0 -14
  49. data/spec/formatter/string_formatter_spec.rb +0 -12
  50. data/spec/formatter_spec.rb +0 -45
  51. data/spec/log_entry_spec.rb +0 -69
  52. data/spec/logger_spec.rb +0 -411
  53. data/spec/lumberjack_spec.rb +0 -29
  54. data/spec/rack/request_id_spec.rb +0 -48
  55. data/spec/rack/unit_of_work_spec.rb +0 -26
  56. data/spec/severity_spec.rb +0 -23
  57. data/spec/spec_helper.rb +0 -32
  58. data/spec/template_spec.rb +0 -34
@@ -1,33 +1,57 @@
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"
16
+ require_relative "formatter/exception_formatter"
17
+ require_relative "formatter/id_formatter"
18
+ require_relative "formatter/inspect_formatter"
19
+ require_relative "formatter/object_formatter"
20
+ require_relative "formatter/pretty_print_formatter"
21
+ require_relative "formatter/string_formatter"
22
+ require_relative "formatter/strip_formatter"
23
+ require_relative "formatter/structured_formatter"
24
+
25
+ class << self
26
+ # Returns a new empty formatter with no mapping. For historical reasons, a formatter
27
+ # is initialized with mappings to help output objects as strings. This will return one
28
+ # without the default mappings.
29
+ def empty
30
+ new.clear
31
+ end
32
+ end
33
+
18
34
  def initialize
19
35
  @class_formatters = {}
20
- @_default_formatter = InspectFormatter.new
21
- add(Object, @_default_formatter)
22
- add(String, :string)
36
+ @module_formatters = {}
37
+ structured_formatter = StructuredFormatter.new(self)
38
+ add([String, Numeric, TrueClass, FalseClass], :object)
39
+ add(Object, InspectFormatter.new)
23
40
  add(Exception, :exception)
41
+ add(Enumerable, structured_formatter)
24
42
  end
25
-
43
+
26
44
  # Add a formatter for a class. The formatter can be specified as either an object
27
45
  # that responds to the +call+ method or as a symbol representing one of the predefined
28
46
  # formatters, or as a block to the method call.
29
47
  #
30
- # The predefined formatters are: <tt>:inspect</tt>, <tt>:string</tt>, <tt>:exception</tt>, and <tt>:pretty_print</tt>.
48
+ # The predefined formatters are: :inspect, :string, :exception, and :pretty_print.
49
+ #
50
+ # You can add multiple classes at once by passing an array of classes.
51
+ #
52
+ # You can also pass class names as strings instead of the classes themselves. This can
53
+ # help avoid loading dependency issues. This applies only to classes; modules cannot be
54
+ # passed in as strings.
31
55
  #
32
56
  # === Examples
33
57
  #
@@ -44,40 +68,85 @@ module Lumberjack
44
68
  # formatter.add(MyClass, :pretty_print).add(YourClass){|obj| obj.humanize}
45
69
  def add(klass, formatter = nil, &block)
46
70
  formatter ||= block
47
- if formatter.is_a?(Symbol)
48
- formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter"
49
- formatter = Formatter.const_get(formatter_class_name).new
71
+ if formatter.nil?
72
+ remove(klass)
73
+ else
74
+ if formatter.is_a?(Symbol)
75
+ formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/) { |m| $~[2].upcase }}Formatter"
76
+ formatter = Formatter.const_get(formatter_class_name).new
77
+ end
78
+
79
+ Array(klass).each do |k|
80
+ if k.class == Module
81
+ @module_formatters[k] = formatter
82
+ else
83
+ k = k.name if k.is_a?(Class)
84
+ @class_formatters[k] = formatter
85
+ end
86
+ end
50
87
  end
51
- @class_formatters[klass] = formatter
52
88
  self
53
89
  end
54
-
90
+
55
91
  # Remove the formatter associated with a class. Remove statements can be chained together.
92
+ #
93
+ # You can remove multiple classes at once by passing an array of classes.
94
+ #
95
+ # You can also pass class names as strings instead of the classes themselves. This can
96
+ # help avoid loading dependency issues. This applies only to classes; modules cannot be
97
+ # passed in as strings.
56
98
  def remove(klass)
57
- @class_formatters.delete(klass)
99
+ Array(klass).each do |k|
100
+ if k.class == Module
101
+ @module_formatters.delete(k)
102
+ else
103
+ k = k.name if k.is_a?(Class)
104
+ @class_formatters.delete(k)
105
+ end
106
+ end
107
+ self
108
+ end
109
+
110
+ # Remove all formatters including the default formatter. Can be chained to add method calls.
111
+ def clear
112
+ @class_formatters.clear
113
+ @module_formatters.clear
58
114
  self
59
115
  end
60
-
116
+
61
117
  # Format a message object as a string.
62
118
  def format(message)
63
- formatter_for(message.class).call(message)
119
+ formatter = formatter_for(message.class)
120
+ if formatter&.respond_to?(:call)
121
+ formatter.call(message)
122
+ else
123
+ message
124
+ end
64
125
  end
65
-
66
- # Hack for compatibility with Logger::Formatter
126
+
127
+ # Compatibility with the Logger::Formatter signature. This method will just convert the message
128
+ # object to a string and ignores the other parameters.
67
129
  def call(severity, timestamp, progname, msg)
68
- "#{format(msg)}\n"
69
- end
130
+ "#{format(msg)}#{Lumberjack::LINE_SEPARATOR}"
131
+ end
70
132
 
71
133
  private
72
-
134
+
73
135
  # Find the formatter for a class by looking it up using the class hierarchy.
74
136
  def formatter_for(klass) #:nodoc:
75
- while klass != nil do
76
- formatter = @class_formatters[klass]
137
+ check_modules = true
138
+ until klass.nil?
139
+ formatter = @class_formatters[klass.name]
77
140
  return formatter if formatter
141
+
142
+ if check_modules
143
+ _, formatter = @module_formatters.detect { |mod, f| klass.include?(mod) }
144
+ check_modules = false
145
+ return formatter if formatter
146
+ end
147
+
78
148
  klass = klass.superclass
79
149
  end
80
- @_default_formatter
81
150
  end
82
151
  end
83
152
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literals: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # Format a Date, Time, or DateTime object. If you don't specify a format in the constructor,
6
+ # it will use the ISO-8601 format.
7
+ class DateTimeFormatter
8
+ attr_reader :format
9
+
10
+ def initialize(format = nil)
11
+ @format = format.dup.to_s.freeze unless format.nil?
12
+ end
13
+
14
+ def call(obj)
15
+ if @format && obj.respond_to?(:strftime)
16
+ obj.strftime(@format)
17
+ elsif obj.respond_to?(:iso8601)
18
+ obj.iso8601
19
+ else
20
+ obj.to_s
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,13 +2,36 @@
2
2
 
3
3
  module Lumberjack
4
4
  class Formatter
5
- # Format an exception including the backtrace.
5
+ # Format an exception including the backtrace. You can specify an object that
6
+ # responds to `call` as a backtrace cleaner. The exception backtrace will be
7
+ # passed to this object and the returned array is what will be logged. You can
8
+ # use this to clean out superfluous lines.
6
9
  class ExceptionFormatter
10
+ attr_accessor :backtrace_cleaner
11
+
12
+ def initialize(backtrace_cleaner = nil)
13
+ self.backtrace_cleaner = backtrace_cleaner
14
+ end
15
+
7
16
  def call(exception)
8
17
  message = "#{exception.class.name}: #{exception.message}"
9
- message << "#{Lumberjack::LINE_SEPARATOR} #{exception.backtrace.join("#{Lumberjack::LINE_SEPARATOR} ")}" if exception.backtrace
18
+ trace = exception.backtrace
19
+ if trace
20
+ trace = clean_backtrace(trace)
21
+ message << "#{Lumberjack::LINE_SEPARATOR} #{trace.join("#{Lumberjack::LINE_SEPARATOR} ")}"
22
+ end
10
23
  message
11
24
  end
25
+
26
+ private
27
+
28
+ def clean_backtrace(trace)
29
+ if trace && backtrace_cleaner
30
+ backtrace_cleaner.call(trace)
31
+ else
32
+ trace
33
+ end
34
+ end
12
35
  end
13
36
  end
14
37
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literals: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # Format an object that has an id as a hash with keys for class and id. This formatter is useful
6
+ # as a default formatter for objects pulled from a data store. By default it will use :id as the
7
+ # id attribute.
8
+ class IdFormatter
9
+ def initialize(id_attribute = :id)
10
+ @id_attribute = id_attribute
11
+ end
12
+
13
+ def call(obj)
14
+ if obj.respond_to?(@id_attribute)
15
+ id = obj.send(@id_attribute)
16
+ {"class" => obj.class.name, "id" => id}
17
+ else
18
+ obj.to_s
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literals: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # No-op formatter that just returns the object itself.
6
+ class ObjectFormatter
7
+ def call(obj)
8
+ obj
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literals: true
2
2
 
3
- require 'pp'
4
- require 'stringio'
3
+ require "pp"
4
+ require "stringio"
5
5
 
6
6
  module Lumberjack
7
7
  class Formatter
8
8
  # Format an object with it's pretty print method.
9
9
  class PrettyPrintFormatter
10
10
  attr_accessor :width
11
-
11
+
12
12
  # Create a new formatter. The maximum width of the message can be specified with the width
13
13
  # parameter (defaults to 79 characters).
14
14
  def initialize(width = 79)
15
15
  @width = width
16
16
  end
17
-
17
+
18
18
  def call(obj)
19
19
  s = StringIO.new
20
20
  PP.pp(obj, s)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lumberjack
4
4
  class Formatter
5
- # Format an object by calling +to_s+ on it.
5
+ # Format an object by calling `to_s` on it.
6
6
  class StringFormatter
7
7
  def call(obj)
8
8
  obj.to_s
@@ -0,0 +1,12 @@
1
+ # frozen_string_literals: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # Format an object by calling `to_s` on it and stripping leading and trailing whitespace.
6
+ class StripFormatter
7
+ def call(obj)
8
+ obj.to_s.strip
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literals: true
2
+
3
+ require "set"
4
+
5
+ module Lumberjack
6
+ class Formatter
7
+ # Dereference arrays and hashes and recursively call formatters on each element.
8
+ class StructuredFormatter
9
+ class RecusiveReferenceError < StandardError
10
+ end
11
+
12
+ def initialize(formatter = nil)
13
+ @formatter = formatter
14
+ end
15
+
16
+ def call(obj)
17
+ call_with_references(obj, Set.new)
18
+ end
19
+
20
+ private
21
+
22
+ def call_with_references(obj, references)
23
+ if obj.is_a?(Hash)
24
+ with_object_reference(obj, references) do
25
+ hash = {}
26
+ obj.each do |name, value|
27
+ value = call_with_references(value, references)
28
+ hash[name.to_s] = value unless value.is_a?(RecusiveReferenceError)
29
+ end
30
+ hash
31
+ end
32
+ elsif obj.is_a?(Enumerable) && obj.respond_to?(:size) && obj.size != Float::INFINITY
33
+ with_object_reference(obj, references) do
34
+ array = []
35
+ obj.each do |value|
36
+ value = call_with_references(value, references)
37
+ array << value unless value.is_a?(RecusiveReferenceError)
38
+ end
39
+ array
40
+ end
41
+ elsif @formatter
42
+ @formatter.format(obj)
43
+ else
44
+ obj
45
+ end
46
+ end
47
+
48
+ def with_object_reference(obj, references)
49
+ if obj.is_a?(Enumerable)
50
+ return RecusiveReferenceError.new if references.include?(obj.object_id)
51
+ references << obj.object_id
52
+ begin
53
+ yield
54
+ ensure
55
+ references.delete(obj.object_id)
56
+ end
57
+ else
58
+ yield
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -4,35 +4,63 @@ module Lumberjack
4
4
  # An entry in a log is a data structure that captures the log message as well as
5
5
  # information about the system that logged the message.
6
6
  class LogEntry
7
- attr_accessor :time, :message, :severity, :progname, :pid, :unit_of_work_id
8
-
9
- TIME_FORMAT = "%Y-%m-%dT%H:%M:%S".freeze
10
-
11
- def initialize(time, severity, message, progname, pid, unit_of_work_id)
7
+ attr_accessor :time, :message, :severity, :progname, :pid, :tags
8
+
9
+ TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
10
+
11
+ UNIT_OF_WORK_ID = "unit_of_work_id"
12
+
13
+ def initialize(time, severity, message, progname, pid, tags)
12
14
  @time = time
13
15
  @severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity))
14
16
  @message = message
15
17
  @progname = progname
16
18
  @pid = pid
17
- @unit_of_work_id = unit_of_work_id
19
+ # backward compatibility with 1.0 API where the last argument was the unit of work id
20
+ @tags = if tags.nil? || tags.is_a?(Hash)
21
+ tags
22
+ else
23
+ {UNIT_OF_WORK_ID => tags}
24
+ end
18
25
  end
19
-
26
+
20
27
  def severity_label
21
28
  Severity.level_to_label(severity)
22
29
  end
23
-
30
+
24
31
  def to_s
25
- buf = "[#{time.strftime(TIME_FORMAT)}.#{(time.usec / 1000.0).round.to_s.rjust(3, '0')} #{severity_label} #{progname}(#{pid})"
26
- if unit_of_work_id
27
- buf << " #"
28
- buf << unit_of_work_id
29
- end
30
- buf << "] "
31
- buf << message
32
+ "[#{time.strftime(TIME_FORMAT)}.#{(time.usec / 1000.0).round.to_s.rjust(3, "0")} #{severity_label} #{progname}(#{pid})#{tags_to_s}] #{message}"
32
33
  end
33
-
34
+
34
35
  def inspect
35
36
  to_s
36
37
  end
38
+
39
+ # Deprecated - backward compatibility with 1.0 API
40
+ def unit_of_work_id
41
+ tags[UNIT_OF_WORK_ID] if tags
42
+ end
43
+
44
+ # Deprecated - backward compatibility with 1.0 API
45
+ def unit_of_work_id=(value)
46
+ if tags
47
+ tags[UNIT_OF_WORK_ID] = value
48
+ else
49
+ @tags = {UNIT_OF_WORK_ID => value}
50
+ end
51
+ end
52
+
53
+ # Return the tag with the specified name.
54
+ def tag(name)
55
+ tags[name.to_s] if tags
56
+ end
57
+
58
+ private
59
+
60
+ def tags_to_s
61
+ tags_string = ""
62
+ tags&.each { |name, value| tags_string << " #{name}:#{value.inspect}" }
63
+ tags_string
64
+ end
37
65
  end
38
66
  end