lumberjack 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6869d51bf38cc7ee1aad46005d947c5313bf58064da86cbd44ab5ca69dd04a71
4
- data.tar.gz: 2817b2ace992f96770a16f1f5d6133a06f1e7277dfe236deb31d8fcad9934d39
3
+ metadata.gz: 10f2db93994b2941a8684c4fd933f07fc2d0067afdcfa3798f4ba3b5cc34b354
4
+ data.tar.gz: e6413b5b475b1c05ffcc17662e4f553af8e6d47d79e961a5802d0d875a413f9e
5
5
  SHA512:
6
- metadata.gz: 6419348e19de6901892b35118ebc4405a773706b817d5c3c443f733973a977ad3be171b1f736b81334d9173cae5271799e267917284d3a28459349bc0cfc76f7
7
- data.tar.gz: 5dca009d8cc55ea4044e9eba50df1ebba04351d019c3322b733ae01fc0d250f29d3a9f625c67da3549a0a7ebf8487a465f1ff4cca1ccfccf662f1cbccc4629cd
6
+ metadata.gz: 809fc20f5cb8cc7d704f5a5e6878f321285953c8c79139fbd9892fa6c954b645b6bdcc0ce0e0265e18b29974fd864a96fdd735a424f57bd1224653759b139fd8
7
+ data.tar.gz: c7cf6e570fe6a1cbba068905ad860c26ad375b5f413d449c8f77f84c4ad81b139585ef2917aa2328dac2b80e0a3f962895a5efb8c5b988a14605e59ccf6eafbc
@@ -1,3 +1,18 @@
1
+ ## 1.2.0
2
+
3
+ * Enable compatibility with ActiveSupport::TaggedLogger by calling `tagged_logger!` on a logger.
4
+ * Add `tag_formatter` to logger to specify formatting of tags for output.
5
+ * Allow adding and removing classes by name to formatters.
6
+ * Allow adding and removing multiple classes in a single call to a formatter.
7
+ * Allow using symbols and strings as log level for silencing a logger.
8
+ * Ensure flusher thread gets stopped when logger is closed.
9
+ * Add writer for logger device attribute.
10
+ * Handle passing an array of devices to a multi device.
11
+ * Helper method to get a tag with a specified name.
12
+ * Add strip formatter to strip whitespace from strings.
13
+ * Support non-alpha numeric characters in template variables.
14
+ * Add backtrace cleaner to ExceptionFormatter.
15
+
1
16
  ## 1.1.1
2
17
 
3
18
  * Replace Procs in tag values with the value of calling the Proc in log entries.
@@ -15,6 +30,7 @@
15
30
  * Add DateTimeFormatter, IdFormatter, ObjectFormatter, and StructuredFormatter
16
31
  * Add rack Context middleware for setting thread global context
17
32
  * End support for ruby versions < 2.3
33
+ * Add support for modules in formatters
18
34
 
19
35
  ## 1.0.13
20
36
 
data/README.md CHANGED
@@ -85,6 +85,16 @@ logger.info("no requests") # Will not include the `request_id` tag
85
85
 
86
86
  Tag keys are always converted to strings. Tags are inherited so that message tags take precedence over block tags which take precedence over global tags.
87
87
 
88
+ #### Compatibility with ActiveSupport::TaggedLogger
89
+
90
+ `Lumberjack::Logger` version 1.1.2 or greater is compatible with `ActiveSupport::TaggedLogger`. This is so that other code that expect to have a logger that responds to the `tagged` method will work. Any tags added with the `tagged` method will be appended to an array in the the "tagged" tag. However, if a tagged value has a colon or equals sign in it, it will be parsed to a name value pair.
91
+
92
+ ```ruby
93
+ logger.tagged("foo", "bar=1", "baz:2", "other") do
94
+ logger.info("here") # will include tags: {"tagged" => ["foo", "other"], "bar" => "1", "baz" => "2"}
95
+ end
96
+ ```
97
+
88
98
  #### Templates
89
99
 
