logging 1.8.2 → 2.0.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/History.txt +20 -0
  4. data/README.md +159 -0
  5. data/Rakefile +9 -5
  6. data/examples/appenders.rb +0 -4
  7. data/examples/layouts.rb +1 -8
  8. data/examples/names.rb +4 -4
  9. data/lib/logging.rb +24 -76
  10. data/lib/logging/appender.rb +71 -16
  11. data/lib/logging/appenders.rb +0 -2
  12. data/lib/logging/appenders/buffering.rb +32 -16
  13. data/lib/logging/appenders/file.rb +2 -2
  14. data/lib/logging/appenders/io.rb +1 -1
  15. data/lib/logging/appenders/rolling_file.rb +228 -165
  16. data/lib/logging/appenders/string_io.rb +1 -1
  17. data/lib/logging/appenders/syslog.rb +4 -4
  18. data/lib/logging/color_scheme.rb +20 -3
  19. data/lib/logging/diagnostic_context.rb +142 -17
  20. data/lib/logging/filter.rb +18 -0
  21. data/lib/logging/filters.rb +4 -0
  22. data/lib/logging/filters/level.rb +29 -0
  23. data/lib/logging/layout.rb +2 -2
  24. data/lib/logging/layouts/parseable.rb +5 -2
  25. data/lib/logging/layouts/pattern.rb +309 -168
  26. data/lib/logging/log_event.rb +5 -5
  27. data/lib/logging/logger.rb +55 -68
  28. data/lib/logging/repository.rb +24 -39
  29. data/lib/logging/root_logger.rb +1 -1
  30. data/lib/logging/utils.rb +4 -65
  31. data/lib/logging/version.rb +8 -0
  32. data/lib/rspec/logging_helper.rb +3 -3
  33. data/logging.gemspec +46 -0
  34. data/test/appenders/test_buffered_io.rb +29 -0
  35. data/test/appenders/test_file.rb +2 -2
  36. data/test/appenders/test_rolling_file.rb +62 -1
  37. data/test/layouts/test_color_pattern.rb +1 -1
  38. data/test/layouts/test_json.rb +3 -0
  39. data/test/layouts/test_pattern.rb +6 -2
  40. data/test/layouts/test_yaml.rb +4 -1
  41. data/test/test_appender.rb +56 -0
  42. data/test/test_filter.rb +33 -0
  43. data/test/test_layout.rb +4 -8
  44. data/test/test_log_event.rb +3 -3
  45. data/test/test_logger.rb +81 -57
  46. data/test/test_logging.rb +0 -59
  47. data/test/test_mapped_diagnostic_context.rb +49 -1
  48. data/test/test_nested_diagnostic_context.rb +16 -1
  49. data/test/test_repository.rb +24 -32
  50. data/test/test_utils.rb +14 -50
  51. metadata +35 -53
  52. data/README.rdoc +0 -143
  53. data/data/bad_logging_1.rb +0 -13
  54. data/data/bad_logging_2.rb +0 -21
  55. data/data/logging.rb +0 -42
  56. data/data/logging.yaml +0 -63
  57. data/data/simple_logging.rb +0 -13
  58. data/examples/consolidation.rb +0 -83
  59. data/lib/logging/appenders/email.rb +0 -178
  60. data/lib/logging/appenders/growl.rb +0 -200
  61. data/lib/logging/config/configurator.rb +0 -187
  62. data/lib/logging/config/yaml_configurator.rb +0 -190
  63. data/lib/logging/stats.rb +0 -277
  64. data/test/appenders/test_email.rb +0 -170
  65. data/test/appenders/test_growl.rb +0 -138
  66. data/test/config/test_configurator.rb +0 -69
  67. data/test/config/test_yaml_configurator.rb +0 -39
  68. data/test/test_consolidate.rb +0 -45
  69. data/test/test_stats.rb +0 -273
  70. data/version.txt +0 -1
@@ -57,7 +57,7 @@ module Logging::Appenders
57
57
  @sio.truncate 0
58
58
  }
59
59
  end
60
- alias :reset :clear
60
+ alias_method :reset, :clear
61
61
 
62
62
  %w[read readline readlines].each do|m|
63
63
  class_eval <<-CODE, __FILE__, __LINE__+1
@@ -93,16 +93,16 @@ module Logging::Appenders
93
93
  # through LOG_LOCAL7.
94
94
  #
95
95
  def initialize( name, opts = {} )
96
- @ident = opts.getopt(:ident, name)
97
- @logopt = opts.getopt(:logopt, (LOG_PID | LOG_CONS), :as => Integer)
98
- @facility = opts.getopt(:facility, LOG_USER, :as => Integer)
96
+ @ident = opts.fetch(:ident, name)
97
+ @logopt = Integer(opts.fetch(:logopt, (LOG_PID | LOG_CONS)))
98
+ @facility = Integer(opts.fetch(:facility, LOG_USER))
99
99
  @syslog = ::Syslog.open(@ident, @logopt, @facility)
100
100
 
101
101
  # provides a mapping from the default Logging levels
102
102
  # to the syslog levels
103
103
  @map = [LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT]
104
104
 
105
- map = opts.getopt(:map)
105
+ map = opts.fetch(:map, nil)
106
106
  self.map = map unless map.nil?
107
107
 
108
108
  super
@@ -1,10 +1,11 @@
1
-
2
1
  # color_scheme.rb
3
2
  #
4
3
  # Created by Jeremy Hinegardner on 2007-01-24
5
4
  # Copyright 2007. All rights reserved
6
5
  #
7
- # This is Free Software. See LICENSE and COPYING for details
6
+ # This file is licensed under the terms of the MIT License.
7
+ # See the README for licensing details.
8
+ #
8
9
 
