dumb-logger 1.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.
@@ -0,0 +1,107 @@
1
+ Proc.new {
2
+ libdir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
3
+ $:.replace($: | [ libdir ])
4
+ }.call
5
+ require('dumb-logger')
6
+ require('aruba/cucumber')
7
+ require('debugger')
8
+
9
+ # @private
10
+ #
11
+ # This module provides helper methods for the Cucumber testing suite.
12
+ #
13
+ module DumbLogger_TestSupport
14
+
15
+ #
16
+ # Suggested by from https://github.com/codegram/spinach
17
+ #
18
+
19
+ #
20
+ # Provide helpers to wrap IO streams by temporarily redirecting them
21
+ # to a StringIO object.
22
+ #
23
+
24
+ # @private
25
+ #
26
+ # Capture IO to one or more streams during block execution.
27
+ #
28
+ # @param [Array<Symbol,String>] stms
29
+ # One or more stream identifiers to be captured. Symbols like `:$stderr`
30
+ # and strings like `"$stdout"` are acceptable.
31
+ #
32
+ # @yield
33
+ # Block for which stream traffic should be captured.
34
+ #
35
+ # @return [String,Hash<<String,Symbol>=>String>]
36
+ # If only one stream was specified, the result will be a simple string.
37
+ #
38
+ def capture_streams(*stms, &block)
39
+ if (stms.any? { |o| (! (o.kind_of?(String) || o.kind_of?(Symbol))) })
40
+ raise ArgumentError.new('streams must be strings or symbols')
41
+ end
42
+ ovalues = stms.inject({}) { |memo,stm|
43
+ stmname = stm.to_s
44
+ stmobj = eval(stmname)
45
+ unless (stmobj.kind_of?(IO))
46
+ raise ArgumentError.new("'#{stm.inspect}' is not an IO object")
47
+ end
48
+ stat = {
49
+ :persistent => stmobj,
50
+ :temporary => StringIO.new,
51
+ }
52
+ eval("#{stmname} = stat[:temporary]")
53
+ memo[stm] = stat
54
+ memo
55
+ }
56
+ #
57
+ # Make sure we restore the streams to their original settings if an
58
+ # exception gets raised. We don't care about the exception, just
59
+ # making sure the streams are as they were when we were called.
60
+ #
61
+ rvalues = stms.map { |o| {o => ''} }.reduce(:merge)
62
+ begin
63
+ yield
64
+ ensure
65
+ rvalues = ovalues.inject({}) { |memo,(stm,stat)|
66
+ eval("#{stm.to_s} = stat[:persistent]")
67
+ memo[stm] = stat[:temporary].string
68
+ memo
69
+ }
70
+ end
71
+ rvalues = rvalues.values.first if (rvalues.count == 1)
72
+ return rvalues
73
+ end # def capture_streams
74
+
75
+ # @private
76
+ #
77
+ # Capture standard output activity as a string.
78
+ #
79
+ # @yield
80
+ # Block during the execution of which output is to be captured.
81
+ #
82
+ # @return [String]
83
+ # Returns whatever was sent to `$stdout` during the block's execution.
84
+ #
85
+ def capture_stdout(&block)
86
+ return capture_stream(:$stdout, &block)
87
+ end # def capture_stdout
88
+
89
+ # @private
90
+ #
91
+ # Capture standard error activity as a string.
92
+ #
93
+ # @yield (see #capture_stdout)
94
+ #
95
+ # @return [String]
96
+ # Returns whatever was sent to `$stderr` during the block's execution.
97
+ #
98
+ # @see #capture_stdout
99
+ # @see #capture_streams
100
+ #
101
+ def capture_stderr(&block)
102
+ return capture_stream(:$stderr, &block)
103
+ end # def capture_stderr
104
+
105
+ end # module DumbLogger_TestSupport
106
+
107
+ include DumbLogger_TestSupport
@@ -0,0 +1,3 @@
1
+ Before() do
2
+ @exception_raised = nil
3
+ end
@@ -0,0 +1,567 @@
1
+ # -*- coding: utf-8 -*-
2
+ #--
3
+ # Copyright © 2015 Ken Coar
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #++
17
+
18
+ require('dumb-logger/version')
19
+
20
+ #
21
+ # This is just a one-off class to allow reporting things to stderr
22
+ # according to the verbosity level. Very simple, not a complete
23
+ # logger at all.
24
+ #
25
+ # @todo
26
+ # Allow assignment of prefices to levels the way we now do labels.
27
+ # Will probably only work with level-based reporting, since
28
+ # mask-based reports may get transmitted due to a mask `AND` that
29
+ # doesn't match any named masks.
30
+ #
31
+ # @todo
32
+ # Add a `:seek_to_eof` option so that text written to a sink is
33
+ # *always* possitioned after any data written by other processes.
34
+ # (Except on the first write to a file in truncation `:append => false`
35
+ # mode, of course.)
36
+ #
37
+ class DumbLogger
38
+
39
+ #
40
+ # class DumbLogger eigenclass.
41
+ #
42
+ # Since we may have had to open up a file, make sure closing it
43
+ # again is part of the instance teardown process.
44
+ #
45
+ class << self
46
+ #
47
+ # If we have a currently open output stream that needs to be
48
+ # closed (usually because we opened it ourself), close it as part
49
+ # of the DumbLogger object teardown.
50
+ #
51
+ # @param [DumbLogger] obj
52
+ # Instance being torn down ('destructed').
53
+ #
54
+ def finalize(obj)
55
+ if (obj.instance_variable_get(:@options)[:needs_close])
56
+ obj.sink.close
57
+ end
58
+ end # def finalize
59
+ end # class DumbLogger eigenclass
60
+
61
+ #
62
+ # Message flag for "do not append a newline".
63
+ #
64
+ NO_NL = :no_nl
65
+
66
+ #
67
+ # Treat loglevel numbers as bitmasks.
68
+ #
69
+ USE_BITMASK = :loglevels_are_bitmasks
70
+
71
+ #
72
+ # Treat loglevel numbers as actual levels.
73
+ #
74
+ USE_LEVELS = :loglevels_are_numbers
75
+
76
+ #
77
+ # Special sink values, which will get evaluated on every transmission.
78
+ #
79
+ SPECIAL_SINKS = [
80
+ :$stdout,
81
+ :$stderr,
82
+ ]
83
+
84
+ #
85
+ # @!attribute [rw] level_style
86
+ #
87
+ # Control whether loglevels are treated as ascending integers, or as
88
+ # bitmasks.
89
+ #
90
+ # @return [Symbol]
91
+ # Returns the current setting (either {USE_LEVELS} or {USE_BITMASK}).
92
+ #
93
+ # @raise [ArgumentError]
94
+ # Raises an *ArgumentError* exception if the style isn't
95
+ # recognised.
96
+ #
97
+ def level_style
98
+ return @options[:level_style]
99
+ end # def level_style
100
+
101
+ def level_style=(style)
102
+ unless ([ USE_LEVELS, USE_BITMASK ].include?(style))
103
+ raise ArgumentError.new('invalid loglevel style')
104
+ end
105
+ @options[:level_style] = style
106
+ end # def level_style=
107
+
108
+ #
109
+ # @!attribute [rw] loglevel
110
+ #
111
+ # If loglevels are being treated as integers, this is the maximum
112
+ # level that will reported; that is, if a message is submitted with
113
+ # level 7, but the loglevel is 5, the message will *not* be
114
+ # reported.
115
+ #
116
+ # If loglevels are being treated as bitmasks, messages will be
117
+ # reported only if submitted with a loglevel which has at least one
118
+ # bit set that is also set in the instance loglevel.
119
+ #
120
+ # When used as an attribute writer (*e.g.*, `obj.loglevel = val`),
121
+ # the argument will be treated as an integer.
122
+ #
123
+ # @return [Integer]
124
+ # Returns the maximum loglevel/logging mask in effect henceforth.
125
+ #
126
+ # @raise [ArgumentError]
127
+ # Raise an *ArgumentError* exception if the new value cannot be
128
+ # converted to an integer.
129
+ def loglevel=(arg)
130
+ unless (arg.respond_to?(:to_i))
131
+ raise ArgumentError.new('loglevels are integers')
132
+ end
133
+ @options[:loglevel] = arg.to_i
134
+ return @options[:loglevel]
135
+ end # def loglevel=
136
+ alias_method(:logmask=, :loglevel=)
137
+
138
+ def loglevel
139
+ return @options[:loglevel].to_i
140
+ end # def loglevel
141
+ alias_method(:logmask, :loglevel)
142
+
143
+ #
144
+ # Returns `true` if loglevel numbers are interpreted as integers
145
+ # rather than bitmasks. (See {#level_style} for more information.)
146
+ #
147
+ # @return [Boolean]
148
+ # Returns `true` if loglevels are regarded as integers rather than
149
+ # bitmasks, or `false` otherwise.
150
+ #
151
+ # @see #log_masks?
152
+ # @see #level_style
153
+ #
154
+ def log_levels?
155
+ return (@options[:level_style] == USE_LEVELS) ? true : false
156
+ end # def log_levels?
157
+
158
+ #
159
+ # Returns `true` if loglevel numbers are interpreted as bitmasks
160
+ # rather than integers. (See {#level_style} for more information.)
161
+ #
162
+ # Determine how loglevel numbers are interpreted. (See
163
+ # {#level_style} for more information.)
164
+ #
165
+ # Returns `true` if they're treated as bitmasks rather than integers.
166
+ #
167
+ # @return [Boolean]
168
+ # Returns `true` if loglevels are regarded as bitmasks rather than
169
+ # integers, or `false` otherwise.
170
+ #
171
+ # @see #log_levels?
172
+ # @see #level_style
173
+ #
174
+ def log_masks?
175
+ return (@options[:level_style] == USE_BITMASK) ? true : false
176
+ end # def log_masks?
177
+
178
+ #
179
+ # Allow the user to assign labels to different log levels or mask
180
+ # combinations. All labels will be downcased and converted to
181
+ # symbols.
182
+ #
183
+ # In addition, the labels are added to the instance as methods that
184
+ # will log messages with the specified level.
185
+ #
186
+ # @see #labeled_levels
187
+ #
188
+ # @param [Hash{String,Symbol=>Integer}] labelhash
189
+ # Hash of names or symbols and the integer log levels/masks they're
190
+ # labelling.
191
+ #
192
+ # @return [Hash<Symbol,Integer>]
193
+ # Returns a hash of the labels (as symbols) and levels/masks that
194
+ # have been assigned.
195
+ #
196
+ # @raise [ArgumentError]
197
+ # Raises an *ArgumentError* exception if the argument isn't a hash
198
+ # with integer values.
199
+ #
200
+ def label_levels(labelhash)
201
+ unless (labelhash.kind_of?(Hash))
202
+ raise ArgumentError.new('level labels must be supplied as a hash')
203
+ end
204
+ unless (labelhash.values.all? { |o| o.kind_of?(Integer) })
205
+ raise ArgumentError.new('labeled levels must be integers')
206
+ end
207
+ newhash = labelhash.inject({}) { |memo,(label,level)|
208
+ label_sym = label.to_s.downcase.to_sym
209
+ memo[label_sym] = level
210
+ memo
211
+ }
212
+ @options[:labels].merge!(newhash)
213
+ newhash.each do |label,level|
214
+ self.define_singleton_method(label) do |*args|
215
+ (scratch, newargs) = args.partition { |o| o.kind_of?(Integer) }
216
+ return self.message(level, *newargs)
217
+ end
218
+ end
219
+ return newhash
220
+ end # def label_levels
221
+
222
+ #
223
+ # Return a list of all the levels or bitmasks that have been labeled.
224
+ # The return value is suitable for use as input to the #label_levels
225
+ # method of this or another instance of this class.
226
+ #
227
+ # @see #label_levels
228
+ #
229
+ # @return [Hash<Symbol,Integer>]
230
+ # Returns a hash of labels (as symbols) and the log levels they
231
+ # identify.
232
+ #
233
+ def labeled_levels
234
+ return Hash[@options[:labels].sort].freeze
235
+ end # def labeled_levels
236
+
237
+ # @private
238
+ #
239
+ # List of option keys settable in the constructor.
240
+ #
241
+ CONSTRUCTOR_OPTIONS = [
242
+ :append,
243
+ :level_style,
244
+ :loglevel,
245
+ :logmask,
246
+ :labels,
247
+ :prefix,
248
+ :sink,
249
+ ]
250
+
251
+ #
252
+ # @!attribute [r] options
253
+ #
254
+ # Options controlling various aspects of `DumbLogger`'s operation.
255
+ #
256
+ # @return [Hash]
257
+ # Returns current set of DumbLogger options for the instance.
258
+ #
259
+ def options
260
+ return @options.dup.freeze
261
+ end # def options
262
+
263
+ #
264
+ # @!attribute [rw] prefix
265
+ #
266
+ # Prefix string to be inserted at the beginning of each line of
267
+ # output we emit.
268
+ #
269
+ # @note
270
+ # This can be overridden at runtime *via* the `:prefix` option hash
271
+ # element to the {#message} method (*q.v.*).
272
+ #
273
+ # @return [String]
274
+ # Sets or returns the prefix string to be used henceforth.
275
+ #
276
+ def prefix
277
+ return @options[:prefix]
278
+ end # def prefix
279
+
280
+ def prefix=(arg)
281
+ @options[:prefix] = arg.to_s
282
+ return self.prefix
283
+ end # def prefix=
284
+
285
+ #
286
+ # Re-open the current sink (unless it's a stream). This may be
287
+ # useful if you want to stop and truncate in the middle of logging
288
+ # (by changing the {#append=} option), or something.
289
+ #
290
+ # @return [Boolean]
291
+ # Returns `true` if the sink was successfully re-opened, or `false`
292
+ # otherwise (such as if it's a stream).
293
+ #
294
+ # @raise [IOError]
295
+ # Raises an *IOError* exception if the sink stream is already
296
+ # closed.
297
+ #
298
+ def reopen
299
+ return false unless (@options[:needs_close] && self.sink.kind_of?(String))
300
+ raise IOError.new('sink stream is already closed') if (@sink_io.closed?)
301
+ @sink_io.reopen(self.sink, (self.append? ? 'a' : 'w'))
302
+ @sink_io.sync = true if (@sink_io.respond_to?(:sync=))
303
+ return true
304
+ end # def reopen
305
+
306
+ #
307
+ # @!attribute [rw] sink
308
+ #
309
+ # Sets or returns the sink to which we send our messages.
310
+ #
311
+ # When setting the sink, the value can be an IO instance, a special
312
+ # symbol, or a string. If a string, the `:append` flag from the
313
+ # instance options (see {#append=} and {#append?}) is used to
314
+ # determine whether the file will be rewritten from the beginning,
315
+ # or just have text appended to it.
316
+ #
317
+ # Sinking to one of the special symbols (`:$stderr` or `:$stdout`;
318
+ # see {SPECIAL_SINKS}) results in the sink being re-evaluated at
319
+ # each call to {#message}. This is useful if these streams might be
320
+ # altered after the logger has been instantiated.
321
+ #
322
+ # @note
323
+ # File sink contents may appear unpredictable under the following
324
+ # conditions:
325
+ # * Messages are being sinked to a file, **and**
326
+ # * the file is being accessed by one or more other processes, **and**
327
+ # * changes to the file are interleaved between those made by the
328
+ # `DumbLogger` {#message} method and activity by the other
329
+ # process(es).
330
+ #
331
+ # @return [IO,String,Symbol]
332
+ # Returns the sink path, special name, or IO object.
333
+ #
334
+ def sink
335
+ return @options[:sink]
336
+ end # def sink
337
+
338
+ def sink=(arg)
339
+ if (@options[:needs_close] \
340
+ && @sink_io.respond_to?(:close) \
341
+ && (! [ self.sink, @sink_io ].include?(arg)))
342
+ @sink_io.close unless (@sink_io.closed?)
343
+ @sink_io = nil
344
+ end
345
+
346
+ @options[:volatile] = false
347
+ if (arg.kind_of?(IO))
348
+ #
349
+ # If it's an IO, then we assume it's already open.
350
+ #
351
+ @options[:sink] = @sink_io = arg
352
+ @options[:needs_close] = false
353
+ elsif (SPECIAL_SINKS.include?(arg))
354
+ #
355
+ # If it's one of our special symbols, we don't actually do
356
+ # anything except record the fact -- because they get
357
+ # interpreted at each #message call.
358
+ #
359
+ @options[:sink] = arg
360
+ @sink_io = nil
361
+ @options[:volatile] = true
362
+ else
363
+ #
364
+ # If it's a string, we treat it as a file name, open it, and
365
+ # flag it for closing later.
366
+ #
367
+ @options[:sink] = arg
368
+ @sink_io = File.open(@options[:sink], (self.append? ? 'a' : 'w'))
369
+ @options[:needs_close] = true
370
+ end
371
+ @sink_io.sync = true if (@sink_io.respond_to?(:sync=))
372
+ return self.sink
373
+ end # def sink=
374
+
375
+ #
376
+ # Constructor.
377
+ #
378
+ # @param [Hash] args
379
+ # @option args [Boolean] :append (true)
380
+ # If true, any **files** opened will have transmitted text appended to
381
+ # them. See {#append=}.
382
+ # @note
383
+ # Streams are **always** treated as being in `:append => true` mode.
384
+ # @option args [String] :prefix ('')
385
+ # String to insert at the beginning of each line of report text.
386
+ # See {#prefix=}.
387
+ # @option args [IO,String] :sink (:$stderr)
388
+ # Where reports should be sent. See {#sink=}.
389
+ # @option args [Integer] :loglevel (0)
390
+ # Maximum log level for reports. See {#loglevel=}.
391
+ # @option args [Integer] :logmask (0)
392
+ # Alias for `:loglevel`.
393
+ # @option args [Symbol] :level_style (USE_LEVELS)
394
+ # Whether message loglevels should be treated as integer levels or
395
+ # as bitmasks. See {#level_style=}.
396
+ #
397
+ # @raise [ArgumentError]
398
+ # Raises an *ArgumentError* exception if the argument isn't a hash.
399
+ #
400
+ def initialize(args={})
401
+ unless (args.kind_of?(Hash))
402
+ raise ArgumentError.new("#{self.class.name}.new requires a hash")
403
+ end
404
+ @options = {
405
+ :labels => {},
406
+ }
407
+ #
408
+ # Here are the default settings for a new instance with no
409
+ # arguments. We put 'em here so they don't show up in docco under
410
+ # the Constants heading.
411
+ #
412
+ default_opts = {
413
+ :append => true,
414
+ :level_style => USE_LEVELS,
415
+ :loglevel => 0,
416
+ :prefix => '',
417
+ :sink => :$stderr,
418
+ }
419
+ #
420
+ # Make a new hash merging the user arguments on top of the
421
+ # defaults. This avoids altering the user's hash.
422
+ #
423
+ temp_opts = default_opts.merge(args)
424
+ #
425
+ # Throw out any option keys we don't recognise.
426
+ #
427
+ temp_opts.delete_if { |k,v| (! CONSTRUCTOR_OPTIONS.include?(k)) }
428
+ #
429
+ # Do loglevel stuff. We're going to run this through the writer
430
+ # method, since it has argument validation code.
431
+ #
432
+ # If the user wants to use bitmasks, then the :logmask argument
433
+ # key takes precedence over the :loglevel one.
434
+ #
435
+ self.level_style = temp_opts[:level_style]
436
+ temp_opts.delete(:level_style)
437
+ if (self.log_masks?)
438
+ temp_opts[:loglevel] = temp_opts[:logmask] if (temp_opts.key?(:logmask))
439
+ end
440
+ temp_opts.delete(:logmask)
441
+ #
442
+ # Now go through the remaining options and handle them. If the
443
+ # option has an associated writer method, call it -- otherwise,
444
+ # just load it into the `@options` hash.
445
+ #
446
+ temp_opts.each do |opt,val|
447
+ wmethod = (opt.to_s + '=').to_sym
448
+ if (self.respond_to?(wmethod))
449
+ self.send(wmethod, val)
450
+ else
451
+ @options[opt] = val
452
+ end
453
+ end
454
+ end # def initialize
455
+
456
+ #
457
+ # @!attribute [rw] append
458
+ #
459
+ # Controls the behaviour of sink files (but *not* IO streams). If
460
+ # `true`, report text will be added to the end of any existing
461
+ # contents; if `false`, files will be truncated and reports will
462
+ # begin at position `0`.
463
+ #
464
+ # @note
465
+ # This setting is only important when a sink is being activated,
466
+ # such as `DumbLogger` object instantiation or because of a call to
467
+ # {#sink=}, and it controls the position of the first write to the
468
+ # sink. Once a sink is activated (opened), writing continues
469
+ # sequentially from that point.
470
+ #
471
+ # @note
472
+ # Setting this attribute *only* affects **files** opened by
473
+ # `DumbLogger`. Stream sinks are *always* in append-mode. As long
474
+ # as the sink is a stream, this setting will be ignored -- but it
475
+ # will become active whenever the sink becomes a file.
476
+ #
477
+ # @return [Boolean]
478
+ # Sets or returns the file append-on-write control value.
479
+ #
480
+ def append
481
+ return (@options[:append] ? true : false)
482
+ end # def append
483
+
484
+ def append=(arg)
485
+ @options[:append] = (arg ? true : false)
486
+ return self.append
487
+ end # def append=
488
+
489
+ #
490
+ # @return [Boolean]
491
+ # Returns `true` if new sink files opened by the instance will have
492
+ # report text appended to them.
493
+ #
494
+ def append?
495
+ return self.append
496
+ end # def append?
497
+
498
+ #
499
+ # Submit a message for possible transmission to the current sink.
500
+ # The argument is an array of arrays, strings, integers, and/or
501
+ # symbols. Reports with a loglevel of zero (the default) are
502
+ # *always* transmitted.
503
+ #
504
+ # @param [Array<Array,String,Symbol,Integer,Hash>] args
505
+ # * The last integer in the array will be treated as the report's
506
+ # loglevel; default is `0`.
507
+ # * Any `Array` elements in the arguments will be merged and the
508
+ # values interpreted as level labels (see {#label_levels}). If
509
+ # loglevels are bitmasks, the labeled levels are ORed together;
510
+ # otherwise the lowest labeled level will be used for the message.
511
+ # * Any `Hash` elements in the array will be merged and will
512
+ # temporarily override instance-wide options -- *e.g.*,
513
+ # `{ :prefix => 'alt' }` .
514
+ # * If the `DumbLogger::NO_NL` value (a `Symbol`) appears in the
515
+ # array, or a hash element of `:return => false`, the report will
516
+ # not include a terminating newline (useful for
517
+ # `"progress:..done"` reports).
518
+ # * Any strings in the array are treated as text to be reported,
519
+ # one _per_ line. Each line will begin with the value of
520
+ # #prefix, and only the final line is subject to the
521
+ # DumbLogger::NO_NL special-casing.
522
+ #
523
+ # @return [nil,Integer]
524
+ # Returns either `nil` if the message's loglevel is higher than the
525
+ # reporting level, or the level of the report.
526
+ #
527
+ # If integer levels are being used, a non-`nil` return value is
528
+ # that of the message. If bitmask levels are being used, the
529
+ # return value is a mask of the active level bits that applied to
530
+ # the message -- *i.e.*, `msg_bits & logging_mask` .
531
+ #
532
+ def message(*args)
533
+ #
534
+ # Extract any symbols, hashes, and integers from the argument
535
+ # list. This makes the calling format very flexible.
536
+ #
537
+ (symopts, args) = args.partition { |elt| elt.kind_of?(Symbol) }
538
+ symlevels = (symopts & self.labeled_levels.keys).map { |o|
539
+ self.labeled_levels[o]
540
+ }.compact
541
+ (hashopts, args) = args.partition { |elt| elt.kind_of?(Hash) }
542
+ hashopts = hashopts.reduce(:merge) || {}
543
+ (level, args) = args.partition { |elt| elt.kind_of?(Integer) }
544
+ level = level.last || hashopts[:level] || hashopts[:mask] || 0
545
+ cur_loglevel = self.loglevel
546
+ cur_style = self.level_style
547
+ unless (level.zero?)
548
+ if (self.log_levels?)
549
+ return nil if ([ cur_loglevel, *symlevels ].min < level)
550
+ elsif (self.log_masks?)
551
+ level = [ level, *symlevels ].reduce(:|) & cur_loglevel
552
+ return nil if (level.zero?)
553
+ end
554
+ end
555
+ prefix_text = hashopts[:prefix] || self.prefix
556
+ text = prefix_text + args.join("\n#{prefix_text}")
557
+ unless (hashopts.key?(:newline))
558
+ hashopts[:newline]= (! symopts.include?(NO_NL))
559
+ end
560
+ text << "\n" if (hashopts[:newline])
561
+ stream = @options[:volatile] ? eval(self.sink.to_s) : @sink_io
562
+ stream.write(text)
563
+ stream.flush if (@options[:volatile])
564
+ return level
565
+ end # def message
566
+
567
+ end # class DumbLogger