90
100
  The built in `Lumberjack::Device::Writer` class has built in support for including tags in the output using the `Lumberjack::Template` class.
@@ -117,33 +127,57 @@ If you'd like to send you log to a different kind of output, you just need to ex
117
127
 
118
128
  #### Formatters
119
129
 
120
- When a message is logged, it is first converted into a string. You can customize how it is converted by adding mappings to a Formatter.
130
+ The message you send to the logger can be any object type and does not need to be a string. You can specify a `Lumberjack::Formatter` to instruct the logger how to format objects before outputting them to the device. You do this by mapping classes or modules to formatter code. This code can be either a block or an object that responds to the `call` method. The formatter will be called with the object logged as the message and the returned value will be what is sent to the device.
131
+
132
+ ```ruby
133
+ # Format all floating point number with three significant digits.
134
+ logger.formatter.add(Float) { |value| value.round(3) }
135
+
136
+ # Format all enumerable objects as a comma delimited string.
137
+ logger.formatter.add(Enumerable) { |value| value.join(", ") }
138
+ ```
121
139
 
122
- There are several built in classes you can add as formatters
140
+ There are several built in classes you can add as formatters. You can use a symbol to reference built in formatters.
123
141
 
124
142
  ```ruby
125
143
  logger.formatter.add(Hash, :pretty_print) # use the Formatter::PrettyPrintFormatter for all Hashes
126
144
  logger.formatter.add(Hash, Lumberjack::Formatter::PrettyPrintFormatter.new) # alternative using a formatter instance
127
145
  ```
128
146
 
129
- * `Lumberjack::Formatter::ObjectFormatter` - no op conversion that returns the object itself.
130
- * `Lumberjack::Formatter::StringFormatter` - calls `to_s` on the object.
131
- * `Lumberjack::Formatter::InspectFormatter` - calls `inspect` on the object.
132
- * `Lumberjack::Formatter::ExceptionFormatter` - special formatter for exceptions which logs them as multi line statements with the message and backtrace.
133
- * `Lumberjack::Formatter::DateTimeFormatter` - special formatter for dates and times to format them using `strftime`.
134
- * `Lumberjack::Formatter::PrettyPrintFormatter` - returns the pretty print format of the object.
135
- * `Lumberjack::Formatter::IdFormatter` - returns a hash of the object with keys for the id attribute and class.
136
- * `Lumberjack::Formatter::StructuredFormatter` - crawls the object and applies the formatter recursively to Enumerable objects found in it (arrays, hashes, etc.).
147
+ * `:object` - `Lumberjack::Formatter::ObjectFormatter` - no op conversion that returns the object itself.
148
+ * `:string` - `Lumberjack::Formatter::StringFormatter` - calls `to_s` on the object.
149
+ * `:strip` - `Lumberjack::Formatter::StripFormatter` - calls `to_s.strip` on the object.
150
+ * `:inspect` - `Lumberjack::Formatter::InspectFormatter` - calls `inspect` on the object.
151
+ * `:exception` - `Lumberjack::Formatter::ExceptionFormatter` - special formatter for exceptions which logs them as multi line statements with the message and backtrace.
152
+ * `:date_time` - `Lumberjack::Formatter::DateTimeFormatter` - special formatter for dates and times to format them using `strftime`.
153
+ * `:pretty_print` - `Lumberjack::Formatter::PrettyPrintFormatter` - returns the pretty print format of the object.
154
+ * `:id` - `Lumberjack::Formatter::IdFormatter` - returns a hash of the object with keys for the id attribute and class.
155
+ * `:structured` - `Lumberjack::Formatter::StructuredFormatter` - crawls the object and applies the formatter recursively to Enumerable objects found in it (arrays, hashes, etc.).
156
+
157
+ To define your own formatter, either provide a block or an object that responds to `call` with a single argument.
158
+
159
+ The default formatter will pass through values for strings, numbers, and booleans, and use the `:inspect` formatter for all objects except for exceptions which will be formatted with the `:exception` formatter.
160
+
161
+ #### Tag Formatters
162
+
163
+ The `logger.formatter` will only apply to log messages. You can use `logger.tag_formatter` to register formatters for tags. You can register both default formatters that will apply to all tag values, as well as tag specifice formatters that will apply only to objects with a specific tag name.
137
164
 