9
10
  module Logging
10
11
 
@@ -71,7 +72,7 @@ module Logging
71
72
  # end of this file. Multiple color codes can be aliased by grouping them
72
73
  # in an array as shown in the example above.
73
74
  #
74
- # Since color schemes are primary intended to be used with the Pattern
75
+ # Since color schemes are primarily intended to be used with the Pattern
75
76
  # layout, there are a few special options of note. First the log levels
76
77
  # are enumerated in their own hash:
77
78
  #
@@ -239,6 +240,22 @@ module Logging
239
240
  ON_CYAN = "\e[46m".freeze # Set the terminal's background ANSI color to cyan.
240
241
  ON_WHITE = "\e[47m".freeze # Set the terminal's background ANSI color to white.
241
242
 
243
+ BRIGHT_RED = "\e[1;31m".freeze # Set the terminal's foreground ANSI color to bright red.
244
+ BRIGHT_GREEN = "\e[1;32m".freeze # Set the terminal's foreground ANSI color to bright green.
245
+ BRIGHT_YELLOW = "\e[1;33m".freeze # Set the terminal's foreground ANSI color to bright yellow.
246
+ BRIGHT_BLUE = "\e[1;34m".freeze # Set the terminal's foreground ANSI color to bright blue.
247
+ BRIGHT_MAGENTA = "\e[1;35m".freeze # Set the terminal's foreground ANSI color to bright magenta.
248
+ BRIGHT_CYAN = "\e[1;36m".freeze # Set the terminal's foreground ANSI color to bright cyan.
249
+ BRIGHT_WHITE = "\e[1;37m".freeze # Set the terminal's foreground ANSI color to bright white.
250
+
251
+ ON_BRIGHT_RED = "\e[1;41m".freeze # Set the terminal's background ANSI color to bright red.
252
+ ON_BRIGHT_GREEN = "\e[1;42m".freeze # Set the terminal's background ANSI color to bright green.
253
+ ON_BRIGHT_YELLOW = "\e[1;43m".freeze # Set the terminal's background ANSI color to bright yellow.
254
+ ON_BRIGHT_BLUE = "\e[1;44m".freeze # Set the terminal's background ANSI color to bright blue.
255
+ ON_BRIGHT_MAGENTA = "\e[1;45m".freeze # Set the terminal's background ANSI color to bright magenta.
256
+ ON_BRIGHT_CYAN = "\e[1;46m".freeze # Set the terminal's background ANSI color to bright cyan.
257
+ ON_BRIGHT_WHITE = "\e[1;47m".freeze # Set the terminal's background ANSI color to bright white.
258
+
242
259
  end # ColorScheme
243
260
 
244
261
  # setup the default color scheme
@@ -34,7 +34,10 @@ module Logging
34
34
  extend self
35
35
 
36
36
  # The name used to retrieve the MDC from thread-local storage.
37
- NAME = 'logging.mapped-diagnostic-context'.freeze
37
+ NAME = :logging_mapped_diagnostic_context
38
+
39
+ # The name used to retrieve the MDC stack from thread-local storage.
40
+ STACK_NAME = :logging_mapped_diagnostic_context_stack
38
41
 
39
42
  # Public: Put a context value as identified with the key parameter into
40
43
  # the current thread's context map.
@@ -45,7 +48,8 @@ module Logging
45
48
  # Returns the value.
46
49
  #
47
50
  def []=( key, value )
48
- context.store(key.to_s, value)
51
+ clear_context
52
+ peek.store(key.to_s, value)
49
53
  end
50
54
 
51
55
  # Public: Get the context value identified with the key parameter.
@@ -67,9 +71,52 @@ module Logging
67
71
  # present.
68
72
  #
69
73
  def delete( key )
70
- context.delete(key.to_s)
74
+ clear_context
75
+ peek.delete(key.to_s)
76
+ end
77
+
78
+ # Public: Add all the key/value pairs from the given hash to the current
79
+ # mapped diagnostic context. The keys will be converted to strings.
80
+ # Existing keys of the same name will be overwritten.
81
+ #
82
+ # hash - The Hash of values to add to the current context.
83
+ #
84
+ # Returns this context.
85
+ #
86
+ def update( hash )
87
+ clear_context
88
+ sanitize(hash, peek)
89
+ self
90
+ end
91
+
92
+ # Public: Push a new Hash of key/value pairs onto the stack of contexts.
93
+ #
94
+ # hash - The Hash of values to push onto the context stack.
95
+ #
96
+ # Returns this context.
97
+ # Raises an ArgumentError if hash is not a Hash.
98
+ #
99
+ def push( hash )
100
+ clear_context
101
+ stack << sanitize(hash)
102
+ self
71
103
  end
72
104
 
105
+ # Public: Remove the most recently pushed Hash from the stack of contexts.
106
+ # If no contexts have been pushed then no action will be taken. The
107
+ # default context cannot be popped off the stack; please use the `clear`
108
+ # method if you want to remove all key/value pairs from the context.
109
+ #
110
+ # Returns nil or the Hash removed from the stack.
111
+ #
112
+ def pop
113
+ return unless Thread.current[STACK_NAME]
114
+ return unless stack.length > 1
115
+ clear_context
116
+ stack.pop
117
+ end
118
+
119
+
73
120
  # Public: Clear all mapped diagnostic information if any. This method is
74
121
  # useful in cases where the same thread can be potentially used over and
75
122
  # over in different unrelated contexts.
@@ -77,7 +124,8 @@ module Logging
77
124
  # Returns the MappedDiagnosticContext.
78
125
  #
79
126
  def clear
80
- context.clear if Thread.current[NAME]
127
+ clear_context
128
+ Thread.current[STACK_NAME] = nil
81
129
  self
82
130
  end
