lumberjack 1.0.13 → 1.2.8

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