138
- You can also specify a block to use as a formatter:
165
+ The fomatter values can be either a `Lumberjack::Formatter` or a block or an object that responds to `call`. If you supply a `Lumberjack::Formatter`, the tag value will be passed through the rules for that formatter. If you supply a block or other object, it will be called with the tag value.
139
166
 
140
167
  ```ruby
141
- logger.formatter.add(MyClass){|obj| "#{obj.class}@#{obj.id}"} # use a block to provide a custom format
168
+ # These will all do the same thing formatting all tag values with `inspect`
169
+ logger.tag_formatter.default(Lumberjack::Formatter.new.clear.add(Object, :inspect))
170
+ logger.tag_formatter.default(Lumberjack::Formatter::InspectFormatter.new)
171
+ logger.tag_formatter.default { |value| value.inspect }
172
+
173
+ # This will register formatters only on specific tag names
174
+ logger.tag_formatter.add(:thread) { |thread| "Thread(#{thread.name})" }
175
+ logger.tag_formatter.add(:current_user, Lumberjack::Formatter::IdFormatter.new)
142
176
  ```
143
177
 
144
178
  #### Templates
145
179
 
146
- If you use the built in devices, you can also customize the Template used to format the LogEntry.
180
+ If you use the built in `Lumberjack::Writer` derived devices, you can also customize the Template used to format the LogEntry.
147
181
 
148
182
  See `Lumberjack::Template` for a complete list of macros you can use in the template. You can also use a block that receives a `Lumberjack::LogEntry` as a template.
149
183
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.1
1
+ 1.2.0
@@ -10,12 +10,15 @@ module Lumberjack
10
10
  LINE_SEPARATOR = (RbConfig::CONFIG['host_os'].match(/mswin/i) ? "\r\n" : "\n")
11
11
 
12
12
  require_relative "lumberjack/severity.rb"
13
+ require_relative "lumberjack/formatter.rb"
14
+
13
15
  require_relative "lumberjack/context.rb"
14
16
  require_relative "lumberjack/log_entry.rb"
15
- require_relative "lumberjack/formatter.rb"
16
17
  require_relative "lumberjack/device.rb"
17
18
  require_relative "lumberjack/logger.rb"
18
19
  require_relative "lumberjack/tags.rb"
20
+ require_relative "lumberjack/tag_formatter.rb"
21
+ require_relative "lumberjack/tagged_logger_support.rb"
19
22
  require_relative "lumberjack/template.rb"
20
23
  require_relative "lumberjack/rack.rb"
21
24
 
@@ -5,7 +5,7 @@ module Lumberjack
5
5
  # This is a logging device that forward log entries to multiple other devices.
6
6
  class Multi < Device
7
7
  def initialize(*devices)
8
- @devices = devices
8
+ @devices = devices.flatten
9
9
  end
10
10
 
11
11
  def write(entry)
@@ -19,18 +19,24 @@ module Lumberjack
19
19
  require_relative "formatter/object_formatter.rb"
20
20
  require_relative "formatter/pretty_print_formatter.rb"
21
21
  require_relative "formatter/string_formatter.rb"
22
+ require_relative "formatter/strip_formatter.rb"
22
23
  require_relative "formatter/structured_formatter.rb"
23
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
+
24
34
  def initialize
25
35
  @class_formatters = {}
26
36
  @module_formatters = {}
27
- @_default_formatter = InspectFormatter.new
28
37
  structured_formatter = StructuredFormatter.new(self)
29
- add(String, :object)
30
- add(Numeric, :object)
31
- add(TrueClass, :object)
32
- add(FalseClass, :object)
33
- add(Object, @_default_formatter)
38
+ add([String, Numeric, TrueClass, FalseClass], :object)
39
+ add(Object, InspectFormatter.new)
34
40
  add(Exception, :exception)