83
131
 
@@ -92,24 +140,90 @@ module Logging
92
140
  def inherit( obj )
93
141
  case obj
94
142
  when Hash
95
- Thread.current[NAME] = obj.dup
143
+ Thread.current[STACK_NAME] = [obj.dup]
96
144
  when Thread
97
145
  return if Thread.current == obj
98
146
  Thread.exclusive {
99
- Thread.current[NAME] = obj[NAME].dup if obj[NAME]
147
+ if obj[STACK_NAME]
148
+ hash = flatten(obj[STACK_NAME])
149
+ Thread.current[STACK_NAME] = [hash]
150
+ end
100
151
  }
101
152
  end
102
153
 
103
154
  self
104
155
  end
105
156
 
106
- # Returns the Hash acting as the storage for this NestedDiagnosticContext.
157
+ # Returns the Hash acting as the storage for this MappedDiagnosticContext.
107
158
  # A new storage Hash is created for each Thread running in the
108
159
  # application.
109
160
  #
110
161
  def context
111
- Thread.current[NAME] ||= Hash.new
162
+ c = Thread.current[NAME]
163
+ return c unless c.nil?
164
+
165
+ return Thread.current[NAME] = {} unless Thread.current[STACK_NAME]
166
+
167
+ Thread.current[NAME] = flatten(stack)
168
+ end
169
+
170
+ # Returns the stack of Hash objects that are storing the diagnostic
171
+ # context information. This stack is guarnteed to always contain at least
172
+ # one Hash.
173
+ #
174
+ def stack
175
+ Thread.current[STACK_NAME] ||= [{}]
112
176
  end
177
+
178
+ # Returns the most current Hash from the stack of contexts.
179
+ #
180
+ def peek
181
+ stack.last
182
+ end
183
+
184
+ # Remove the flattened context.
185
+ #
186
+ def clear_context
187
+ Thread.current[NAME] = nil
188
+ self
189
+ end
190
+
191
+ # Given a Hash convert all keys into Strings. The values are not altered
192
+ # in any way. The converted keys and their values are stored in the target
193
+ # Hash if provided. Otherwise a new Hash is created and returned.
194
+ #
195
+ # hash - The Hash of values to push onto the context stack.
196
+ # target - The target Hash to store the key value pairs.
197
+ #
198
+ # Returns a new Hash with all keys converted to Strings.
199
+ # Raises an ArgumentError if hash is not a Hash.
200
+ #
201
+ def sanitize( hash, target = {} )
202
+ unless Hash === hash
203
+ raise ArgumentError, "Expecting a Hash but received a #{hash.class.name}"
204
+ end
205
+
206
+ hash.each { |k,v| target[k.to_s] = v }
207
+ return target
208
+ end
209
+
210
+ # Given an Array of Hash objects, flatten all the key/value pairs from the
211
+ # Hash objects in the ary into a single Hash. The flattening occurs left
212
+ # to right. So that the key/value in the very last Hash overrides any
213
+ # other key from the previous Hash objcts.
214
+ #
215
+ # ary - An Array of Hash objects.
216
+ #
217
+ # Returns a Hash.
218
+ #
219
+ def flatten( ary )
220
+ return ary.first.dup if ary.length == 1
221
+
222
+ hash = {}
223
+ ary.each { |h| hash.update h }
224
+ return hash
225
+ end
226
+
113
227
  end # MappedDiagnosticContext
114
228
 
115
229
 
@@ -154,7 +268,7 @@ module Logging
154
268
  extend self
155
269
 
156
270
  # The name used to retrieve the NDC from thread-local storage.
157
- NAME = 'logging.nested-diagnostic-context'.freeze
271
+ NAME = :logging_nested_diagnostic_context
158
272
 
159
273
  # Public: Push new diagnostic context information for the current thread.
160
274
  # The contents of the message parameter is determined solely by the
@@ -166,9 +280,16 @@ module Logging
166
280
  #
167
281
  def push( message )
168
282
  context.push(message)
283
+ if block_given?
284
+ begin
285
+ yield
286
+ ensure
287
+ context.pop
288
+ end
289
+ end
169
290
  self
170
291
  end
171
- alias :<< :push
292
+ alias_method :<<, :push
172
293
 
173
294
  # Public: Clients should call this method before leaving a diagnostic
174
295
  # context. The returned value is the last pushed message. If no
@@ -199,7 +320,7 @@ module Logging
199
320
  # Returns the NestedDiagnosticContext.
200
321
  #
201
322
  def clear
202
- context.clear if Thread.current[NAME]
323
+ Thread.current[NAME] = nil
203
324
  self
204
325
  end
205
326
 
@@ -262,8 +383,9 @@ module Logging
262
383
  if all
263
384
  Thread.exclusive {
264
385
  Thread.list.each { |thread|
265
- thread[MappedDiagnosticContext::NAME].clear if thread[MappedDiagnosticContext::NAME]
266
- thread[NestedDiagnosticContext::NAME].clear if thread[NestedDiagnosticContext::NAME]
386
+ thread[MappedDiagnosticContext::NAME] = nil if thread[MappedDiagnosticContext::NAME]
387
+ thread[NestedDiagnosticContext::NAME] = nil if thread[NestedDiagnosticContext::NAME]
388
+ thread[MappedDiagnosticContext::STACK_NAME] = nil if thread[MappedDiagnosticContext::STACK_NAME]
267
389
  }
268
390
  }
269
391
  else
@@ -283,7 +405,7 @@ class Thread
283
405
 
284
406
  %w[new start fork].each do |m|
285
407
  class_eval <<-__, __FILE__, __LINE__
286
- alias :_orig_#{m} :#{m}
408
+ alias_method :_orig_#{m}, :#{m}
287
409
  private :_orig_#{m}