35
41
  add(Enumerable, structured_formatter)
36
42
  end
@@ -41,6 +47,12 @@ module Lumberjack
41
47
  #
42
48
  # The predefined formatters are: :inspect, :string, :exception, and :pretty_print.
43
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.
55
+ #
44
56
  # === Examples
45
57
  #
46
58
  # # Use a predefined formatter
@@ -56,24 +68,41 @@ module Lumberjack
56
68
  # formatter.add(MyClass, :pretty_print).add(YourClass){|obj| obj.humanize}
57
69
  def add(klass, formatter = nil, &block)
58
70
  formatter ||= block
59
- if formatter.is_a?(Symbol)
60
- formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter"
61
- formatter = Formatter.const_get(formatter_class_name).new
62
- end
63
- if klass.is_a?(Class)
64
- @class_formatters[klass] = formatter
71
+ if formatter.nil?
72
+ remove(klass)
65
73
  else
66
- @module_formatters[klass] = formatter
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
67
87
  end
68
88
  self
69
89
  end
70
90
 
71
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.
72
98
  def remove(klass)
73
- if klass.is_a?(Class)
74
- @class_formatters.delete(klass)
75
- else
76
- @module_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
77
106
  end
78
107
  self
79
108
  end
@@ -87,7 +116,12 @@ module Lumberjack
87
116
 
88
117
  # Format a message object as a string.
89
118
  def format(message)
90
- formatter_for(message.class).call(message)
119
+ formatter = formatter_for(message.class)
120
+ if formatter && formatter.respond_to?(:call)
121
+ formatter.call(message)
122
+ else
123
+ message
124
+ end
91
125
  end
92
126
 
93
127
  # Compatibility with the Logger::Formatter signature. This method will just convert the message
@@ -102,7 +136,7 @@ module Lumberjack
102
136
  def formatter_for(klass) #:nodoc:
103
137
  check_modules = true
104
138
  while klass != nil do
105
- formatter = @class_formatters[klass]
139
+ formatter = @class_formatters[klass.name]
106
140
  return formatter if formatter
107
141
 
108
142
  if check_modules
@@ -113,7 +147,6 @@ module Lumberjack
113
147
 
114
148
  klass = klass.superclass
115
149
  end
116
- @_default_formatter
117
150
  end
118
151
  end
119
152
  end
@@ -2,13 +2,38 @@
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
+
11
+ attr_accessor :backtrace_cleaner
12
+
13
+ def initialize(backtrace_cleaner = nil)
14
+ self.backtrace_cleaner = backtrace_cleaner
15
+ end
16
+
7
17
  def call(exception)
8
18
  message = "#{exception.class.name}: #{exception.message}"
9
- message << "#{Lumberjack::LINE_SEPARATOR} #{exception.backtrace.join("#{Lumberjack::LINE_SEPARATOR} ")}" if exception.backtrace
19
+ trace = exception.backtrace
20
+ if trace
21
+ trace = clean_backtrace(trace)
22
+ message << "#{Lumberjack::LINE_SEPARATOR} #{trace.join("#{Lumberjack::LINE_SEPARATOR} ")}"
23
+ end
10
24
  message
11
25
  end
26
+
27
+ private
28
+
29
+ def clean_backtrace(trace)
30
+ if trace && backtrace_cleaner
31
+ backtrace_cleaner.call(trace)
32
+ else
33
+ trace
34
+ end
35
+ end
36
+
12
37
  end
13
38
  end
14
39
  end
@@ -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
@@ -10,7 +10,7 @@ module Lumberjack
10
10
  @formatter = formatter
11
11
  end
12
12
 
13
- def call(obj)
13
+ def call(obj)
14
14
  if obj.is_a?(Hash)
15
15
  hash = {}
16
16
  references ||= Set.new
@@ -18,7 +18,7 @@ module Lumberjack
18
18
  hash[name.to_s] = call(value)
19
19
  end
20
20
  hash
21
- elsif obj.is_a?(Enumerable)
21
+ elsif obj.is_a?(Enumerable) && obj.respond_to?(:size) && obj.size != Float::INFINITY
22
22
  obj.collect { |element| call(element) }
23
23
  elsif @formatter
24
24
  @formatter.format(obj)
@@ -49,6 +49,11 @@ module Lumberjack
49
49
  @tags = { UNIT_OF_WORK_ID => value }
50
50
  end
51
51
  end
52
+
53
+ # Return the tag with the specified name.
54
+ def tag(name)
55
+ tags[name.to_s] if tags
56
+ end
52
57
 
53
58
  private
54
59
 
@@ -37,7 +37,10 @@ module Lumberjack
37
37
  attr_writer :progname
38
38
 
39
39
  # The device being written to
40
- attr_reader :device
40
+ attr_accessor :device
41
+
42
+ # The TagFormatter used for formatting tags for output
43
+ attr_accessor :tag_formatter
41
44
 
42
45
  # Create a new logger to log to a Device.
43
46
  #
@@ -53,6 +56,7 @@ module Lumberjack
53
56
  # * :level - The logging level below which messages will be ignored.
54
57
  # * :formatter - The formatter to use for outputting messages to the log.
55
58
  # * :datetime_format - The format to use for log timestamps.
59
+ # * :tag_formatter - The TagFormatter to use for formatting tags.
56
60
  # * :progname - The name of the program that will be recorded with each log entry.
57
61
  # * :flush_seconds - The maximum number of seconds between flush calls.
58
62
  # * :roll - If the log device is a file path, it will be a Device::DateRollingLogFile if this is set.
@@ -66,25 +70,27 @@ module Lumberjack
66
70
  max_flush_seconds = options.delete(:flush_seconds).to_f
67
71
 
68
72
  @device = open_device(device, options) if device
69
- @_formatter = (options[:formatter] || Formatter.new)
73
+ self.formatter = (options[:formatter] || Formatter.new)
74
+ @tag_formatter = (options[:tag_formatter] || TagFormatter.new)
70
75
  time_format = (options[:datetime_format] || options[:time_format])
71
76
  self.datetime_format = time_format if time_format
72
77
  @last_flushed_at = Time.now
73
78
  @silencer = true
74
79
  @tags = {}
80
+ @closed = false
75
81
 
76
82
  create_flusher_thread(max_flush_seconds) if max_flush_seconds > 0
77
83
  end
78
84
 
79
85
  # Get the timestamp format on the device if it has one.
80
86
  def datetime_format
81
- @device.datetime_format if @device.respond_to?(:datetime_format)
87
+ device.datetime_format if device.respond_to?(:datetime_format)
82
88
  end
83
89
 
84
90
  # Set the timestamp format on the device if it is supported.
85
91
  def datetime_format=(format)
86
- if @device.respond_to?(:datetime_format=)
87
- @device.datetime_format = format
92
+ if device.respond_to?(:datetime_format=)
93
+ device.datetime_format = format
88
94
  end
89
95
  end
90
96
 
@@ -110,12 +116,29 @@ module Lumberjack
110
116
 
111
117
  # Set the Lumberjack::Formatter used to format objects for logging as messages.
112
118
  def formatter=(value)
113
- @_formatter = value
119
+ @_formatter = (value.is_a?(TaggedLoggerSupport::Formatter) ? value.__formatter : value)
114
120
  end
115
121
 
116
122
  # Get the Lumberjack::Formatter used to format objects for logging as messages.
117
123
  def formatter