288
410
  def #{m}( *a, &b )
289
411
  create_with_logging_context(:_orig_#{m}, *a ,&b)
@@ -309,14 +431,17 @@ class Thread
309
431
  def create_with_logging_context( m, *a, &b )
310
432
  mdc, ndc = nil
311
433
 
312
- if Thread.current[Logging::MappedDiagnosticContext::NAME]
313
- mdc = Thread.current[Logging::MappedDiagnosticContext::NAME].dup
434
+ if Thread.current[Logging::MappedDiagnosticContext::STACK_NAME]
435
+ mdc = Logging::MappedDiagnosticContext.context.dup
314
436
  end
315
437
 
316
438
  if Thread.current[Logging::NestedDiagnosticContext::NAME]
317
- ndc = Thread.current[Logging::NestedDiagnosticContext::NAME].dup
439
+ ndc = Logging::NestedDiagnosticContext.context.dup
318
440
  end
319
441
 
442
+ # This calls the actual `Thread#new` method to create the Thread instance.
443
+ # If your memory profiling tool says this method is leaking memory, then
444
+ # you are leaking Thread instances somewhere.
320
445
  self.send(m, *a) { |*args|
321
446
  Logging::MappedDiagnosticContext.inherit(mdc)
322
447
  Logging::NestedDiagnosticContext.inherit(ndc)
@@ -0,0 +1,18 @@
1
+ module Logging
2
+
3
+ # The `Filter` class allows for filtering messages based on event
4
+ # properties independently of the standard minimum-level restriction.
5
+ #
6
+ # All other Filters inherit from this class, and must override the
7
+ # `allow` method to return the event if it should be allowed into the log.
8
+ # Otherwise the `allow` method should return `nil`.
9
+ class Filter
10
+
11
+ # Returns the event if it should be allowed into the log. Returns `nil` if
12
+ # the event should _not_ be allowed into the log. Subclasses should override
13
+ # this method and provide their own filtering semantics.
14
+ def allow( event )
15
+ event
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module Logging
2
+ module Filters ; end
3
+ require libpath('logging/filters/level')
4
+ end
@@ -0,0 +1,29 @@
1
+ require 'set'
2
+
3
+ module Logging
4
+ module Filters
5
+
6
+ # The `Level` filter class provides a simple level-based filtering mechanism
7
+ # that filters messages to only include those from an enumerated list of
8
+ # levels to log.
9
+ class Level < ::Logging::Filter
10
+
11
+ # Creates a new level filter that will only allow the given _levels_ to
12
+ # propagate through to the logging destination. The _levels_ should be
13
+ # given in symbolic form.
14
+ #
15
+ # Examples
16
+ # Logging::Filters::Level.new(:debug, :info)
17
+ #
18
+ def initialize( *levels )
19
+ levels = levels.map { |level| ::Logging::level_num(level) }
20
+ @levels = Set.new levels
21
+ end
22
+
23
+ def allow( event )
24
+ @levels.include?(event.level) ? event : nil
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -34,14 +34,14 @@ class Layout
34
34
  default = ::Logging.const_defined?('OBJ_FORMAT') ?
35
35
  ::Logging::OBJ_FORMAT : nil
36
36
 
37
- f = opts.getopt(:format_as, default)
37
+ f = opts.fetch(:format_as, default)
38
38
  f = f.intern if f.instance_of? String
39
39
 
40
40
  @obj_format = case f
41
41
  when :inspect, :yaml, :json; f
42
42
  else :string end
43
43
 
44
- b = opts.getopt(:backtrace, ::Logging.backtrace)
44
+ b = opts.fetch(:backtrace, ::Logging.backtrace)
45
45
  @backtrace = case b
46
46
  when :on, 'on', true; true
47
47
  when :off, 'off', false; false
@@ -1,3 +1,4 @@
1
+ require 'socket'
1
2
 
2
3
  module Logging::Layouts
3
4
 
@@ -39,6 +40,7 @@ module Logging::Layouts
39
40
  # was issued.
40
41
  # 'method' Used to output the method name where the logging request
41
42
  # was issued.
43
+ # 'hostname' Used to output the hostname
42
44
  # 'pid' Used to output the process ID of the currently running
43
45
  # program.
44
46
  # 'millis' Used to output the number of milliseconds elapsed from
@@ -102,6 +104,7 @@ module Logging::Layouts
102
104
  'file' => 'event.file'.freeze,
103
105
  'line' => 'event.line'.freeze,
104
106
  'method' => 'event.method'.freeze,
107
+ 'hostname' => "'#{Socket.gethostname}'".freeze,
105
108
  'pid' => 'Process.pid'.freeze,
106
109
  'millis' => 'Integer((event.time-@created_at)*1000)'.freeze,
107
110
  'thread_id' => 'Thread.current.object_id'.freeze,
@@ -180,8 +183,8 @@ module Logging::Layouts
180
183
  def initialize( opts = {} )
181
184
  super
182
185
  @created_at = Time.now
183
- @style = opts.getopt(:style, 'json').to_s.intern
184
- self.items = opts.getopt(:items, %w[timestamp level logger message])
186
+ @style = opts.fetch(:style, 'json').to_s.intern
187
+ self.items = opts.fetch(:items, %w[timestamp level logger message])
185
188
  end
186
189
 
187
190
  attr_reader :items
@@ -1,14 +1,14 @@
1
-
2
1
  module Logging::Layouts
3
2
 
4
3
  # Accessor / Factory for the Pattern layout.
5
4
  #
5
+ # Returns a new Pattern layout instance
6
6
  def self.pattern( *args )
7
7
  return ::Logging::Layouts::Pattern if args.empty?
8
8
  ::Logging::Layouts::Pattern.new(*args)
9
9
  end
10
10
 
11
- # A flexible layout configurable with pattern string.
11
+ # A flexible layout configurable via a conversion pattern string.
12
12
  #
13
13
  # The goal of this class is to format a LogEvent and return the results as
14
14
  # a String. The results depend on the conversion pattern.
@@ -57,6 +57,7 @@ module Logging::Layouts
57
57
  # the log event.
58
58
  # [M] Used to output the method name where the logging request was
59
59
  # issued.
60
+ # [h] Used to output the hostname
60
61
  # [p] Used to output the process ID of the currently running program.
61
62
  # [r] Used to output the number of milliseconds elapsed from the
62
63
  # construction of the Layout until creation of the log event.
@@ -74,7 +75,7 @@ module Logging::Layouts
74
75
  # [%] The sequence '%%' outputs a single percent sign.
75
76
  #
76
77
  # The logger name directive 'c' accepts an optional precision that will
77
- # only print the rightmost number of namespace identifiers for the logger.
78
+ # only print the rightmost number of name space identifiers for the logger.
78
79
  # By default the logger name is printed in full. For example, for the
79
80
  # logger name "Foo::Bar::Baz" the pattern %c{2} will output "Bar::Baz".
80
81
  #
@@ -150,184 +151,51 @@ module Logging::Layouts
150
151
 
151
152
  # :stopdoc:
152
153
 
153
- # Arguments to sprintf keyed to directive letters
154
- DIRECTIVE_TABLE = {
155
- 'c' => 'event.logger'.freeze,
156
- 'd' => 'format_date(event.time)'.freeze,
157
- 'F' => 'event.file'.freeze,
158
- 'l' => '::Logging::LNAMES[event.level]'.freeze,
159
- 'L' => 'event.line'.freeze,
160
- 'm' => 'format_obj(event.data)'.freeze,
161
- 'M' => 'event.method'.freeze,
162
- 'p' => 'Process.pid'.freeze,
163
- 'r' => 'Integer((event.time-@created_at)*1000).to_s'.freeze,
164
- 't' => 'Thread.current.object_id.to_s'.freeze,
165
- 'T' => 'Thread.current[:name]'.freeze,
166
- 'X' => :placeholder,
167
- 'x' => :placeholder,
168
- '%' => :placeholder
169
- }.freeze
170
-
171
- # Matches the first directive encountered and the stuff around it.
172
- #
173
- # * $1 is the stuff before directive or "" if not applicable
174
- # * $2 is the %#.# match within directive group
175
- # * $3 is the directive letter
176
- # * $4 is the precision specifier for the logger name
177
- # * $5 is the stuff after the directive or "" if not applicable
178
- DIRECTIVE_RGXP = %r/([^%]*)(?:(%-?\d*(?:\.\d+)?)([a-zA-Z%])(?:\{([^\}]+)\})?)?(.*)/m
179
-
180
154
  # default date format
181
- ISO8601 = "%Y-%m-%d %H:%M:%S".freeze
182
-
183
- # Human name aliases for directives - used for colorization of tokens
184
- COLOR_ALIAS_TABLE = {
185
- 'c' => :logger,
186
- 'd' => :date,
187
- 'm' => :message,
188
- 'p' => :pid,
189
- 'r' => :time,
190
- 'T' => :thread,
191
- 't' => :thread_id,
192
- 'F' => :file,
193
- 'L' => :line,
194
- 'M' => :method,
195
- 'X' => :mdc,
196
- 'x' => :ndc
197
- }.freeze
155
+ ISO8601 = "%Y-%m-%dT%H:%M:%S".freeze
198
156
 
199
157
  # call-seq:
200
- # Pattern.create_date_format_methods( pf )
158
+ # Pattern.create_date_format_methods( pl )
201
159
  #
202
160
  # This method will create the +date_format+ method in the given Pattern
203
- # Layout _pf_ based on the configured date patten and/or date method
161
+ # Layout _pl_ based on the configured date pattern and/or date method
204
162
  # specified by the user.
205
163
  #
206
- def self.create_date_format_methods( pf )
164
+ def self.create_date_format_methods( pl )
207
165
  code = "undef :format_date if method_defined? :format_date\n"
208
166
  code << "def format_date( time )\n"
209
- if pf.date_method.nil?
210
- if pf.date_pattern =~ %r/%s/
167
+ if pl.date_method.nil?
168
+ if pl.date_pattern =~ %r/%s/
211
169
  code << <<-CODE
212
- dp = '#{pf.date_pattern}'.gsub('%s','%06d' % time.usec)
170
+ dp = '#{pl.date_pattern}'.gsub('%s','%06d' % time.usec)
213
171
  time.strftime dp
214
172
  CODE
215
173
  else
216
- code << "time.strftime '#{pf.date_pattern}'\n"
174
+ code << "time.strftime '#{pl.date_pattern}'\n"
217
175
  end
218
176
  else
219
- code << "time.#{pf.date_method}\n"
177
+ code << "time.#{pl.date_method}\n"
220
178
  end
221
179
  code << "end\n"
222
180
  ::Logging.log_internal(0) {code}
223
181
 
224
- pf._meta_eval(code, __FILE__, __LINE__)
182
+ pl._meta_eval(code, __FILE__, __LINE__)
225
183
  end
226
184
 
227
185
  # call-seq:
228
- # Pattern.create_format_method( pf )
186
+ # Pattern.create_format_method( pl )
229
187
  #
230
- # This method will create the +format+ method in the given Pattern
231
- # Layout _pf_ based on the configured format pattern specified by the
188
+ # This method will create the `format` method in the given Pattern
189
+ # Layout `pl` based on the configured format pattern specified by the
232
190
  # user.
233
191
  #
234
- def self.create_format_method( pf )
235
- # Create the format(event) method
236
- format_string = '"'
237
- pattern = pf.pattern.dup
238
- color_scheme = pf.color_scheme
239
- args = []
240
- name_map_count = 0
241
-
242
- while true
243
- m = DIRECTIVE_RGXP.match(pattern)
244
- format_string << m[1] unless m[1].empty?
245
-
246
- case m[3]
247
- when '%'; format_string << '%%'
248
- when 'c'
249
- fmt = m[2] + 's'
250
- fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[m[3]]) if color_scheme and !color_scheme.lines?
251
-
252
- format_string << fmt
253
- args << DIRECTIVE_TABLE[m[3]].dup
254
- if m[4]
255
- precision = Integer(m[4]) rescue nil
256
- if precision
257
- raise ArgumentError, "logger name precision must be an integer greater than zero: #{precision}" unless precision > 0
258
- args.last <<
259
- ".split(::Logging::Repository::PATH_DELIMITER)" \
260
- ".last(#{m[4]}).join(::Logging::Repository::PATH_DELIMITER)"
261
- else
262
- format_string << "{#{m[4]}}"
263
- end
264
- end
265
- when 'l'
266
- if color_scheme and color_scheme.levels?
267
- name_map = ::Logging::LNAMES.map { |name| color_scheme.color(("#{m[2]}s" % name), name) }
268
- var = "@name_map_#{name_map_count}"
269
- pf.instance_variable_set(var.to_sym, name_map)
270
- name_map_count += 1
271
-
272
- format_string << '%s'
273
- format_string << "{#{m[4]}}" if m[4]
274
- args << "#{var}[event.level]"
275
- else
276
- format_string << m[2] + 's'
277
- format_string << "{#{m[4]}}" if m[4]
278
- args << DIRECTIVE_TABLE[m[3]]
279
- end
280
-
281
- when 'X'
282
- raise ArgumentError, "MDC must have a key reference" unless m[4]
283
- fmt = m[2] + 's'
284
- fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[m[3]]) if color_scheme and !color_scheme.lines?
285
-
286
- format_string << fmt
287
- args << "::Logging.mdc['#{m[4]}']"
288
-
289
- when 'x'
290
- fmt = m[2] + 's'
291
- fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[m[3]]) if color_scheme and !color_scheme.lines?
292
-
293
- format_string << fmt
294
- separator = m[4].to_s
295
- separator = ' ' if separator.empty?
296
- args << "::Logging.ndc.context.join('#{separator}')"
297
-
298
- when *DIRECTIVE_TABLE.keys
299
- fmt = m[2] + 's'
300
- fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[m[3]]) if color_scheme and !color_scheme.lines?
301
-
302
- format_string << fmt
303
- format_string << "{#{m[4]}}" if m[4]
304
- args << DIRECTIVE_TABLE[m[3]]
305
-
306
- when nil; break
307
- else
308
- raise ArgumentError, "illegal format character - '#{m[3]}'"
309
- end
310
-
311
- break if m[5].empty?
312
- pattern = m[5]
313
- end
192
+ def self.create_format_method( pl )
193
+ builder = FormatMethodBuilder.new(pl)
194
+ code = builder.build_code
314
195
 