118
- @_formatter
124
+ if respond_to?(:tagged)
125
+ # Wrap in an object that supports ActiveSupport::TaggedLogger API
126
+ TaggedLoggerSupport::Formatter.new(logger: self, formatter: @_formatter)
127
+ else
128
+ @_formatter
129
+ end
130
+ end
131
+
132
+ # Enable this logger to function like an ActiveSupport::TaggedLogger. This will make the logger
133
+ # API compatible with ActiveSupport::TaggedLogger and is provided as a means of compatibility
134
+ # with other libraries that assume they can call the `tagged` method on a logger to add tags.
135
+ #
136
+ # The tags added with this method are just strings so they are stored in the logger tags
137
+ # in an array under the "tagged" tag. So calling `logger.tagged("foo", "bar")` will result
138
+ # in tags `{"tagged" => ["foo", "bar"]}`.
139
+ def tagged_logger!
140
+ self.extend(TaggedLoggerSupport)
141
+ self
119
142
  end
120
143
 
121
144
  # Add a message to the log with a given severity. The message can be either
@@ -135,7 +158,7 @@ module Lumberjack
135
158
  def add_entry(severity, message, progname = nil, tags = nil)
136
159
  severity = Severity.label_to_level(severity) unless severity.is_a?(Integer)
137
160
 
138
- return true unless @device && severity && severity >= level
161
+ return true unless device && severity && severity >= level
139
162
 
140
163
  time = Time.now
141
164
  message = message.call if message.is_a?(Proc)
@@ -154,6 +177,7 @@ module Lumberjack
154
177
  end
155
178
  end
156
179
  tags = Tags.expand_runtime_values(tags)
180
+ tags = tag_formatter.format(tags) if tag_formatter
157
181
 
158
182
  entry = LogEntry.new(time, severity, message, progname, $$, tags)
159
183
  write_to_device(entry)
@@ -186,10 +210,16 @@ module Lumberjack
186
210
  # Close the logging device.
187
211
  def close
188
212
  flush
189
- @device.close if @device.respond_to?(:close)
213
+ device.close if device.respond_to?(:close)
214
+ @closed = true
215
+ end
216
+
217
+ def closed?
218
+ @closed
190
219
  end
191
220
 
192
221
  def reopen(logdev = nil)
222
+ @closed = false
193
223
  device.reopen(logdev) if device.respond_to?(:reopen)
194
224
  end
195
225
 
@@ -264,6 +294,9 @@ module Lumberjack
264
294
  # end
265
295
  def silence(temporary_level = ERROR, &block)
266
296
  if silencer
297
+ unless temporary_level.is_a?(Integer)
298
+ temporary_level = Severity::label_to_level(temporary_level)
299
+ end
267
300
  push_thread_local_value(:lumberjack_logger_level, temporary_level, &block)
268
301
  else
269
302
  yield
@@ -393,7 +426,7 @@ module Lumberjack
393
426
 
394
427
  def write_to_device(entry) #:nodoc:
395
428
  begin
396
- @device.write(entry)
429
+ device.write(entry)
397
430
  rescue => e
398
431
  $stderr.puts("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}")
399
432
  $stderr.puts(entry.to_s)
@@ -406,7 +439,7 @@ module Lumberjack
406
439
  begin
407
440
  logger = self
408
441
  Thread.new do
409
- loop do
442
+ while !closed?
410
443
  begin
411
444
  sleep(flush_seconds)