315
- format_string << '"'
196
+ ::Logging.log_internal(0) { code }
316
197
 
317
- sprintf = "sprintf("
318
- sprintf << format_string
319
- sprintf << ', ' + args.join(', ') unless args.empty?
320
- sprintf << ")"
321
-
322
- if color_scheme and color_scheme.lines?
323
- sprintf = "color_scheme.color(#{sprintf}, ::Logging::LNAMES[event.level])"
324
- end
325
-
326
- code = "undef :format if method_defined? :format\n"
327
- code << "def format( event )\n#{sprintf}\nend\n"
328
- ::Logging.log_internal(0) {code}
329
-
330
- pf._meta_eval(code, __FILE__, __LINE__)
198
+ pl._meta_eval(code, __FILE__, __LINE__)
331
199
  end
332
200
  # :startdoc:
333
201
 
@@ -353,22 +221,22 @@ module Logging::Layouts
353
221
  super
354
222
  @created_at = Time.now
355
223
 
356
- @date_pattern = opts.getopt(:date_pattern)
357
- @date_method = opts.getopt(:date_method)
358
- @date_pattern = ISO8601 if @date_pattern.nil? and @date_method.nil?
224
+ @date_pattern = opts.fetch(:date_pattern, nil)
225
+ @date_method = opts.fetch(:date_method, nil)
226
+ @date_pattern = ISO8601 if @date_pattern.nil? && @date_method.nil?
359
227
 
360
- @pattern = opts.getopt(:pattern,
228
+ @pattern = opts.fetch(:pattern,
361
229
  "[%d] %-#{::Logging::MAX_LEVEL_LENGTH}l -- %c : %m\n")
362
230
 
363
- cs_name = opts.getopt(:color_scheme)
231
+ cs_name = opts.fetch(:color_scheme, nil)
364
232
  @color_scheme =
365
233
  case cs_name
366
234
  when false, nil; nil
367
235
  when true; ::Logging::ColorScheme[:default]
368
236
  else ::Logging::ColorScheme[cs_name] end
369
237
 
370
- Pattern.create_date_format_methods(self)
371
- Pattern.create_format_method(self)
238
+ self.class.create_date_format_methods(self)
239
+ self.class.create_format_method(self)
372
240
  end
373
241
 
374
242
  attr_reader :pattern, :date_pattern, :date_method, :color_scheme
@@ -409,18 +277,291 @@ module Logging::Layouts
409
277
 
410
278
  # :stopdoc:
411
279
 
412
- # call-seq:
413
- # _meta_eval( code )
414
- #
415
- # Evaluates the given string of _code_ if the singleton class of this
280
+ # Evaluates the given string of `code` if the singleton class of this
416
281
  # Pattern Layout object.
417
282
  #
283
+ # Returns this Pattern Layout instance.
418
284
  def _meta_eval( code, file = nil, line = nil )
419
285
  meta = class << self; self end
420
286
  meta.class_eval code, file, line
287
+ self
421
288
  end
422
- # :startdoc:
423
289
 
424
- end # Pattern
425
- end # Logging::Layouts
290
+ # This class is used to build the `format` method for the Pattern layout. It
291
+ # parses the user defined pattern and emits Ruby source code (as a string)
292
+ # that can be `eval`d in the context of the Pattern layout instance.
293
+ class FormatMethodBuilder
294
+ # Matches the first directive encountered and the stuff around it.
295
+ #
296
+ # * $1 is the stuff before directive or "" if not applicable
297
+ # * $2 is the %#.# match within directive group
298
+ # * $3 is the directive letter
299
+ # * $4 is the precision specifier for the logger name
300
+ # * $5 is the stuff after the directive or "" if not applicable
301
+ DIRECTIVE_RGXP = %r/([^%]*)(?:(%-?\d*(?:\.\d+)?)([a-zA-Z%])(?:\{([^\}]+)\})?)?(.*)/m
302
+
303
+ # Arguments to sprintf keyed to directive letters
304
+ DIRECTIVE_TABLE = {
305
+ 'c' => 'event.logger'.freeze,
306
+ 'd' => 'format_date(event.time)'.freeze,
307
+ 'F' => 'event.file'.freeze,
308
+ 'l' => '::Logging::LNAMES[event.level]'.freeze,
309
+ 'L' => 'event.line'.freeze,
310
+ 'm' => 'format_obj(event.data)'.freeze,
311
+ 'M' => 'event.method'.freeze,
312
+ 'h' => "'#{Socket.gethostname}'".freeze,
313
+ 'p' => 'Process.pid'.freeze,
314
+ 'r' => 'Integer((event.time-@created_at)*1000).to_s'.freeze,
315
+ 't' => 'Thread.current.object_id.to_s'.freeze,
316
+ 'T' => 'Thread.current[:name]'.freeze,
317
+ 'X' => :placeholder,
318
+ 'x' => :placeholder,
319
+ '%' => :placeholder
320
+ }.freeze
321
+
322
+ # Human name aliases for directives - used for colorization of tokens
323
+ COLOR_ALIAS_TABLE = {
324
+ 'c' => :logger,
325
+ 'd' => :date,
326
+ 'm' => :message,
327
+ 'h' => :hostname,
328
+ 'p' => :pid,
329
+ 'r' => :time,
330
+ 'T' => :thread,
331
+ 't' => :thread_id,
332
+ 'F' => :file,
333
+ 'L' => :line,
334
+ 'M' => :method,
335
+ 'X' => :mdc,
336
+ 'x' => :ndc
337
+ }.freeze
338
+
339
+ attr_reader :layout
340
+ attr_accessor :pattern
341
+ attr_reader :color_scheme
342
+ attr_reader :sprintf_args
343
+ attr_reader :format_string
344
+ attr_accessor :name_map_count
345
+
346
+ # Creates the format method builder and initializes some variables from
347
+ # the given Patter layout instance.
348
+ #
349
+ # pattern_layout - The Pattern Layout instance
350
+ #
351
+ def initialize( pattern_layout )
352
+ @layout = pattern_layout
353
+ @pattern = layout.pattern.dup
354
+ @color_scheme = layout.color_scheme
355
+
356
+ @sprintf_args = []
357
+ @format_string = '"'
358
+ @name_map_count = 0
359
+ end
360
+
361
+ # Returns `true` if the log messages should be colorized.
362
+ def colorize?
363
+ color_scheme && !color_scheme.lines?
364
+ end
365
+
366
+ # Returns `true` if the log messages should be colorized by line.
367
+ def colorize_lines?
368
+ color_scheme && color_scheme.lines?
369
+ end
426
370
 