412
445
  logger.flush if Time.now - logger.last_flushed_at >= flush_seconds
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ # Class for formatting tags. You can register a default formatter and tag
5
+ # name specific formatters. Formatters can be either `Lumberjack::Formatter`
6
+ # objects or any object that responds to `call`.
7
+ #
8
+ # tag_formatter = Lumberjack::TagFormatter.new.default(Lumberjack::Formatter.new)
9
+ # tag_formatter.add(["password", "email"]) { |value| "***" }
10
+ # tag_formatter.add("finished_at", Lumberjack::Formatter::DateTimeFormatter.new("%Y-%m-%dT%H:%m:%S%z"))
11
+ class TagFormatter
12
+
13
+ def initialize
14
+ @formatters = {}
15
+ @default_formatter = nil
16
+ end
17
+
18
+ # Add a default formatter applied to all tag values. This can either be a Lumberjack::Formatter
19
+ # or an object that responds to `call` or a block.
20
+ def default(formatter = nil, &block)
21
+ formatter ||= block
22
+ formatter = dereference_formatter(formatter)
23
+ @default_formatter = formatter
24
+ self
25
+ end
26
+
27
+ # Remove the default formatter.
28
+ def remove_default
29
+ @default_formatter = nil
30
+ self
31
+ end
32
+
33
+ # Add a formatter for specific tag names. This can either be a Lumberjack::Formatter
34
+ # or an object that responds to `call` or a block. The default formatter will not be
35
+ # applied.
36
+ def add(names, formatter = nil, &block)
37
+ formatter ||= block
38
+ formatter = dereference_formatter(formatter)
39
+ if formatter.nil?
40
+ remove(key)
41
+ else
42
+ Array(names).each do |name|
43
+ @formatters[name.to_s] = formatter
44
+ end
45
+ end
46
+ self
47
+ end
48
+
49
+ # Remove formatters for specific tag names. The default formatter will still be applied.
50
+ def remove(names)
51
+ Array(names).each do |name|
52
+ @formatters.delete(name.to_s)
53
+ end
54
+ self
55
+ end
56
+
57
+ # Remove all formatters.
58
+ def clear
59
+ @default_formatter = nil
60
+ @formatters.clear
61
+ self
62
+ end
63
+
64
+ # Format a hash of tags using the formatters
65
+ def format(tags)
66
+ return nil if tags.nil?
67
+ if @default_formatter.nil? && (@formatters.empty? || (@formatters.keys & tags.keys).empty?)
68
+ tags
69
+ else
70
+ formatted = {}
71
+ tags.each do |name, value|
72
+ formatter = (@formatters[name.to_s] || @default_formatter)
73
+ if formatter.is_a?(Lumberjack::Formatter)
74
+ value = formatter.format(value)
75
+ elsif formatter.respond_to?(:call)
76
+ value = formatter.call(value)
77
+ end
78
+ formatted[name.to_s] = value
79
+ end
80
+ formatted
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def dereference_formatter(formatter)
87
+ if formatter.is_a?(TaggedLoggerSupport::Formatter)
88
+ formatter.__formatter
89
+ elsif formatter.is_a?(Symbol)
90
+ formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter"
91
+ Formatter.const_get(formatter_class_name).new
92
+ else
93
+ formatter
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "forwardable"
5
+
6
+ module Lumberjack
7
+ # Methods to make Lumberjack::Logger API compatible with ActiveSupport::TaggedLogger.
8
+ module TaggedLoggerSupport
9
+
10
+ class Formatter < DelegateClass(Lumberjack::Formatter)
11
+ extend Forwardable
12
+ def_delegators :@logger, :tagged, :push_tags, :pop_tags, :clear_tags!
13
+
14
+ def initialize(formatter:, logger:)
15
+ @logger = logger
16
+ @formatter = formatter
17
+ super(formatter)
18
+ end
19
+
20
+ def current_tags
21
+ tags = @logger.instance_variable_get(:@tags)
22
+ if tags.is_a?(Hash)
23
+ Array(tags["tagged"])
24
+ else
25
+ []
26
+ end
27
+ end
28
+
29
+ def tags_text
30
+ tags = current_tags
31
+ if tags.any?
32
+ tags.collect { |tag| "[#{tag}] " }.join
33
+ end
34
+ end
35
+
36
+ def __formatter
37
+ @formatter
38
+ end
39
+ end
40
+
41
+ # Compatibility with ActiveSupport::TaggedLogging which only supports adding tags as strings.
42
+ # If a tag looks like "key:value" or "key=value", it will be added as a key value pair.
43
+ # Otherwise it will be appended to a list named "tagged".
44
+ def tagged(*tags, &block)
45
+ tag_hash = {}
46
+ tags.flatten.each do |tag|
47
+ tagged_values = Array(tag_hash["tagged"] || self.tags["tagged"])
48
+ tag_hash["tagged"] = tagged_values + [tag]
49
+ end
50
+ tag(tag_hash, &block)
51
+ end
52
+
53
+ def push_tags(*tags)
54
+ tagged(*tags)
55
+ end
56
+
57
+ def pop_tags(size = 1)
58
+ tagged_values = Array(@tags["tagged"])
59
+ tagged_values = (tagged_values.size > size ? tagged_values[0, tagged_values.size - size] : nil)
60
+ tag("tagged" => tagged_values)
61
+ end
62
+
63
+ def clear_tags!
64
+ tag("tagged" => nil)
65
+ end
66
+ end
67
+ end
@@ -11,10 +11,13 @@ module Lumberjack
11
11
  # * :message