371
+ # Returns `true` if the log levels have special colorization defined.
372
+ def colorize_levels?
373
+ color_scheme && color_scheme.levels?
374
+ end
375
+
376
+ # This method returns a String which can be `eval`d in the context of the
377
+ # Pattern layout. When it is `eval`d, a `format` method is defined in the
378
+ # Pattern layout.
379
+ #
380
+ # At the heart of the format method is `sprintf`. The conversion pattern
381
+ # specified in the Pattern layout is parsed and converted into a format
382
+ # string and corresponding arguments list. The format string and arguments
383
+ # are then processed by `sprintf` to format log events.
384
+ #
385
+ # Returns a Ruby code as a String.
386
+ def build_code
387
+ build_format_string
388
+
389
+ sprintf = "sprintf("
390
+ sprintf << format_string
391
+ sprintf << ', ' + sprintf_args.join(', ') unless sprintf_args.empty?
392
+ sprintf << ")"
393
+
394
+ if colorize_lines?
395
+ sprintf = "color_scheme.color(#{sprintf}, ::Logging::LNAMES[event.level])"
396
+ end
397
+
398
+ code = "undef :format if method_defined? :format\n"
399
+ code << "def format( event )\n#{sprintf}\nend\n"
400
+ end
401
+
402
+ # This method builds the format string used by `sprintf` to format log
403
+ # events. The conversion pattern given by the user is iteratively parsed
404
+ # by a regular expression into separate format directives. Each directive
405
+ # builds up the format string and the corresponding arguments list that
406
+ # will be formatted.
407
+ #
408
+ # The actual building of the format string is handled by separate
409
+ # directive specific methods. Those handlers also populate the arguments
410
+ # list passed to `sprintf`.
411
+ #
412
+ # Returns the format String.
413
+ def build_format_string
414
+ while true
415
+ match = DIRECTIVE_RGXP.match(pattern)
416
+ _, pre, format, directive, precision, post = *match
417
+
418
+ format_string << pre unless pre.empty?
419
+
420
+ case directive
421
+ when '%'; format_string << '%%'
422
+ when 'c'; handle_logger( format, directive, precision )
423
+ when 'l'; handle_level( format, directive, precision )
424
+ when 'X'; handle_mdc( format, directive, precision )
425
+ when 'x'; handle_ndc( format, directive, precision )
426
+
427
+ when *DIRECTIVE_TABLE.keys
428
+ handle_directives(format, directive, precision)
429
+
430
+ when nil; break
431
+ else
432
+ raise ArgumentError, "illegal format character - '#{directive}'"
433
+ end
434
+
435
+ break if post.empty?
436
+ self.pattern = post
437
+ end
438
+
439
+ format_string << '"'
440
+ end
441
+
442
+ # Add the logger name to the `format_string` and the `sprintf_args`. The
443
+ # `slice` argument is a little interesting - this is the number of logger
444
+ # name segments to keep. If we have a logger named "Foo::Bar::Baz" and our
445
+ # `slice` is 2, then "Bar::Baz" will appear in the generated log message.
446
+ # So the `slice` selects the last two parts of the logger name.
447
+ #
448
+ # format - format String
449
+ # directive - the directive character ('c')
450
+ # slice - the number of name segments to keep
451
+ #
452
+ # Returns nil
453
+ def handle_logger( format, directive, slice )
454
+ fmt = format + 's'
455
+ fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?
456
+
457
+ format_string << fmt
458
+ sprintf_args << DIRECTIVE_TABLE[directive].dup
459
+
460
+ if slice
461
+ numeric = Integer(slice) rescue nil
462
+ if numeric
463
+ raise ArgumentError, "logger name slice must be an integer greater than zero: #{numeric}" unless numeric > 0
464
+ sprintf_args.last <<
465
+ ".split(::Logging::Repository::PATH_DELIMITER)" \
466
+ ".last(#{slice}).join(::Logging::Repository::PATH_DELIMITER)"
467
+ else
468
+ format_string << "{#{slice}}"
469
+ end
470
+ end
471
+
472
+ nil
473
+ end
474
+
475
+ # Add the log event level to the `format_string` and the `sprintf_args`.
476
+ # The color scheme is taken into account when formatting the log event
477
+ # level.
478
+ #
479
+ # format - format String
480
+ # directive - the directive character ('l')
481
+ # precision - added back to the format string
482
+ #
483
+ # Returns nil
484
+ def handle_level( format, directive, precision )
485
+ if colorize_levels?
486
+ name_map = ::Logging::LNAMES.map { |name| color_scheme.color(("#{format}s" % name), name) }
487
+ var = "@name_map_#{name_map_count}"
488
+ layout.instance_variable_set(var.to_sym, name_map)
489
+ self.name_map_count += 1
490
+
491
+ format_string << '%s'
492
+ format_string << "{#{precision}}" if precision
493
+ sprintf_args << "#{var}[event.level]"
494
+ else
495
+ format_string << format + 's'
496
+ format_string << "{#{precision}}" if precision
497
+ sprintf_args << DIRECTIVE_TABLE[directive]
498
+ end
499
+
500
+ nil
501
+ end
502
+
503
+ # Add a Mapped Diagnostic Context to the `format_string` and the
504
+ # `sprintf_args`. Only one MDC value is added at a time, so this directive
505
+ # can appear multiple times using various keys.
506
+ #
507
+ # format - format String
508
+ # directive - the directive character ('X')
509
+ # key - which MDC value to add to the log message
510
+ #
511
+ # Returns nil
512
+ def handle_mdc( format, directive, key )
513
+ raise ArgumentError, "MDC must have a key reference" unless key
514
+ fmt = format + 's'
515
+ fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?
516
+
517
+ format_string << fmt
518
+ sprintf_args << "::Logging.mdc['#{key}']"
519
+
520
+ nil
521
+ end
522
+
523
+ # Add a Nested Diagnostic Context to the `format_string` and the
524
+ # `sprintf_args`. Since the NDC is an Array of values, the directive will
525
+ # appear only once in the conversion pattern. A `separator` is inserted
526
+ # between the values in generated log message.
527
+ #
528
+ # format - format String
529
+ # directive - the directive character ('x')
530
+ # separator - used to separate the values in the NDC array
531
+ #
532
+ # Returns nil
533
+ def handle_ndc( format, directive, separator )
534
+ fmt = format + 's'
535
+ fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?
536
+
537
+ format_string << fmt
538
+ separator = separator.to_s
539
+ separator = ' ' if separator.empty?
540
+ sprintf_args << "::Logging.ndc.context.join('#{separator}')"
541
+
542
+ nil
543
+ end
544
+
545
+ # Handles the rest of the directives; none of these need any special
546
+ # handling.
547
+ #
548
+ # format - format String
549
+ # directive - the directive character
550
+ # precision - added back to the format string
551
+ #
552
+ # Returns nil
553
+ def handle_directives( format, directive, precision )
554
+ fmt = format + 's'
555
+ fmt = color_scheme.color(fmt, COLOR_ALIAS_TABLE[directive]) if colorize?
556
+
557
+ format_string << fmt
558
+ format_string << "{#{precision}}" if precision
559
+ sprintf_args << DIRECTIVE_TABLE[directive]
560
+
561
+ nil
562
+ end
563
+ end
564
+ # :startdoc:
565
+
566
+ end
567
+ end