12
12
  #
13
13
  # Any other words prefixed with a colon will be substituted with the value of the tag with that name.
14
+ # If your tag name contains characters other than alpha numerics and the underscore, you must surround it
15
+ # with curly brackets: `:{http.request-id}`.
14
16
  class Template
15
17
  TEMPLATE_ARGUMENT_ORDER = %w(:time :severity :progname :pid :message :tags).freeze
16
18
  MILLISECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N"
17
19
  MICROSECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N"
20
+ PLACEHOLDER_PATTERN = /:(([a-z0-9_]+)|({[^}]+}))/i.freeze
18
21
 
19
22
  # Create a new template from the markup. The +first_line+ argument is used to format only the first
20
23
  # line of a message. Additional lines will be added to the message unformatted. If you wish to format
@@ -85,8 +88,10 @@ module Lumberjack
85
88
 
86
89
  tags_string = String.new
87
90
  tags.each do |name, value|
88
- unless tag_vars.include?(name)
89
- tags_string << "[#{name}:#{value.inspect}] "
91
+ unless value.nil? || tag_vars.include?(name)
92
+ value = value.to_s
93
+ value = value.gsub(Lumberjack::LINE_SEPARATOR, " ") if value.include?(Lumberjack::LINE_SEPARATOR)
94
+ tags_string << "[#{name}:#{value}] "
90
95
  end
91
96
  end
92
97
 
@@ -100,12 +105,13 @@ module Lumberjack
100
105
  # Compile the template string into a value that can be used with sprintf.
101
106
  def compile(template) #:nodoc:
102
107
  tag_vars = []
103
- template = template.gsub(/:[a-z0-9_]+/) do |match|
104
- position = TEMPLATE_ARGUMENT_ORDER.index(match)
108
+ template = template.gsub(PLACEHOLDER_PATTERN) do |match|
109
+ var_name = match.sub("{", "").sub("}", "")
110
+ position = TEMPLATE_ARGUMENT_ORDER.index(var_name)
105
111
  if position
106
112
  "%#{position + 1}$s"
107
113
  else
108
- tag_vars << match[1, match.length]
114
+ tag_vars << var_name[1, var_name.length]
109
115
  "%#{TEMPLATE_ARGUMENT_ORDER.size + tag_vars.size}$s"
110
116
  end
111
117
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-09 00:00:00.000000000 Z
11
+ date: 2020-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -95,6 +95,7 @@ files:
95
95
  - lib/lumberjack/formatter/object_formatter.rb
96
96
  - lib/lumberjack/formatter/pretty_print_formatter.rb
97
97
  - lib/lumberjack/formatter/string_formatter.rb
98
+ - lib/lumberjack/formatter/strip_formatter.rb
98
99
  - lib/lumberjack/formatter/structured_formatter.rb
99
100
  - lib/lumberjack/log_entry.rb
100
101
  - lib/lumberjack/logger.rb
@@ -103,6 +104,8 @@ files:
103
104
  - lib/lumberjack/rack/request_id.rb
104
105
  - lib/lumberjack/rack/unit_of_work.rb
105
106
  - lib/lumberjack/severity.rb
107
+ - lib/lumberjack/tag_formatter.rb
108
+ - lib/lumberjack/tagged_logger_support.rb
106
109
  - lib/lumberjack/tags.rb
107
110
  - lib/lumberjack/template.rb
108
111
  - lumberjack.gemspec